diff options
Diffstat (limited to 'drivers/video/omap2')
43 files changed, 9897 insertions, 3753 deletions
diff --git a/drivers/video/omap2/Kconfig b/drivers/video/omap2/Kconfig index d877c36..c4ec41a 100644 --- a/drivers/video/omap2/Kconfig +++ b/drivers/video/omap2/Kconfig @@ -7,3 +7,5 @@ config OMAP2_VRFB source "drivers/video/omap2/dss/Kconfig" source "drivers/video/omap2/omapfb/Kconfig" source "drivers/video/omap2/displays/Kconfig" +source "drivers/video/omap2/dsscomp/Kconfig" +source "drivers/video/omap2/hdcp/Kconfig" diff --git a/drivers/video/omap2/Makefile b/drivers/video/omap2/Makefile index 5ddef12..ceb1dd9 100644 --- a/drivers/video/omap2/Makefile +++ b/drivers/video/omap2/Makefile @@ -3,4 +3,6 @@ obj-$(CONFIG_OMAP2_VRFB) += vrfb.o obj-$(CONFIG_OMAP2_DSS) += dss/ obj-$(CONFIG_FB_OMAP2) += omapfb/ +obj-$(CONFIG_OMAP4_HDCP) += hdcp/ obj-y += displays/ +obj-y += dsscomp/ diff --git a/drivers/video/omap2/displays/panel-generic-dpi.c b/drivers/video/omap2/displays/panel-generic-dpi.c index 9c90f75..611be7e 100644 --- a/drivers/video/omap2/displays/panel-generic-dpi.c +++ b/drivers/video/omap2/displays/panel-generic-dpi.c @@ -82,6 +82,30 @@ static struct panel_config generic_dpi_panels[] = { .name = "generic", }, + /* generic 720p */ + { + { + .x_res = 1280, + .y_res = 720, + + .pixel_clock = 74250, + + .hfp = 110, + .hsw = 40, + .hbp = 20, + + .vfp = 5, + .vsw = 5, + .vbp = 20, + }, + .acbi = 0x0, + .acb = 0x0, + .config = OMAP_DSS_LCD_TFT, + .power_on_delay = 0, + .power_off_delay = 0, + .name = "generic_720p", + }, + /* Sharp LQ043T1DG01 */ { { diff --git a/drivers/video/omap2/displays/panel-taal.c b/drivers/video/omap2/displays/panel-taal.c index fdd5d4ae..b82bcc1 100644 --- a/drivers/video/omap2/displays/panel-taal.c +++ b/drivers/video/omap2/displays/panel-taal.c @@ -504,14 +504,18 @@ static int taal_exit_ulps(struct omap_dss_device *dssdev) return 0; r = omapdss_dsi_display_enable(dssdev); - if (r) - goto err; + if (r) { + dev_err(&dssdev->dev, "failed to enable DSI\n"); + goto err1; + } omapdss_dsi_vc_enable_hs(dssdev, td->channel, true); r = _taal_enable_te(dssdev, true); - if (r) - goto err; + if (r) { + dev_err(&dssdev->dev, "failed to re-enable TE"); + goto err2; + } enable_irq(gpio_to_irq(panel_data->ext_te_gpio)); @@ -521,13 +525,15 @@ static int taal_exit_ulps(struct omap_dss_device *dssdev) return 0; -err: - dev_err(&dssdev->dev, "exit ULPS failed"); - r = taal_panel_reset(dssdev); - - enable_irq(gpio_to_irq(panel_data->ext_te_gpio)); - td->ulps_enabled = false; +err2: + dev_err(&dssdev->dev, "failed to exit ULPS"); + r = taal_panel_reset(dssdev); + if (!r) { + enable_irq(gpio_to_irq(panel_data->ext_te_gpio)); + td->ulps_enabled = false; + } +err1: taal_queue_ulps_work(dssdev); return r; @@ -1317,8 +1323,11 @@ static void taal_disable(struct omap_dss_device *dssdev) dsi_bus_lock(dssdev); if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { - taal_wake_up(dssdev); - taal_power_off(dssdev); + int r; + + r = taal_wake_up(dssdev); + if (!r) + taal_power_off(dssdev); } dsi_bus_unlock(dssdev); diff --git a/drivers/video/omap2/dss/Kconfig b/drivers/video/omap2/dss/Kconfig index 6b3e2da..8f9420e 100644 --- a/drivers/video/omap2/dss/Kconfig +++ b/drivers/video/omap2/dss/Kconfig @@ -63,6 +63,7 @@ config OMAP2_DSS_VENC config OMAP4_DSS_HDMI bool "HDMI support" depends on ARCH_OMAP4 + select HDMI_TI_4XXX_IP default y help HDMI Interface. This adds the High Definition Multimedia Interface. @@ -117,18 +118,6 @@ config OMAP2_DSS_MIN_FCK_PER_PCK Max FCK is 173MHz, so this doesn't work if your PCK is very high. -config OMAP2_DSS_SLEEP_BEFORE_RESET - bool "Sleep 50ms before DSS reset" - default y - help - For some unknown reason we may get SYNC_LOST errors from the display - subsystem at initialization time if we don't sleep before resetting - the DSS. See the source (dss.c) for more comments. - - However, 50ms is quite long time to sleep, and with some - configurations the SYNC_LOST may never happen, so the sleep can - be disabled here. - config OMAP2_DSS_SLEEP_AFTER_VENC_RESET bool "Sleep 20ms after VENC reset" default y diff --git a/drivers/video/omap2/dss/Makefile b/drivers/video/omap2/dss/Makefile index 10d9d3b..e67db6e 100644 --- a/drivers/video/omap2/dss/Makefile +++ b/drivers/video/omap2/dss/Makefile @@ -1,9 +1,9 @@ obj-$(CONFIG_OMAP2_DSS) += omapdss.o -omapdss-y := core.o dss.o dss_features.o dispc.o display.o manager.o overlay.o +omapdss-y := core.o dss.o dss_features.o dispc.o display.o manager.o overlay.o fifothreshold.o omapdss-$(CONFIG_OMAP2_DSS_DPI) += dpi.o omapdss-$(CONFIG_OMAP2_DSS_RFBI) += rfbi.o omapdss-$(CONFIG_OMAP2_DSS_VENC) += venc.o omapdss-$(CONFIG_OMAP2_DSS_SDI) += sdi.o omapdss-$(CONFIG_OMAP2_DSS_DSI) += dsi.o omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi.o \ - hdmi_omap4_panel.o + hdmi_panel.o diff --git a/drivers/video/omap2/dss/core.c b/drivers/video/omap2/dss/core.c index 3da4267..2a1e6ed 100644 --- a/drivers/video/omap2/dss/core.c +++ b/drivers/video/omap2/dss/core.c @@ -145,6 +145,8 @@ static int dss_initialize_debugfs(void) debugfs_create_file("venc", S_IRUGO, dss_debugfs_dir, &venc_dump_regs, &dss_debug_fops); #endif + debugfs_create_file("hdmi", S_IRUGO, dss_debugfs_dir, + &hdmi_dump_regs, &dss_debug_fops); return 0; } @@ -183,8 +185,11 @@ static int omap_dss_probe(struct platform_device *pdev) goto err_dss; } - /* keep clocks enabled to prevent context saves/restores during init */ - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + r = dispc_init_platform_driver(); + if (r) { + DSSERR("Failed to initialize dispc platform driver\n"); + goto err_dispc; + } r = rfbi_init_platform_driver(); if (r) { @@ -192,12 +197,6 @@ static int omap_dss_probe(struct platform_device *pdev) goto err_rfbi; } - r = dispc_init_platform_driver(); - if (r) { - DSSERR("Failed to initialize dispc platform driver\n"); - goto err_dispc; - } - r = venc_init_platform_driver(); if (r) { DSSERR("Failed to initialize venc platform driver\n"); @@ -238,8 +237,6 @@ static int omap_dss_probe(struct platform_device *pdev) pdata->default_device = dssdev; } - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); - return 0; err_register: @@ -424,6 +421,24 @@ static int dss_driver_remove(struct device *dev) return 0; } +static void omap_dss_driver_disable(struct omap_dss_device *dssdev) +{ + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) + blocking_notifier_call_chain(&dssdev->state_notifiers, + OMAP_DSS_DISPLAY_DISABLED, dssdev); + dssdev->driver->disable_orig(dssdev); + dssdev->first_vsync = false; +} + +static int omap_dss_driver_enable(struct omap_dss_device *dssdev) +{ + int r = dssdev->driver->enable_orig(dssdev); + if (!r && dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + blocking_notifier_call_chain(&dssdev->state_notifiers, + OMAP_DSS_DISPLAY_ACTIVE, dssdev); + return r; +} + int omap_dss_register_driver(struct omap_dss_driver *dssdriver) { dssdriver->driver.bus = &dss_bus_type; @@ -436,6 +451,11 @@ int omap_dss_register_driver(struct omap_dss_driver *dssdriver) dssdriver->get_recommended_bpp = omapdss_default_get_recommended_bpp; + dssdriver->disable_orig = dssdriver->disable; + dssdriver->disable = omap_dss_driver_disable; + dssdriver->enable_orig = dssdriver->enable; + dssdriver->enable = omap_dss_driver_enable; + return driver_register(&dssdriver->driver); } EXPORT_SYMBOL(omap_dss_register_driver); diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c index 7a9a2e7..c5eaf90 100644 --- a/drivers/video/omap2/dss/dispc.c +++ b/drivers/video/omap2/dss/dispc.c @@ -33,12 +33,17 @@ #include <linux/workqueue.h> #include <linux/hardirq.h> #include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/ratelimit.h> #include <plat/sram.h> #include <plat/clock.h> - +#include <mach/tiler.h> +#include <plat/omap-pm.h> #include <video/omapdss.h> +#include "../clockdomain.h" #include "dss.h" #include "dss_features.h" #include "dispc.h" @@ -55,26 +60,20 @@ #define DISPC_MAX_NR_ISRS 8 +static struct clockdomain *l3_1_clkdm, *l3_2_clkdm; + struct omap_dispc_isr_data { omap_dispc_isr_t isr; void *arg; u32 mask; }; -struct dispc_h_coef { - s8 hc4; - s8 hc3; - u8 hc2; - s8 hc1; - s8 hc0; -}; - -struct dispc_v_coef { - s8 vc22; - s8 vc2; - u8 vc1; - s8 vc0; - s8 vc00; +struct dispc_hv_coef { + s8 hc0_vc00; + s8 hc1_vc0; + u8 hc2_vc1; + s8 hc3_vc2; + s8 hc4_vc22; }; #define REG_GET(idx, start, end) \ @@ -92,9 +91,17 @@ struct dispc_irq_stats { static struct { struct platform_device *pdev; void __iomem *base; + + int ctx_loss_cnt; + struct mutex runtime_lock; + int runtime_count; + int irq; + struct clk *dss_clk; + + u32 fifo_size[MAX_DSS_OVERLAYS]; - u32 fifo_size[3]; + u32 channel_irq[3]; /* Max channels hardcoded to 3*/ spinlock_t irq_lock; u32 irq_error_mask; @@ -102,6 +109,7 @@ static struct { u32 error_irqs; struct work_struct error_work; + bool ctx_valid; u32 ctx[DISPC_SZ_REGS / sizeof(u32)]; #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS @@ -134,18 +142,34 @@ static inline u32 dispc_read_reg(const u16 idx) return __raw_readl(dispc.base + idx); } +static int dispc_get_ctx_loss_count(void) +{ + struct device *dev = &dispc.pdev->dev; + struct omap_display_platform_data *pdata = dev->platform_data; + struct omap_dss_board_info *board_data = pdata->board_data; + int cnt; + + if (!board_data->get_context_loss_count) + return -ENOENT; + + cnt = board_data->get_context_loss_count(dev); + + WARN_ONCE(cnt < 0, "get_context_loss_count failed: %d\n", cnt); + + return cnt; +} + #define SR(reg) \ dispc.ctx[DISPC_##reg / sizeof(u32)] = dispc_read_reg(DISPC_##reg) #define RR(reg) \ dispc_write_reg(DISPC_##reg, dispc.ctx[DISPC_##reg / sizeof(u32)]) -void dispc_save_context(void) +static void dispc_save_context(void) { - int i; - if (cpu_is_omap24xx()) - return; + int i, o; + + DSSDBG("dispc_save_context\n"); - SR(SYSCONFIG); SR(IRQENABLE); SR(CONTROL); SR(CONFIG); @@ -158,7 +182,8 @@ void dispc_save_context(void) SR(TIMING_V(OMAP_DSS_CHANNEL_LCD)); SR(POL_FREQ(OMAP_DSS_CHANNEL_LCD)); SR(DIVISORo(OMAP_DSS_CHANNEL_LCD)); - SR(GLOBAL_ALPHA); + if (dss_has_feature(FEAT_GLOBAL_ALPHA)) + SR(GLOBAL_ALPHA); SR(SIZE_MGR(OMAP_DSS_CHANNEL_DIGIT)); SR(SIZE_MGR(OMAP_DSS_CHANNEL_LCD)); if (dss_has_feature(FEAT_MGR_LCD2)) { @@ -188,123 +213,108 @@ void dispc_save_context(void) SR(DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD)); SR(DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD)); - SR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); - SR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); - SR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + if (dss_has_feature(FEAT_CPR)) { + SR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); + SR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); + SR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + } if (dss_has_feature(FEAT_MGR_LCD2)) { - SR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); - SR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); - SR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); + if (dss_has_feature(FEAT_CPR)) { + SR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); + SR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); + SR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); + } SR(DATA_CYCLE1(OMAP_DSS_CHANNEL_LCD2)); SR(DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD2)); SR(DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD2)); } - SR(OVL_PRELOAD(OMAP_DSS_GFX)); + if (dss_has_feature(FEAT_PRELOAD)) + SR(OVL_PRELOAD(OMAP_DSS_GFX)); - /* VID1 */ - SR(OVL_BA0(OMAP_DSS_VIDEO1)); - SR(OVL_BA1(OMAP_DSS_VIDEO1)); - SR(OVL_POSITION(OMAP_DSS_VIDEO1)); - SR(OVL_SIZE(OMAP_DSS_VIDEO1)); - SR(OVL_ATTRIBUTES(OMAP_DSS_VIDEO1)); - SR(OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO1)); - SR(OVL_ROW_INC(OMAP_DSS_VIDEO1)); - SR(OVL_PIXEL_INC(OMAP_DSS_VIDEO1)); - SR(OVL_FIR(OMAP_DSS_VIDEO1)); - SR(OVL_PICTURE_SIZE(OMAP_DSS_VIDEO1)); - SR(OVL_ACCU0(OMAP_DSS_VIDEO1)); - SR(OVL_ACCU1(OMAP_DSS_VIDEO1)); + /* VID1-3 */ + for (o = OMAP_DSS_VIDEO1; o <= OMAP_DSS_VIDEO3; o++) { + if (o == OMAP_DSS_VIDEO3 && !dss_has_feature(FEAT_OVL_VID3)) + continue; - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, i)); + SR(OVL_BA0(o)); + SR(OVL_BA1(o)); + SR(OVL_POSITION(o)); + SR(OVL_SIZE(o)); + SR(OVL_ATTRIBUTES(o)); + SR(OVL_FIFO_THRESHOLD(o)); + SR(OVL_ROW_INC(o)); + SR(OVL_PIXEL_INC(o)); + SR(OVL_FIR(o)); + SR(OVL_PICTURE_SIZE(o)); + SR(OVL_ACCU0(o)); + SR(OVL_ACCU1(o)); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, i)); + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_H(o, i)); - for (i = 0; i < 5; i++) - SR(OVL_CONV_COEF(OMAP_DSS_VIDEO1, i)); + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_HV(o, i)); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, i)); + for (i = 0; i < 5; i++) + SR(OVL_CONV_COEF(o, i)); - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - SR(OVL_BA0_UV(OMAP_DSS_VIDEO1)); - SR(OVL_BA1_UV(OMAP_DSS_VIDEO1)); - SR(OVL_FIR2(OMAP_DSS_VIDEO1)); - SR(OVL_ACCU2_0(OMAP_DSS_VIDEO1)); - SR(OVL_ACCU2_1(OMAP_DSS_VIDEO1)); + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_V(o, i)); + } - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, i)); + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + SR(OVL_BA0_UV(o)); + SR(OVL_BA1_UV(o)); + SR(OVL_FIR2(o)); + SR(OVL_ACCU2_0(o)); + SR(OVL_ACCU2_1(o)); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, i)); + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_H2(o, i)); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, i)); - } - if (dss_has_feature(FEAT_ATTR2)) - SR(OVL_ATTRIBUTES2(OMAP_DSS_VIDEO1)); + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_HV2(o, i)); - SR(OVL_PRELOAD(OMAP_DSS_VIDEO1)); - - /* VID2 */ - SR(OVL_BA0(OMAP_DSS_VIDEO2)); - SR(OVL_BA1(OMAP_DSS_VIDEO2)); - SR(OVL_POSITION(OMAP_DSS_VIDEO2)); - SR(OVL_SIZE(OMAP_DSS_VIDEO2)); - SR(OVL_ATTRIBUTES(OMAP_DSS_VIDEO2)); - SR(OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO2)); - SR(OVL_ROW_INC(OMAP_DSS_VIDEO2)); - SR(OVL_PIXEL_INC(OMAP_DSS_VIDEO2)); - SR(OVL_FIR(OMAP_DSS_VIDEO2)); - SR(OVL_PICTURE_SIZE(OMAP_DSS_VIDEO2)); - SR(OVL_ACCU0(OMAP_DSS_VIDEO2)); - SR(OVL_ACCU1(OMAP_DSS_VIDEO2)); + for (i = 0; i < 8; i++) + SR(OVL_FIR_COEF_V2(o, i)); + } + if (dss_has_feature(FEAT_ATTR2)) + SR(OVL_ATTRIBUTES2(o)); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, i)); + if (dss_has_feature(FEAT_PRELOAD)) + SR(OVL_PRELOAD(o)); + } - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, i)); + if (dss_has_feature(FEAT_CORE_CLK_DIV)) + SR(DIVISOR); - for (i = 0; i < 5; i++) - SR(OVL_CONV_COEF(OMAP_DSS_VIDEO2, i)); + dispc.ctx_loss_cnt = dispc_get_ctx_loss_count(); + dispc.ctx_valid = true; - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, i)); + DSSDBG("context saved, ctx_loss_count %d\n", dispc.ctx_loss_cnt); +} - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - SR(OVL_BA0_UV(OMAP_DSS_VIDEO2)); - SR(OVL_BA1_UV(OMAP_DSS_VIDEO2)); - SR(OVL_FIR2(OMAP_DSS_VIDEO2)); - SR(OVL_ACCU2_0(OMAP_DSS_VIDEO2)); - SR(OVL_ACCU2_1(OMAP_DSS_VIDEO2)); +static void dispc_restore_context(void) +{ + struct device *dev = &dispc.pdev->dev; + int i, o, ctx; - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, i)); + DSSDBG("dispc_restore_context\n"); - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, i)); + if (!dispc.ctx_valid) + return; - for (i = 0; i < 8; i++) - SR(OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, i)); - } - if (dss_has_feature(FEAT_ATTR2)) - SR(OVL_ATTRIBUTES2(OMAP_DSS_VIDEO2)); + ctx = dispc_get_ctx_loss_count(); - SR(OVL_PRELOAD(OMAP_DSS_VIDEO2)); + if (!omap_pm_was_context_lost(dev)) + return; - if (dss_has_feature(FEAT_CORE_CLK_DIV)) - SR(DIVISOR); -} + DSSDBG("ctx_loss_count: saved %d, current %d\n", + dispc.ctx_loss_cnt, ctx); -void dispc_restore_context(void) -{ - int i; - RR(SYSCONFIG); /*RR(IRQENABLE);*/ /*RR(CONTROL);*/ RR(CONFIG); @@ -317,7 +327,8 @@ void dispc_restore_context(void) RR(TIMING_V(OMAP_DSS_CHANNEL_LCD)); RR(POL_FREQ(OMAP_DSS_CHANNEL_LCD)); RR(DIVISORo(OMAP_DSS_CHANNEL_LCD)); - RR(GLOBAL_ALPHA); + if (dss_has_feature(FEAT_GLOBAL_ALPHA)) + RR(GLOBAL_ALPHA); RR(SIZE_MGR(OMAP_DSS_CHANNEL_DIGIT)); RR(SIZE_MGR(OMAP_DSS_CHANNEL_LCD)); if (dss_has_feature(FEAT_MGR_LCD2)) { @@ -347,114 +358,81 @@ void dispc_restore_context(void) RR(DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD)); RR(DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD)); - RR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); - RR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); - RR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + if (dss_has_feature(FEAT_CPR)) { + RR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); + RR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); + RR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + } if (dss_has_feature(FEAT_MGR_LCD2)) { RR(DATA_CYCLE1(OMAP_DSS_CHANNEL_LCD2)); RR(DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD2)); RR(DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD2)); - RR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); - RR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); - RR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); + if (dss_has_feature(FEAT_CPR)) { + RR(CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); + RR(CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); + RR(CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); + } } - RR(OVL_PRELOAD(OMAP_DSS_GFX)); - - /* VID1 */ - RR(OVL_BA0(OMAP_DSS_VIDEO1)); - RR(OVL_BA1(OMAP_DSS_VIDEO1)); - RR(OVL_POSITION(OMAP_DSS_VIDEO1)); - RR(OVL_SIZE(OMAP_DSS_VIDEO1)); - RR(OVL_ATTRIBUTES(OMAP_DSS_VIDEO1)); - RR(OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO1)); - RR(OVL_ROW_INC(OMAP_DSS_VIDEO1)); - RR(OVL_PIXEL_INC(OMAP_DSS_VIDEO1)); - RR(OVL_FIR(OMAP_DSS_VIDEO1)); - RR(OVL_PICTURE_SIZE(OMAP_DSS_VIDEO1)); - RR(OVL_ACCU0(OMAP_DSS_VIDEO1)); - RR(OVL_ACCU1(OMAP_DSS_VIDEO1)); - - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, i)); - - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, i)); + if (dss_has_feature(FEAT_PRELOAD)) + RR(OVL_PRELOAD(OMAP_DSS_GFX)); - for (i = 0; i < 5; i++) - RR(OVL_CONV_COEF(OMAP_DSS_VIDEO1, i)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, i)); - - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - RR(OVL_BA0_UV(OMAP_DSS_VIDEO1)); - RR(OVL_BA1_UV(OMAP_DSS_VIDEO1)); - RR(OVL_FIR2(OMAP_DSS_VIDEO1)); - RR(OVL_ACCU2_0(OMAP_DSS_VIDEO1)); - RR(OVL_ACCU2_1(OMAP_DSS_VIDEO1)); + /* VID1-3 */ + for (o = OMAP_DSS_VIDEO1; o <= OMAP_DSS_VIDEO3; o++) { + if (o == OMAP_DSS_VIDEO3 && !dss_has_feature(FEAT_OVL_VID3)) + continue; - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, i)); + RR(OVL_BA0(o)); + RR(OVL_BA1(o)); + RR(OVL_POSITION(o)); + RR(OVL_SIZE(o)); + RR(OVL_ATTRIBUTES(o)); + RR(OVL_FIFO_THRESHOLD(o)); + RR(OVL_ROW_INC(o)); + RR(OVL_PIXEL_INC(o)); + RR(OVL_FIR(o)); + RR(OVL_PICTURE_SIZE(o)); + RR(OVL_ACCU0(o)); + RR(OVL_ACCU1(o)); for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, i)); + RR(OVL_FIR_COEF_H(o, i)); for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, i)); - } - if (dss_has_feature(FEAT_ATTR2)) - RR(OVL_ATTRIBUTES2(OMAP_DSS_VIDEO1)); - - RR(OVL_PRELOAD(OMAP_DSS_VIDEO1)); - - /* VID2 */ - RR(OVL_BA0(OMAP_DSS_VIDEO2)); - RR(OVL_BA1(OMAP_DSS_VIDEO2)); - RR(OVL_POSITION(OMAP_DSS_VIDEO2)); - RR(OVL_SIZE(OMAP_DSS_VIDEO2)); - RR(OVL_ATTRIBUTES(OMAP_DSS_VIDEO2)); - RR(OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO2)); - RR(OVL_ROW_INC(OMAP_DSS_VIDEO2)); - RR(OVL_PIXEL_INC(OMAP_DSS_VIDEO2)); - RR(OVL_FIR(OMAP_DSS_VIDEO2)); - RR(OVL_PICTURE_SIZE(OMAP_DSS_VIDEO2)); - RR(OVL_ACCU0(OMAP_DSS_VIDEO2)); - RR(OVL_ACCU1(OMAP_DSS_VIDEO2)); + RR(OVL_FIR_COEF_HV(o, i)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, i)); + for (i = 0; i < 5; i++) + RR(OVL_CONV_COEF(o, i)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, i)); - - for (i = 0; i < 5; i++) - RR(OVL_CONV_COEF(OMAP_DSS_VIDEO2, i)); + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (i = 0; i < 8; i++) + RR(OVL_FIR_COEF_V(o, i)); + } - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, i)); + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + RR(OVL_BA0_UV(o)); + RR(OVL_BA1_UV(o)); + RR(OVL_FIR2(o)); + RR(OVL_ACCU2_0(o)); + RR(OVL_ACCU2_1(o)); - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - RR(OVL_BA0_UV(OMAP_DSS_VIDEO2)); - RR(OVL_BA1_UV(OMAP_DSS_VIDEO2)); - RR(OVL_FIR2(OMAP_DSS_VIDEO2)); - RR(OVL_ACCU2_0(OMAP_DSS_VIDEO2)); - RR(OVL_ACCU2_1(OMAP_DSS_VIDEO2)); + for (i = 0; i < 8; i++) + RR(OVL_FIR_COEF_H2(o, i)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, i)); + for (i = 0; i < 8; i++) + RR(OVL_FIR_COEF_HV2(o, i)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, i)); + for (i = 0; i < 8; i++) + RR(OVL_FIR_COEF_V2(o, i)); + } + if (dss_has_feature(FEAT_ATTR2)) + RR(OVL_ATTRIBUTES2(o)); - for (i = 0; i < 8; i++) - RR(OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, i)); + if (dss_has_feature(FEAT_PRELOAD)) + RR(OVL_PRELOAD(o)); } - if (dss_has_feature(FEAT_ATTR2)) - RR(OVL_ATTRIBUTES2(OMAP_DSS_VIDEO2)); - - RR(OVL_PRELOAD(OMAP_DSS_VIDEO2)); if (dss_has_feature(FEAT_CORE_CLK_DIV)) RR(DIVISOR); @@ -471,19 +449,176 @@ void dispc_restore_context(void) * the context is fully restored */ RR(IRQENABLE); + + DSSDBG("context restored\n"); } #undef SR #undef RR -static inline void enable_clocks(bool enable) +static u32 dispc_calculate_threshold(enum omap_plane plane, u32 paddr, + u32 puv_addr, u16 width, u16 height, + s32 row_inc, s32 pix_inc) { - if (enable) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); - else - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + int shift; + u32 channel_no = plane; + u32 val, burstsize, doublestride; + u32 rotation, bursttype, color_mode; + struct dispc_config dispc_reg_config; + + if (width >= 1920) + return 1500; + + /* Get the burst size */ + shift = (plane == OMAP_DSS_GFX) ? 6 : 14; + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + burstsize = FLD_GET(val, shift + 1, shift); + doublestride = FLD_GET(val, 22, 22); + rotation = FLD_GET(val, 13, 12); + bursttype = FLD_GET(val, 29, 29); + color_mode = FLD_GET(val, 4, 1); + + /* base address for frame (Luma frame in case of YUV420) */ + dispc_reg_config.ba = paddr; + /* base address for Chroma frame in case of YUV420 */ + dispc_reg_config.bacbcr = puv_addr; + /* OrgSizeX for frame */ + dispc_reg_config.sizex = width - 1; + /* OrgSizeY for frame */ + dispc_reg_config.sizey = height - 1; + /* burst size */ + dispc_reg_config.burstsize = burstsize; + /* pixel increment */ + dispc_reg_config.pixelinc = pix_inc; + /* row increment */ + dispc_reg_config.rowinc = row_inc; + /* burst type: 1D/2D */ + dispc_reg_config.bursttype = bursttype; + /* chroma DoubleStride when in YUV420 format */ + dispc_reg_config.doublestride = doublestride; + /* Pixcel format of the frame.*/ + dispc_reg_config.format = color_mode; + /* Rotation of frame */ + dispc_reg_config.rotation = rotation; + + /* DMA buffer allications - assuming reset values */ + dispc_reg_config.gfx_top_buffer = 0; + dispc_reg_config.gfx_bottom_buffer = 0; + dispc_reg_config.vid1_top_buffer = 1; + dispc_reg_config.vid1_bottom_buffer = 1; + dispc_reg_config.vid2_top_buffer = 2; + dispc_reg_config.vid2_bottom_buffer = 2; + dispc_reg_config.vid3_top_buffer = 3; + dispc_reg_config.vid3_bottom_buffer = 3; + dispc_reg_config.wb_top_buffer = 4; + dispc_reg_config.wb_bottom_buffer = 4; + + /* antiFlicker is off */ + dispc_reg_config.antiflicker = 0; + + return sa_calc_wrap(&dispc_reg_config, channel_no); } +int dispc_runtime_get(void) +{ + int r; + + mutex_lock(&dispc.runtime_lock); + + if (dispc.runtime_count++ == 0) { + DSSDBG("dispc_runtime_get\n"); + + /* + * OMAP4 ERRATUM xxxx: Mstandby and disconnect protocol issue + * Impacts: all OMAP4 devices + * Simplfied Description: + * issue #1: The handshake between IP modules on L3_1 and L3_2 + * peripherals with PRCM has a limitation in a certain time + * window of L4 clock cycle. Due to the fact that a wrong + * variant of stall signal was used in circuit of PRCM, the + * intitator-interconnect protocol is broken when the time + * window is hit where the PRCM requires the interconnect to go + * to idle while intitator asks to wakeup. + * Issue #2: DISPC asserts a sub-mstandby signal for a short + * period. In this time interval, IP block requests + * disconnection of Master port, and results in Mstandby and + * wait request to PRCM. In parallel, if mstandby is de-asserted + * by DISPC simultaneously, interconnect requests for a + * reconnect for one cycle alone resulting in a disconnect + * protocol violation and a deadlock of the system. + * + * Workaround: + * L3_1 clock domain must not be programmed in HW_AUTO if + * Static dependency with DSS is enabled and DSS clock domain + * is ON. Same for L3_2. + */ + if (cpu_is_omap44xx()) { + clkdm_deny_idle(l3_1_clkdm); + clkdm_deny_idle(l3_2_clkdm); + } + + r = dss_runtime_get(); + if (r) + goto err_dss_get; + + /* XXX dispc fclk can also come from DSI PLL */ + clk_enable(dispc.dss_clk); + + r = pm_runtime_get_sync(&dispc.pdev->dev); + WARN_ON(r); + if (r < 0) + goto err_runtime_get; + + dispc_restore_context(); + } + + mutex_unlock(&dispc.runtime_lock); + + return 0; + +err_runtime_get: + clk_disable(dispc.dss_clk); + dss_runtime_put(); +err_dss_get: + mutex_unlock(&dispc.runtime_lock); + + return r; +} + +void dispc_runtime_put(void) +{ + mutex_lock(&dispc.runtime_lock); + + if (--dispc.runtime_count == 0) { + int r; + + DSSDBG("dispc_runtime_put\n"); + + dispc_save_context(); + + r = pm_runtime_put_sync(&dispc.pdev->dev); + WARN_ON(r); + + clk_disable(dispc.dss_clk); + + dss_runtime_put(); + + /* + * OMAP4 ERRATUM xxxx: Mstandby and disconnect protocol issue + * Workaround: + * Restore L3_1 amd L3_2 CD to HW_AUTO, when DSS module idles. + */ + if (cpu_is_omap44xx()) { + clkdm_allow_idle(l3_1_clkdm); + clkdm_allow_idle(l3_2_clkdm); + } + + } + + mutex_unlock(&dispc.runtime_lock); +} + + bool dispc_go_busy(enum omap_channel channel) { int bit; @@ -505,8 +640,6 @@ void dispc_go(enum omap_channel channel) int bit; bool enable_bit, go_bit; - enable_clocks(1); - if (channel == OMAP_DSS_CHANNEL_LCD || channel == OMAP_DSS_CHANNEL_LCD2) bit = 0; /* LCDENABLE */ @@ -520,7 +653,7 @@ void dispc_go(enum omap_channel channel) enable_bit = REG_GET(DISPC_CONTROL, bit, bit) == 1; if (!enable_bit) - goto end; + return; if (channel == OMAP_DSS_CHANNEL_LCD || channel == OMAP_DSS_CHANNEL_LCD2) @@ -535,7 +668,7 @@ void dispc_go(enum omap_channel channel) if (go_bit) { DSSERR("GO bit not down for channel %d\n", channel); - goto end; + return; } DSSDBG("GO %s\n", channel == OMAP_DSS_CHANNEL_LCD ? "LCD" : @@ -545,8 +678,6 @@ void dispc_go(enum omap_channel channel) REG_FLD_MOD(DISPC_CONTROL2, 1, bit, bit); else REG_FLD_MOD(DISPC_CONTROL, 1, bit, bit); -end: - enable_clocks(0); } static void _dispc_write_firh_reg(enum omap_plane plane, int reg, u32 value) @@ -585,105 +716,227 @@ static void _dispc_write_firv2_reg(enum omap_plane plane, int reg, u32 value) dispc_write_reg(DISPC_OVL_FIR_COEF_V2(plane, reg), value); } -static void _dispc_set_scale_coef(enum omap_plane plane, int hscaleup, - int vscaleup, int five_taps, - enum omap_color_component color_comp) +static const struct dispc_hv_coef * +dispc_get_scaling_coef(u32 inc, bool five_taps) { - /* Coefficients for horizontal up-sampling */ - static const struct dispc_h_coef coef_hup[8] = { - { 0, 0, 128, 0, 0 }, - { -1, 13, 124, -8, 0 }, - { -2, 30, 112, -11, -1 }, - { -5, 51, 95, -11, -2 }, - { 0, -9, 73, 73, -9 }, - { -2, -11, 95, 51, -5 }, - { -1, -11, 112, 30, -2 }, - { 0, -8, 124, 13, -1 }, + static const struct dispc_hv_coef coef3_M8[8] = { + { 0, 0, 128, 0, 0 }, + { 0, 2, 123, 3, 0 }, + { 0, 5, 111, 12, 0 }, + { 0, 7, 89, 32, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 32, 89, 7, 0 }, + { 0, 12, 111, 5, 0 }, + { 0, 3, 123, 2, 0 }, }; - /* Coefficients for vertical up-sampling */ - static const struct dispc_v_coef coef_vup_3tap[8] = { - { 0, 0, 128, 0, 0 }, - { 0, 3, 123, 2, 0 }, - { 0, 12, 111, 5, 0 }, - { 0, 32, 89, 7, 0 }, - { 0, 0, 64, 64, 0 }, - { 0, 7, 89, 32, 0 }, - { 0, 5, 111, 12, 0 }, - { 0, 2, 123, 3, 0 }, + static const struct dispc_hv_coef coef3_M16[8] = { + { 0, 36, 56, 36, 0 }, + { 0, 31, 57, 40, 0 }, + { 0, 27, 56, 45, 0 }, + { 0, 23, 55, 50, 0 }, + { 0, 55, 55, 18, 0 }, + { 0, 50, 55, 23, 0 }, + { 0, 45, 56, 27, 0 }, + { 0, 40, 57, 31, 0 }, }; - static const struct dispc_v_coef coef_vup_5tap[8] = { - { 0, 0, 128, 0, 0 }, - { -1, 13, 124, -8, 0 }, - { -2, 30, 112, -11, -1 }, - { -5, 51, 95, -11, -2 }, - { 0, -9, 73, 73, -9 }, - { -2, -11, 95, 51, -5 }, - { -1, -11, 112, 30, -2 }, - { 0, -8, 124, 13, -1 }, + static const struct dispc_hv_coef coef_M8[8] = { + { 0, 0, 128, 0, 0 }, + { 0, -8, 124, 13, -1 }, + { -1, -11, 112, 30, -2 }, + { -2, -11, 95, 51, -5 }, + { -9, 73, 73, -9, 0 }, + { -5, 51, 95, -11, -2 }, + { -2, 30, 112, -11, -1 }, + { -1, 13, 124, -8, 0 }, }; - /* Coefficients for horizontal down-sampling */ - static const struct dispc_h_coef coef_hdown[8] = { - { 0, 36, 56, 36, 0 }, - { 4, 40, 55, 31, -2 }, - { 8, 44, 54, 27, -5 }, - { 12, 48, 53, 22, -7 }, - { -9, 17, 52, 51, 17 }, - { -7, 22, 53, 48, 12 }, - { -5, 27, 54, 44, 8 }, - { -2, 31, 55, 40, 4 }, + static const struct dispc_hv_coef coef_M9[8] = { + { 8, -8, 128, -8, 8 }, + { 14, -21, 126, 8, 1 }, + { 17, -27, 117, 30, -9 }, + { 17, -30, 103, 56, -18 }, + { -26, 83, 83, -26, 14 }, + { -18, 56, 103, -30, 17 }, + { -9, 30, 117, -27, 17 }, + { 1, 8, 126, -21, 14 }, }; - /* Coefficients for vertical down-sampling */ - static const struct dispc_v_coef coef_vdown_3tap[8] = { - { 0, 36, 56, 36, 0 }, - { 0, 40, 57, 31, 0 }, - { 0, 45, 56, 27, 0 }, - { 0, 50, 55, 23, 0 }, - { 0, 18, 55, 55, 0 }, - { 0, 23, 55, 50, 0 }, - { 0, 27, 56, 45, 0 }, - { 0, 31, 57, 40, 0 }, + static const struct dispc_hv_coef coef_M10[8] = { + { -2, 2, 128, 2, -2 }, + { 5, -12, 125, 20, -10 }, + { 11, -22, 116, 41, -18 }, + { 15, -27, 102, 62, -24 }, + { -28, 83, 83, -28, 18 }, + { -24, 62, 102, -27, 15 }, + { -18, 41, 116, -22, 11 }, + { -10, 20, 125, -12, 5 }, }; - static const struct dispc_v_coef coef_vdown_5tap[8] = { - { 0, 36, 56, 36, 0 }, - { 4, 40, 55, 31, -2 }, - { 8, 44, 54, 27, -5 }, - { 12, 48, 53, 22, -7 }, - { -9, 17, 52, 51, 17 }, - { -7, 22, 53, 48, 12 }, - { -5, 27, 54, 44, 8 }, - { -2, 31, 55, 40, 4 }, + static const struct dispc_hv_coef coef_M11[8] = { + { -12, 12, 128, 12, -12 }, + { -4, -3, 124, 30, -19 }, + { 3, -15, 115, 49, -24 }, + { 9, -22, 101, 67, -27 }, + { -26, 83, 83, -26, 14 }, + { -27, 67, 101, -22, 9 }, + { -24, 49, 115, -15, 3 }, + { -19, 30, 124, -3, -4 }, }; - const struct dispc_h_coef *h_coef; - const struct dispc_v_coef *v_coef; - int i; + static const struct dispc_hv_coef coef_M12[8] = { + { -19, 21, 124, 21, -19 }, + { -12, 6, 120, 38, -24 }, + { -6, -7, 112, 55, -26 }, + { 1, -16, 98, 70, -25 }, + { -21, 82, 82, -21, 6 }, + { -25, 70, 98, -16, 1 }, + { -26, 55, 112, -7, -6 }, + { -24, 38, 120, 6, -12 }, + }; - if (hscaleup) - h_coef = coef_hup; - else - h_coef = coef_hdown; + static const struct dispc_hv_coef coef_M13[8] = { + { -22, 27, 118, 27, -22 }, + { -18, 13, 115, 43, -25 }, + { -12, 0, 107, 58, -25 }, + { -6, -10, 95, 71, -22 }, + { -17, 81, 81, -17, 0 }, + { -22, 71, 95, -10, -6 }, + { -25, 58, 107, 0, -12 }, + { -25, 43, 115, 13, -18 }, + }; - if (vscaleup) - v_coef = five_taps ? coef_vup_5tap : coef_vup_3tap; - else - v_coef = five_taps ? coef_vdown_5tap : coef_vdown_3tap; + static const struct dispc_hv_coef coef_M14[8] = { + { -23, 32, 110, 32, -23 }, + { -20, 18, 108, 46, -24 }, + { -16, 6, 101, 59, -22 }, + { -11, -4, 91, 70, -18 }, + { -11, 78, 78, -11, -6 }, + { -18, 70, 91, -4, -11 }, + { -22, 59, 101, 6, -16 }, + { -24, 46, 108, 18, -20 }, + }; + + static const struct dispc_hv_coef coef_M16[8] = { + { -20, 37, 94, 37, -20 }, + { -21, 26, 93, 48, -18 }, + { -19, 15, 88, 58, -14 }, + { -17, 6, 82, 66, -9 }, + { -2, 73, 73, -2, -14 }, + { -9, 66, 82, 6, -17 }, + { -14, 58, 88, 15, -19 }, + { -18, 48, 93, 26, -21 }, + }; + + static const struct dispc_hv_coef coef_M19[8] = { + { -12, 38, 76, 38, -12 }, + { -14, 31, 72, 47, -8 }, + { -16, 22, 73, 53, -4 }, + { -16, 15, 69, 59, 1 }, + { 8, 64, 64, 8, -16 }, + { 1, 59, 69, 15, -16 }, + { -4, 53, 73, 22, -16 }, + { -9, 47, 72, 31, -13 }, + }; + + static const struct dispc_hv_coef coef_M22[8] = { + { -6, 37, 66, 37, -6 }, + { -8, 32, 61, 44, -1 }, + { -11, 25, 63, 48, 3 }, + { -13, 19, 61, 53, 8 }, + { 13, 58, 58, 13, -14 }, + { 8, 53, 61, 19, -13 }, + { 3, 48, 63, 25, -11 }, + { -2, 44, 61, 32, -7 }, + }; + + static const struct dispc_hv_coef coef_M26[8] = { + { 1, 36, 54, 36, 1 }, + { -2, 31, 55, 40, 4 }, + { -5, 27, 54, 44, 8 }, + { -8, 22, 53, 48, 13 }, + { 18, 51, 51, 18, -10 }, + { 13, 48, 53, 22, -8 }, + { 8, 44, 54, 27, -5 }, + { 4, 40, 55, 31, -2 }, + }; + + static const struct dispc_hv_coef coef_M32[8] = { + { 7, 34, 46, 34, 7 }, + { 4, 31, 46, 37, 10 }, + { 1, 27, 46, 39, 14 }, + { -1, 24, 46, 42, 17 }, + { 21, 45, 45, 21, -4 }, + { 17, 42, 46, 24, -1 }, + { 14, 39, 46, 28, 1 }, + { 10, 37, 46, 31, 4 }, + }; + + inc >>= 7; /* /= 128 */ + if (five_taps) { + if (inc > 26) + return coef_M32; + if (inc > 22) + return coef_M26; + if (inc > 19) + return coef_M22; + if (inc > 16) + return coef_M19; + if (inc > 14) + return coef_M16; + if (inc > 13) + return coef_M14; + if (inc > 12) + return coef_M13; + if (inc > 11) + return coef_M12; + if (inc > 10) + return coef_M11; + if (inc > 9) + return coef_M10; + if (inc > 8) + return coef_M9; + /* reduce blockiness when upscaling much */ + if (inc > 3) + return coef_M8; + if (inc > 2) + return coef_M11; + if (inc > 1) + return coef_M16; + return coef_M19; + } else { + if (inc > 14) + return coef3_M16; + /* reduce blockiness when upscaling much */ + if (inc > 3) + return coef3_M8; + return coef3_M16; + } +} + +static void _dispc_set_scale_coef(enum omap_plane plane, int hinc, + int vinc, bool five_taps, + enum omap_color_component color_comp) +{ + const struct dispc_hv_coef *h_coef; + const struct dispc_hv_coef *v_coef; + int i; + + h_coef = dispc_get_scaling_coef(hinc, true); + v_coef = dispc_get_scaling_coef(vinc, five_taps); for (i = 0; i < 8; i++) { u32 h, hv; - h = FLD_VAL(h_coef[i].hc0, 7, 0) - | FLD_VAL(h_coef[i].hc1, 15, 8) - | FLD_VAL(h_coef[i].hc2, 23, 16) - | FLD_VAL(h_coef[i].hc3, 31, 24); - hv = FLD_VAL(h_coef[i].hc4, 7, 0) - | FLD_VAL(v_coef[i].vc0, 15, 8) - | FLD_VAL(v_coef[i].vc1, 23, 16) - | FLD_VAL(v_coef[i].vc2, 31, 24); + h = FLD_VAL(h_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(h_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(h_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(h_coef[i].hc3_vc2, 31, 24); + hv = FLD_VAL(h_coef[i].hc4_vc22, 7, 0) + | FLD_VAL(v_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(v_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(v_coef[i].hc3_vc2, 31, 24); if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { _dispc_write_firh_reg(plane, i, h); @@ -692,14 +945,13 @@ static void _dispc_set_scale_coef(enum omap_plane plane, int hscaleup, _dispc_write_firh2_reg(plane, i, h); _dispc_write_firhv2_reg(plane, i, hv); } - } if (five_taps) { for (i = 0; i < 8; i++) { u32 v; - v = FLD_VAL(v_coef[i].vc00, 7, 0) - | FLD_VAL(v_coef[i].vc22, 15, 8); + v = FLD_VAL(v_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(v_coef[i].hc4_vc22, 15, 8); if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) _dispc_write_firv_reg(plane, i, v); else @@ -708,49 +960,22 @@ static void _dispc_set_scale_coef(enum omap_plane plane, int hscaleup, } } -static void _dispc_setup_color_conv_coef(void) +void _dispc_setup_color_conv_coef(enum omap_plane plane, + const struct omap_dss_cconv_coefs *ct) { - const struct color_conv_coef { - int ry, rcr, rcb, gy, gcr, gcb, by, bcr, bcb; - int full_range; - } ctbl_bt601_5 = { - 298, 409, 0, 298, -208, -100, 298, 0, 517, 0, - }; - - const struct color_conv_coef *ct; + BUG_ON(plane < OMAP_DSS_VIDEO1 || plane > OMAP_DSS_VIDEO3); #define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) - ct = &ctbl_bt601_5; - - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 0), - CVAL(ct->rcr, ct->ry)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 1), - CVAL(ct->gy, ct->rcb)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 2), - CVAL(ct->gcb, ct->gcr)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 3), - CVAL(ct->bcr, ct->by)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 4), - CVAL(0, ct->bcb)); - - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 0), - CVAL(ct->rcr, ct->ry)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 1), - CVAL(ct->gy, ct->rcb)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 2), - CVAL(ct->gcb, ct->gcr)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 3), - CVAL(ct->bcr, ct->by)); - dispc_write_reg(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 4), - CVAL(0, ct->bcb)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 0), CVAL(ct->rcr, ct->ry)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 1), CVAL(ct->gy, ct->rcb)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 2), CVAL(ct->gcb, ct->gcr)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 3), CVAL(ct->bcr, ct->by)); + dispc_write_reg(DISPC_OVL_CONV_COEF(plane, 4), CVAL(0, ct->bcb)); #undef CVAL - REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(OMAP_DSS_VIDEO1), - ct->full_range, 11, 11); - REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(OMAP_DSS_VIDEO2), - ct->full_range, 11, 11); + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), ct->full_range, 11, 11); } @@ -825,8 +1050,12 @@ static void _dispc_setup_global_alpha(enum omap_plane plane, u8 global_alpha) if (plane == OMAP_DSS_GFX) REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, 7, 0); + else if (plane == OMAP_DSS_VIDEO1) + REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, 15, 8); else if (plane == OMAP_DSS_VIDEO2) REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, 23, 16); + else if (plane == OMAP_DSS_VIDEO3) + REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, 31, 24); } static void _dispc_set_pix_inc(enum omap_plane plane, s32 inc) @@ -847,11 +1076,11 @@ static void _dispc_set_color_mode(enum omap_plane plane, switch (color_mode) { case OMAP_DSS_COLOR_NV12: m = 0x0; break; - case OMAP_DSS_COLOR_RGB12U: + case OMAP_DSS_COLOR_RGBX16: m = 0x1; break; case OMAP_DSS_COLOR_RGBA16: m = 0x2; break; - case OMAP_DSS_COLOR_RGBX16: + case OMAP_DSS_COLOR_RGB12U: m = 0x4; break; case OMAP_DSS_COLOR_ARGB16: m = 0x5; break; @@ -901,8 +1130,10 @@ static void _dispc_set_color_mode(enum omap_plane plane, case OMAP_DSS_COLOR_RGB24P: m = 0x9; break; case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_RGBX16: m = 0xa; break; case OMAP_DSS_COLOR_UYVY: + case OMAP_DSS_COLOR_RGBA16: m = 0xb; break; case OMAP_DSS_COLOR_ARGB32: m = 0xc; break; @@ -920,7 +1151,7 @@ static void _dispc_set_color_mode(enum omap_plane plane, REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), m, 4, 1); } -static void _dispc_set_channel_out(enum omap_plane plane, +void dispc_set_channel_out(enum omap_plane plane, enum omap_channel channel) { int shift; @@ -933,6 +1164,7 @@ static void _dispc_set_channel_out(enum omap_plane plane, break; case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: shift = 16; break; default: @@ -973,14 +1205,13 @@ void dispc_set_burst_size(enum omap_plane plane, int shift; u32 val; - enable_clocks(1); - switch (plane) { case OMAP_DSS_GFX: shift = 6; break; case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: shift = 14; break; default: @@ -991,8 +1222,6 @@ void dispc_set_burst_size(enum omap_plane plane, val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); val = FLD_MOD(val, burst_size, shift+1, shift); dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); - - enable_clocks(0); } void dispc_enable_gamma_table(bool enable) @@ -1009,6 +1238,63 @@ void dispc_enable_gamma_table(bool enable) REG_FLD_MOD(DISPC_CONFIG, enable, 9, 9); } +void dispc_set_zorder(enum omap_plane plane, + enum omap_overlay_zorder zorder) +{ + u32 val; + + if (!dss_has_feature(FEAT_OVL_ZORDER)) + return; + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + val = FLD_MOD(val, zorder, 27, 26); + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} + +void dispc_enable_zorder(enum omap_plane plane, bool enable) +{ + u32 val; + + if (!dss_has_feature(FEAT_OVL_ZORDER)) + return; + val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + val = FLD_MOD(val, enable, 25, 25); + dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} + +void dispc_enable_cpr(enum omap_channel channel, bool enable) +{ + u16 reg; + + if (channel == OMAP_DSS_CHANNEL_LCD) + reg = DISPC_CONFIG; + else if (channel == OMAP_DSS_CHANNEL_LCD2) + reg = DISPC_CONFIG2; + else + return; + + REG_FLD_MOD(reg, enable, 15, 15); +} + +void dispc_set_cpr_coef(enum omap_channel channel, + struct omap_dss_cpr_coefs *coefs) +{ + u32 coef_r, coef_g, coef_b; + + if (channel != OMAP_DSS_CHANNEL_LCD && channel != OMAP_DSS_CHANNEL_LCD2) + return; + + coef_r = FLD_VAL(coefs->rr, 31, 22) | FLD_VAL(coefs->rg, 20, 11) | + FLD_VAL(coefs->rb, 9, 0); + coef_g = FLD_VAL(coefs->gr, 31, 22) | FLD_VAL(coefs->gg, 20, 11) | + FLD_VAL(coefs->gb, 9, 0); + coef_b = FLD_VAL(coefs->br, 31, 22) | FLD_VAL(coefs->bg, 20, 11) | + FLD_VAL(coefs->bb, 9, 0); + + dispc_write_reg(DISPC_CPR_COEF_R(channel), coef_r); + dispc_write_reg(DISPC_CPR_COEF_G(channel), coef_g); + dispc_write_reg(DISPC_CPR_COEF_B(channel), coef_b); +} + static void _dispc_set_vid_color_conv(enum omap_plane plane, bool enable) { u32 val; @@ -1029,9 +1315,7 @@ void dispc_enable_replication(enum omap_plane plane, bool enable) else bit = 10; - enable_clocks(1); REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable, bit, bit); - enable_clocks(0); } void dispc_set_lcd_size(enum omap_channel channel, u16 width, u16 height) @@ -1039,9 +1323,7 @@ void dispc_set_lcd_size(enum omap_channel channel, u16 width, u16 height) u32 val; BUG_ON((width > (1 << 11)) || (height > (1 << 11))); val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); - enable_clocks(1); dispc_write_reg(DISPC_SIZE_MGR(channel), val); - enable_clocks(0); } void dispc_set_digit_size(u16 width, u16 height) @@ -1049,9 +1331,7 @@ void dispc_set_digit_size(u16 width, u16 height) u32 val; BUG_ON((width > (1 << 11)) || (height > (1 << 11))); val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); - enable_clocks(1); dispc_write_reg(DISPC_SIZE_MGR(OMAP_DSS_CHANNEL_DIGIT), val); - enable_clocks(0); } static void dispc_read_plane_fifo_sizes(void) @@ -1060,8 +1340,6 @@ static void dispc_read_plane_fifo_sizes(void) int plane; u8 start, end; - enable_clocks(1); - dss_feat_get_reg_field(FEAT_REG_FIFOSIZE, &start, &end); for (plane = 0; plane < ARRAY_SIZE(dispc.fifo_size); ++plane) { @@ -1069,8 +1347,6 @@ static void dispc_read_plane_fifo_sizes(void) start, end); dispc.fifo_size[plane] = size; } - - enable_clocks(0); } u32 dispc_get_plane_fifo_size(enum omap_plane plane) @@ -1085,8 +1361,6 @@ void dispc_setup_plane_fifo(enum omap_plane plane, u32 low, u32 high) dss_feat_get_reg_field(FEAT_REG_FIFOHIGHTHRESHOLD, &hi_start, &hi_end); dss_feat_get_reg_field(FEAT_REG_FIFOLOWTHRESHOLD, &lo_start, &lo_end); - enable_clocks(1); - DSSDBG("fifo(%d) low/high old %u/%u, new %u/%u\n", plane, REG_GET(DISPC_OVL_FIFO_THRESHOLD(plane), @@ -1095,21 +1369,18 @@ void dispc_setup_plane_fifo(enum omap_plane plane, u32 low, u32 high) hi_start, hi_end), low, high); + /* preload to high threshold to avoid FIFO underflow */ + dispc_write_reg(DISPC_OVL_PRELOAD(plane), min(high, 0xfffu)); + dispc_write_reg(DISPC_OVL_FIFO_THRESHOLD(plane), FLD_VAL(high, hi_start, hi_end) | FLD_VAL(low, lo_start, lo_end)); - - enable_clocks(0); } void dispc_enable_fifomerge(bool enable) { - enable_clocks(1); - DSSDBG("FIFO merge %s\n", enable ? "enabled" : "disabled"); REG_FLD_MOD(DISPC_CONFIG, enable ? 1 : 0, 14, 14); - - enable_clocks(0); } static void _dispc_set_fir(enum omap_plane plane, @@ -1209,8 +1480,9 @@ static void _dispc_set_scaling_common(enum omap_plane plane, int accu0 = 0; int accu1 = 0; u32 l; + u16 y_adjust = color_mode == OMAP_DSS_COLOR_NV12 ? 2 : 0; - _dispc_set_scale_param(plane, orig_width, orig_height, + _dispc_set_scale_param(plane, orig_width, orig_height - y_adjust, out_width, out_height, five_taps, rotation, DISPC_COLOR_COMPONENT_RGB_Y); l = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); @@ -1262,6 +1534,7 @@ static void _dispc_set_scaling_uv(enum omap_plane plane, { int scale_x = out_width != orig_width; int scale_y = out_height != orig_height; + u16 y_adjust = 0; if (!dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) return; @@ -1278,6 +1551,7 @@ static void _dispc_set_scaling_uv(enum omap_plane plane, orig_height >>= 1; /* UV is subsampled by 2 horz.*/ orig_width >>= 1; + y_adjust = 1; break; case OMAP_DSS_COLOR_YUV2: case OMAP_DSS_COLOR_UYVY: @@ -1301,7 +1575,7 @@ static void _dispc_set_scaling_uv(enum omap_plane plane, if (out_height != orig_height) scale_y = true; - _dispc_set_scale_param(plane, orig_width, orig_height, + _dispc_set_scale_param(plane, orig_width, orig_height - y_adjust, out_width, out_height, five_taps, rotation, DISPC_COLOR_COMPONENT_UV); @@ -1341,7 +1615,8 @@ static void _dispc_set_scaling(enum omap_plane plane, } static void _dispc_set_rotation_attrs(enum omap_plane plane, u8 rotation, - bool mirroring, enum omap_color_mode color_mode) + bool mirroring, enum omap_color_mode color_mode, + enum omap_dss_rotation_type type) { bool row_repeat = false; int vidrot = 0; @@ -1391,6 +1666,16 @@ static void _dispc_set_rotation_attrs(enum omap_plane plane, u8 rotation, if (dss_has_feature(FEAT_ROWREPEATENABLE)) REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), row_repeat ? 1 : 0, 18, 18); + + if (color_mode == OMAP_DSS_COLOR_NV12) { + /* this will never happen for GFX */ + /* 1D NV12 buffer is always non-rotated or vert. mirrored */ + bool doublestride = (rotation == OMAP_DSS_ROT_0 || + rotation == OMAP_DSS_ROT_180) && + type == OMAP_DSS_ROT_TILER; + /* DOUBLESTRIDE */ + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), doublestride, 22, 22); + } } static int color_mode_to_bpp(enum omap_color_mode color_mode) @@ -1439,6 +1724,28 @@ static s32 pixinc(int pixels, u8 ps) BUG(); } +static void calc_tiler_row_rotation(struct tiler_view_t *view, + u16 width, int bpp, int y_decim, + s32 *row_inc, unsigned *offset1, bool ilace) +{ + /* assume TB. We worry about swapping top/bottom outside of this call */ + + if (ilace) { + /* even and odd frames are interleaved */ + + /* offset1 is always at an odd line */ + *offset1 = view->v_inc * (y_decim | 1); + y_decim *= 2; + } + *row_inc = view->v_inc * y_decim + 1 - width * bpp; + + DSSDBG(" ps: %d/%d, width: %d/%d, offset1: %d," + " height: %d, row_inc:%d\n", view->bpp, bpp, + view->width, width, *offset1, view->height, *row_inc); + + return; +} + static void calc_vrfb_rotation_offset(u8 rotation, bool mirror, u16 screen_width, u16 width, u16 height, @@ -1529,7 +1836,7 @@ static void calc_dma_rotation_offset(u8 rotation, bool mirror, enum omap_color_mode color_mode, bool fieldmode, unsigned int field_offset, unsigned *offset0, unsigned *offset1, - s32 *row_inc, s32 *pix_inc) + s32 *row_inc, s32 *pix_inc, int x_decim, int y_decim) { u8 ps; u16 fbw, fbh; @@ -1542,6 +1849,15 @@ static void calc_dma_rotation_offset(u8 rotation, bool mirror, case OMAP_DSS_COLOR_CLUT8: BUG(); return; + case OMAP_DSS_COLOR_YUV2: + case OMAP_DSS_COLOR_UYVY: + if (cpu_is_omap44xx()) { + /* on OMAP4 YUYV is handled as 32-bit data */ + ps = 4; + screen_width /= 2; + break; + } + /* fall through */ default: ps = color_mode_to_bpp(color_mode) / 8; break; @@ -1571,10 +1887,10 @@ static void calc_dma_rotation_offset(u8 rotation, bool mirror, *offset0 = *offset1 + field_offset * screen_width * ps; else *offset0 = *offset1; - *row_inc = pixinc(1 + (screen_width - fbw) + + *row_inc = pixinc(1 + (y_decim * screen_width - fbw * x_decim) + (fieldmode ? screen_width : 0), ps); - *pix_inc = pixinc(1, ps); + *pix_inc = pixinc(x_decim, ps); break; case OMAP_DSS_ROT_90: *offset1 = screen_width * (fbh - 1) * ps; @@ -1671,6 +1987,18 @@ static unsigned long calc_fclk_five_taps(enum omap_channel channel, u16 width, /* FIXME venc pclk? */ u64 tmp, pclk = dispc_pclk_rate(channel); + if (cpu_is_omap44xx()) { + /* do conservative TRM value on OMAP4 ES1.0 */ + if (omap_rev() == OMAP4430_REV_ES1_0) + return pclk * DIV_ROUND_UP(width, out_width) * + DIV_ROUND_UP(height, out_height); + + /* since 4430 ES2.0, fclk requirement only depends on width */ + pclk *= max(width, out_width); + do_div(pclk, out_width); + return pclk; + } + if (height > out_height) { /* FIXME get real display PPL */ unsigned int ppl = 800; @@ -1706,6 +2034,11 @@ static unsigned long calc_fclk(enum omap_channel channel, u16 width, { unsigned int hf, vf; + /* on OMAP4 three-tap and five-tap clock requirements are the same */ + if (cpu_is_omap44xx()) + return calc_fclk_five_taps(channel, width, height, out_width, + out_height, 0); + /* * FIXME how to determine the 'A' factor * for the no downscaling case ? @@ -1729,27 +2062,188 @@ static unsigned long calc_fclk(enum omap_channel channel, u16 width, return dispc_pclk_rate(channel) * vf * hf; } -void dispc_set_channel_out(enum omap_plane plane, enum omap_channel channel_out) +int dispc_scaling_decision(u16 width, u16 height, + u16 out_width, u16 out_height, + enum omap_plane plane, + enum omap_color_mode color_mode, + enum omap_channel channel, u8 rotation, + enum omap_dss_rotation_type type, + u16 min_x_decim, u16 max_x_decim, + u16 min_y_decim, u16 max_y_decim, + u16 *x_decim, u16 *y_decim, bool *five_taps) { - enable_clocks(1); - _dispc_set_channel_out(plane, channel_out); - enable_clocks(0); + int maxdownscale = cpu_is_omap24xx() ? 2 : 4; + int bpp = color_mode_to_bpp(color_mode); + + /* + * For now only whole byte formats on OMAP4 can be predecimated. + * Later SDMA decimation support may be added + */ + bool can_decimate_x = cpu_is_omap44xx() && !(bpp & 7); + bool can_decimate_y = can_decimate_x; + + bool can_scale = plane != OMAP_DSS_GFX; + + u16 in_width, in_height; + unsigned long fclk = 0, fclk5 = 0; + int min_factor, max_factor; /* decimation search limits */ + int x, y; /* decimation search variables */ + unsigned long fclk_max = dispc_fclk_rate(); + u16 y_decim_limit = type == OMAP_DSS_ROT_TILER ? 2 : 16; + + /* No decimation for bitmap formats */ + if (color_mode == OMAP_DSS_COLOR_CLUT1 || + color_mode == OMAP_DSS_COLOR_CLUT2 || + color_mode == OMAP_DSS_COLOR_CLUT4 || + color_mode == OMAP_DSS_COLOR_CLUT8) { + *x_decim = 1; + *y_decim = 1; + *five_taps = false; + return 0; + } + + /* restrict search region based on whether we can decimate */ + if (!can_decimate_x) { + if (min_x_decim > 1) + return -EINVAL; + min_x_decim = max_x_decim = 1; + } else { + if (max_x_decim > 16) + max_x_decim = 16; + } + + if (!can_decimate_y) { + if (min_y_decim > 1) + return -EINVAL; + min_y_decim = max_y_decim = 1; + } else { + if (max_y_decim > y_decim_limit) + max_y_decim = y_decim_limit; + } + + /* + * Find best supported quality. In the search algorithm, we make use + * of the fact, that increased decimation in either direction will have + * lower quality. However, we do not differentiate horizontal and + * vertical decimation even though they may affect quality differently + * given the exact geometry involved. + * + * Also, since the clock calculations are abstracted, we cannot make + * assumptions on how decimation affects the clock rates in our search. + * + * We search the whole search region in increasing layers from + * min_factor to max_factor. In each layer we search in increasing + * factors alternating between x and y axis: + * + * x: 1 2 3 + * y: + * 1 1st | 3rd | 6th | + * ----+ | | + * 2 2nd 4th | 8th | + * ------------+ | + * 3 5th 7th 9th | + * --------------------+ + */ + min_factor = min(min_x_decim, min_y_decim); + max_factor = max(max_x_decim, max_y_decim); + x = min_x_decim; + y = min_y_decim; + while (1) { + if (x < min_x_decim || x > max_x_decim || + y < min_y_decim || y > max_y_decim) + goto loop; + + in_width = DIV_ROUND_UP(width, x); + in_height = DIV_ROUND_UP(height, y); + + if (in_width == out_width && in_height == out_height) + break; + + if (!can_scale) + goto loop; + + if (out_width * maxdownscale < in_width || + out_height * maxdownscale < in_height) + goto loop; + + /* Use 5-tap filter unless must use 3-tap */ + if (!cpu_is_omap44xx()) + *five_taps = in_width <= 1024; + else if (omap_rev() == OMAP4430_REV_ES1_0) + *five_taps = in_width <= 1280; + else + *five_taps = true; + + /* + * Predecimation on OMAP4 still fetches the whole lines + * :TODO: How does it affect the required clock speed? + */ + fclk = calc_fclk(channel, in_width, in_height, + out_width, out_height); + fclk5 = *five_taps ? + calc_fclk_five_taps(channel, in_width, in_height, + out_width, out_height, color_mode) : 0; + + DSSDBG("%d*%d,%d*%d->%d,%d requires %lu(3T), %lu(5T) Hz\n", + in_width, x, in_height, y, out_width, out_height, + fclk, fclk5); + + /* for now we always use 5-tap unless 3-tap is required */ + if (*five_taps) + fclk = fclk5; + + /* OMAP2/3 has a scaler size limitation */ + if (!cpu_is_omap44xx() && in_width > (1024 << !*five_taps)) + goto loop; + + DSSDBG("required fclk rate = %lu Hz\n", fclk); + DSSDBG("current fclk rate = %lu Hz\n", fclk_max); + + if (fclk > fclk_max) + goto loop; + break; + +loop: + /* err if exhausted search region */ + if (x == max_x_decim && y == max_y_decim) { + DSSERR("failed to set up scaling %u*%u to %u*%u, " + "required fclk rate = %lu Hz, " + "current = %lu Hz\n", + width, height, out_width, out_height, + fclk, fclk_max); + return -EINVAL; + } + + /* get to next factor */ + if (x == y) { + x = min_factor; + y++; + } else { + swap(x, y); + if (x < y) + x++; + } + } + + *x_decim = x; + *y_decim = y; + return 0; } -static int _dispc_setup_plane(enum omap_plane plane, +int dispc_setup_plane(enum omap_plane plane, u32 paddr, u16 screen_width, u16 pos_x, u16 pos_y, u16 width, u16 height, u16 out_width, u16 out_height, enum omap_color_mode color_mode, bool ilace, + int x_decim, int y_decim, bool five_taps, enum omap_dss_rotation_type rotation_type, - u8 rotation, int mirror, + u8 rotation, bool mirror, u8 global_alpha, u8 pre_mult_alpha, enum omap_channel channel, u32 puv_addr) { - const int maxdownscale = cpu_is_omap34xx() ? 4 : 2; - bool five_taps = 0; + const int maxdownscale = cpu_is_omap24xx() ? 2 : 4; bool fieldmode = 0; int cconv = 0; unsigned offset0, offset1; @@ -1757,6 +2251,18 @@ static int _dispc_setup_plane(enum omap_plane plane, s32 pix_inc; u16 frame_height = height; unsigned int field_offset = 0; + int pixpg = (color_mode & + (OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY)) ? 2 : 1; + unsigned long tiler_width, tiler_height; + u32 fifo_high, fifo_low; + + DSSDBG("dispc_setup_plane %d, pa %x, sw %d, %d,%d, %d/%dx%d/%d -> " + "%dx%d, ilace %d, cmode %x, rot %d, mir %d chan %d %dtap\n", + plane, paddr, screen_width, pos_x, pos_y, + width, x_decim, height, y_decim, + out_width, out_height, + ilace, color_mode, + rotation, mirror, channel, five_taps ? 5 : 3); if (paddr == 0) return -EINVAL; @@ -1778,59 +2284,41 @@ static int _dispc_setup_plane(enum omap_plane plane, if (!dss_feat_color_mode_supported(plane, color_mode)) return -EINVAL; + /* predecimate */ + + /* adjust for group-of-pixels*/ + if (rotation & 1) + height /= pixpg; + else + width /= pixpg; + + /* remember tiler block's size as we are reconstructing it */ + tiler_width = width; + tiler_height = height; + + width = DIV_ROUND_UP(width, x_decim); + height = DIV_ROUND_UP(height, y_decim); + + /* NV12 width has to be even (height apparently does not) */ + if (color_mode == OMAP_DSS_COLOR_NV12) + width &= ~1; + if (plane == OMAP_DSS_GFX) { if (width != out_width || height != out_height) return -EINVAL; } else { /* video plane */ - unsigned long fclk = 0; - - if (out_width < width / maxdownscale || - out_width > width * 8) + if (out_width < width / maxdownscale) return -EINVAL; - if (out_height < height / maxdownscale || - out_height > height * 8) + if (out_height < height / maxdownscale) return -EINVAL; if (color_mode == OMAP_DSS_COLOR_YUV2 || color_mode == OMAP_DSS_COLOR_UYVY || color_mode == OMAP_DSS_COLOR_NV12) cconv = 1; - - /* Must use 5-tap filter? */ - five_taps = height > out_height * 2; - - if (!five_taps) { - fclk = calc_fclk(channel, width, height, out_width, - out_height); - - /* Try 5-tap filter if 3-tap fclk is too high */ - if (cpu_is_omap34xx() && height > out_height && - fclk > dispc_fclk_rate()) - five_taps = true; - } - - if (width > (2048 >> five_taps)) { - DSSERR("failed to set up scaling, fclk too low\n"); - return -EINVAL; - } - - if (five_taps) - fclk = calc_fclk_five_taps(channel, width, height, - out_width, out_height, color_mode); - - DSSDBG("required fclk rate = %lu Hz\n", fclk); - DSSDBG("current fclk rate = %lu Hz\n", dispc_fclk_rate()); - - if (!fclk || fclk > dispc_fclk_rate()) { - DSSERR("failed to set up scaling, " - "required fclk rate = %lu Hz, " - "current fclk rate = %lu Hz\n", - fclk, dispc_fclk_rate()); - return -EINVAL; - } } if (ilace && !fieldmode) { @@ -1851,17 +2339,69 @@ static int _dispc_setup_plane(enum omap_plane plane, if (fieldmode) field_offset = 1; - if (rotation_type == OMAP_DSS_ROT_DMA) + /* default values */ + row_inc = pix_inc = 0x1; + offset0 = offset1 = 0x0; + + /* + * :HACK: we piggy back on UV separate feature for TILER to avoid + * having to keep rebase our FEAT_ enum until they add TILER. + */ + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + /* set BURSTTYPE */ + bool use_tiler = rotation_type == OMAP_DSS_ROT_TILER; + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), use_tiler, 29, 29); + } + + if (rotation_type == OMAP_DSS_ROT_TILER) { + struct tiler_view_t view = {0}; + int bpp = color_mode_to_bpp(color_mode) / 8; + /* tiler needs 0-degree width & height */ + if (rotation & 1) + swap(tiler_width, tiler_height); + + if (color_mode == OMAP_DSS_COLOR_YUV2 || + color_mode == OMAP_DSS_COLOR_UYVY) + tiler_width /= 2; + + tilview_create(&view, paddr, tiler_width, tiler_height); + tilview_rotate(&view, rotation * 90); + tilview_flip(&view, mirror, false); + paddr = view.tsptr; + + /* we cannot do TB field interlaced in rotated view */ + pix_inc = 1 + (x_decim - 1) * bpp * pixpg; + calc_tiler_row_rotation(&view, width * x_decim, bpp * pixpg, + y_decim, &row_inc, &offset1, ilace); + + DSSDBG("w, h = %ld %ld\n", tiler_width, tiler_height); + + if (puv_addr) { + tilview_create(&view, puv_addr, tiler_width / 2, + tiler_height / 2); + tilview_rotate(&view, rotation * 90); + tilview_flip(&view, mirror, false); + puv_addr = view.tsptr; + } + + } else if (rotation_type == OMAP_DSS_ROT_DMA) { calc_dma_rotation_offset(rotation, mirror, screen_width, width, frame_height, color_mode, fieldmode, field_offset, - &offset0, &offset1, &row_inc, &pix_inc); - else + &offset0, &offset1, &row_inc, &pix_inc, + x_decim, y_decim); + } else { calc_vrfb_rotation_offset(rotation, mirror, screen_width, width, frame_height, color_mode, fieldmode, field_offset, &offset0, &offset1, &row_inc, &pix_inc); + } + /* adjust back to pixels */ + if (rotation & 1) + height *= pixpg; + else + width *= pixpg; DSSDBG("offset0 %u, offset1 %u, row_inc %d, pix_inc %d\n", offset0, offset1, row_inc, pix_inc); @@ -1879,8 +2419,8 @@ static int _dispc_setup_plane(enum omap_plane plane, _dispc_set_row_inc(plane, row_inc); _dispc_set_pix_inc(plane, pix_inc); - DSSDBG("%d,%d %dx%d -> %dx%d\n", pos_x, pos_y, width, height, - out_width, out_height); + DSSDBG("%d,%d %d*%dx%d*%d -> %dx%d\n", pos_x, pos_y, width, x_decim, + height, y_decim, out_width, out_height); _dispc_set_plane_pos(plane, pos_x, pos_y); @@ -1895,17 +2435,30 @@ static int _dispc_setup_plane(enum omap_plane plane, _dispc_set_vid_color_conv(plane, cconv); } - _dispc_set_rotation_attrs(plane, rotation, mirror, color_mode); + _dispc_set_rotation_attrs(plane, rotation, mirror, color_mode, + rotation_type); _dispc_set_pre_mult_alpha(plane, pre_mult_alpha); _dispc_setup_global_alpha(plane, global_alpha); + if (cpu_is_omap44xx()) { + fifo_low = dispc_calculate_threshold(plane, paddr + offset0, + puv_addr + offset0, width, height, + row_inc, pix_inc); + fifo_high = dispc_get_plane_fifo_size(plane) - 1; + dispc_setup_plane_fifo(plane, fifo_low, fifo_high); + } + return 0; } -static void _dispc_enable_plane(enum omap_plane plane, bool enable) +int dispc_enable_plane(enum omap_plane plane, bool enable) { + DSSDBG("dispc_enable_plane %d, %d\n", plane, enable); + REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 0, 0); + + return 0; } static void dispc_disable_isr(void *data, u32 mask) @@ -1922,6 +2475,17 @@ static void _enable_lcd_out(enum omap_channel channel, bool enable) REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 0, 0); } +void omap_dispc_set_irq_type(int channel, enum omap_dispc_irq_type type) +{ + if (type == OMAP_DISPC_IRQ_TYPE_VSYNC) { + dispc.channel_irq[channel] = channel == OMAP_DSS_CHANNEL_LCD2 ? + DISPC_IRQ_VSYNC2 : DISPC_IRQ_VSYNC; + } else { + dispc.channel_irq[channel] = channel == OMAP_DSS_CHANNEL_LCD2 ? + DISPC_IRQ_FRAMEDONE2 : DISPC_IRQ_FRAMEDONE; + } +} + static void dispc_enable_lcd_out(enum omap_channel channel, bool enable) { struct completion frame_done_completion; @@ -1929,8 +2493,6 @@ static void dispc_enable_lcd_out(enum omap_channel channel, bool enable) int r; u32 irq; - enable_clocks(1); - /* When we disable LCD output, we need to wait until frame is done. * Otherwise the DSS is still working, and turning off the clocks * prevents DSS from going to OFF mode */ @@ -1938,8 +2500,7 @@ static void dispc_enable_lcd_out(enum omap_channel channel, bool enable) REG_GET(DISPC_CONTROL2, 0, 0) : REG_GET(DISPC_CONTROL, 0, 0); - irq = channel == OMAP_DSS_CHANNEL_LCD2 ? DISPC_IRQ_FRAMEDONE2 : - DISPC_IRQ_FRAMEDONE; + irq = dispc.channel_irq[channel]; if (!enable && is_on) { init_completion(&frame_done_completion); @@ -1964,8 +2525,6 @@ static void dispc_enable_lcd_out(enum omap_channel channel, bool enable) if (r) DSSERR("failed to unregister FRAMEDONE isr\n"); } - - enable_clocks(0); } static void _enable_digit_out(bool enable) @@ -1973,17 +2532,13 @@ static void _enable_digit_out(bool enable) REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 1, 1); } -static void dispc_enable_digit_out(bool enable) +static void dispc_enable_digit_out(enum omap_display_type type, bool enable) { struct completion frame_done_completion; int r; - enable_clocks(1); - - if (REG_GET(DISPC_CONTROL, 1, 1) == enable) { - enable_clocks(0); + if (REG_GET(DISPC_CONTROL, 1, 1) == enable) return; - } if (enable) { unsigned long flags; @@ -2002,7 +2557,8 @@ static void dispc_enable_digit_out(bool enable) init_completion(&frame_done_completion); r = omap_dispc_register_isr(dispc_disable_isr, &frame_done_completion, - DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD); + DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD + | DISPC_IRQ_FRAMEDONETV); if (r) DSSERR("failed to register EVSYNC isr\n"); @@ -2015,13 +2571,17 @@ static void dispc_enable_digit_out(bool enable) msecs_to_jiffies(100))) DSSERR("timeout waiting for EVSYNC\n"); - if (!wait_for_completion_timeout(&frame_done_completion, - msecs_to_jiffies(100))) - DSSERR("timeout waiting for EVSYNC\n"); + /* Don't wait for the odd field in the case of HDMI */ + if (type != OMAP_DISPLAY_TYPE_HDMI) { + if (!wait_for_completion_timeout(&frame_done_completion, + msecs_to_jiffies(100))) + DSSERR("timeout waiting for EVSYNC\n"); + } r = omap_dispc_unregister_isr(dispc_disable_isr, &frame_done_completion, - DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD); + DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD + | DISPC_IRQ_FRAMEDONETV); if (r) DSSERR("failed to unregister EVSYNC isr\n"); @@ -2031,12 +2591,12 @@ static void dispc_enable_digit_out(bool enable) dispc.irq_error_mask = DISPC_IRQ_MASK_ERROR; if (dss_has_feature(FEAT_MGR_LCD2)) dispc.irq_error_mask |= DISPC_IRQ_SYNC_LOST2; + if (dss_has_feature(FEAT_OVL_VID3)) + dispc.irq_error_mask |= DISPC_IRQ_VID3_FIFO_UNDERFLOW; dispc_write_reg(DISPC_IRQSTATUS, DISPC_IRQ_SYNC_LOST_DIGIT); _omap_dispc_set_irqs(); spin_unlock_irqrestore(&dispc.irq_lock, flags); } - - enable_clocks(0); } bool dispc_is_channel_enabled(enum omap_channel channel) @@ -2051,13 +2611,14 @@ bool dispc_is_channel_enabled(enum omap_channel channel) BUG(); } -void dispc_enable_channel(enum omap_channel channel, bool enable) +void dispc_enable_channel(enum omap_channel channel, + enum omap_display_type type, bool enable) { if (channel == OMAP_DSS_CHANNEL_LCD || channel == OMAP_DSS_CHANNEL_LCD2) dispc_enable_lcd_out(channel, enable); else if (channel == OMAP_DSS_CHANNEL_DIGIT) - dispc_enable_digit_out(enable); + dispc_enable_digit_out(type, enable); else BUG(); } @@ -2067,9 +2628,7 @@ void dispc_lcd_enable_signal_polarity(bool act_high) if (!dss_has_feature(FEAT_LCDENABLEPOL)) return; - enable_clocks(1); REG_FLD_MOD(DISPC_CONTROL, act_high ? 1 : 0, 29, 29); - enable_clocks(0); } void dispc_lcd_enable_signal(bool enable) @@ -2077,9 +2636,7 @@ void dispc_lcd_enable_signal(bool enable) if (!dss_has_feature(FEAT_LCDENABLESIGNAL)) return; - enable_clocks(1); REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 28, 28); - enable_clocks(0); } void dispc_pck_free_enable(bool enable) @@ -2087,19 +2644,15 @@ void dispc_pck_free_enable(bool enable) if (!dss_has_feature(FEAT_PCKFREEENABLE)) return; - enable_clocks(1); REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 27, 27); - enable_clocks(0); } void dispc_enable_fifohandcheck(enum omap_channel channel, bool enable) { - enable_clocks(1); if (channel == OMAP_DSS_CHANNEL_LCD2) REG_FLD_MOD(DISPC_CONFIG2, enable ? 1 : 0, 16, 16); else REG_FLD_MOD(DISPC_CONFIG, enable ? 1 : 0, 16, 16); - enable_clocks(0); } @@ -2122,27 +2675,21 @@ void dispc_set_lcd_display_type(enum omap_channel channel, return; } - enable_clocks(1); if (channel == OMAP_DSS_CHANNEL_LCD2) REG_FLD_MOD(DISPC_CONTROL2, mode, 3, 3); else REG_FLD_MOD(DISPC_CONTROL, mode, 3, 3); - enable_clocks(0); } void dispc_set_loadmode(enum omap_dss_load_mode mode) { - enable_clocks(1); REG_FLD_MOD(DISPC_CONFIG, mode, 2, 1); - enable_clocks(0); } void dispc_set_default_color(enum omap_channel channel, u32 color) { - enable_clocks(1); dispc_write_reg(DISPC_DEFAULT_COLOR(channel), color); - enable_clocks(0); } u32 dispc_get_default_color(enum omap_channel channel) @@ -2153,9 +2700,7 @@ u32 dispc_get_default_color(enum omap_channel channel) channel != OMAP_DSS_CHANNEL_LCD && channel != OMAP_DSS_CHANNEL_LCD2); - enable_clocks(1); l = dispc_read_reg(DISPC_DEFAULT_COLOR(channel)); - enable_clocks(0); return l; } @@ -2164,7 +2709,6 @@ void dispc_set_trans_key(enum omap_channel ch, enum omap_dss_trans_key_type type, u32 trans_key) { - enable_clocks(1); if (ch == OMAP_DSS_CHANNEL_LCD) REG_FLD_MOD(DISPC_CONFIG, type, 11, 11); else if (ch == OMAP_DSS_CHANNEL_DIGIT) @@ -2173,14 +2717,12 @@ void dispc_set_trans_key(enum omap_channel ch, REG_FLD_MOD(DISPC_CONFIG2, type, 11, 11); dispc_write_reg(DISPC_TRANS_COLOR(ch), trans_key); - enable_clocks(0); } void dispc_get_trans_key(enum omap_channel ch, enum omap_dss_trans_key_type *type, u32 *trans_key) { - enable_clocks(1); if (type) { if (ch == OMAP_DSS_CHANNEL_LCD) *type = REG_GET(DISPC_CONFIG, 11, 11); @@ -2194,33 +2736,27 @@ void dispc_get_trans_key(enum omap_channel ch, if (trans_key) *trans_key = dispc_read_reg(DISPC_TRANS_COLOR(ch)); - enable_clocks(0); } void dispc_enable_trans_key(enum omap_channel ch, bool enable) { - enable_clocks(1); if (ch == OMAP_DSS_CHANNEL_LCD) REG_FLD_MOD(DISPC_CONFIG, enable, 10, 10); else if (ch == OMAP_DSS_CHANNEL_DIGIT) REG_FLD_MOD(DISPC_CONFIG, enable, 12, 12); else /* OMAP_DSS_CHANNEL_LCD2 */ REG_FLD_MOD(DISPC_CONFIG2, enable, 10, 10); - enable_clocks(0); } void dispc_enable_alpha_blending(enum omap_channel ch, bool enable) { if (!dss_has_feature(FEAT_GLOBAL_ALPHA)) return; - enable_clocks(1); + /* :NOTE: compatibility mode is not supported on LCD2 */ if (ch == OMAP_DSS_CHANNEL_LCD) REG_FLD_MOD(DISPC_CONFIG, enable, 18, 18); else if (ch == OMAP_DSS_CHANNEL_DIGIT) REG_FLD_MOD(DISPC_CONFIG, enable, 19, 19); - else /* OMAP_DSS_CHANNEL_LCD2 */ - REG_FLD_MOD(DISPC_CONFIG2, enable, 18, 18); - enable_clocks(0); } bool dispc_alpha_blending_enabled(enum omap_channel ch) { @@ -2229,16 +2765,14 @@ bool dispc_alpha_blending_enabled(enum omap_channel ch) if (!dss_has_feature(FEAT_GLOBAL_ALPHA)) return false; - enable_clocks(1); if (ch == OMAP_DSS_CHANNEL_LCD) enabled = REG_GET(DISPC_CONFIG, 18, 18); else if (ch == OMAP_DSS_CHANNEL_DIGIT) enabled = REG_GET(DISPC_CONFIG, 19, 19); else if (ch == OMAP_DSS_CHANNEL_LCD2) - enabled = REG_GET(DISPC_CONFIG2, 18, 18); + enabled = false; else BUG(); - enable_clocks(0); return enabled; } @@ -2248,7 +2782,6 @@ bool dispc_trans_key_enabled(enum omap_channel ch) { bool enabled; - enable_clocks(1); if (ch == OMAP_DSS_CHANNEL_LCD) enabled = REG_GET(DISPC_CONFIG, 10, 10); else if (ch == OMAP_DSS_CHANNEL_DIGIT) @@ -2257,7 +2790,6 @@ bool dispc_trans_key_enabled(enum omap_channel ch) enabled = REG_GET(DISPC_CONFIG2, 10, 10); else BUG(); - enable_clocks(0); return enabled; } @@ -2285,12 +2817,10 @@ void dispc_set_tft_data_lines(enum omap_channel channel, u8 data_lines) return; } - enable_clocks(1); if (channel == OMAP_DSS_CHANNEL_LCD2) REG_FLD_MOD(DISPC_CONTROL2, code, 9, 8); else REG_FLD_MOD(DISPC_CONTROL, code, 9, 8); - enable_clocks(0); } void dispc_set_parallel_interface_mode(enum omap_channel channel, @@ -2322,8 +2852,6 @@ void dispc_set_parallel_interface_mode(enum omap_channel channel, return; } - enable_clocks(1); - if (channel == OMAP_DSS_CHANNEL_LCD2) { l = dispc_read_reg(DISPC_CONTROL2); l = FLD_MOD(l, stallmode, 11, 11); @@ -2335,8 +2863,6 @@ void dispc_set_parallel_interface_mode(enum omap_channel channel, l = FLD_MOD(l, gpout1, 16, 16); dispc_write_reg(DISPC_CONTROL, l); } - - enable_clocks(0); } static bool _dispc_lcd_timings_ok(int hsw, int hfp, int hbp, @@ -2389,10 +2915,8 @@ static void _dispc_set_lcd_timings(enum omap_channel channel, int hsw, FLD_VAL(vbp, 31, 20); } - enable_clocks(1); dispc_write_reg(DISPC_TIMING_H(channel), timing_h); dispc_write_reg(DISPC_TIMING_V(channel), timing_v); - enable_clocks(0); } /* change name to mode? */ @@ -2435,10 +2959,8 @@ static void dispc_set_lcd_divisor(enum omap_channel channel, u16 lck_div, BUG_ON(lck_div < 1); BUG_ON(pck_div < 2); - enable_clocks(1); dispc_write_reg(DISPC_DIVISORo(channel), FLD_VAL(lck_div, 23, 16) | FLD_VAL(pck_div, 7, 0)); - enable_clocks(0); } static void dispc_get_lcd_divisor(enum omap_channel channel, int *lck_div, @@ -2457,7 +2979,7 @@ unsigned long dispc_fclk_rate(void) switch (dss_get_dispc_clk_source()) { case OMAP_DSS_CLK_SRC_FCK: - r = dss_clk_get_rate(DSS_CLK_FCK); + r = clk_get_rate(dispc.dss_clk); break; case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: dsidev = dsi_get_dsidev_from_id(0); @@ -2487,7 +3009,7 @@ unsigned long dispc_lclk_rate(enum omap_channel channel) switch (dss_get_lcd_clk_source(channel)) { case OMAP_DSS_CLK_SRC_FCK: - r = dss_clk_get_rate(DSS_CLK_FCK); + r = clk_get_rate(dispc.dss_clk); break; case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: dsidev = dsi_get_dsidev_from_id(0); @@ -2510,13 +3032,23 @@ unsigned long dispc_pclk_rate(enum omap_channel channel) unsigned long r; u32 l; - l = dispc_read_reg(DISPC_DIVISORo(channel)); + if (channel == OMAP_DSS_CHANNEL_LCD || + channel == OMAP_DSS_CHANNEL_LCD2) { + l = dispc_read_reg(DISPC_DIVISORo(channel)); + + pcd = FLD_GET(l, 7, 0); - pcd = FLD_GET(l, 7, 0); + r = dispc_lclk_rate(channel); - r = dispc_lclk_rate(channel); + return r / pcd; + } else { + struct omap_overlay_manager *mgr; + mgr = omap_dss_get_overlay_manager(channel); + if (!mgr || !mgr->device) + return 0; - return r / pcd; + return mgr->device->panel.timings.pixel_clock * 1000; + } } void dispc_dump_clocks(struct seq_file *s) @@ -2526,7 +3058,8 @@ void dispc_dump_clocks(struct seq_file *s) enum omap_dss_clk_source dispc_clk_src = dss_get_dispc_clk_source(); enum omap_dss_clk_source lcd_clk_src; - enable_clocks(1); + if (dispc_runtime_get()) + return; seq_printf(s, "- DISPC -\n"); @@ -2574,7 +3107,8 @@ void dispc_dump_clocks(struct seq_file *s) seq_printf(s, "pck\t\t%-16lupck div\t%u\n", dispc_pclk_rate(OMAP_DSS_CHANNEL_LCD2), pcd); } - enable_clocks(0); + + dispc_runtime_put(); } #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS @@ -2612,6 +3146,10 @@ void dispc_dump_irqs(struct seq_file *s) PIS(VID1_END_WIN); PIS(VID2_FIFO_UNDERFLOW); PIS(VID2_END_WIN); + if (dss_has_feature(FEAT_OVL_VID3)) { + PIS(VID3_FIFO_UNDERFLOW); + PIS(VID3_END_WIN); + } PIS(SYNC_LOST); PIS(SYNC_LOST_DIGIT); PIS(WAKEUP); @@ -2627,9 +3165,11 @@ void dispc_dump_irqs(struct seq_file *s) void dispc_dump_regs(struct seq_file *s) { + int i, o; #define DUMPREG(r) seq_printf(s, "%-50s %08x\n", #r, dispc_read_reg(r)) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + if (dispc_runtime_get()) + return; DUMPREG(DISPC_REVISION); DUMPREG(DISPC_SYSCONFIG); @@ -2649,7 +3189,8 @@ void dispc_dump_regs(struct seq_file *s) DUMPREG(DISPC_TIMING_V(OMAP_DSS_CHANNEL_LCD)); DUMPREG(DISPC_POL_FREQ(OMAP_DSS_CHANNEL_LCD)); DUMPREG(DISPC_DIVISORo(OMAP_DSS_CHANNEL_LCD)); - DUMPREG(DISPC_GLOBAL_ALPHA); + if (dss_has_feature(FEAT_GLOBAL_ALPHA)) + DUMPREG(DISPC_GLOBAL_ALPHA); DUMPREG(DISPC_SIZE_MGR(OMAP_DSS_CHANNEL_DIGIT)); DUMPREG(DISPC_SIZE_MGR(OMAP_DSS_CHANNEL_LCD)); if (dss_has_feature(FEAT_MGR_LCD2)) { @@ -2680,188 +3221,82 @@ void dispc_dump_regs(struct seq_file *s) DUMPREG(DISPC_DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD)); DUMPREG(DISPC_DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD)); - DUMPREG(DISPC_CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); - DUMPREG(DISPC_CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); - DUMPREG(DISPC_CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + if (dss_has_feature(FEAT_CPR)) { + DUMPREG(DISPC_CPR_COEF_R(OMAP_DSS_CHANNEL_LCD)); + DUMPREG(DISPC_CPR_COEF_G(OMAP_DSS_CHANNEL_LCD)); + DUMPREG(DISPC_CPR_COEF_B(OMAP_DSS_CHANNEL_LCD)); + } if (dss_has_feature(FEAT_MGR_LCD2)) { DUMPREG(DISPC_DATA_CYCLE1(OMAP_DSS_CHANNEL_LCD2)); DUMPREG(DISPC_DATA_CYCLE2(OMAP_DSS_CHANNEL_LCD2)); DUMPREG(DISPC_DATA_CYCLE3(OMAP_DSS_CHANNEL_LCD2)); - DUMPREG(DISPC_CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); - DUMPREG(DISPC_CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); - DUMPREG(DISPC_CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); - } - - DUMPREG(DISPC_OVL_PRELOAD(OMAP_DSS_GFX)); - - DUMPREG(DISPC_OVL_BA0(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_BA1(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_POSITION(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_SIZE(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ATTRIBUTES(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_FIFO_SIZE_STATUS(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ROW_INC(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_PIXEL_INC(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_FIR(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_PICTURE_SIZE(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ACCU0(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ACCU1(OMAP_DSS_VIDEO1)); - - DUMPREG(DISPC_OVL_BA0(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_BA1(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_POSITION(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_SIZE(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ATTRIBUTES(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_FIFO_THRESHOLD(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_FIFO_SIZE_STATUS(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ROW_INC(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_PIXEL_INC(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_FIR(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_PICTURE_SIZE(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ACCU0(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ACCU1(OMAP_DSS_VIDEO2)); - - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO1, 7)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO1, 7)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO1, 7)); + if (dss_has_feature(FEAT_CPR)) { + DUMPREG(DISPC_CPR_COEF_R(OMAP_DSS_CHANNEL_LCD2)); + DUMPREG(DISPC_CPR_COEF_G(OMAP_DSS_CHANNEL_LCD2)); + DUMPREG(DISPC_CPR_COEF_B(OMAP_DSS_CHANNEL_LCD2)); + } + } - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - DUMPREG(DISPC_OVL_BA0_UV(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_BA1_UV(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_FIR2(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ACCU2_0(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_ACCU2_1(OMAP_DSS_VIDEO1)); - - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO1, 7)); - - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO1, 7)); - - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO1, 7)); - } - if (dss_has_feature(FEAT_ATTR2)) - DUMPREG(DISPC_OVL_ATTRIBUTES2(OMAP_DSS_VIDEO1)); - - - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_H(OMAP_DSS_VIDEO2, 7)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_HV(OMAP_DSS_VIDEO2, 7)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_CONV_COEF(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_V(OMAP_DSS_VIDEO2, 7)); + if (dss_has_feature(FEAT_PRELOAD)) + DUMPREG(DISPC_OVL_PRELOAD(OMAP_DSS_GFX)); - if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { - DUMPREG(DISPC_OVL_BA0_UV(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_BA1_UV(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_FIR2(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ACCU2_0(OMAP_DSS_VIDEO2)); - DUMPREG(DISPC_OVL_ACCU2_1(OMAP_DSS_VIDEO2)); - - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_H2(OMAP_DSS_VIDEO2, 7)); - - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_HV2(OMAP_DSS_VIDEO2, 7)); - - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 0)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 1)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 2)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 3)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 4)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 5)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 6)); - DUMPREG(DISPC_OVL_FIR_COEF_V2(OMAP_DSS_VIDEO2, 7)); - } - if (dss_has_feature(FEAT_ATTR2)) - DUMPREG(DISPC_OVL_ATTRIBUTES2(OMAP_DSS_VIDEO2)); - - DUMPREG(DISPC_OVL_PRELOAD(OMAP_DSS_VIDEO1)); - DUMPREG(DISPC_OVL_PRELOAD(OMAP_DSS_VIDEO2)); - - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + for (o = OMAP_DSS_VIDEO1; o <= OMAP_DSS_VIDEO3; o++) { + if (o == OMAP_DSS_VIDEO3 && !dss_has_feature(FEAT_OVL_VID3)) + continue; + + DUMPREG(DISPC_OVL_BA0(o)); + DUMPREG(DISPC_OVL_BA1(o)); + DUMPREG(DISPC_OVL_POSITION(o)); + DUMPREG(DISPC_OVL_SIZE(o)); + DUMPREG(DISPC_OVL_ATTRIBUTES(o)); + DUMPREG(DISPC_OVL_FIFO_THRESHOLD(o)); + DUMPREG(DISPC_OVL_FIFO_SIZE_STATUS(o)); + DUMPREG(DISPC_OVL_ROW_INC(o)); + DUMPREG(DISPC_OVL_PIXEL_INC(o)); + DUMPREG(DISPC_OVL_FIR(o)); + DUMPREG(DISPC_OVL_PICTURE_SIZE(o)); + DUMPREG(DISPC_OVL_ACCU0(o)); + DUMPREG(DISPC_OVL_ACCU1(o)); + + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_H(o, i)); + + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_HV(o, i)); + + for (i = 0; i < 5; i++) + DUMPREG(DISPC_OVL_CONV_COEF(o, i)); + + if (dss_has_feature(FEAT_FIR_COEF_V)) { + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_V(o, i)); + } + + if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { + DUMPREG(DISPC_OVL_BA0_UV(o)); + DUMPREG(DISPC_OVL_BA1_UV(o)); + DUMPREG(DISPC_OVL_FIR2(o)); + DUMPREG(DISPC_OVL_ACCU2_0(o)); + DUMPREG(DISPC_OVL_ACCU2_1(o)); + + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_H2(o, i)); + + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_HV2(o, i)); + + for (i = 0; i < 8; i++) + DUMPREG(DISPC_OVL_FIR_COEF_V2(o, i)); + } + if (dss_has_feature(FEAT_ATTR2)) + DUMPREG(DISPC_OVL_ATTRIBUTES2(o)); + + if (dss_has_feature(FEAT_PRELOAD)) + DUMPREG(DISPC_OVL_PRELOAD(o)); + } + + dispc_runtime_put(); #undef DUMPREG } @@ -2882,9 +3317,7 @@ static void _dispc_set_pol_freq(enum omap_channel channel, bool onoff, bool rf, l |= FLD_VAL(acbi, 11, 8); l |= FLD_VAL(acb, 7, 0); - enable_clocks(1); dispc_write_reg(DISPC_POL_FREQ(channel), l); - enable_clocks(0); } void dispc_set_pol_freq(enum omap_channel channel, @@ -3005,15 +3438,11 @@ static void _omap_dispc_set_irqs(void) mask |= isr_data->mask; } - enable_clocks(1); - old_mask = dispc_read_reg(DISPC_IRQENABLE); /* clear the irqstatus for newly enabled irqs */ dispc_write_reg(DISPC_IRQSTATUS, (mask ^ old_mask) & mask); dispc_write_reg(DISPC_IRQENABLE, mask); - - enable_clocks(0); } int omap_dispc_register_isr(omap_dispc_isr_t isr, void *arg, u32 mask) @@ -3119,6 +3548,8 @@ static void print_irq_status(u32 status) PIS(OCP_ERR); PIS(VID1_FIFO_UNDERFLOW); PIS(VID2_FIFO_UNDERFLOW); + if (dss_has_feature(FEAT_OVL_VID3)) + PIS(VID3_FIFO_UNDERFLOW); PIS(SYNC_LOST); PIS(SYNC_LOST_DIGIT); if (dss_has_feature(FEAT_MGR_LCD2)) @@ -3218,6 +3649,8 @@ static void dispc_error_worker(struct work_struct *work) dispc.error_irqs = 0; spin_unlock_irqrestore(&dispc.irq_lock, flags); + dispc_runtime_get(); + if (errors & DISPC_IRQ_GFX_FIFO_UNDERFLOW) { DSSERR("GFX_FIFO_UNDERFLOW, disabling GFX\n"); for (i = 0; i < omap_dss_get_num_overlays(); ++i) { @@ -3272,6 +3705,24 @@ static void dispc_error_worker(struct work_struct *work) } } + if (errors & DISPC_IRQ_VID3_FIFO_UNDERFLOW) { + DSSERR("VID3_FIFO_UNDERFLOW, disabling VID3\n"); + for (i = 0; i < omap_dss_get_num_overlays(); ++i) { + struct omap_overlay *ovl; + ovl = omap_dss_get_overlay(i); + + if (!(ovl->caps & OMAP_DSS_OVL_CAP_DISPC)) + continue; + + if (ovl->id == 3) { + dispc_enable_plane(ovl->id, 0); + dispc_go(ovl->manager->id); + mdelay(50); + break; + } + } + } + if (errors & DISPC_IRQ_SYNC_LOST) { struct omap_overlay_manager *manager = NULL; bool enable = false; @@ -3283,6 +3734,11 @@ static void dispc_error_worker(struct work_struct *work) mgr = omap_dss_get_overlay_manager(i); if (mgr->id == OMAP_DSS_CHANNEL_LCD) { + if(!mgr->device->first_vsync){ + DSSERR("First SYNC_LOST.. ignoring \n"); + break; + } + manager = mgr; enable = mgr->device->state == OMAP_DSS_DISPLAY_ACTIVE; @@ -3315,17 +3771,23 @@ static void dispc_error_worker(struct work_struct *work) struct omap_overlay_manager *manager = NULL; bool enable = false; - DSSERR("SYNC_LOST_DIGIT, disabling TV\n"); + pr_err_ratelimited("SYNC_LOST_DIGIT, disabling TV\n"); for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { struct omap_overlay_manager *mgr; mgr = omap_dss_get_overlay_manager(i); if (mgr->id == OMAP_DSS_CHANNEL_DIGIT) { + if(!mgr->device->first_vsync){ + DSSERR("First SYNC_LOST..TV ignoring\n"); + } + manager = mgr; enable = mgr->device->state == OMAP_DSS_DISPLAY_ACTIVE; + mgr->device->sync_lost_error = 1; mgr->device->driver->disable(mgr->device); + mgr->device->sync_lost_error = 0; break; } } @@ -3361,6 +3823,11 @@ static void dispc_error_worker(struct work_struct *work) mgr = omap_dss_get_overlay_manager(i); if (mgr->id == OMAP_DSS_CHANNEL_LCD2) { + if(!mgr->device->first_vsync){ + DSSERR("First SYNC_LOST.. ignoring \n"); + break; + } + manager = mgr; enable = mgr->device->state == OMAP_DSS_DISPLAY_ACTIVE; @@ -3404,6 +3871,8 @@ static void dispc_error_worker(struct work_struct *work) dispc.irq_error_mask |= errors; _omap_dispc_set_irqs(); spin_unlock_irqrestore(&dispc.irq_lock, flags); + + dispc_runtime_put(); } int omap_dispc_wait_for_irq_timeout(u32 irqmask, unsigned long timeout) @@ -3446,11 +3915,15 @@ int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, int r; DECLARE_COMPLETION_ONSTACK(completion); + r = dispc_runtime_get(); + if (r) + return r; + r = omap_dispc_register_isr(dispc_irq_wait_handler, &completion, irqmask); if (r) - return r; + goto done; timeout = wait_for_completion_interruptible_timeout(&completion, timeout); @@ -3458,12 +3931,14 @@ int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, omap_dispc_unregister_isr(dispc_irq_wait_handler, &completion, irqmask); if (timeout == 0) - return -ETIMEDOUT; + r = -ETIMEDOUT; + else if (timeout == -ERESTARTSYS) + r = timeout; - if (timeout == -ERESTARTSYS) - return -ERESTARTSYS; +done: + dispc_runtime_put(); - return 0; + return r; } #ifdef CONFIG_OMAP2_DSS_FAKE_VSYNC @@ -3498,7 +3973,8 @@ static void _omap_dispc_initialize_irq(void) dispc.irq_error_mask = DISPC_IRQ_MASK_ERROR; if (dss_has_feature(FEAT_MGR_LCD2)) dispc.irq_error_mask |= DISPC_IRQ_SYNC_LOST2; - + if (dss_has_feature(FEAT_OVL_VID3)) + dispc.irq_error_mask |= DISPC_IRQ_VID3_FIFO_UNDERFLOW; /* there's SYNC_LOST_DIGIT waiting after enabling the DSS, * so clear it */ dispc_write_reg(DISPC_IRQSTATUS, dispc_read_reg(DISPC_IRQSTATUS)); @@ -3522,13 +3998,6 @@ static void _omap_dispc_initial_config(void) { u32 l; - l = dispc_read_reg(DISPC_SYSCONFIG); - l = FLD_MOD(l, 2, 13, 12); /* MIDLEMODE: smart standby */ - l = FLD_MOD(l, 2, 4, 3); /* SIDLEMODE: smart idle */ - l = FLD_MOD(l, 1, 2, 2); /* ENWAKEUP */ - l = FLD_MOD(l, 1, 0, 0); /* AUTOIDLE */ - dispc_write_reg(DISPC_SYSCONFIG, l); - /* Exclusively enable DISPC_CORE_CLK and set divider to 1 */ if (dss_has_feature(FEAT_CORE_CLK_DIV)) { l = dispc_read_reg(DISPC_DIVISOR); @@ -3538,83 +4007,47 @@ static void _omap_dispc_initial_config(void) dispc_write_reg(DISPC_DIVISOR, l); } + /* for OMAP4 ERRATUM xxxx: Mstandby and disconnect protocol issue */ + if (cpu_is_omap44xx()) { + l3_1_clkdm = clkdm_lookup("l3_1_clkdm"); + l3_2_clkdm = clkdm_lookup("l3_2_clkdm"); + } + /* FUNCGATED */ if (dss_has_feature(FEAT_FUNCGATED)) REG_FLD_MOD(DISPC_CONFIG, 1, 9, 9); + REG_FLD_MOD(DISPC_CONFIG, 1, 17, 17); + /* L3 firewall setting: enable access to OCM RAM */ /* XXX this should be somewhere in plat-omap */ if (cpu_is_omap24xx()) __raw_writel(0x402000b0, OMAP2_L3_IO_ADDRESS(0x680050a0)); - _dispc_setup_color_conv_coef(); - dispc_set_loadmode(OMAP_DSS_LOAD_FRAME_ONLY); dispc_read_plane_fifo_sizes(); } -int dispc_enable_plane(enum omap_plane plane, bool enable) -{ - DSSDBG("dispc_enable_plane %d, %d\n", plane, enable); - - enable_clocks(1); - _dispc_enable_plane(plane, enable); - enable_clocks(0); - - return 0; -} - -int dispc_setup_plane(enum omap_plane plane, - u32 paddr, u16 screen_width, - u16 pos_x, u16 pos_y, - u16 width, u16 height, - u16 out_width, u16 out_height, - enum omap_color_mode color_mode, - bool ilace, - enum omap_dss_rotation_type rotation_type, - u8 rotation, bool mirror, u8 global_alpha, - u8 pre_mult_alpha, enum omap_channel channel, - u32 puv_addr) -{ - int r = 0; - - DSSDBG("dispc_setup_plane %d, pa %x, sw %d, %d, %d, %dx%d -> " - "%dx%d, ilace %d, cmode %x, rot %d, mir %d chan %d\n", - plane, paddr, screen_width, pos_x, pos_y, - width, height, - out_width, out_height, - ilace, color_mode, - rotation, mirror, channel); - - enable_clocks(1); - - r = _dispc_setup_plane(plane, - paddr, screen_width, - pos_x, pos_y, - width, height, - out_width, out_height, - color_mode, ilace, - rotation_type, - rotation, mirror, - global_alpha, - pre_mult_alpha, - channel, puv_addr); - - enable_clocks(0); - - return r; -} - /* DISPC HW IP initialisation */ static int omap_dispchw_probe(struct platform_device *pdev) { u32 rev; int r = 0; struct resource *dispc_mem; + struct clk *clk; dispc.pdev = pdev; + clk = clk_get(&pdev->dev, "dss_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get dss_clk\n"); + r = PTR_ERR(clk); + goto err_get_clk; + } + + dispc.dss_clk = clk; + spin_lock_init(&dispc.irq_lock); #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS @@ -3628,51 +4061,65 @@ static int omap_dispchw_probe(struct platform_device *pdev) if (!dispc_mem) { DSSERR("can't get IORESOURCE_MEM DISPC\n"); r = -EINVAL; - goto fail0; + goto err_ioremap; } dispc.base = ioremap(dispc_mem->start, resource_size(dispc_mem)); if (!dispc.base) { DSSERR("can't ioremap DISPC\n"); r = -ENOMEM; - goto fail0; + goto err_ioremap; } dispc.irq = platform_get_irq(dispc.pdev, 0); if (dispc.irq < 0) { DSSERR("platform_get_irq failed\n"); r = -ENODEV; - goto fail1; + goto err_irq; } r = request_irq(dispc.irq, omap_dispc_irq_handler, IRQF_SHARED, "OMAP DISPC", dispc.pdev); if (r < 0) { DSSERR("request_irq failed\n"); - goto fail1; + goto err_irq; } - enable_clocks(1); + mutex_init(&dispc.runtime_lock); + + pm_runtime_enable(&pdev->dev); + + r = dispc_runtime_get(); + if (r) + goto err_runtime_get; _omap_dispc_initial_config(); _omap_dispc_initialize_irq(); - dispc_save_context(); - rev = dispc_read_reg(DISPC_REVISION); dev_dbg(&pdev->dev, "OMAP DISPC rev %d.%d\n", FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); - enable_clocks(0); + dispc_runtime_put(); return 0; -fail1: + +err_runtime_get: + pm_runtime_disable(&pdev->dev); + free_irq(dispc.irq, dispc.pdev); +err_irq: iounmap(dispc.base); -fail0: +err_ioremap: + clk_put(dispc.dss_clk); +err_get_clk: return r; } static int omap_dispchw_remove(struct platform_device *pdev) { + pm_runtime_disable(&pdev->dev); + + clk_put(dispc.dss_clk); + free_irq(dispc.irq, dispc.pdev); iounmap(dispc.base); return 0; diff --git a/drivers/video/omap2/dss/dispc.h b/drivers/video/omap2/dss/dispc.h index 6c9ee0a..ee08b69 100644 --- a/drivers/video/omap2/dss/dispc.h +++ b/drivers/video/omap2/dss/dispc.h @@ -174,7 +174,8 @@ static inline u16 DISPC_DIVISORo(enum omap_channel channel) case OMAP_DSS_CHANNEL_LCD: return 0x0070; case OMAP_DSS_CHANNEL_DIGIT: - BUG(); + /* FIXME venc pclk? */ + return 0x0070; case OMAP_DSS_CHANNEL_LCD2: return 0x040C; default: @@ -291,6 +292,8 @@ static inline u16 DISPC_OVL_BASE(enum omap_plane plane) return 0x00BC; case OMAP_DSS_VIDEO2: return 0x014C; + case OMAP_DSS_VIDEO3: + return 0x0300; default: BUG(); } @@ -304,6 +307,8 @@ static inline u16 DISPC_BA0_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0000; + case OMAP_DSS_VIDEO3: + return 0x0008; default: BUG(); } @@ -316,6 +321,8 @@ static inline u16 DISPC_BA1_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0004; + case OMAP_DSS_VIDEO3: + return 0x000C; default: BUG(); } @@ -330,6 +337,8 @@ static inline u16 DISPC_BA0_UV_OFFSET(enum omap_plane plane) return 0x0544; case OMAP_DSS_VIDEO2: return 0x04BC; + case OMAP_DSS_VIDEO3: + return 0x0310; default: BUG(); } @@ -344,6 +353,8 @@ static inline u16 DISPC_BA1_UV_OFFSET(enum omap_plane plane) return 0x0548; case OMAP_DSS_VIDEO2: return 0x04C0; + case OMAP_DSS_VIDEO3: + return 0x0314; default: BUG(); } @@ -356,6 +367,8 @@ static inline u16 DISPC_POS_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0008; + case OMAP_DSS_VIDEO3: + return 0x009C; default: BUG(); } @@ -368,6 +381,8 @@ static inline u16 DISPC_SIZE_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x000C; + case OMAP_DSS_VIDEO3: + return 0x00A8; default: BUG(); } @@ -381,6 +396,8 @@ static inline u16 DISPC_ATTR_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0010; + case OMAP_DSS_VIDEO3: + return 0x0070; default: BUG(); } @@ -395,6 +412,8 @@ static inline u16 DISPC_ATTR2_OFFSET(enum omap_plane plane) return 0x0568; case OMAP_DSS_VIDEO2: return 0x04DC; + case OMAP_DSS_VIDEO3: + return 0x032C; default: BUG(); } @@ -408,6 +427,8 @@ static inline u16 DISPC_FIFO_THRESH_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0014; + case OMAP_DSS_VIDEO3: + return 0x008C; default: BUG(); } @@ -421,6 +442,8 @@ static inline u16 DISPC_FIFO_SIZE_STATUS_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0018; + case OMAP_DSS_VIDEO3: + return 0x0088; default: BUG(); } @@ -434,6 +457,8 @@ static inline u16 DISPC_ROW_INC_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x001C; + case OMAP_DSS_VIDEO3: + return 0x00A4; default: BUG(); } @@ -447,6 +472,8 @@ static inline u16 DISPC_PIX_INC_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0020; + case OMAP_DSS_VIDEO3: + return 0x0098; default: BUG(); } @@ -459,6 +486,7 @@ static inline u16 DISPC_WINDOW_SKIP_OFFSET(enum omap_plane plane) return 0x0034; case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: BUG(); default: BUG(); @@ -472,6 +500,7 @@ static inline u16 DISPC_TABLE_BA_OFFSET(enum omap_plane plane) return 0x0038; case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: BUG(); default: BUG(); @@ -486,6 +515,8 @@ static inline u16 DISPC_FIR_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0024; + case OMAP_DSS_VIDEO3: + return 0x0090; default: BUG(); } @@ -500,6 +531,8 @@ static inline u16 DISPC_FIR2_OFFSET(enum omap_plane plane) return 0x0580; case OMAP_DSS_VIDEO2: return 0x055C; + case OMAP_DSS_VIDEO3: + return 0x0424; default: BUG(); } @@ -513,6 +546,8 @@ static inline u16 DISPC_PIC_SIZE_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0028; + case OMAP_DSS_VIDEO3: + return 0x0094; default: BUG(); } @@ -527,6 +562,8 @@ static inline u16 DISPC_ACCU0_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x002C; + case OMAP_DSS_VIDEO3: + return 0x0000; default: BUG(); } @@ -541,6 +578,8 @@ static inline u16 DISPC_ACCU2_0_OFFSET(enum omap_plane plane) return 0x0584; case OMAP_DSS_VIDEO2: return 0x0560; + case OMAP_DSS_VIDEO3: + return 0x0428; default: BUG(); } @@ -554,6 +593,8 @@ static inline u16 DISPC_ACCU1_OFFSET(enum omap_plane plane) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0030; + case OMAP_DSS_VIDEO3: + return 0x0004; default: BUG(); } @@ -568,6 +609,8 @@ static inline u16 DISPC_ACCU2_1_OFFSET(enum omap_plane plane) return 0x0588; case OMAP_DSS_VIDEO2: return 0x0564; + case OMAP_DSS_VIDEO3: + return 0x042C; default: BUG(); } @@ -582,6 +625,8 @@ static inline u16 DISPC_FIR_COEF_H_OFFSET(enum omap_plane plane, u16 i) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0034 + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0010 + i * 0x8; default: BUG(); } @@ -597,6 +642,8 @@ static inline u16 DISPC_FIR_COEF_H2_OFFSET(enum omap_plane plane, u16 i) return 0x058C + i * 0x8; case OMAP_DSS_VIDEO2: return 0x0568 + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0430 + i * 0x8; default: BUG(); } @@ -611,6 +658,8 @@ static inline u16 DISPC_FIR_COEF_HV_OFFSET(enum omap_plane plane, u16 i) case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: return 0x0038 + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0014 + i * 0x8; default: BUG(); } @@ -626,6 +675,8 @@ static inline u16 DISPC_FIR_COEF_HV2_OFFSET(enum omap_plane plane, u16 i) return 0x0590 + i * 8; case OMAP_DSS_VIDEO2: return 0x056C + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0434 + i * 0x8; default: BUG(); } @@ -639,6 +690,7 @@ static inline u16 DISPC_CONV_COEF_OFFSET(enum omap_plane plane, u16 i) BUG(); case OMAP_DSS_VIDEO1: case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: return 0x0074 + i * 0x4; default: BUG(); @@ -655,6 +707,8 @@ static inline u16 DISPC_FIR_COEF_V_OFFSET(enum omap_plane plane, u16 i) return 0x0124 + i * 0x4; case OMAP_DSS_VIDEO2: return 0x00B4 + i * 0x4; + case OMAP_DSS_VIDEO3: + return 0x0050 + i * 0x4; default: BUG(); } @@ -670,6 +724,8 @@ static inline u16 DISPC_FIR_COEF_V2_OFFSET(enum omap_plane plane, u16 i) return 0x05CC + i * 0x4; case OMAP_DSS_VIDEO2: return 0x05A8 + i * 0x4; + case OMAP_DSS_VIDEO3: + return 0x0470 + i * 0x4; default: BUG(); } @@ -684,6 +740,8 @@ static inline u16 DISPC_PRELOAD_OFFSET(enum omap_plane plane) return 0x0174; case OMAP_DSS_VIDEO2: return 0x00E8; + case OMAP_DSS_VIDEO3: + return 0x00A0; default: BUG(); } diff --git a/drivers/video/omap2/dss/display.c b/drivers/video/omap2/dss/display.c index c2dfc8c..8b3b360 100644 --- a/drivers/video/omap2/dss/display.c +++ b/drivers/video/omap2/dss/display.c @@ -339,6 +339,18 @@ void default_get_overlay_fifo_thresholds(enum omap_plane plane, *fifo_low = fifo_size - burst_size_bytes; } +void omapdss_display_get_dimensions(struct omap_dss_device *dssdev, + u32 *width_in_um, u32 *height_in_um) +{ + if (dssdev->driver->get_dimensions) { + dssdev->driver->get_dimensions(dssdev, + width_in_um, width_in_um); + } else { + *width_in_um = dssdev->panel.width_in_um; + *height_in_um = dssdev->panel.height_in_um; + } +} + int omapdss_default_get_recommended_bpp(struct omap_dss_device *dssdev) { switch (dssdev->type) { @@ -446,6 +458,8 @@ void dss_init_device(struct platform_device *pdev, return; } + BLOCKING_INIT_NOTIFIER_HEAD(&dssdev->state_notifiers); + /* create device sysfs files */ i = 0; while ((attr = display_sysfs_attrs[i++]) != NULL) { diff --git a/drivers/video/omap2/dss/dpi.c b/drivers/video/omap2/dss/dpi.c index ff6bd30..f053b18 100644 --- a/drivers/video/omap2/dss/dpi.c +++ b/drivers/video/omap2/dss/dpi.c @@ -23,7 +23,6 @@ #define DSS_SUBSYS_NAME "DPI" #include <linux/kernel.h> -#include <linux/clk.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/errno.h> @@ -130,8 +129,6 @@ static int dpi_set_mode(struct omap_dss_device *dssdev) bool is_tft; int r = 0; - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); - dispc_set_pol_freq(dssdev->manager->id, dssdev->panel.config, dssdev->panel.acbi, dssdev->panel.acb); @@ -144,7 +141,7 @@ static int dpi_set_mode(struct omap_dss_device *dssdev) r = dpi_set_dispc_clk(dssdev, is_tft, t->pixel_clock * 1000, &fck, &lck_div, &pck_div); if (r) - goto err0; + return r; pck = fck / lck_div / pck_div / 1000; @@ -158,12 +155,10 @@ static int dpi_set_mode(struct omap_dss_device *dssdev) dispc_set_lcd_timings(dssdev->manager->id, t); -err0: - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); - return r; + return 0; } -static int dpi_basic_init(struct omap_dss_device *dssdev) +static void dpi_basic_init(struct omap_dss_device *dssdev) { bool is_tft; @@ -175,8 +170,6 @@ static int dpi_basic_init(struct omap_dss_device *dssdev) OMAP_DSS_LCD_DISPLAY_TFT : OMAP_DSS_LCD_DISPLAY_STN); dispc_set_tft_data_lines(dssdev->manager->id, dssdev->phy.dpi.data_lines); - - return 0; } int omapdss_dpi_display_enable(struct omap_dss_device *dssdev) @@ -186,31 +179,38 @@ int omapdss_dpi_display_enable(struct omap_dss_device *dssdev) r = omap_dss_start_device(dssdev); if (r) { DSSERR("failed to start device\n"); - goto err0; + goto err_start_dev; } if (cpu_is_omap34xx()) { r = regulator_enable(dpi.vdds_dsi_reg); if (r) - goto err1; + goto err_reg_enable; } - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + r = dss_runtime_get(); + if (r) + goto err_get_dss; - r = dpi_basic_init(dssdev); + r = dispc_runtime_get(); if (r) - goto err2; + goto err_get_dispc; + + dpi_basic_init(dssdev); if (dpi_use_dsi_pll(dssdev)) { - dss_clk_enable(DSS_CLK_SYSCK); + r = dsi_runtime_get(dpi.dsidev); + if (r) + goto err_get_dsi; + r = dsi_pll_init(dpi.dsidev, 0, 1); if (r) - goto err3; + goto err_dsi_pll_init; } r = dpi_set_mode(dssdev); if (r) - goto err4; + goto err_set_mode; mdelay(2); @@ -218,19 +218,22 @@ int omapdss_dpi_display_enable(struct omap_dss_device *dssdev) return 0; -err4: +err_set_mode: if (dpi_use_dsi_pll(dssdev)) dsi_pll_uninit(dpi.dsidev, true); -err3: +err_dsi_pll_init: if (dpi_use_dsi_pll(dssdev)) - dss_clk_disable(DSS_CLK_SYSCK); -err2: - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dsi_runtime_put(dpi.dsidev); +err_get_dsi: + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: if (cpu_is_omap34xx()) regulator_disable(dpi.vdds_dsi_reg); -err1: +err_reg_enable: omap_dss_stop_device(dssdev); -err0: +err_start_dev: return r; } EXPORT_SYMBOL(omapdss_dpi_display_enable); @@ -242,10 +245,11 @@ void omapdss_dpi_display_disable(struct omap_dss_device *dssdev) if (dpi_use_dsi_pll(dssdev)) { dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK); dsi_pll_uninit(dpi.dsidev, true); - dss_clk_disable(DSS_CLK_SYSCK); + dsi_runtime_put(dpi.dsidev); } - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dispc_runtime_put(); + dss_runtime_put(); if (cpu_is_omap34xx()) regulator_disable(dpi.vdds_dsi_reg); @@ -257,11 +261,26 @@ EXPORT_SYMBOL(omapdss_dpi_display_disable); void dpi_set_timings(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { + int r; + DSSDBG("dpi_set_timings\n"); dssdev->panel.timings = *timings; if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + r = dss_runtime_get(); + if (r) + return; + + r = dispc_runtime_get(); + if (r) { + dss_runtime_put(); + return; + } + dpi_set_mode(dssdev); dispc_go(dssdev->manager->id); + + dispc_runtime_put(); + dss_runtime_put(); } } EXPORT_SYMBOL(dpi_set_timings); diff --git a/drivers/video/omap2/dss/dsi.c b/drivers/video/omap2/dss/dsi.c index 345757c..93b52f6 100644 --- a/drivers/video/omap2/dss/dsi.c +++ b/drivers/video/omap2/dss/dsi.c @@ -36,6 +36,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/debugfs.h> +#include <linux/pm_runtime.h> #include <video/omapdss.h> #include <plat/clock.h> @@ -205,6 +206,7 @@ struct dsi_reg { u16 idx; }; #define DSI_DT_DCS_LONG_WRITE 0x39 #define DSI_DT_RX_ACK_WITH_ERR 0x02 +#define DSI_DT_RX_LONG_READ 0x1a #define DSI_DT_RX_DCS_LONG_READ 0x1c #define DSI_DT_RX_SHORT_READ_1 0x21 #define DSI_DT_RX_SHORT_READ_2 0x22 @@ -267,8 +269,15 @@ struct dsi_isr_tables { struct dsi_data { struct platform_device *pdev; void __iomem *base; + + struct mutex runtime_lock; + int runtime_count; + int irq; + struct clk *dss_clk; + struct clk *sys_clk; + void (*dsi_mux_pads)(bool enable); struct dsi_clock_info current_cinfo; @@ -389,15 +398,6 @@ static inline u32 dsi_read_reg(struct platform_device *dsidev, return __raw_readl(dsi->base + idx.idx); } - -void dsi_save_context(void) -{ -} - -void dsi_restore_context(void) -{ -} - void dsi_bus_lock(struct omap_dss_device *dssdev) { struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); @@ -493,9 +493,18 @@ static void dsi_perf_show(struct platform_device *dsidev, const char *name) total_bytes * 1000 / total_us); } #else -#define dsi_perf_mark_setup(x) -#define dsi_perf_mark_start(x) -#define dsi_perf_show(x, y) +static inline void dsi_perf_mark_setup(struct platform_device *dsidev) +{ +} + +static inline void dsi_perf_mark_start(struct platform_device *dsidev) +{ +} + +static inline void dsi_perf_show(struct platform_device *dsidev, + const char *name) +{ +} #endif static void print_irq_status(u32 status) @@ -1039,13 +1048,69 @@ static u32 dsi_get_errors(struct platform_device *dsidev) return e; } -/* DSI func clock. this could also be dsi_pll_hsdiv_dsi_clk */ -static inline void enable_clocks(bool enable) +int dsi_runtime_get(struct platform_device *dsidev) { - if (enable) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); - else - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + int r; + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + mutex_lock(&dsi->runtime_lock); + + if (dsi->runtime_count++ == 0) { + DSSDBG("dsi_runtime_get\n"); + + r = dss_runtime_get(); + if (r) + goto err_get_dss; + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; + + /* XXX dsi fclk can also come from DSI PLL */ + clk_enable(dsi->dss_clk); + + r = pm_runtime_get_sync(&dsi->pdev->dev); + WARN_ON(r); + if (r < 0) + goto err_runtime_get; + } + + mutex_unlock(&dsi->runtime_lock); + + return 0; + +err_runtime_get: + clk_disable(dsi->dss_clk); + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: + mutex_unlock(&dsi->runtime_lock); + + return r; +} + +void dsi_runtime_put(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + mutex_lock(&dsi->runtime_lock); + + if (--dsi->runtime_count == 0) { + int r; + + DSSDBG("dsi_runtime_put\n"); + + r = pm_runtime_put_sync(&dsi->pdev->dev); + WARN_ON(r); + + clk_disable(dsi->dss_clk); + + dispc_runtime_put(); + dss_runtime_put(); + } + + mutex_unlock(&dsi->runtime_lock); } /* source clock for DSI PLL. this could also be PCLKFREE */ @@ -1055,9 +1120,9 @@ static inline void dsi_enable_pll_clock(struct platform_device *dsidev, struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); if (enable) - dss_clk_enable(DSS_CLK_SYSCK); + clk_enable(dsi->sys_clk); else - dss_clk_disable(DSS_CLK_SYSCK); + clk_disable(dsi->sys_clk); if (enable && dsi->pll_locked) { if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 1, 1) != 1) @@ -1150,10 +1215,11 @@ static unsigned long dsi_fclk_rate(struct platform_device *dsidev) { unsigned long r; int dsi_module = dsi_get_dsidev_id(dsidev); + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); if (dss_get_dsi_clk_source(dsi_module) == OMAP_DSS_CLK_SRC_FCK) { /* DSI FCLK source is DSS_CLK_FCK */ - r = dss_clk_get_rate(DSS_CLK_FCK); + r = clk_get_rate(dsi->dss_clk); } else { /* DSI FCLK source is dsi_pll_hsdiv_dsi_clk */ r = dsi_get_pll_hsdiv_dsi_rate(dsidev); @@ -1262,7 +1328,7 @@ static int dsi_calc_clock_rates(struct omap_dss_device *dssdev, return -EINVAL; if (cinfo->use_sys_clk) { - cinfo->clkin = dss_clk_get_rate(DSS_CLK_SYSCK); + cinfo->clkin = clk_get_rate(dsi->sys_clk); /* XXX it is unclear if highfreq should be used * with DSS_SYS_CLK source also */ cinfo->highfreq = 0; @@ -1311,7 +1377,7 @@ int dsi_pll_calc_clock_div_pck(struct platform_device *dsidev, bool is_tft, int match = 0; unsigned long dss_sys_clk, max_dss_fck; - dss_sys_clk = dss_clk_get_rate(DSS_CLK_SYSCK); + dss_sys_clk = clk_get_rate(dsi->sys_clk); max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); @@ -1539,6 +1605,9 @@ int dsi_pll_set_clock_div(struct platform_device *dsidev, l = FLD_MOD(l, 1, 13, 13); /* DSI_PLL_REFEN */ l = FLD_MOD(l, 0, 14, 14); /* DSIPHY_CLKINEN */ l = FLD_MOD(l, 1, 20, 20); /* DSI_HSDIVBYPASS */ + + if (cpu_is_omap44xx()) + l = FLD_MOD(l, 3, 22, 21); /* DSI_REF_SEL */ dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION2, l); REG_FLD_MOD(dsidev, DSI_PLL_GO, 1, 0, 0); /* DSI_PLL_GO */ @@ -1601,7 +1670,6 @@ int dsi_pll_init(struct platform_device *dsidev, bool enable_hsclk, dsi->vdds_dsi_reg = vdds_dsi; } - enable_clocks(1); dsi_enable_pll_clock(dsidev, 1); /* * Note: SCP CLK is not required on OMAP3, but it is required on OMAP4. @@ -1653,7 +1721,6 @@ err1: } err0: dsi_disable_scp_clk(dsidev); - enable_clocks(0); dsi_enable_pll_clock(dsidev, 0); return r; } @@ -1671,7 +1738,6 @@ void dsi_pll_uninit(struct platform_device *dsidev, bool disconnect_lanes) } dsi_disable_scp_clk(dsidev); - enable_clocks(0); dsi_enable_pll_clock(dsidev, 0); DSSDBG("PLL uninit done\n"); @@ -1688,7 +1754,8 @@ static void dsi_dump_dsidev_clocks(struct platform_device *dsidev, dispc_clk_src = dss_get_dispc_clk_source(); dsi_clk_src = dss_get_dsi_clk_source(dsi_module); - enable_clocks(1); + if (dsi_runtime_get(dsidev)) + return; seq_printf(s, "- DSI%d PLL -\n", dsi_module + 1); @@ -1731,7 +1798,7 @@ static void dsi_dump_dsidev_clocks(struct platform_device *dsidev, seq_printf(s, "LP_CLK\t\t%lu\n", cinfo->lp_clk); - enable_clocks(0); + dsi_runtime_put(dsidev); } void dsi_dump_clocks(struct seq_file *s) @@ -1873,7 +1940,8 @@ static void dsi_dump_dsidev_regs(struct platform_device *dsidev, { #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dsi_read_reg(dsidev, r)) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + if (dsi_runtime_get(dsidev)) + return; dsi_enable_scp_clk(dsidev); DUMPREG(DSI_REVISION); @@ -1947,7 +2015,7 @@ static void dsi_dump_dsidev_regs(struct platform_device *dsidev, DUMPREG(DSI_PLL_CONFIGURATION2); dsi_disable_scp_clk(dsidev); - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dsi_runtime_put(dsidev); #undef DUMPREG } @@ -1994,6 +2062,10 @@ static int dsi_cio_power(struct platform_device *dsidev, /* PWR_CMD */ REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG1, state, 28, 27); + if (cpu_is_omap44xx()) + /*bit 30 has to be set to 1 to GO in omap4*/ + REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG1, 1, 30, 30); + /* PWR_STATUS */ while (FLD_GET(dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG1), 26, 25) != state) { @@ -2354,6 +2426,13 @@ static int dsi_cio_init(struct omap_dss_device *dssdev) if (dsi->dsi_mux_pads) dsi->dsi_mux_pads(true); + if (cpu_is_omap44xx()) { + /* DDR_CLK_ALWAYS_ON */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 13, 13); + /* HS_AUTO_STOP_ENABLE */ + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 18, 18); + } + dsi_enable_scp_clk(dsidev); /* A dummy read using the SCP interface to any DSIPHY register is @@ -2463,28 +2542,6 @@ static void dsi_cio_uninit(struct platform_device *dsidev) dsi->dsi_mux_pads(false); } -static int _dsi_wait_reset(struct platform_device *dsidev) -{ - int t = 0; - - while (REG_GET(dsidev, DSI_SYSSTATUS, 0, 0) == 0) { - if (++t > 5) { - DSSERR("soft reset failed\n"); - return -ENODEV; - } - udelay(1); - } - - return 0; -} - -static int _dsi_reset(struct platform_device *dsidev) -{ - /* Soft reset */ - REG_FLD_MOD(dsidev, DSI_SYSCONFIG, 1, 1, 1); - return _dsi_wait_reset(dsidev); -} - static void dsi_config_tx_fifo(struct platform_device *dsidev, enum fifo_size size1, enum fifo_size size2, enum fifo_size size3, enum fifo_size size4) @@ -2722,6 +2779,8 @@ static void dsi_vc_initial_config(struct platform_device *dsidev, int channel) r = FLD_MOD(r, 0, 9, 9); /* MODE_SPEED, high speed on/off */ if (dss_has_feature(FEAT_DSI_VC_OCP_WIDTH)) r = FLD_MOD(r, 3, 11, 10); /* OCP_WIDTH = 32 bit */ + if (channel == 0) + r = FLD_MOD(r, 1, 11, 10); /* OCP_WIDTH = 32 bit */ r = FLD_MOD(r, 4, 29, 27); /* DMA_RX_REQ_NB = no dma */ r = FLD_MOD(r, 4, 23, 21); /* DMA_TX_REQ_NB = no dma */ @@ -2885,6 +2944,10 @@ static u16 dsi_vc_flush_receive_data(struct platform_device *dsidev, } else if (dt == DSI_DT_RX_SHORT_READ_2) { DSSERR("\tDCS short response, 2 byte: %#x\n", FLD_GET(val, 23, 8)); + } else if (dt == DSI_DT_RX_LONG_READ) { + DSSERR("\tlong response, len %d\n", + FLD_GET(val, 23, 8)); + dsi_vc_flush_long_data(dsidev, channel); } else if (dt == DSI_DT_RX_DCS_LONG_READ) { DSSERR("\tDCS long response, len %d\n", FLD_GET(val, 23, 8)); @@ -3229,7 +3292,7 @@ int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int channel, u8 dcs_cmd, buf[1] = (data >> 8) & 0xff; return 2; - } else if (dt == DSI_DT_RX_DCS_LONG_READ) { + } else if (dt == DSI_DT_RX_DCS_LONG_READ || dt == DSI_DT_RX_LONG_READ) { int w; int len = FLD_GET(val, 23, 8); if (dsi->debug_read) @@ -3386,6 +3449,10 @@ static int dsi_enter_ulps(struct platform_device *dsidev) dsi_unregister_isr_cio(dsidev, dsi_completion_handler, &completion, DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); + /* Reset LANEx_ULPS_SIG2 */ + REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG2, (0 << 0) | (0 << 1) | (0 << 2), + 7, 5); + dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ULPS); dsi_if_enable(dsidev, false); @@ -3401,7 +3468,7 @@ err: } static void dsi_set_lp_rx_timeout(struct platform_device *dsidev, - unsigned ticks, bool x4, bool x16) + unsigned ticks, bool x4, bool x16, bool to) { unsigned long fck; unsigned long total_ticks; @@ -3413,7 +3480,7 @@ static void dsi_set_lp_rx_timeout(struct platform_device *dsidev, fck = dsi_fclk_rate(dsidev); r = dsi_read_reg(dsidev, DSI_TIMING2); - r = FLD_MOD(r, 1, 15, 15); /* LP_RX_TO */ + r = FLD_MOD(r, to ? 1 : 0, 15, 15); /* LP_RX_TO */ r = FLD_MOD(r, x16 ? 1 : 0, 14, 14); /* LP_RX_TO_X16 */ r = FLD_MOD(r, x4 ? 1 : 0, 13, 13); /* LP_RX_TO_X4 */ r = FLD_MOD(r, ticks, 12, 0); /* LP_RX_COUNTER */ @@ -3428,7 +3495,7 @@ static void dsi_set_lp_rx_timeout(struct platform_device *dsidev, } static void dsi_set_ta_timeout(struct platform_device *dsidev, unsigned ticks, - bool x8, bool x16) + bool x8, bool x16, bool to) { unsigned long fck; unsigned long total_ticks; @@ -3440,7 +3507,7 @@ static void dsi_set_ta_timeout(struct platform_device *dsidev, unsigned ticks, fck = dsi_fclk_rate(dsidev); r = dsi_read_reg(dsidev, DSI_TIMING1); - r = FLD_MOD(r, 1, 31, 31); /* TA_TO */ + r = FLD_MOD(r, to ? 1 : 0, 31, 31); /* TA_TO */ r = FLD_MOD(r, x16 ? 1 : 0, 30, 30); /* TA_TO_X16 */ r = FLD_MOD(r, x8 ? 1 : 0, 29, 29); /* TA_TO_X8 */ r = FLD_MOD(r, ticks, 28, 16); /* TA_TO_COUNTER */ @@ -3455,7 +3522,8 @@ static void dsi_set_ta_timeout(struct platform_device *dsidev, unsigned ticks, } static void dsi_set_stop_state_counter(struct platform_device *dsidev, - unsigned ticks, bool x4, bool x16) + unsigned ticks, bool x4, bool x16, + bool stop_mode) { unsigned long fck; unsigned long total_ticks; @@ -3467,7 +3535,7 @@ static void dsi_set_stop_state_counter(struct platform_device *dsidev, fck = dsi_fclk_rate(dsidev); r = dsi_read_reg(dsidev, DSI_TIMING1); - r = FLD_MOD(r, 1, 15, 15); /* FORCE_TX_STOP_MODE_IO */ + r = FLD_MOD(r, stop_mode ? 1 : 0, 15, 15); /* FORCE_TX_STOP_MODE_IO */ r = FLD_MOD(r, x16 ? 1 : 0, 14, 14); /* STOP_STATE_X16_IO */ r = FLD_MOD(r, x4 ? 1 : 0, 13, 13); /* STOP_STATE_X4_IO */ r = FLD_MOD(r, ticks, 12, 0); /* STOP_STATE_COUNTER_IO */ @@ -3482,7 +3550,7 @@ static void dsi_set_stop_state_counter(struct platform_device *dsidev, } static void dsi_set_hs_tx_timeout(struct platform_device *dsidev, - unsigned ticks, bool x4, bool x16) + unsigned ticks, bool x4, bool x16, bool to) { unsigned long fck; unsigned long total_ticks; @@ -3494,7 +3562,7 @@ static void dsi_set_hs_tx_timeout(struct platform_device *dsidev, fck = dsi_get_txbyteclkhs(dsidev); r = dsi_read_reg(dsidev, DSI_TIMING2); - r = FLD_MOD(r, 1, 31, 31); /* HS_TX_TO */ + r = FLD_MOD(r, to ? 1 : 0, 31, 31); /* HS_TX_TO */ r = FLD_MOD(r, x16 ? 1 : 0, 30, 30); /* HS_TX_TO_X16 */ r = FLD_MOD(r, x4 ? 1 : 0, 29, 29); /* HS_TX_TO_X8 (4 really) */ r = FLD_MOD(r, ticks, 28, 16); /* HS_TX_TO_COUNTER */ @@ -3507,7 +3575,8 @@ static void dsi_set_hs_tx_timeout(struct platform_device *dsidev, ticks, x4 ? " x4" : "", x16 ? " x16" : "", (total_ticks * 1000) / (fck / 1000 / 1000)); } -static int dsi_proto_config(struct omap_dss_device *dssdev) + +static int dsi_cmd_proto_config(struct omap_dss_device *dssdev) { struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); u32 r; @@ -3524,10 +3593,10 @@ static int dsi_proto_config(struct omap_dss_device *dssdev) DSI_FIFO_SIZE_32); /* XXX what values for the timeouts? */ - dsi_set_stop_state_counter(dsidev, 0x1000, false, false); - dsi_set_ta_timeout(dsidev, 0x1fff, true, true); - dsi_set_lp_rx_timeout(dsidev, 0x1fff, true, true); - dsi_set_hs_tx_timeout(dsidev, 0x1fff, true, true); + dsi_set_stop_state_counter(dsidev, 0x1000, false, false, true); + dsi_set_ta_timeout(dsidev, 0x1fff, true, true, true); + dsi_set_lp_rx_timeout(dsidev, 0x1fff, true, true, true); + dsi_set_hs_tx_timeout(dsidev, 0x1fff, true, true, true); switch (dssdev->ctrl.pixel_size) { case 16: @@ -3569,6 +3638,165 @@ static int dsi_proto_config(struct omap_dss_device *dssdev) return 0; } +static int dispc_to_dsi_clock(int val, int bytes_per_pixel, int lanes) +{ + return (val * bytes_per_pixel + lanes / 2) / lanes; +} +static int dsi_video_proto_config(struct omap_dss_device *dssdev) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + struct omap_video_timings *timings = &dssdev->panel.timings; + int buswidth = 0; + u32 r; + int bytes_per_pixel; + int hbp, hfp, hsa, tl; + + dsi_config_tx_fifo(dsidev, DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32); + + dsi_config_rx_fifo(dsidev, DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32, + DSI_FIFO_SIZE_32); + + dsi_set_stop_state_counter(dsidev, 0x1fff, true, true, false); + dsi_set_ta_timeout(dsidev, 0x1fff, true, true, true); + dsi_set_lp_rx_timeout(dsidev, 0x1fff, true, true, false); + dsi_set_hs_tx_timeout(dsidev, 0x1fff, true, true, true); + + switch (dssdev->ctrl.pixel_size) { + case 16: + buswidth = 0; + bytes_per_pixel = 2; + break; + case 18: + buswidth = 1; + bytes_per_pixel = 3; + break; + case 24: + buswidth = 2; + bytes_per_pixel = 3; + break; + default: + BUG(); + } + + r = dsi_read_reg(dsidev, DSI_CTRL); + r = FLD_MOD(r, 1, 3, 3); /* TX_FIFO_ARBITRATION */ + r = FLD_MOD(r, 1, 4, 4); /* VP_CLK_RATIO, always 1, see errata*/ + r = FLD_MOD(r, buswidth, 7, 6); /* VP_DATA_BUS_WIDTH */ + r = FLD_MOD(r, 0, 8, 8); /* VP_CLK_POL */ + r = FLD_MOD(r, 1, 9, 9); /* VP_DE_POL */ + r = FLD_MOD(r, 0, 10, 10); /* VP_HSYNC_POL */ + r = FLD_MOD(r, 1, 11, 11); /* VP_VSYNC_POL */ + r = FLD_MOD(r, 2, 13, 12); /* LINE_BUFFER */ + r = FLD_MOD(r, 1, 14, 14); /* TRIGGER_RESET_MODE */ + r = FLD_MOD(r, 1, 15, 15); /* VP_VSYNC_START */ + r = FLD_MOD(r, 1, 17, 17); /* VP_HSYNC_START */ + r = FLD_MOD(r, 1, 19, 19); /* EOT_ENABLE */ + r = FLD_MOD(r, 1, 20, 20); /* BLANKING_MODE */ + r = FLD_MOD(r, 1, 21, 21); /* HFP_BLANKING */ + r = FLD_MOD(r, 1, 22, 22); /* HBP_BLANKING */ + r = FLD_MOD(r, 1, 23, 23); /* HSA_BLANKING */ + dsi_write_reg(dsidev, DSI_CTRL, r); + + if(!dssdev->skip_init){ + dsi_vc_initial_config(dsidev, 0); + dsi_vc_initial_config(dsidev, 1); + dsi_vc_initial_config(dsidev, 2); + dsi_vc_initial_config(dsidev, 3); + } + + hbp = dispc_to_dsi_clock(timings->hbp, bytes_per_pixel, 4); + hfp = dispc_to_dsi_clock(timings->hfp, bytes_per_pixel, 4); + hsa = dispc_to_dsi_clock(timings->hsw, bytes_per_pixel, 4); + tl = hbp + hfp + hsa + + dispc_to_dsi_clock(timings->x_res, bytes_per_pixel, 4); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING1); + r = FLD_MOD(r, hbp - 1, 11, 0); /* HBP */ + r = FLD_MOD(r, hfp - 1, 23, 12); /* HFP */ + r = FLD_MOD(r, hsa - 1, 31, 24); /* HSA */ + dsi_write_reg(dsidev, DSI_VM_TIMING1, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING2); + r = FLD_MOD(r, timings->vbp, 7, 0); /* VBP */ + r = FLD_MOD(r, timings->vfp, 15, 8); /* VFP */ + r = FLD_MOD(r, timings->vsw, 23, 16); /* VSA */ + r = FLD_MOD(r, 4, 27, 24); /* WINDOW_SYNC */ + dsi_write_reg(dsidev, DSI_VM_TIMING2, r); + + r = dsi_read_reg(dsidev, DSI_VM_TIMING3); + r = FLD_MOD(r, timings->y_res, 14, 0); + r = FLD_MOD(r, tl - 1, 31, 16); + dsi_write_reg(dsidev, DSI_VM_TIMING3, r); + + /* TODO: either calculate these values or make them configurable */ + r = FLD_VAL(72, 23, 16) | /* HSA_HS_INTERLEAVING */ + FLD_VAL(114, 15, 8) | /* HFB_HS_INTERLEAVING */ + FLD_VAL(150, 7, 0); /* HbB_HS_INTERLEAVING */ + dsi_write_reg(dsidev, DSI_VM_TIMING4, r); + + r = FLD_VAL(130, 23, 16) | /* HSA_LP_INTERLEAVING */ + FLD_VAL(223, 15, 8) | /* HFB_LP_INTERLEAVING */ + FLD_VAL(59, 7, 0); /* HBB_LP_INTERLEAVING */ + dsi_write_reg(dsidev, DSI_VM_TIMING5, r); + + r = FLD_VAL(0x7A67, 31, 16) | /* BL_HS_INTERLEAVING */ + FLD_VAL(0x31D1, 15, 0); /* BL_LP_INTERLEAVING */ + dsi_write_reg(dsidev, DSI_VM_TIMING6, r); + + r = FLD_VAL(18, 31, 16) | /* ENTER_HS_MODE_LATENCY */ + FLD_VAL(15, 15, 0); /* EXIT_HS_MODE_LATENCY */ + dsi_write_reg(dsidev, DSI_VM_TIMING7, r); + + return 0; +} + +int dsi_video_mode_enable(struct omap_dss_device *dssdev, u8 data_type) +{ + struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + u16 word_count; + u32 r; + u32 header; + + dsi_if_enable(dsidev, 0); + dsi_vc_enable(dsidev, 1, 0); + dsi_vc_enable(dsidev, 0, 0); + + r = dsi_read_reg(dsidev, DSI_TIMING1); + r = FLD_MOD(r, 1, 15, 15); /* FORCE_TX_STOP_MODE_IO */ + dsi_write_reg(dsidev, DSI_TIMING1, r); + + if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 15, 0) != 0) + BUG(); + + r = dsi_read_reg(dsidev, DSI_VC_CTRL(0)); + r = FLD_MOD(r, 1, 4, 4); + r = FLD_MOD(r, 1, 9, 9); + dsi_write_reg(dsidev, DSI_VC_CTRL(0), r); + + r = dsi_read_reg(dsidev, DSI_VC_CTRL(1)); + r = FLD_MOD(r, 0, 4, 4); + r = FLD_MOD(r, 1, 9, 9); + dsi_write_reg(dsidev, DSI_VC_CTRL(1), r); + + word_count = dssdev->panel.timings.x_res * 3; + header = FLD_VAL(0, 31, 24) | /* ECC */ + FLD_VAL(word_count, 23, 8) | /* WORD_COUNT */ + FLD_VAL(0, 7, 6) | /* VC_ID */ + FLD_VAL(data_type, 5, 0); + dsi_write_reg(dsidev, DSI_VC_LONG_PACKET_HEADER(0), header); + + dsi_vc_enable(dsidev, 1, 1); + dsi_vc_enable(dsidev, 0, 1); + dsi_if_enable(dsidev, 1); + + return 0; +} + static void dsi_proto_timings(struct omap_dss_device *dssdev) { struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); @@ -3579,6 +3807,7 @@ static void dsi_proto_timings(struct omap_dss_device *dssdev) unsigned ddr_clk_pre, ddr_clk_post; unsigned enter_hs_mode_lat, exit_hs_mode_lat; unsigned ths_eot; + unsigned offset_ddr_clk; u32 r; r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); @@ -3603,9 +3832,13 @@ static void dsi_proto_timings(struct omap_dss_device *dssdev) ths_eot = DIV_ROUND_UP(4, dsi_get_num_data_lanes_dssdev(dssdev)); + /* DDR PRE & DDR POST increased to keep LP-11 under 10 usec */ + offset_ddr_clk = dssdev->clocks.dsi.offset_ddr_clk; + ddr_clk_pre = DIV_ROUND_UP(tclk_pre + tlpx + tclk_zero + tclk_prepare, - 4); - ddr_clk_post = DIV_ROUND_UP(tclk_post + ths_trail, 4) + ths_eot; + 4) + offset_ddr_clk; + ddr_clk_post = DIV_ROUND_UP(tclk_post + ths_trail, 4) + ths_eot + + offset_ddr_clk; BUG_ON(ddr_clk_pre == 0 || ddr_clk_pre > 255); BUG_ON(ddr_clk_post == 0 || ddr_clk_post > 255); @@ -3807,6 +4040,11 @@ static void dsi_update_screen_dispc(struct omap_dss_device *dssdev, DSSDBG("dsi_update_screen_dispc(%d,%d %dx%d)\n", x, y, w, h); + if (dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_VIDEO_MODE) { + dss_start_update(dssdev); + return; + } + dsi_vc_config_vp(dsidev, channel); bytespp = dssdev->ctrl.pixel_size / 8; @@ -4017,23 +4255,30 @@ static int dsi_display_init_dispc(struct omap_dss_device *dssdev) irq = dssdev->manager->id == OMAP_DSS_CHANNEL_LCD ? DISPC_IRQ_FRAMEDONE : DISPC_IRQ_FRAMEDONE2; - r = omap_dispc_register_isr(dsi_framedone_irq_callback, (void *) dssdev, - irq); - if (r) { - DSSERR("can't get FRAMEDONE irq\n"); - return r; + if (dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_CMD_MODE) { + r = omap_dispc_register_isr(dsi_framedone_irq_callback, (void *) dssdev, + irq); + if (r) { + DSSERR("can't get FRAMEDONE irq\n"); + return r; + } + + dispc_set_parallel_interface_mode(dssdev->manager->id, + OMAP_DSS_PARALLELMODE_DSI); + dispc_enable_fifohandcheck(dssdev->manager->id, 1); + } else { + dispc_set_parallel_interface_mode(dssdev->manager->id, + OMAP_DSS_PARALLELMODE_BYPASS); + dispc_enable_fifohandcheck(dssdev->manager->id, 0); } dispc_set_lcd_display_type(dssdev->manager->id, OMAP_DSS_LCD_DISPLAY_TFT); - dispc_set_parallel_interface_mode(dssdev->manager->id, - OMAP_DSS_PARALLELMODE_DSI); - dispc_enable_fifohandcheck(dssdev->manager->id, 1); dispc_set_tft_data_lines(dssdev->manager->id, dssdev->ctrl.pixel_size); - { + if(dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_CMD_MODE) { struct omap_video_timings timings = { .hsw = 1, .hfp = 1, @@ -4044,6 +4289,9 @@ static int dsi_display_init_dispc(struct omap_dss_device *dssdev) }; dispc_set_lcd_timings(dssdev->manager->id, &timings); + } else { + dispc_set_lcd_timings(dssdev->manager->id, + &dssdev->panel.timings); } return 0; @@ -4056,8 +4304,9 @@ static void dsi_display_uninit_dispc(struct omap_dss_device *dssdev) irq = dssdev->manager->id == OMAP_DSS_CHANNEL_LCD ? DISPC_IRQ_FRAMEDONE : DISPC_IRQ_FRAMEDONE2; - omap_dispc_unregister_isr(dsi_framedone_irq_callback, (void *) dssdev, - irq); + if(dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_CMD_MODE) + omap_dispc_unregister_isr(dsi_framedone_irq_callback, (void *) dssdev, + irq); } static int dsi_configure_dsi_clocks(struct omap_dss_device *dssdev) @@ -4120,13 +4369,20 @@ static int dsi_display_init_dsi(struct omap_dss_device *dssdev) int dsi_module = dsi_get_dsidev_id(dsidev); int r; + /* The SCPClk is required for PLL and complexio registers on OMAP4 */ + if (cpu_is_omap44xx()) + REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 14, 14); + r = dsi_pll_init(dsidev, true, true); + if (r) goto err0; - r = dsi_configure_dsi_clocks(dssdev); - if (r) - goto err1; + if(!dssdev->skip_init){ + r = dsi_configure_dsi_clocks(dssdev); + if (r) + goto err1; + } dss_select_dispc_clk_source(dssdev->clocks.dispc.dispc_fclk_src); dss_select_dsi_clk_source(dsi_module, dssdev->clocks.dsi.dsi_fclk_src); @@ -4135,13 +4391,19 @@ static int dsi_display_init_dsi(struct omap_dss_device *dssdev) DSSDBG("PLL OK\n"); - r = dsi_configure_dispc_clocks(dssdev); - if (r) - goto err2; + if(!dssdev->skip_init){ + r = dsi_configure_dispc_clocks(dssdev); + if (r) + goto err2; + } - r = dsi_cio_init(dssdev); - if (r) - goto err2; + if(!dssdev->skip_init){ + r = dsi_cio_init(dssdev); + if (r) + goto err2; + } + else + dsi_enable_scp_clk(dsidev); _dsi_print_reset_status(dsidev); @@ -4151,17 +4413,23 @@ static int dsi_display_init_dsi(struct omap_dss_device *dssdev) if (1) _dsi_print_reset_status(dsidev); - r = dsi_proto_config(dssdev); + if(dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_CMD_MODE) + r = dsi_cmd_proto_config(dssdev); + else + r = dsi_video_proto_config(dssdev); + if (r) goto err3; /* enable interface */ - dsi_vc_enable(dsidev, 0, 1); - dsi_vc_enable(dsidev, 1, 1); - dsi_vc_enable(dsidev, 2, 1); - dsi_vc_enable(dsidev, 3, 1); - dsi_if_enable(dsidev, 1); - dsi_force_tx_stop_mode_io(dsidev); + if(!dssdev->skip_init){ + dsi_vc_enable(dsidev, 0, 1); + dsi_vc_enable(dsidev, 1, 1); + dsi_vc_enable(dsidev, 2, 1); + dsi_vc_enable(dsidev, 3, 1); + dsi_if_enable(dsidev, 1); + dsi_force_tx_stop_mode_io(dsidev); + } return 0; err3: @@ -4198,22 +4466,6 @@ static void dsi_display_uninit_dsi(struct omap_dss_device *dssdev, dsi_pll_uninit(dsidev, disconnect_lanes); } -static int dsi_core_init(struct platform_device *dsidev) -{ - /* Autoidle */ - REG_FLD_MOD(dsidev, DSI_SYSCONFIG, 1, 0, 0); - - /* ENWAKEUP */ - REG_FLD_MOD(dsidev, DSI_SYSCONFIG, 1, 2, 2); - - /* SIDLEMODE smart-idle */ - REG_FLD_MOD(dsidev, DSI_SYSCONFIG, 2, 4, 3); - - _dsi_initialize_irq(dsidev); - - return 0; -} - int omapdss_dsi_display_enable(struct omap_dss_device *dssdev) { struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); @@ -4229,37 +4481,40 @@ int omapdss_dsi_display_enable(struct omap_dss_device *dssdev) r = omap_dss_start_device(dssdev); if (r) { DSSERR("failed to start device\n"); - goto err0; + goto err_start_dev; } - enable_clocks(1); - dsi_enable_pll_clock(dsidev, 1); - - r = _dsi_reset(dsidev); + r = dsi_runtime_get(dsidev); if (r) - goto err1; + goto err_get_dsi; - dsi_core_init(dsidev); + if(!dssdev->skip_init) + dsi_enable_pll_clock(dsidev, 1); - r = dsi_display_init_dispc(dssdev); - if (r) - goto err1; + _dsi_initialize_irq(dsidev); + + if(!dssdev->skip_init){ + r = dsi_display_init_dispc(dssdev); + if (r) + goto err_init_dispc; + } r = dsi_display_init_dsi(dssdev); if (r) - goto err2; + goto err_init_dsi; mutex_unlock(&dsi->lock); return 0; -err2: +err_init_dsi: dsi_display_uninit_dispc(dssdev); -err1: - enable_clocks(0); +err_init_dispc: dsi_enable_pll_clock(dsidev, 0); + dsi_runtime_put(dsidev); +err_get_dsi: omap_dss_stop_device(dssdev); -err0: +err_start_dev: mutex_unlock(&dsi->lock); DSSDBG("dsi_display_enable FAILED\n"); return r; @@ -4282,7 +4537,7 @@ void omapdss_dsi_display_disable(struct omap_dss_device *dssdev, dsi_display_uninit_dsi(dssdev, disconnect_lanes, enter_ulps); - enable_clocks(0); + dsi_runtime_put(dsidev); dsi_enable_pll_clock(dsidev, 0); omap_dss_stop_device(dssdev); @@ -4322,9 +4577,12 @@ int dsi_init_display(struct omap_dss_device *dssdev) DSSDBG("DSI init\n"); - /* XXX these should be figured out dynamically */ - dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | + if(dssdev->phy.dsi.type == OMAP_DSS_DSI_TYPE_CMD_MODE) { + dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | OMAP_DSS_DISPLAY_CAP_TEAR_ELIM; + } else { + dssdev->caps = 0; + } if (dsi->vdds_dsi_reg == NULL) { struct regulator *vdds_dsi; @@ -4437,7 +4695,44 @@ static void dsi_calc_clock_param_ranges(struct platform_device *dsidev) dsi->lpdiv_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_LPDIV); } -static int dsi_init(struct platform_device *dsidev) +static int dsi_get_clocks(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + struct clk *clk; + + clk = clk_get(&dsidev->dev, "dss_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get dss_clk\n"); + return PTR_ERR(clk); + } + + dsi->dss_clk = clk; + + clk = clk_get(&dsidev->dev, "sys_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get sys_clk\n"); + clk_put(dsi->dss_clk); + dsi->dss_clk = NULL; + return PTR_ERR(clk); + } + + dsi->sys_clk = clk; + + return 0; +} + +static void dsi_put_clocks(struct platform_device *dsidev) +{ + struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + + if (dsi->dss_clk) + clk_put(dsi->dss_clk); + if (dsi->sys_clk) + clk_put(dsi->sys_clk); +} + +/* DSI1 HW IP initialisation */ +static int omap_dsi1hw_probe(struct platform_device *dsidev) { struct omap_display_platform_data *dss_plat_data; struct omap_dss_board_info *board_info; @@ -4449,7 +4744,7 @@ static int dsi_init(struct platform_device *dsidev) dsi = kzalloc(sizeof(*dsi), GFP_KERNEL); if (!dsi) { r = -ENOMEM; - goto err0; + goto err_alloc; } dsi->pdev = dsidev; @@ -4472,6 +4767,14 @@ static int dsi_init(struct platform_device *dsidev) mutex_init(&dsi->lock); sema_init(&dsi->bus_lock, 1); + r = dsi_get_clocks(dsidev); + if (r) + goto err_get_clk; + + mutex_init(&dsi->runtime_lock); + + pm_runtime_enable(&dsidev->dev); + INIT_DELAYED_WORK_DEFERRABLE(&dsi->framedone_timeout_work, dsi_framedone_timeout_work_callback); @@ -4484,26 +4787,26 @@ static int dsi_init(struct platform_device *dsidev) if (!dsi_mem) { DSSERR("can't get IORESOURCE_MEM DSI\n"); r = -EINVAL; - goto err1; + goto err_ioremap; } dsi->base = ioremap(dsi_mem->start, resource_size(dsi_mem)); if (!dsi->base) { DSSERR("can't ioremap DSI\n"); r = -ENOMEM; - goto err1; + goto err_ioremap; } dsi->irq = platform_get_irq(dsi->pdev, 0); if (dsi->irq < 0) { DSSERR("platform_get_irq failed\n"); r = -ENODEV; - goto err2; + goto err_get_irq; } r = request_irq(dsi->irq, omap_dsi_irq_handler, IRQF_SHARED, dev_name(&dsidev->dev), dsi->pdev); if (r < 0) { DSSERR("request_irq failed\n"); - goto err2; + goto err_get_irq; } /* DSI VCs initialization */ @@ -4515,7 +4818,9 @@ static int dsi_init(struct platform_device *dsidev) dsi_calc_clock_param_ranges(dsidev); - enable_clocks(1); + r = dsi_runtime_get(dsidev); + if (r) + goto err_get_dsi; rev = dsi_read_reg(dsidev, DSI_REVISION); dev_dbg(&dsidev->dev, "OMAP DSI rev %d.%d\n", @@ -4523,21 +4828,32 @@ static int dsi_init(struct platform_device *dsidev) dsi->num_data_lanes = dsi_get_num_data_lanes(dsidev); - enable_clocks(0); + dsi_runtime_put(dsidev); return 0; -err2: + +err_get_dsi: + free_irq(dsi->irq, dsi->pdev); +err_get_irq: iounmap(dsi->base); -err1: +err_ioremap: + pm_runtime_disable(&dsidev->dev); +err_get_clk: kfree(dsi); -err0: +err_alloc: return r; } -static void dsi_exit(struct platform_device *dsidev) +static int omap_dsi1hw_remove(struct platform_device *dsidev) { struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + WARN_ON(dsi->scp_clk_refcount > 0); + + pm_runtime_disable(&dsidev->dev); + + dsi_put_clocks(dsidev); + if (dsi->vdds_dsi_reg != NULL) { if (dsi->vdds_dsi_enabled) { regulator_disable(dsi->vdds_dsi_reg); @@ -4553,29 +4869,6 @@ static void dsi_exit(struct platform_device *dsidev) kfree(dsi); - DSSDBG("omap_dsi_exit\n"); -} - -/* DSI1 HW IP initialisation */ -static int omap_dsi1hw_probe(struct platform_device *dsidev) -{ - int r; - - r = dsi_init(dsidev); - if (r) { - DSSERR("Failed to initialize DSI\n"); - goto err_dsi; - } -err_dsi: - return r; -} - -static int omap_dsi1hw_remove(struct platform_device *dsidev) -{ - struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); - - dsi_exit(dsidev); - WARN_ON(dsi->scp_clk_refcount > 0); return 0; } diff --git a/drivers/video/omap2/dss/dss.c b/drivers/video/omap2/dss/dss.c index d9489d5..964eb08 100644 --- a/drivers/video/omap2/dss/dss.c +++ b/drivers/video/omap2/dss/dss.c @@ -28,6 +28,8 @@ #include <linux/delay.h> #include <linux/seq_file.h> #include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <video/omapdss.h> #include <plat/clock.h> @@ -59,15 +61,12 @@ struct dss_reg { static struct { struct platform_device *pdev; void __iomem *base; - int ctx_id; + + struct mutex runtime_lock; + int runtime_count; struct clk *dpll4_m4_ck; - struct clk *dss_ick; - struct clk *dss_fck; - struct clk *dss_sys_clk; - struct clk *dss_tv_fck; - struct clk *dss_video_fck; - unsigned num_clks_enabled; + struct clk *dss_clk; unsigned long cache_req_pck; unsigned long cache_prate; @@ -78,6 +77,7 @@ static struct { enum omap_dss_clk_source dispc_clk_source; enum omap_dss_clk_source lcd_clk_source[MAX_DSS_LCD_MANAGERS]; + bool ctx_valid; u32 ctx[DSS_SZ_REGS / sizeof(u32)]; } dss; @@ -87,13 +87,6 @@ static const char * const dss_generic_clk_source_names[] = { [OMAP_DSS_CLK_SRC_FCK] = "DSS_FCK", }; -static void dss_clk_enable_all_no_ctx(void); -static void dss_clk_disable_all_no_ctx(void); -static void dss_clk_enable_no_ctx(enum dss_clock clks); -static void dss_clk_disable_no_ctx(enum dss_clock clks); - -static int _omap_dss_wait_reset(void); - static inline void dss_write_reg(const struct dss_reg idx, u32 val) { __raw_writel(val, dss.base + idx.idx); @@ -109,12 +102,10 @@ static inline u32 dss_read_reg(const struct dss_reg idx) #define RR(reg) \ dss_write_reg(DSS_##reg, dss.ctx[(DSS_##reg).idx / sizeof(u32)]) -void dss_save_context(void) +static void dss_save_context(void) { - if (cpu_is_omap24xx()) - return; + DSSDBG("dss_save_context\n"); - SR(SYSCONFIG); SR(CONTROL); if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & @@ -122,14 +113,19 @@ void dss_save_context(void) SR(SDI_CONTROL); SR(PLL_CONTROL); } + + dss.ctx_valid = true; + + DSSDBG("context saved\n"); } -void dss_restore_context(void) +static void dss_restore_context(void) { - if (_omap_dss_wait_reset()) - DSSERR("DSS not coming out of reset after sleep\n"); + DSSDBG("dss_restore_context\n"); + + if (!dss.ctx_valid) + return; - RR(SYSCONFIG); RR(CONTROL); if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & @@ -137,6 +133,8 @@ void dss_restore_context(void) RR(SDI_CONTROL); RR(PLL_CONTROL); } + + DSSDBG("context restored\n"); } #undef SR @@ -234,6 +232,7 @@ const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src) return dss_generic_clk_source_names[clk_src]; } + void dss_dump_clocks(struct seq_file *s) { unsigned long dpll4_ck_rate; @@ -241,13 +240,14 @@ void dss_dump_clocks(struct seq_file *s) const char *fclk_name, *fclk_real_name; unsigned long fclk_rate; - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + if (dss_runtime_get()) + return; seq_printf(s, "- DSS -\n"); fclk_name = dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_FCK); fclk_real_name = dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_FCK); - fclk_rate = dss_clk_get_rate(DSS_CLK_FCK); + fclk_rate = clk_get_rate(dss.dss_clk); if (dss.dpll4_m4_ck) { dpll4_ck_rate = clk_get_rate(clk_get_parent(dss.dpll4_m4_ck)); @@ -273,14 +273,15 @@ void dss_dump_clocks(struct seq_file *s) fclk_rate); } - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dss_runtime_put(); } void dss_dump_regs(struct seq_file *s) { #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dss_read_reg(r)) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + if (dss_runtime_get()) + return; DUMPREG(DSS_REVISION); DUMPREG(DSS_SYSCONFIG); @@ -294,7 +295,7 @@ void dss_dump_regs(struct seq_file *s) DUMPREG(DSS_SDI_STATUS); } - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dss_runtime_put(); #undef DUMPREG } @@ -437,7 +438,7 @@ int dss_calc_clock_rates(struct dss_clock_info *cinfo) } else { if (cinfo->fck_div != 0) return -EINVAL; - cinfo->fck = dss_clk_get_rate(DSS_CLK_FCK); + cinfo->fck = clk_get_rate(dss.dss_clk); } return 0; @@ -467,7 +468,7 @@ int dss_set_clock_div(struct dss_clock_info *cinfo) int dss_get_clock_div(struct dss_clock_info *cinfo) { - cinfo->fck = dss_clk_get_rate(DSS_CLK_FCK); + cinfo->fck = clk_get_rate(dss.dss_clk); if (dss.dpll4_m4_ck) { unsigned long prate; @@ -512,7 +513,7 @@ int dss_calc_clock_div(bool is_tft, unsigned long req_pck, max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); - fck = dss_clk_get_rate(DSS_CLK_FCK); + fck = clk_get_rate(dss.dss_clk); if (req_pck == dss.cache_req_pck && ((cpu_is_omap34xx() && prate == dss.cache_prate) || dss.cache_dss_cinfo.fck == fck)) { @@ -539,7 +540,7 @@ retry: if (dss.dpll4_m4_ck == NULL) { struct dispc_clock_info cur_dispc; /* XXX can we change the clock on omap2? */ - fck = dss_clk_get_rate(DSS_CLK_FCK); + fck = clk_get_rate(dss.dss_clk); fck_div = 1; dispc_find_clk_divs(is_tft, req_pck, fck, &cur_dispc); @@ -616,28 +617,6 @@ found: return 0; } -static int _omap_dss_wait_reset(void) -{ - int t = 0; - - while (REG_GET(DSS_SYSSTATUS, 0, 0) == 0) { - if (++t > 1000) { - DSSERR("soft reset failed\n"); - return -ENODEV; - } - udelay(1); - } - - return 0; -} - -static int _omap_dss_reset(void) -{ - /* Soft reset */ - REG_FLD_MOD(DSS_SYSCONFIG, 1, 1, 1); - return _omap_dss_wait_reset(); -} - void dss_set_venc_output(enum omap_dss_venc_type type) { int l = 0; @@ -663,424 +642,111 @@ void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select hdmi) REG_FLD_MOD(DSS_CONTROL, hdmi, 15, 15); /* VENC_HDMI_SWITCH */ } -static int dss_init(void) +static int dss_get_clocks(void) { + struct clk *clk; int r; - u32 rev; - struct resource *dss_mem; - struct clk *dpll4_m4_ck; - dss_mem = platform_get_resource(dss.pdev, IORESOURCE_MEM, 0); - if (!dss_mem) { - DSSERR("can't get IORESOURCE_MEM DSS\n"); - r = -EINVAL; - goto fail0; - } - dss.base = ioremap(dss_mem->start, resource_size(dss_mem)); - if (!dss.base) { - DSSERR("can't ioremap DSS\n"); - r = -ENOMEM; - goto fail0; + clk = clk_get(&dss.pdev->dev, "dss_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get clock dss_clk\n"); + r = PTR_ERR(clk); + goto err; } - /* disable LCD and DIGIT output. This seems to fix the synclost - * problem that we get, if the bootloader starts the DSS and - * the kernel resets it */ - omap_writel(omap_readl(0x48050440) & ~0x3, 0x48050440); - -#ifdef CONFIG_OMAP2_DSS_SLEEP_BEFORE_RESET - /* We need to wait here a bit, otherwise we sometimes start to - * get synclost errors, and after that only power cycle will - * restore DSS functionality. I have no idea why this happens. - * And we have to wait _before_ resetting the DSS, but after - * enabling clocks. - * - * This bug was at least present on OMAP3430. It's unknown - * if it happens on OMAP2 or OMAP3630. - */ - msleep(50); -#endif - - _omap_dss_reset(); - - /* autoidle */ - REG_FLD_MOD(DSS_SYSCONFIG, 1, 0, 0); + dss.dss_clk = clk; - /* Select DPLL */ - REG_FLD_MOD(DSS_CONTROL, 0, 0, 0); - -#ifdef CONFIG_OMAP2_DSS_VENC - REG_FLD_MOD(DSS_CONTROL, 1, 4, 4); /* venc dac demen */ - REG_FLD_MOD(DSS_CONTROL, 1, 3, 3); /* venc clock 4x enable */ - REG_FLD_MOD(DSS_CONTROL, 0, 2, 2); /* venc clock mode = normal */ -#endif if (cpu_is_omap34xx()) { - dpll4_m4_ck = clk_get(NULL, "dpll4_m4_ck"); - if (IS_ERR(dpll4_m4_ck)) { + clk = clk_get(NULL, "dpll4_m4_ck"); + if (IS_ERR(clk)) { DSSERR("Failed to get dpll4_m4_ck\n"); - r = PTR_ERR(dpll4_m4_ck); - goto fail1; + r = PTR_ERR(clk); + goto err; } } else if (cpu_is_omap44xx()) { - dpll4_m4_ck = clk_get(NULL, "dpll_per_m5x2_ck"); - if (IS_ERR(dpll4_m4_ck)) { - DSSERR("Failed to get dpll4_m4_ck\n"); - r = PTR_ERR(dpll4_m4_ck); - goto fail1; + clk = clk_get(NULL, "dpll_per_m5x2_ck"); + if (IS_ERR(clk)) { + DSSERR("Failed to get dpll_per_m5x2_ck\n"); + r = PTR_ERR(clk); + goto err; } } else { /* omap24xx */ - dpll4_m4_ck = NULL; + clk = NULL; } - dss.dpll4_m4_ck = dpll4_m4_ck; - - dss.dsi_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; - dss.dsi_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; - dss.dispc_clk_source = OMAP_DSS_CLK_SRC_FCK; - dss.lcd_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; - dss.lcd_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; - - dss_save_context(); - - rev = dss_read_reg(DSS_REVISION); - printk(KERN_INFO "OMAP DSS rev %d.%d\n", - FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + dss.dpll4_m4_ck = clk; return 0; -fail1: - iounmap(dss.base); -fail0: - return r; -} - -static void dss_exit(void) -{ +err: + if (dss.dss_clk) + clk_put(dss.dss_clk); if (dss.dpll4_m4_ck) clk_put(dss.dpll4_m4_ck); - iounmap(dss.base); -} - -/* CONTEXT */ -static int dss_get_ctx_id(void) -{ - struct omap_display_platform_data *pdata = dss.pdev->dev.platform_data; - int r; - - if (!pdata->board_data->get_last_off_on_transaction_id) - return 0; - r = pdata->board_data->get_last_off_on_transaction_id(&dss.pdev->dev); - if (r < 0) { - dev_err(&dss.pdev->dev, "getting transaction ID failed, " - "will force context restore\n"); - r = -1; - } return r; } -int dss_need_ctx_restore(void) -{ - int id = dss_get_ctx_id(); - - if (id < 0 || id != dss.ctx_id) { - DSSDBG("ctx id %d -> id %d\n", - dss.ctx_id, id); - dss.ctx_id = id; - return 1; - } else { - return 0; - } -} - -static void save_all_ctx(void) -{ - DSSDBG("save context\n"); - - dss_clk_enable_no_ctx(DSS_CLK_ICK | DSS_CLK_FCK); - - dss_save_context(); - dispc_save_context(); -#ifdef CONFIG_OMAP2_DSS_DSI - dsi_save_context(); -#endif - - dss_clk_disable_no_ctx(DSS_CLK_ICK | DSS_CLK_FCK); -} - -static void restore_all_ctx(void) -{ - DSSDBG("restore context\n"); - - dss_clk_enable_all_no_ctx(); - - dss_restore_context(); - dispc_restore_context(); -#ifdef CONFIG_OMAP2_DSS_DSI - dsi_restore_context(); -#endif - - dss_clk_disable_all_no_ctx(); -} - -static int dss_get_clock(struct clk **clock, const char *clk_name) +static void dss_put_clocks(void) { - struct clk *clk; - - clk = clk_get(&dss.pdev->dev, clk_name); - - if (IS_ERR(clk)) { - DSSERR("can't get clock %s", clk_name); - return PTR_ERR(clk); - } - - *clock = clk; - - DSSDBG("clk %s, rate %ld\n", clk_name, clk_get_rate(clk)); - - return 0; + if (dss.dpll4_m4_ck) + clk_put(dss.dpll4_m4_ck); + clk_put(dss.dss_clk); } -static int dss_get_clocks(void) +int dss_runtime_get(void) { int r; - struct omap_display_platform_data *pdata = dss.pdev->dev.platform_data; - dss.dss_ick = NULL; - dss.dss_fck = NULL; - dss.dss_sys_clk = NULL; - dss.dss_tv_fck = NULL; - dss.dss_video_fck = NULL; - - r = dss_get_clock(&dss.dss_ick, "ick"); - if (r) - goto err; + mutex_lock(&dss.runtime_lock); - r = dss_get_clock(&dss.dss_fck, "fck"); - if (r) - goto err; + if (dss.runtime_count++ == 0) { + DSSDBG("dss_runtime_get\n"); - if (!pdata->opt_clock_available) { - r = -ENODEV; - goto err; - } + clk_enable(dss.dss_clk); - if (pdata->opt_clock_available("sys_clk")) { - r = dss_get_clock(&dss.dss_sys_clk, "sys_clk"); - if (r) + r = pm_runtime_get_sync(&dss.pdev->dev); + WARN_ON(r); + if (r < 0) goto err; - } - if (pdata->opt_clock_available("tv_clk")) { - r = dss_get_clock(&dss.dss_tv_fck, "tv_clk"); - if (r) - goto err; + dss_restore_context(); } - if (pdata->opt_clock_available("video_clk")) { - r = dss_get_clock(&dss.dss_video_fck, "video_clk"); - if (r) - goto err; - } + mutex_unlock(&dss.runtime_lock); return 0; err: - if (dss.dss_ick) - clk_put(dss.dss_ick); - if (dss.dss_fck) - clk_put(dss.dss_fck); - if (dss.dss_sys_clk) - clk_put(dss.dss_sys_clk); - if (dss.dss_tv_fck) - clk_put(dss.dss_tv_fck); - if (dss.dss_video_fck) - clk_put(dss.dss_video_fck); - + clk_disable(dss.dss_clk); + mutex_unlock(&dss.runtime_lock); return r; } -static void dss_put_clocks(void) +void dss_runtime_put(void) { - if (dss.dss_video_fck) - clk_put(dss.dss_video_fck); - if (dss.dss_tv_fck) - clk_put(dss.dss_tv_fck); - if (dss.dss_sys_clk) - clk_put(dss.dss_sys_clk); - clk_put(dss.dss_fck); - clk_put(dss.dss_ick); -} - -unsigned long dss_clk_get_rate(enum dss_clock clk) -{ - switch (clk) { - case DSS_CLK_ICK: - return clk_get_rate(dss.dss_ick); - case DSS_CLK_FCK: - return clk_get_rate(dss.dss_fck); - case DSS_CLK_SYSCK: - return clk_get_rate(dss.dss_sys_clk); - case DSS_CLK_TVFCK: - return clk_get_rate(dss.dss_tv_fck); - case DSS_CLK_VIDFCK: - return clk_get_rate(dss.dss_video_fck); - } - - BUG(); - return 0; -} - -static unsigned count_clk_bits(enum dss_clock clks) -{ - unsigned num_clks = 0; - - if (clks & DSS_CLK_ICK) - ++num_clks; - if (clks & DSS_CLK_FCK) - ++num_clks; - if (clks & DSS_CLK_SYSCK) - ++num_clks; - if (clks & DSS_CLK_TVFCK) - ++num_clks; - if (clks & DSS_CLK_VIDFCK) - ++num_clks; - - return num_clks; -} - -static void dss_clk_enable_no_ctx(enum dss_clock clks) -{ - unsigned num_clks = count_clk_bits(clks); - - if (clks & DSS_CLK_ICK) - clk_enable(dss.dss_ick); - if (clks & DSS_CLK_FCK) - clk_enable(dss.dss_fck); - if ((clks & DSS_CLK_SYSCK) && dss.dss_sys_clk) - clk_enable(dss.dss_sys_clk); - if ((clks & DSS_CLK_TVFCK) && dss.dss_tv_fck) - clk_enable(dss.dss_tv_fck); - if ((clks & DSS_CLK_VIDFCK) && dss.dss_video_fck) - clk_enable(dss.dss_video_fck); - - dss.num_clks_enabled += num_clks; -} + mutex_lock(&dss.runtime_lock); -void dss_clk_enable(enum dss_clock clks) -{ - bool check_ctx = dss.num_clks_enabled == 0; - - dss_clk_enable_no_ctx(clks); - - /* - * HACK: On omap4 the registers may not be accessible right after - * enabling the clocks. At some point this will be handled by - * pm_runtime, but for the time begin this should make things work. - */ - if (cpu_is_omap44xx() && check_ctx) - udelay(10); - - if (check_ctx && cpu_is_omap34xx() && dss_need_ctx_restore()) - restore_all_ctx(); -} + if (--dss.runtime_count == 0) { + int r; -static void dss_clk_disable_no_ctx(enum dss_clock clks) -{ - unsigned num_clks = count_clk_bits(clks); - - if (clks & DSS_CLK_ICK) - clk_disable(dss.dss_ick); - if (clks & DSS_CLK_FCK) - clk_disable(dss.dss_fck); - if ((clks & DSS_CLK_SYSCK) && dss.dss_sys_clk) - clk_disable(dss.dss_sys_clk); - if ((clks & DSS_CLK_TVFCK) && dss.dss_tv_fck) - clk_disable(dss.dss_tv_fck); - if ((clks & DSS_CLK_VIDFCK) && dss.dss_video_fck) - clk_disable(dss.dss_video_fck); - - dss.num_clks_enabled -= num_clks; -} + DSSDBG("dss_runtime_put\n"); -void dss_clk_disable(enum dss_clock clks) -{ - if (cpu_is_omap34xx()) { - unsigned num_clks = count_clk_bits(clks); + dss_save_context(); - BUG_ON(dss.num_clks_enabled < num_clks); + r = pm_runtime_put_sync(&dss.pdev->dev); + WARN_ON(r); - if (dss.num_clks_enabled == num_clks) - save_all_ctx(); + clk_disable(dss.dss_clk); } - dss_clk_disable_no_ctx(clks); -} - -static void dss_clk_enable_all_no_ctx(void) -{ - enum dss_clock clks; - - clks = DSS_CLK_ICK | DSS_CLK_FCK | DSS_CLK_SYSCK | DSS_CLK_TVFCK; - if (cpu_is_omap34xx()) - clks |= DSS_CLK_VIDFCK; - dss_clk_enable_no_ctx(clks); + mutex_unlock(&dss.runtime_lock); } -static void dss_clk_disable_all_no_ctx(void) -{ - enum dss_clock clks; - - clks = DSS_CLK_ICK | DSS_CLK_FCK | DSS_CLK_SYSCK | DSS_CLK_TVFCK; - if (cpu_is_omap34xx()) - clks |= DSS_CLK_VIDFCK; - dss_clk_disable_no_ctx(clks); -} - -#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_OMAP2_DSS_DEBUG_SUPPORT) -/* CLOCKS */ -static void core_dump_clocks(struct seq_file *s) -{ - int i; - struct clk *clocks[5] = { - dss.dss_ick, - dss.dss_fck, - dss.dss_sys_clk, - dss.dss_tv_fck, - dss.dss_video_fck - }; - - const char *names[5] = { - "ick", - "fck", - "sys_clk", - "tv_fck", - "video_fck" - }; - - seq_printf(s, "- CORE -\n"); - - seq_printf(s, "internal clk count\t\t%u\n", dss.num_clks_enabled); - - for (i = 0; i < 5; i++) { - if (!clocks[i]) - continue; - seq_printf(s, "%s (%s)%*s\t%lu\t%d\n", - names[i], - clocks[i]->name, - 24 - strlen(names[i]) - strlen(clocks[i]->name), - "", - clk_get_rate(clocks[i]), - clocks[i]->usecount); - } -} -#endif /* defined(CONFIG_DEBUG_FS) && defined(CONFIG_OMAP2_DSS_DEBUG_SUPPORT) */ - /* DEBUGFS */ #if defined(CONFIG_DEBUG_FS) && defined(CONFIG_OMAP2_DSS_DEBUG_SUPPORT) void dss_debug_dump_clocks(struct seq_file *s) { - core_dump_clocks(s); dss_dump_clocks(s); dispc_dump_clocks(s); #ifdef CONFIG_OMAP2_DSS_DSI @@ -1089,28 +755,53 @@ void dss_debug_dump_clocks(struct seq_file *s) } #endif - /* DSS HW IP initialisation */ static int omap_dsshw_probe(struct platform_device *pdev) { + struct resource *dss_mem; + u32 rev; int r; dss.pdev = pdev; + dss_mem = platform_get_resource(dss.pdev, IORESOURCE_MEM, 0); + if (!dss_mem) { + DSSERR("can't get IORESOURCE_MEM DSS\n"); + r = -EINVAL; + goto err_ioremap; + } + dss.base = ioremap(dss_mem->start, resource_size(dss_mem)); + if (!dss.base) { + DSSERR("can't ioremap DSS\n"); + r = -ENOMEM; + goto err_ioremap; + } + r = dss_get_clocks(); if (r) goto err_clocks; - dss_clk_enable_all_no_ctx(); + mutex_init(&dss.runtime_lock); - dss.ctx_id = dss_get_ctx_id(); - DSSDBG("initial ctx id %u\n", dss.ctx_id); + pm_runtime_enable(&pdev->dev); - r = dss_init(); - if (r) { - DSSERR("Failed to initialize DSS\n"); - goto err_dss; - } + r = dss_runtime_get(); + if (r) + goto err_runtime_get; + + /* Select DPLL */ + REG_FLD_MOD(DSS_CONTROL, 0, 0, 0); + +#ifdef CONFIG_OMAP2_DSS_VENC + REG_FLD_MOD(DSS_CONTROL, 1, 4, 4); /* venc dac demen */ + REG_FLD_MOD(DSS_CONTROL, 1, 3, 3); /* venc clock 4x enable */ + REG_FLD_MOD(DSS_CONTROL, 0, 2, 2); /* venc clock mode = normal */ +#endif + dss.dsi_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; + dss.dsi_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; + dss.dispc_clk_source = OMAP_DSS_CLK_SRC_FCK; + dss.lcd_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; + dss.lcd_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; r = dpi_init(); if (r) { @@ -1124,33 +815,37 @@ static int omap_dsshw_probe(struct platform_device *pdev) goto err_sdi; } - dss_clk_disable_all_no_ctx(); + rev = dss_read_reg(DSS_REVISION); + printk(KERN_INFO "OMAP DSS rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + dss_runtime_put(); + return 0; err_sdi: dpi_exit(); err_dpi: - dss_exit(); -err_dss: - dss_clk_disable_all_no_ctx(); + dss_runtime_put(); +err_runtime_get: + pm_runtime_disable(&pdev->dev); dss_put_clocks(); err_clocks: + iounmap(dss.base); +err_ioremap: return r; } static int omap_dsshw_remove(struct platform_device *pdev) { + dpi_exit(); + sdi_exit(); - dss_exit(); + iounmap(dss.base); - /* - * As part of hwmod changes, DSS is not the only controller of dss - * clocks; hwmod framework itself will also enable clocks during hwmod - * init for dss, and autoidle is set in h/w for DSS. Hence, there's no - * need to disable clocks if their usecounts > 1. - */ - WARN_ON(dss.num_clks_enabled > 0); + pm_runtime_disable(&pdev->dev); dss_put_clocks(); + return 0; } diff --git a/drivers/video/omap2/dss/dss.h b/drivers/video/omap2/dss/dss.h index 8ab6d43..22cd979 100644 --- a/drivers/video/omap2/dss/dss.h +++ b/drivers/video/omap2/dss/dss.h @@ -109,14 +109,6 @@ enum omap_parallel_interface_mode { OMAP_DSS_PARALLELMODE_DSI, }; -enum dss_clock { - DSS_CLK_ICK = 1 << 0, /* DSS_L3_ICLK and DSS_L4_ICLK */ - DSS_CLK_FCK = 1 << 1, /* DSS1_ALWON_FCLK */ - DSS_CLK_SYSCK = 1 << 2, /* DSS2_ALWON_FCLK */ - DSS_CLK_TVFCK = 1 << 3, /* DSS_TV_FCLK */ - DSS_CLK_VIDFCK = 1 << 4, /* DSS_96M_FCLK*/ -}; - enum dss_hdmi_venc_clk_source_select { DSS_VENC_TV_CLK = 0, DSS_HDMI_M_PCLK = 1, @@ -164,14 +156,28 @@ struct dsi_clock_info { bool use_sys_clk; }; -/* HDMI PLL structure */ -struct hdmi_pll_info { - u16 regn; - u16 regm; - u32 regmf; - u16 regm2; - u16 regsd; - u16 dcofreq; +struct dispc_config { + u32 sizex, sizey; + u32 burstsize; + u32 pixelinc; + u32 rowinc; + u32 bursttype; + u32 antiflicker; + u32 doublestride; + u32 ba; + u32 bacbcr; + u32 format; + u32 rotation; + u32 gfx_top_buffer; + u32 gfx_bottom_buffer; + u32 vid1_top_buffer; + u32 vid1_bottom_buffer; + u32 vid2_top_buffer; + u32 vid2_bottom_buffer; + u32 vid3_top_buffer; + u32 vid3_bottom_buffer; + u32 wb_top_buffer; + u32 wb_bottom_buffer; }; struct seq_file; @@ -220,13 +226,10 @@ void dss_recheck_connections(struct omap_dss_device *dssdev, bool force); int dss_init_platform_driver(void); void dss_uninit_platform_driver(void); +int dss_runtime_get(void); +void dss_runtime_put(void); + void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select); -void dss_save_context(void); -void dss_restore_context(void); -void dss_clk_enable(enum dss_clock clks); -void dss_clk_disable(enum dss_clock clks); -unsigned long dss_clk_get_rate(enum dss_clock clk); -int dss_need_ctx_restore(void); const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src); void dss_dump_clocks(struct seq_file *s); @@ -283,15 +286,15 @@ struct file_operations; int dsi_init_platform_driver(void); void dsi_uninit_platform_driver(void); +int dsi_runtime_get(struct platform_device *dsidev); +void dsi_runtime_put(struct platform_device *dsidev); + void dsi_dump_clocks(struct seq_file *s); void dsi_create_debugfs_files_irq(struct dentry *debugfs_dir, const struct file_operations *debug_fops); void dsi_create_debugfs_files_reg(struct dentry *debugfs_dir, const struct file_operations *debug_fops); -void dsi_save_context(void); -void dsi_restore_context(void); - int dsi_init_display(struct omap_dss_device *display); void dsi_irq_handler(void); unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev); @@ -317,6 +320,13 @@ static inline int dsi_init_platform_driver(void) static inline void dsi_uninit_platform_driver(void) { } +static inline int dsi_runtime_get(struct platform_device *dsidev) +{ + return 0; +} +static inline void dsi_runtime_put(struct platform_device *dsidev) +{ +} static inline unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev) { WARN("%s: DSI not compiled in, returning rate as 0\n", __func__); @@ -384,8 +394,8 @@ void dispc_dump_regs(struct seq_file *s); void dispc_irq_handler(void); void dispc_fake_vsync_irq(void); -void dispc_save_context(void); -void dispc_restore_context(void); +int dispc_runtime_get(void); +void dispc_runtime_put(void); void dispc_enable_sidle(void); void dispc_disable_sidle(void); @@ -402,6 +412,14 @@ void dispc_setup_plane_fifo(enum omap_plane plane, u32 low, u32 high); void dispc_enable_fifomerge(bool enable); void dispc_set_burst_size(enum omap_plane plane, enum omap_burst_size burst_size); +void dispc_set_zorder(enum omap_plane plane, + enum omap_overlay_zorder zorder); +void dispc_enable_zorder(enum omap_plane plane, bool enable); +void dispc_enable_cpr(enum omap_channel channel, bool enable); +void dispc_set_cpr_coef(enum omap_channel channel, + struct omap_dss_cpr_coefs *coefs); +void _dispc_setup_color_conv_coef(enum omap_plane plane, + const struct omap_dss_cconv_coefs *ct); void dispc_set_plane_ba0(enum omap_plane plane, u32 paddr); void dispc_set_plane_ba1(enum omap_plane plane, u32 paddr); @@ -418,15 +436,26 @@ int dispc_setup_plane(enum omap_plane plane, u16 out_width, u16 out_height, enum omap_color_mode color_mode, bool ilace, + int x_decim, int y_decim, bool five_taps, enum omap_dss_rotation_type rotation_type, u8 rotation, bool mirror, u8 global_alpha, u8 pre_mult_alpha, enum omap_channel channel, u32 puv_addr); +int dispc_scaling_decision(u16 width, u16 height, + u16 out_width, u16 out_height, + enum omap_plane plane, + enum omap_color_mode color_mode, + enum omap_channel channel, u8 rotation, + enum omap_dss_rotation_type type, + u16 min_x_decim, u16 max_x_decim, + u16 min_y_decim, u16 max_y_decim, + u16 *x_decim, u16 *y_decim, bool *three_tap); bool dispc_go_busy(enum omap_channel channel); void dispc_go(enum omap_channel channel); -void dispc_enable_channel(enum omap_channel channel, bool enable); +void dispc_enable_channel(enum omap_channel channel, + enum omap_display_type type, bool enable); bool dispc_is_channel_enabled(enum omap_channel channel); int dispc_enable_plane(enum omap_plane plane, bool enable); void dispc_enable_replication(enum omap_plane plane, bool enable); @@ -467,7 +496,7 @@ int dispc_set_clock_div(enum omap_channel channel, struct dispc_clock_info *cinfo); int dispc_get_clock_div(enum omap_channel channel, struct dispc_clock_info *cinfo); - +u32 sa_calc_wrap(struct dispc_config *dispc_reg_config, u32 channel_no); /* VENC */ #ifdef CONFIG_OMAP2_DSS_VENC @@ -508,8 +537,26 @@ void omapdss_hdmi_display_disable(struct omap_dss_device *dssdev); void omapdss_hdmi_display_set_timing(struct omap_dss_device *dssdev); int omapdss_hdmi_display_check_timing(struct omap_dss_device *dssdev, struct omap_video_timings *timings); +int omapdss_hdmi_display_set_mode(struct omap_dss_device *dssdev, + struct fb_videomode *mode); +void omapdss_hdmi_restart(void); +int hdmi_panel_hpd_handler(int hpd); +int omapdss_hdmi_get_pixel_clock(void); +int omapdss_hdmi_get_mode(void); +int omapdss_hdmi_get_deepcolor(void); +void omapdss_hdmi_set_deepcolor(int val); +int hdmi_get_current_hpd(void); +void hdmi_get_monspecs(struct fb_monspecs *specs); +u8 *hdmi_read_edid(struct omap_video_timings *); + int hdmi_panel_init(void); void hdmi_panel_exit(void); +void hdmi_dump_regs(struct seq_file *s); +int omapdss_hdmi_register_hdcp_callbacks(void (*hdmi_start_frame_cb)(void), + void (*hdmi_irq_cb)(int status), + bool (*hdmi_power_on_cb)(void)); +int omap_dss_ovl_set_info(struct omap_overlay *ovl, + struct omap_overlay_info *info); /* RFBI */ #ifdef CONFIG_OMAP2_DSS_RFBI diff --git a/drivers/video/omap2/dss/dss_features.c b/drivers/video/omap2/dss/dss_features.c index 1c18888..aba2250 100644 --- a/drivers/video/omap2/dss/dss_features.c +++ b/drivers/video/omap2/dss/dss_features.c @@ -197,7 +197,17 @@ static const enum omap_color_mode omap4_dss_supported_color_modes[] = { OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | OMAP_DSS_COLOR_RGBX32, - /* OMAP_DSS_VIDEO2 */ + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | + OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | + OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | + OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_RGB24U | + OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_UYVY | + OMAP_DSS_COLOR_ARGB16 | OMAP_DSS_COLOR_XRGB16_1555 | + OMAP_DSS_COLOR_ARGB32 | OMAP_DSS_COLOR_RGBX16 | + OMAP_DSS_COLOR_RGBX32, + + /* OMAP_DSS_VIDEO3 */ OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_ARGB16_1555 | OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_NV12 | @@ -286,7 +296,9 @@ static const struct omap_dss_features omap3430_dss_features = { FEAT_LCDENABLESIGNAL | FEAT_PCKFREEENABLE | FEAT_FUNCGATED | FEAT_ROWREPEATENABLE | FEAT_LINEBUFFERSPLIT | FEAT_RESIZECONF | - FEAT_DSI_PLL_FREQSEL | FEAT_DSI_REVERSE_TXCLKESC, + FEAT_DSI_PLL_FREQSEL | FEAT_DSI_REVERSE_TXCLKESC | + FEAT_VENC_REQUIRES_TV_DAC_CLK | FEAT_CPR | FEAT_PRELOAD | + FEAT_FIR_COEF_V, .num_mgrs = 2, .num_ovls = 3, @@ -306,7 +318,8 @@ static const struct omap_dss_features omap3630_dss_features = { FEAT_PRE_MULT_ALPHA | FEAT_FUNCGATED | FEAT_ROWREPEATENABLE | FEAT_LINEBUFFERSPLIT | FEAT_RESIZECONF | FEAT_DSI_PLL_PWR_BUG | - FEAT_DSI_PLL_FREQSEL, + FEAT_DSI_PLL_FREQSEL | FEAT_CPR | FEAT_PRELOAD | + FEAT_FIR_COEF_V, .num_mgrs = 2, .num_ovls = 3, @@ -327,10 +340,13 @@ static const struct omap_dss_features omap4430_es1_0_dss_features = { FEAT_MGR_LCD2 | FEAT_GLOBAL_ALPHA_VID1 | FEAT_CORE_CLK_DIV | FEAT_LCD_CLK_SRC | FEAT_DSI_DCS_CMD_CONFIG_VC | FEAT_DSI_VC_OCP_WIDTH | - FEAT_DSI_GNQ | FEAT_HANDLE_UV_SEPARATE | FEAT_ATTR2, + FEAT_DSI_GNQ | FEAT_HANDLE_UV_SEPARATE | FEAT_ATTR2 | + FEAT_CPR | FEAT_PRELOAD | FEAT_FIR_COEF_V | + FEAT_ALPHA_OMAP3_COMPAT | FEAT_OVL_VID3 | + FEAT_OVL_ZORDER, .num_mgrs = 3, - .num_ovls = 3, + .num_ovls = 4, .supported_displays = omap4_dss_supported_displays, .supported_color_modes = omap4_dss_supported_color_modes, .clksrc_names = omap4_dss_clk_source_names, @@ -348,10 +364,13 @@ static const struct omap_dss_features omap4_dss_features = { FEAT_CORE_CLK_DIV | FEAT_LCD_CLK_SRC | FEAT_DSI_DCS_CMD_CONFIG_VC | FEAT_DSI_VC_OCP_WIDTH | FEAT_DSI_GNQ | FEAT_HDMI_CTS_SWMODE | - FEAT_HANDLE_UV_SEPARATE | FEAT_ATTR2, + FEAT_HANDLE_UV_SEPARATE | FEAT_ATTR2 | FEAT_CPR | + FEAT_PRELOAD | FEAT_FIR_COEF_V | + FEAT_ALPHA_OMAP3_COMPAT | FEAT_OVL_VID3 | + FEAT_OVL_ZORDER, .num_mgrs = 3, - .num_ovls = 3, + .num_ovls = 4, .supported_displays = omap4_dss_supported_displays, .supported_color_modes = omap4_dss_supported_color_modes, .clksrc_names = omap4_dss_clk_source_names, diff --git a/drivers/video/omap2/dss/dss_features.h b/drivers/video/omap2/dss/dss_features.h index 07b346f..28f44ed 100644 --- a/drivers/video/omap2/dss/dss_features.h +++ b/drivers/video/omap2/dss/dss_features.h @@ -21,7 +21,7 @@ #define __OMAP2_DSS_FEATURES_H #define MAX_DSS_MANAGERS 3 -#define MAX_DSS_OVERLAYS 3 +#define MAX_DSS_OVERLAYS 4 #define MAX_DSS_LCD_MANAGERS 2 #define MAX_NUM_DSI 2 @@ -51,6 +51,13 @@ enum dss_feat_id { FEAT_HDMI_CTS_SWMODE = 1 << 19, FEAT_HANDLE_UV_SEPARATE = 1 << 20, FEAT_ATTR2 = 1 << 21, + FEAT_VENC_REQUIRES_TV_DAC_CLK = 1 << 22, + FEAT_CPR = 1 << 23, + FEAT_PRELOAD = 1 << 24, + FEAT_FIR_COEF_V = 1 << 25, + FEAT_ALPHA_OMAP3_COMPAT = 1 << 26, + FEAT_OVL_VID3 = 1 << 27, + FEAT_OVL_ZORDER = 1 << 28, }; /* DSS register field id */ diff --git a/drivers/video/omap2/dss/fifothreshold.c b/drivers/video/omap2/dss/fifothreshold.c new file mode 100644 index 0000000..431f4c1 --- /dev/null +++ b/drivers/video/omap2/dss/fifothreshold.c @@ -0,0 +1,422 @@ +/* + * linux/drivers/video/omap2/dss/fifothreshold.c + * + * Copyright (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <video/omapdss.h> +#include "dss.h" + +#define YUV422_UYVY 10 +#define YUV422_YUV2 11 + +struct sa_struct { + u32 min_sa; + u32 max_lt; + u32 min_lt; +}; + +struct ddma_config { + u16 twomode; + u16 antifckr; + u16 double_stride; + u16 bpp; + u16 bitmap; + u16 pixel_inc; + u16 max_burst; + u16 gballoc; + u16 vballoc; + u16 yuv420; + u32 rowincr; + u32 ba; + u32 size_x; + u32 size_y; +}; + +/* + * bitpk : used to return partial bit vectors of bigger + * bit vector, as and when required in algorithm. + * Ex: bitpk(BaseAddress,28,27) = BaseAddress[28:27] + */ +static inline u32 bitpk(unsigned long a, u32 left, u32 right) +{ + return (a >> right) & ((2 << (left - right)) - 1); +} + +/* + * dispc_reg_to_ddma converts the DISPC register values into information + * used by the DDMA. Ex: format=15 => BytesPerPixel = 3 + * dispcRegConfig :: Dispc register information + * ChannelNo :: ChannelNo + * y_nuv :: 1->Luma frame parameters, calculation ; + * 0->Chroma frame parameters and calculation + * bh_config :: Output struct having information useful for the algorithm + */ +static void dispc_reg_to_ddma(struct dispc_config *dispc_reg_config, + u32 channel_no, u32 y_nuv, struct ddma_config *bh_config) +{ + u16 i; + /* GFX pipe specific conversions */ + if (channel_no == 0) { + /* + * For bitmap formats the pixcel information is stored in bits. + * This needs to be divided by 8 to convert into bytes. + */ + bh_config->bitmap = (dispc_reg_config->format <= 2) ? 8 : 1; + /* + * In case of GFX there is no YUV420 mode: + * yuv420: 1->nonYUV420 2-> YUV420 + */ + bh_config->yuv420 = 1; + bh_config->pixel_inc = dispc_reg_config->pixelinc; + switch (dispc_reg_config->format) { + /* LUT for format<-->Bytesper pixel */ + case 0: + case 3: + i = 1; + break; + case 1: + case 4: + case 5: + case 6: + case 7: + case 10: + case 11: + case 15: + i = 2; + break; + case 9: + i = 3; + break; + default: + i = 4; + break; + } + bh_config->bpp = i; + i = 0; + /* + * Chroma double_stride value of DISPC registers is invalid + * for GFX where there is np YUV420 format. + */ + bh_config->double_stride = 0; + bh_config->antifckr = dispc_reg_config->antiflicker; + bh_config->ba = dispc_reg_config->ba; + bh_config->size_x = dispc_reg_config->sizex; + } else { + /* + * For all Video channels + * + * In Video there is no bitmap format, All format pixcel is + * stored in multiples of bytes. + */ + bh_config->bitmap = 1; + /* No antiflicker mode for Video channels */ + bh_config->antifckr = 0; + /* + * 1->nonYUV420 2-> YUV420 : Used in breaking up the buffer + * allocation:: Top:Luma, Bottom:Chroma + */ + bh_config->yuv420 = (dispc_reg_config->format == 0) ? 2 : 1; + + switch (dispc_reg_config->format) { + /* LUT for format<-->Bytesper pixel */ + /* bpp:1 for Luma bpp:2 for Chroma */ + case 0: + i = (y_nuv ? 1 : 2); + break; + case 1: + case 2: + case 4: + case 5: + case 6: + case 7: + case 15: + i = 2; + break; + case 9: + i = 3; + break; + case 10: + case 11: + i = (dispc_reg_config->rotation == 1 || + dispc_reg_config->rotation == 3) ? 4 : 2; + break; + + /* Format 10,11 => YUV422. YUV422+No rotation : bpp =bpp/2 */ + default: + i = 4; + break; + } + + /* + * PixcelIncrement = numberOfPixcelsInterleaving*BytesPerPixcel + * + 1. For Chroma pixcelincrement should be doubled to leave + * same number of Chroma pixels and Luma. + */ + bh_config->pixel_inc = + (dispc_reg_config->format == 0 && y_nuv == 0) ? + (dispc_reg_config->pixelinc - 1) * 2 + 1 : + dispc_reg_config->pixelinc; + + /* + * for YUV422+No rotation : bpp =bpp/2:: To correct Pixcel + * increment accordingly use in stride calculation. + */ + bh_config->pixel_inc = + ((dispc_reg_config->format == YUV422_UYVY || + dispc_reg_config->format == YUV422_YUV2) && + (dispc_reg_config->rotation == 0 || + dispc_reg_config->rotation == 2)) ? + (dispc_reg_config->pixelinc - 1) / 2 + 1 : + bh_config->pixel_inc; + bh_config->bpp = i; + bh_config->double_stride = + (dispc_reg_config->format == 0 && y_nuv == 0) ? + dispc_reg_config->doublestride : 0; + + /* Conditions in which SizeY is halfed = i; */ + i = (((dispc_reg_config->rotation == 1 || + dispc_reg_config->rotation == 3) && + (dispc_reg_config->format == YUV422_UYVY || + dispc_reg_config->format == YUV422_YUV2)) || + ((dispc_reg_config->rotation == 1 || + dispc_reg_config->rotation == 3 || + bh_config->double_stride == 1) && + dispc_reg_config->format == 0 && y_nuv == 0)) ? 1 : 0; + + /* Choosing between BA_Y and BA_CbCr */ + bh_config->ba = + (dispc_reg_config->format == 0 && y_nuv == 0) ? + dispc_reg_config->bacbcr : + dispc_reg_config->ba; + + /* SizeX halfed for Chroma frame */ + bh_config->size_x = + (dispc_reg_config->format == 0 && y_nuv == 0) ? + (dispc_reg_config->sizex + 1) / 2 - 1 : + dispc_reg_config->sizex; + } + + bh_config->twomode = dispc_reg_config->bursttype; + bh_config->size_y = ((dispc_reg_config->sizey + 1) >> i) - 1; + bh_config->rowincr = dispc_reg_config->rowinc; + bh_config->max_burst = 1 << (dispc_reg_config->burstsize + 1); + + /* Decoding the burstSize to be used in BH calculation algorithm */ + bh_config->gballoc = + (dispc_reg_config->gfx_bottom_buffer == channel_no) + + (dispc_reg_config->gfx_top_buffer == channel_no); + bh_config->vballoc = + (dispc_reg_config->vid1_bottom_buffer == channel_no) + + (dispc_reg_config->vid1_top_buffer == channel_no) + + (dispc_reg_config->vid2_bottom_buffer == channel_no) + + (dispc_reg_config->vid2_top_buffer == channel_no) + + (dispc_reg_config->vid3_bottom_buffer == channel_no) + + (dispc_reg_config->vid3_top_buffer == channel_no) + + (dispc_reg_config->wb_bottom_buffer == channel_no) + + (dispc_reg_config->wb_top_buffer == channel_no); +} + +/* + * sa_calc calculates SA and LT values for one set of DISPC reg inputs + * dispc_reg_config :: Dispc register information + * channel_no :: channel_no + * y_nuv :: 1->Luma frame parameters, calculation + * 0->Chroma frame parameters and calculation + * sa_info :: Output struct having information of SA and LT values + */ +static void sa_calc(struct dispc_config *dispc_reg_config, u32 channel_no, + u32 y_nuv, struct sa_struct *sa_info) +{ + u32 Sorientation, mode, mode_0, mode_1; + int blkh_opt; + int pagemode; + long int sizeX_nopred, sizeX_pred; + u32 pict_16word; + long int pict_16word_ceil; + long int stride; + int stride_8k; + int stride_16k; + int stride_32k; + int stride_64k; + int stride_ok, stride_val; + int linesRem; + u32 BA_bhbit, bh_max; + int burstHeight; + int i; + int bh2d_cond; + int C1, c1flag, C2; + long int Tot_mem; + struct ddma_config bh_config; + + dispc_reg_to_ddma(dispc_reg_config, channel_no, y_nuv, &bh_config); + + mode = bitpk(bh_config.ba, 28, 27); + mode_1 = bitpk(bh_config.ba, 28, 28); + mode_0 = bitpk(bh_config.ba, 27, 27); + Sorientation = bitpk(bh_config.ba, 31, 31); + + pagemode = (mode == 3); + blkh_opt = mode_1 ? 2 : 4; + + bh_config.double_stride = (bh_config.double_stride == 1 + && bh_config.twomode == 1) ? 2 : 1; + + /* SizeX in frame = number of pixels * BytesPerPixel */ + sizeX_nopred = ((bh_config.size_x + 1) * bh_config.bpp) / + bh_config.bitmap; + /* Size including skipped pixels */ + sizeX_pred = ((bh_config.size_x + 1) * + (bh_config.pixel_inc - 1 + bh_config.bpp)) / bh_config.bitmap; + stride = ((bh_config.rowincr - 1) + sizeX_pred) * + bh_config.double_stride; + stride_8k = stride == 8192 && mode_1 == 0 && Sorientation; + stride_16k = stride == 16384 && !(mode_0 != mode_1 && !Sorientation); + stride_32k = stride == 32768 && (mode_1 == 1 || !Sorientation); + stride_64k = stride == 65536 && !(mode_0 == mode_1) && !Sorientation; + stride_ok = (stride_8k || stride_16k || stride_32k || stride_64k); + stride_val = stride_64k ? 16 : stride_32k ? 15 : stride_16k ? 14 : 13; + + linesRem = bh_config.size_y + 1; + + /* Condition than enables 2D fetch of OCP */ + bh2d_cond = (bh_config.twomode && (pagemode == 0) + && stride_ok && (linesRem > 0)); + + /* + * BH calculation algorithm depending on stride,NumberofLinesInFrame, + * other parameters of Tiler alignment of base address. + */ + C1 = C2 = c1flag = 0; + for (i = 1; i <= 5 && linesRem > 0 && c1flag == 0; i++) { + if (bh2d_cond) { + /* 2D transfer */ + BA_bhbit = bitpk(bh_config.ba, + stride_val + (mode_1 == 0), + stride_val); + bh_max = blkh_opt - BA_bhbit; + + burstHeight = min(linesRem, + min((int) bh_config.max_burst, (int) bh_max)); + + if (burstHeight == 3 || + (burstHeight == 4 && bh_config.antifckr == 1)) + burstHeight = 2; + } else { + burstHeight = 1; + } + if ((C1 + burstHeight) <= 4 && c1flag == 0) { + /* + * C1 incremented until its >= 4. ensures howmany + * full lines are requested just before SA reaches + */ + C1 += burstHeight; + } else { + if (c1flag == 0) + /* + * After C1 saturated to 4, next burstHeight + * decides C2: the number of partially filled + * lines when SA condition is reached + */ + C2 = burstHeight; + c1flag = 1; + } + linesRem -= burstHeight; + bh_config.ba += stride * burstHeight; + } + + /* + * Total line buffer memory GFXBuffers+Vid/WB bufers allocated in terms + * of 16Byte Word locations + */ + Tot_mem = (640 * bh_config.gballoc + 1024 * bh_config.vballoc) / + (4 * bh_config.yuv420); + /* + * Ceil(rounded to higher integer) of Number of 16Byte Word locations + * used by single line of frame. + */ + pict_16word_ceil = DIV_ROUND_UP(sizeX_nopred, 16); + + /* Exact Number of 16Byte Word locations used by single line of frame */ + pict_16word = sizeX_nopred / 16; + + /* + * Number of sets of 4 lines that can fully fit into the memory + * buffers allocated. + */ + i = Tot_mem / pict_16word_ceil; + + if (i == 0) { + /* LineSize > MemoryLineBufferSize (Valid only for 1D) */ + sa_info->min_sa = Tot_mem - 8; + } else if (i == 1) { + /* + * When MemoryLineBufferSize > LineSize > + * (MemoryLineBufferSize/2) + */ + sa_info->min_sa = pict_16word + C2 * (Tot_mem - + pict_16word_ceil - 8); + } else { + /* All other cases */ + sa_info->min_sa = i * pict_16word + C1 * pict_16word + C2 * + (Tot_mem - (pict_16word_ceil * i) - 8); + } + + /* C2=0:: no partialy filed lines:: Then minLT = 0 */ + if (C2 == 0) { + sa_info->min_lt = 0; + } else if (bh_config.antifckr == 1) { + if (C1 == 3) + sa_info->min_lt = 3 * pict_16word_ceil + C2 * (Tot_mem - + (pict_16word_ceil*i)); + else if (C1 == 4) + sa_info->min_lt = 2 * pict_16word_ceil + C2 * (Tot_mem - + (pict_16word_ceil*i)); + } else { + sa_info->min_lt = C2 * (Tot_mem - (pict_16word_ceil*i)); + } + + sa_info->max_lt = max(sa_info->min_sa - 8, sa_info->min_lt + 1); +} + +/* + * sa_calc calculates SA and LT values for one set of DISPC reg inputs + * This takes care of calling the actual sa_calc function once/twice + * as per nonYUV420/YUV420 format and gives final value of output + * dispc_reg_config :: Dispc register information + * channel_no :: channel_no + * y_nuv :: 1->Luma frame parameters, calculation; + * 0->Chroma frame parameters and calculation + * sa_info :: Output struct having information of SA and LT values + */ +u32 sa_calc_wrap(struct dispc_config *dispc_reg_config, u32 channel_no) +{ + struct sa_struct sa_info_y; + struct sa_struct sa_info_uv; + + /* SA values calculated for Luma frame */ + sa_calc(dispc_reg_config, channel_no, 1, &sa_info_y); + + /* Going into this looop only for YUV420 Format and Channel != GFX */ + if (dispc_reg_config->format == 0 && channel_no > 0) { + /* SA values calculated for Chroma Frame */ + sa_calc(dispc_reg_config, channel_no, 0, &sa_info_uv); + return 2 * max(max(sa_info_y.min_sa - 8, sa_info_y.min_lt + 1), + max(sa_info_uv.min_sa - 8, sa_info_uv.min_lt + 1)); + } else { + return sa_info_y.max_lt; + } +} diff --git a/drivers/video/omap2/dss/hdmi.c b/drivers/video/omap2/dss/hdmi.c index fadd6a0..cfb82b5 100644 --- a/drivers/video/omap2/dss/hdmi.c +++ b/drivers/video/omap2/dss/hdmi.c @@ -26,1099 +26,268 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/interrupt.h> +#include <linux/seq_file.h> #include <linux/mutex.h> #include <linux/delay.h> #include <linux/string.h> -#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> #include <video/omapdss.h> -#if defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) || \ - defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI_MODULE) -#include <sound/soc.h> -#include <sound/pcm_params.h> -#endif +#include <video/hdmi_ti_4xxx_ip.h> +#include <linux/gpio.h> +#include <linux/fb.h> +#include <linux/omapfb.h> #include "dss.h" -#include "hdmi.h" #include "dss_features.h" -#define HDMI_DEFAULT_REGN 15 -#define HDMI_DEFAULT_REGM2 1 +#define HDMI_WP 0x0 +#define HDMI_CORE_SYS 0x400 +#define HDMI_CORE_AV 0x900 +#define HDMI_PLLCTRL 0x200 +#define HDMI_PHY 0x300 + +/* HDMI EDID Length move this */ +#define HDMI_EDID_MAX_LENGTH 512 +#define EDID_TIMING_DESCRIPTOR_SIZE 0x12 +#define EDID_DESCRIPTOR_BLOCK0_ADDRESS 0x36 +#define EDID_DESCRIPTOR_BLOCK1_ADDRESS 0x80 +#define EDID_SIZE_BLOCK0_TIMING_DESCRIPTOR 4 +#define EDID_SIZE_BLOCK1_TIMING_DESCRIPTOR 4 + +#define OMAP_HDMI_TIMINGS_NB 34 static struct { struct mutex lock; struct omap_display_platform_data *pdata; struct platform_device *pdev; - void __iomem *base_wp; /* HDMI wrapper */ + struct omap_dss_device *dssdev; + struct hdmi_ip_data hdmi_data; int code; int mode; u8 edid[HDMI_EDID_MAX_LENGTH]; u8 edid_set; + bool can_do_hdmi; + bool custom_set; + enum hdmi_deep_color_mode deep_color; struct hdmi_config cfg; + struct regulator *hdmi_reg; - int hpd_gpio; - bool phy_tx_enabled; -} hdmi; + int hdmi_irq; + struct clk *sys_clk; + struct clk *hdmi_clk; -/* - * Logic for the below structure : - * user enters the CEA or VESA timings by specifying the HDMI/DVI code. - * There is a correspondence between CEA/VESA timing and code, please - * refer to section 6.3 in HDMI 1.3 specification for timing code. - * - * In the below structure, cea_vesa_timings corresponds to all OMAP4 - * supported CEA and VESA timing values.code_cea corresponds to the CEA - * code, It is used to get the timing from cea_vesa_timing array.Similarly - * with code_vesa. Code_index is used for back mapping, that is once EDID - * is read from the TV, EDID is parsed to find the timing values and then - * map it to corresponding CEA or VESA index. - */ - -static const struct hdmi_timings cea_vesa_timings[OMAP_HDMI_TIMINGS_NB] = { - { {640, 480, 25200, 96, 16, 48, 2, 10, 33} , 0 , 0}, - { {1280, 720, 74250, 40, 440, 220, 5, 5, 20}, 1, 1}, - { {1280, 720, 74250, 40, 110, 220, 5, 5, 20}, 1, 1}, - { {720, 480, 27027, 62, 16, 60, 6, 9, 30}, 0, 0}, - { {2880, 576, 108000, 256, 48, 272, 5, 5, 39}, 0, 0}, - { {1440, 240, 27027, 124, 38, 114, 3, 4, 15}, 0, 0}, - { {1440, 288, 27000, 126, 24, 138, 3, 2, 19}, 0, 0}, - { {1920, 540, 74250, 44, 528, 148, 5, 2, 15}, 1, 1}, - { {1920, 540, 74250, 44, 88, 148, 5, 2, 15}, 1, 1}, - { {1920, 1080, 148500, 44, 88, 148, 5, 4, 36}, 1, 1}, - { {720, 576, 27000, 64, 12, 68, 5, 5, 39}, 0, 0}, - { {1440, 576, 54000, 128, 24, 136, 5, 5, 39}, 0, 0}, - { {1920, 1080, 148500, 44, 528, 148, 5, 4, 36}, 1, 1}, - { {2880, 480, 108108, 248, 64, 240, 6, 9, 30}, 0, 0}, - { {1920, 1080, 74250, 44, 638, 148, 5, 4, 36}, 1, 1}, - /* VESA From Here */ - { {640, 480, 25175, 96, 16, 48, 2 , 11, 31}, 0, 0}, - { {800, 600, 40000, 128, 40, 88, 4 , 1, 23}, 1, 1}, - { {848, 480, 33750, 112, 16, 112, 8 , 6, 23}, 1, 1}, - { {1280, 768, 79500, 128, 64, 192, 7 , 3, 20}, 1, 0}, - { {1280, 800, 83500, 128, 72, 200, 6 , 3, 22}, 1, 0}, - { {1360, 768, 85500, 112, 64, 256, 6 , 3, 18}, 1, 1}, - { {1280, 960, 108000, 112, 96, 312, 3 , 1, 36}, 1, 1}, - { {1280, 1024, 108000, 112, 48, 248, 3 , 1, 38}, 1, 1}, - { {1024, 768, 65000, 136, 24, 160, 6, 3, 29}, 0, 0}, - { {1400, 1050, 121750, 144, 88, 232, 4, 3, 32}, 1, 0}, - { {1440, 900, 106500, 152, 80, 232, 6, 3, 25}, 1, 0}, - { {1680, 1050, 146250, 176 , 104, 280, 6, 3, 30}, 1, 0}, - { {1366, 768, 85500, 143, 70, 213, 3, 3, 24}, 1, 1}, - { {1920, 1080, 148500, 44, 148, 80, 5, 4, 36}, 1, 1}, - { {1280, 768, 68250, 32, 48, 80, 7, 3, 12}, 0, 1}, - { {1400, 1050, 101000, 32, 48, 80, 4, 3, 23}, 0, 1}, - { {1680, 1050, 119000, 32, 48, 80, 6, 3, 21}, 0, 1}, - { {1280, 800, 79500, 32, 48, 80, 6, 3, 14}, 0, 1}, - { {1280, 720, 74250, 40, 110, 220, 5, 5, 20}, 1, 1} -}; - -/* - * This is a static mapping array which maps the timing values - * with corresponding CEA / VESA code - */ -static const int code_index[OMAP_HDMI_TIMINGS_NB] = { - 1, 19, 4, 2, 37, 6, 21, 20, 5, 16, 17, 29, 31, 35, 32, - /* <--15 CEA 17--> vesa*/ - 4, 9, 0xE, 0x17, 0x1C, 0x27, 0x20, 0x23, 0x10, 0x2A, - 0X2F, 0x3A, 0X51, 0X52, 0x16, 0x29, 0x39, 0x1B -}; + int runtime_count; + int enabled; + bool set_mode; + bool wp_reset_done; -/* - * This is reverse static mapping which maps the CEA / VESA code - * to the corresponding timing values - */ -static const int code_cea[39] = { - -1, 0, 3, 3, 2, 8, 5, 5, -1, -1, - -1, -1, -1, -1, -1, -1, 9, 10, 10, 1, - 7, 6, 6, -1, -1, -1, -1, -1, -1, 11, - 11, 12, 14, -1, -1, 13, 13, 4, 4 -}; - -static const int code_vesa[85] = { - -1, -1, -1, -1, 15, -1, -1, -1, -1, 16, - -1, -1, -1, -1, 17, -1, 23, -1, -1, -1, - -1, -1, 29, 18, -1, -1, -1, 32, 19, -1, - -1, -1, 21, -1, -1, 22, -1, -1, -1, 20, - -1, 30, 24, -1, -1, -1, -1, 25, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 31, 26, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 27, 28, -1, 33}; + void (*hdmi_start_frame_cb)(void); + void (*hdmi_irq_cb)(int); + bool (*hdmi_power_on_cb)(void); +} hdmi; static const u8 edid_header[8] = {0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}; -static inline void hdmi_write_reg(const struct hdmi_reg idx, u32 val) -{ - __raw_writel(val, hdmi.base_wp + idx.idx); -} - -static inline u32 hdmi_read_reg(const struct hdmi_reg idx) -{ - return __raw_readl(hdmi.base_wp + idx.idx); -} - -static inline int hdmi_wait_for_bit_change(const struct hdmi_reg idx, - int b2, int b1, u32 val) -{ - u32 t = 0; - while (val != REG_GET(idx, b2, b1)) { - udelay(1); - if (t++ > 10000) - return !val; - } - return val; -} - -int hdmi_init_display(struct omap_dss_device *dssdev) -{ - DSSDBG("init_display\n"); - - return 0; -} - -static int hdmi_pll_init(enum hdmi_clk_refsel refsel, int dcofreq, - struct hdmi_pll_info *fmt, u16 sd) -{ - u32 r; - - /* PLL start always use manual mode */ - REG_FLD_MOD(PLLCTRL_PLL_CONTROL, 0x0, 0, 0); - - r = hdmi_read_reg(PLLCTRL_CFG1); - r = FLD_MOD(r, fmt->regm, 20, 9); /* CFG1_PLL_REGM */ - r = FLD_MOD(r, fmt->regn, 8, 1); /* CFG1_PLL_REGN */ - - hdmi_write_reg(PLLCTRL_CFG1, r); - - r = hdmi_read_reg(PLLCTRL_CFG2); - - r = FLD_MOD(r, 0x0, 12, 12); /* PLL_HIGHFREQ divide by 2 */ - r = FLD_MOD(r, 0x1, 13, 13); /* PLL_REFEN */ - r = FLD_MOD(r, 0x0, 14, 14); /* PHY_CLKINEN de-assert during locking */ - - if (dcofreq) { - /* divider programming for frequency beyond 1000Mhz */ - REG_FLD_MOD(PLLCTRL_CFG3, sd, 17, 10); - r = FLD_MOD(r, 0x4, 3, 1); /* 1000MHz and 2000MHz */ - } else { - r = FLD_MOD(r, 0x2, 3, 1); /* 500MHz and 1000MHz */ - } - - hdmi_write_reg(PLLCTRL_CFG2, r); - - r = hdmi_read_reg(PLLCTRL_CFG4); - r = FLD_MOD(r, fmt->regm2, 24, 18); - r = FLD_MOD(r, fmt->regmf, 17, 0); - - hdmi_write_reg(PLLCTRL_CFG4, r); - - /* go now */ - REG_FLD_MOD(PLLCTRL_PLL_GO, 0x1, 0, 0); - - /* wait for bit change */ - if (hdmi_wait_for_bit_change(PLLCTRL_PLL_GO, 0, 0, 1) != 1) { - DSSERR("PLL GO bit not set\n"); - return -ETIMEDOUT; - } - - /* Wait till the lock bit is set in PLL status */ - if (hdmi_wait_for_bit_change(PLLCTRL_PLL_STATUS, 1, 1, 1) != 1) { - DSSWARN("cannot lock PLL\n"); - DSSWARN("CFG1 0x%x\n", - hdmi_read_reg(PLLCTRL_CFG1)); - DSSWARN("CFG2 0x%x\n", - hdmi_read_reg(PLLCTRL_CFG2)); - DSSWARN("CFG4 0x%x\n", - hdmi_read_reg(PLLCTRL_CFG4)); - return -ETIMEDOUT; - } - - DSSDBG("PLL locked!\n"); - - return 0; -} - -/* PHY_PWR_CMD */ -static int hdmi_set_phy_pwr(enum hdmi_phy_pwr val) +static int hdmi_runtime_get(void) { - /* Command for power control of HDMI PHY */ - REG_FLD_MOD(HDMI_WP_PWR_CTRL, val, 7, 6); + int r; - /* Status of the power control of HDMI PHY */ - if (hdmi_wait_for_bit_change(HDMI_WP_PWR_CTRL, 5, 4, val) != val) { - DSSERR("Failed to set PHY power mode to %d\n", val); - return -ETIMEDOUT; - } + DSSDBG("hdmi_runtime_get\n"); - return 0; -} + if (hdmi.runtime_count++ == 0) { + r = dss_runtime_get(); + if (r) + goto err_get_dss; -/* PLL_PWR_CMD */ -static int hdmi_set_pll_pwr(enum hdmi_pll_pwr val) -{ - /* Command for power control of HDMI PLL */ - REG_FLD_MOD(HDMI_WP_PWR_CTRL, val, 3, 2); + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; - /* wait till PHY_PWR_STATUS is set */ - if (hdmi_wait_for_bit_change(HDMI_WP_PWR_CTRL, 1, 0, val) != val) { - DSSERR("Failed to set PHY_PWR_STATUS\n"); - return -ETIMEDOUT; - } + clk_enable(hdmi.sys_clk); + clk_enable(hdmi.hdmi_clk); - return 0; -} - -static int hdmi_pll_reset(void) -{ - /* SYSRESET controlled by power FSM */ - REG_FLD_MOD(PLLCTRL_PLL_CONTROL, 0x0, 3, 3); - - /* READ 0x0 reset is in progress */ - if (hdmi_wait_for_bit_change(PLLCTRL_PLL_STATUS, 0, 0, 1) != 1) { - DSSERR("Failed to sysreset PLL\n"); - return -ETIMEDOUT; + r = pm_runtime_get_sync(&hdmi.pdev->dev); + WARN_ON(r); + if (r < 0) + goto err_runtime_get; } return 0; -} - -static int hdmi_check_hpd_state(void) -{ - unsigned long flags; - bool hpd; - int r; - /* this should be in ti_hdmi_4xxx_ip private data */ - static DEFINE_SPINLOCK(phy_tx_lock); - spin_lock_irqsave(&phy_tx_lock, flags); - - hpd = gpio_get_value(hdmi.hpd_gpio); - - if (hpd == hdmi.phy_tx_enabled) { - spin_unlock_irqrestore(&phy_tx_lock, flags); - return 0; - } - - if (hpd) - r = hdmi_set_phy_pwr(HDMI_PHYPWRCMD_TXON); - else - r = hdmi_set_phy_pwr(HDMI_PHYPWRCMD_LDOON); - - if (r) { - DSSERR("Failed to %s PHY TX power\n", - hpd ? "enable" : "disable"); - goto err; - } - - hdmi.phy_tx_enabled = hpd; -err: - spin_unlock_irqrestore(&phy_tx_lock, flags); +err_runtime_get: + clk_disable(hdmi.sys_clk); + clk_disable(hdmi.hdmi_clk); + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: return r; } -static irqreturn_t hpd_irq_handler(int irq, void *data) +static void hdmi_runtime_put(void) { - hdmi_check_hpd_state(); - - return IRQ_HANDLED; -} - -static int hdmi_phy_init(void) -{ - u16 r = 0; - - r = hdmi_set_phy_pwr(HDMI_PHYPWRCMD_LDOON); - if (r) - return r; - - /* - * Read address 0 in order to get the SCP reset done completed - * Dummy access performed to make sure reset is done - */ - hdmi_read_reg(HDMI_TXPHY_TX_CTRL); - - /* - * Write to phy address 0 to configure the clock - * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field - */ - REG_FLD_MOD(HDMI_TXPHY_TX_CTRL, 0x1, 31, 30); - - /* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */ - hdmi_write_reg(HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000); - - /* Setup max LDO voltage */ - REG_FLD_MOD(HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0); - - /* Write to phy address 3 to change the polarity control */ - REG_FLD_MOD(HDMI_TXPHY_PAD_CFG_CTRL, 0x1, 27, 27); - - r = request_threaded_irq(gpio_to_irq(hdmi.hpd_gpio), - NULL, hpd_irq_handler, - IRQF_DISABLED | IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING, "hpd", NULL); - if (r) { - DSSERR("HPD IRQ request failed\n"); - hdmi_set_phy_pwr(HDMI_PHYPWRCMD_OFF); - return r; - } + int r; - r = hdmi_check_hpd_state(); - if (r) { - free_irq(gpio_to_irq(hdmi.hpd_gpio), NULL); - hdmi_set_phy_pwr(HDMI_PHYPWRCMD_OFF); - return r; - } + DSSDBG("hdmi_runtime_put\n"); - return 0; -} + if (--hdmi.runtime_count == 0) { + r = pm_runtime_put_sync(&hdmi.pdev->dev); + WARN_ON(r); -static int hdmi_wait_softreset(void) -{ - /* reset W1 */ - REG_FLD_MOD(HDMI_WP_SYSCONFIG, 0x1, 0, 0); + clk_disable(hdmi.sys_clk); + clk_disable(hdmi.hdmi_clk); - /* wait till SOFTRESET == 0 */ - if (hdmi_wait_for_bit_change(HDMI_WP_SYSCONFIG, 0, 0, 0) != 0) { - DSSERR("sysconfig reset failed\n"); - return -ETIMEDOUT; + dispc_runtime_put(); + dss_runtime_put(); } - - return 0; } -static int hdmi_pll_program(struct hdmi_pll_info *fmt) +int hdmi_init_display(struct omap_dss_device *dssdev) { - u16 r = 0; - enum hdmi_clk_refsel refsel; - - /* wait for wrapper reset */ - r = hdmi_wait_softreset(); - if (r) - return r; - - r = hdmi_set_pll_pwr(HDMI_PLLPWRCMD_ALLOFF); - if (r) - return r; - - r = hdmi_set_pll_pwr(HDMI_PLLPWRCMD_BOTHON_ALLCLKS); - if (r) - return r; - - r = hdmi_pll_reset(); - if (r) - return r; - - refsel = HDMI_REFSEL_SYSCLK; - - r = hdmi_pll_init(refsel, fmt->dcofreq, fmt, fmt->regsd); - if (r) - return r; + DSSDBG("init_display\n"); return 0; } -static void hdmi_phy_off(void) +static int relaxed_fb_mode_is_equal(const struct fb_videomode *mode1, + const struct fb_videomode *mode2) { - free_irq(gpio_to_irq(hdmi.hpd_gpio), NULL); - hdmi_set_phy_pwr(HDMI_PHYPWRCMD_OFF); - hdmi.phy_tx_enabled = false; -} - -static int hdmi_core_ddc_edid(u8 *pedid, int ext) -{ - u32 i, j; - char checksum = 0; - u32 offset = 0; - - /* Turn on CLK for DDC */ - REG_FLD_MOD(HDMI_CORE_AV_DPD, 0x7, 2, 0); - - /* - * SW HACK : Without the Delay DDC(i2c bus) reads 0 values / - * right shifted values( The behavior is not consistent and seen only - * with some TV's) - */ - usleep_range(800, 1000); - - if (!ext) { - /* Clk SCL Devices */ - REG_FLD_MOD(HDMI_CORE_DDC_CMD, 0xA, 3, 0); - - /* HDMI_CORE_DDC_STATUS_IN_PROG */ - if (hdmi_wait_for_bit_change(HDMI_CORE_DDC_STATUS, - 4, 4, 0) != 0) { - DSSERR("Failed to program DDC\n"); - return -ETIMEDOUT; - } - - /* Clear FIFO */ - REG_FLD_MOD(HDMI_CORE_DDC_CMD, 0x9, 3, 0); - - /* HDMI_CORE_DDC_STATUS_IN_PROG */ - if (hdmi_wait_for_bit_change(HDMI_CORE_DDC_STATUS, - 4, 4, 0) != 0) { - DSSERR("Failed to program DDC\n"); - return -ETIMEDOUT; - } - - } else { - if (ext % 2 != 0) - offset = 0x80; - } - - /* Load Segment Address Register */ - REG_FLD_MOD(HDMI_CORE_DDC_SEGM, ext/2, 7, 0); - - /* Load Slave Address Register */ - REG_FLD_MOD(HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1); - - /* Load Offset Address Register */ - REG_FLD_MOD(HDMI_CORE_DDC_OFFSET, offset, 7, 0); - - /* Load Byte Count */ - REG_FLD_MOD(HDMI_CORE_DDC_COUNT1, 0x80, 7, 0); - REG_FLD_MOD(HDMI_CORE_DDC_COUNT2, 0x0, 1, 0); - - /* Set DDC_CMD */ - if (ext) - REG_FLD_MOD(HDMI_CORE_DDC_CMD, 0x4, 3, 0); - else - REG_FLD_MOD(HDMI_CORE_DDC_CMD, 0x2, 3, 0); - - /* HDMI_CORE_DDC_STATUS_BUS_LOW */ - if (REG_GET(HDMI_CORE_DDC_STATUS, 6, 6) == 1) { - DSSWARN("I2C Bus Low?\n"); - return -EIO; - } - /* HDMI_CORE_DDC_STATUS_NO_ACK */ - if (REG_GET(HDMI_CORE_DDC_STATUS, 5, 5) == 1) { - DSSWARN("I2C No Ack\n"); - return -EIO; - } - - i = ext * 128; - j = 0; - while (((REG_GET(HDMI_CORE_DDC_STATUS, 4, 4) == 1) || - (REG_GET(HDMI_CORE_DDC_STATUS, 2, 2) == 0)) && - j < 128) { - - if (REG_GET(HDMI_CORE_DDC_STATUS, 2, 2) == 0) { - /* FIFO not empty */ - pedid[i++] = REG_GET(HDMI_CORE_DDC_DATA, 7, 0); - j++; - } - } + u32 ratio1 = mode1->flag & (FB_FLAG_RATIO_4_3 | FB_FLAG_RATIO_16_9); + u32 ratio2 = mode2->flag & (FB_FLAG_RATIO_4_3 | FB_FLAG_RATIO_16_9); - for (j = 0; j < 128; j++) - checksum += pedid[j]; - - if (checksum != 0) { - DSSERR("E-EDID checksum failed!!\n"); - return -EIO; - } - - return 0; + return (mode1->xres == mode2->xres && + mode1->yres == mode2->yres && + mode1->pixclock <= mode2->pixclock * 201 / 200 && + mode1->pixclock >= mode2->pixclock * 200 / 201 && + mode1->hsync_len + mode1->left_margin + mode1->right_margin == + mode2->hsync_len + mode2->left_margin + mode2->right_margin && + mode1->vsync_len + mode1->upper_margin + mode1->lower_margin == + mode2->vsync_len + mode2->upper_margin + mode2->lower_margin && + (!ratio1 || !ratio2 || ratio1 == ratio2) && + (mode1->vmode & FB_VMODE_INTERLACED) == + (mode2->vmode & FB_VMODE_INTERLACED)); } -static int read_edid(u8 *pedid, u16 max_length) +static int hdmi_set_timings(struct fb_videomode *vm, bool check_only) { - int r = 0, n = 0, i = 0; - int max_ext_blocks = (max_length / 128) - 1; + int i = 0; + DSSDBG("hdmi_get_code\n"); - r = hdmi_core_ddc_edid(pedid, 0); - if (r) { - return r; - } else { - n = pedid[0x7e]; - - /* - * README: need to comply with max_length set by the caller. - * Better implementation should be to allocate necessary - * memory to store EDID according to nb_block field found - * in first block - */ - if (n > max_ext_blocks) - n = max_ext_blocks; - - for (i = 1; i <= n; i++) { - r = hdmi_core_ddc_edid(pedid, i); - if (r) - return r; + if (!vm->xres || !vm->yres || !vm->pixclock) + goto fail; + + for (i = 0; i < CEA_MODEDB_SIZE; i++) { + if (relaxed_fb_mode_is_equal(cea_modes + i, vm)) { + *vm = cea_modes[i]; + if (check_only) + return 1; + hdmi.cfg.cm.code = i; + hdmi.cfg.cm.mode = HDMI_HDMI; + hdmi.cfg.timings = cea_modes[hdmi.cfg.cm.code]; + goto done; } } - return 0; -} - -static int get_timings_index(void) -{ - int code; - - if (hdmi.mode == 0) - code = code_vesa[hdmi.code]; - else - code = code_cea[hdmi.code]; - - if (code == -1) { - /* HDMI code 4 corresponds to 640 * 480 VGA */ - hdmi.code = 4; - /* DVI mode 1 corresponds to HDMI 0 to DVI */ - hdmi.mode = HDMI_DVI; - code = code_vesa[hdmi.code]; - } - return code; -} - -static struct hdmi_cm hdmi_get_code(struct omap_video_timings *timing) -{ - int i = 0, code = -1, temp_vsync = 0, temp_hsync = 0; - int timing_vsync = 0, timing_hsync = 0; - struct omap_video_timings temp; - struct hdmi_cm cm = {-1}; - DSSDBG("hdmi_get_code\n"); - - for (i = 0; i < OMAP_HDMI_TIMINGS_NB; i++) { - temp = cea_vesa_timings[i].timings; - if ((temp.pixel_clock == timing->pixel_clock) && - (temp.x_res == timing->x_res) && - (temp.y_res == timing->y_res)) { - - temp_hsync = temp.hfp + temp.hsw + temp.hbp; - timing_hsync = timing->hfp + timing->hsw + timing->hbp; - temp_vsync = temp.vfp + temp.vsw + temp.vbp; - timing_vsync = timing->vfp + timing->vsw + timing->vbp; - - DSSDBG("temp_hsync = %d , temp_vsync = %d" - "timing_hsync = %d, timing_vsync = %d\n", - temp_hsync, temp_hsync, - timing_hsync, timing_vsync); - - if ((temp_hsync == timing_hsync) && - (temp_vsync == timing_vsync)) { - code = i; - cm.code = code_index[i]; - if (code < 14) - cm.mode = HDMI_HDMI; - else - cm.mode = HDMI_DVI; - DSSDBG("Hdmi_code = %d mode = %d\n", - cm.code, cm.mode); - break; - } + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (relaxed_fb_mode_is_equal(vesa_modes + i, vm)) { + *vm = vesa_modes[i]; + if (check_only) + return 1; + hdmi.cfg.cm.code = i; + hdmi.cfg.cm.mode = HDMI_DVI; + hdmi.cfg.timings = vesa_modes[hdmi.cfg.cm.code]; + goto done; } } - return cm; -} +fail: + if (check_only) + return 0; + hdmi.cfg.cm.code = 1; + hdmi.cfg.cm.mode = HDMI_HDMI; + hdmi.cfg.timings = cea_modes[hdmi.cfg.cm.code]; -static void get_horz_vert_timing_info(int current_descriptor_addrs, u8 *edid , - struct omap_video_timings *timings) -{ - /* X and Y resolution */ - timings->x_res = (((edid[current_descriptor_addrs + 4] & 0xF0) << 4) | - edid[current_descriptor_addrs + 2]); - timings->y_res = (((edid[current_descriptor_addrs + 7] & 0xF0) << 4) | - edid[current_descriptor_addrs + 5]); - - timings->pixel_clock = ((edid[current_descriptor_addrs + 1] << 8) | - edid[current_descriptor_addrs]); - - timings->pixel_clock = 10 * timings->pixel_clock; - - /* HORIZONTAL FRONT PORCH */ - timings->hfp = edid[current_descriptor_addrs + 8] | - ((edid[current_descriptor_addrs + 11] & 0xc0) << 2); - /* HORIZONTAL SYNC WIDTH */ - timings->hsw = edid[current_descriptor_addrs + 9] | - ((edid[current_descriptor_addrs + 11] & 0x30) << 4); - /* HORIZONTAL BACK PORCH */ - timings->hbp = (((edid[current_descriptor_addrs + 4] & 0x0F) << 8) | - edid[current_descriptor_addrs + 3]) - - (timings->hfp + timings->hsw); - /* VERTICAL FRONT PORCH */ - timings->vfp = ((edid[current_descriptor_addrs + 10] & 0xF0) >> 4) | - ((edid[current_descriptor_addrs + 11] & 0x0f) << 2); - /* VERTICAL SYNC WIDTH */ - timings->vsw = (edid[current_descriptor_addrs + 10] & 0x0F) | - ((edid[current_descriptor_addrs + 11] & 0x03) << 4); - /* VERTICAL BACK PORCH */ - timings->vbp = (((edid[current_descriptor_addrs + 7] & 0x0F) << 8) | - edid[current_descriptor_addrs + 6]) - - (timings->vfp + timings->vsw); + i = -1; +done: + DSSDBG("%s-%d\n", hdmi.cfg.cm.mode ? "CEA" : "VESA", hdmi.cfg.cm.code); + return i >= 0; } -/* Description : This function gets the resolution information from EDID */ -static void get_edid_timing_data(u8 *edid) +void hdmi_get_monspecs(struct fb_monspecs *specs) { - u8 count; - u16 current_descriptor_addrs; - struct hdmi_cm cm; - struct omap_video_timings edid_timings; - - /* search block 0, there are 4 DTDs arranged in priority order */ - for (count = 0; count < EDID_SIZE_BLOCK0_TIMING_DESCRIPTOR; count++) { - current_descriptor_addrs = - EDID_DESCRIPTOR_BLOCK0_ADDRESS + - count * EDID_TIMING_DESCRIPTOR_SIZE; - get_horz_vert_timing_info(current_descriptor_addrs, - edid, &edid_timings); - cm = hdmi_get_code(&edid_timings); - DSSDBG("Block0[%d] value matches code = %d , mode = %d\n", - count, cm.code, cm.mode); - if (cm.code == -1) { - continue; - } else { - hdmi.code = cm.code; - hdmi.mode = cm.mode; - DSSDBG("code = %d , mode = %d\n", - hdmi.code, hdmi.mode); - return; - } - } - if (edid[0x7e] != 0x00) { - for (count = 0; count < EDID_SIZE_BLOCK1_TIMING_DESCRIPTOR; - count++) { - current_descriptor_addrs = - EDID_DESCRIPTOR_BLOCK1_ADDRESS + - count * EDID_TIMING_DESCRIPTOR_SIZE; - get_horz_vert_timing_info(current_descriptor_addrs, - edid, &edid_timings); - cm = hdmi_get_code(&edid_timings); - DSSDBG("Block1[%d] value matches code = %d, mode = %d", - count, cm.code, cm.mode); - if (cm.code == -1) { - continue; - } else { - hdmi.code = cm.code; - hdmi.mode = cm.mode; - DSSDBG("code = %d , mode = %d\n", - hdmi.code, hdmi.mode); - return; - } - } - } - - DSSINFO("no valid timing found , falling back to VGA\n"); - hdmi.code = 4; /* setting default value of 640 480 VGA */ - hdmi.mode = HDMI_DVI; -} - -static void hdmi_read_edid(struct omap_video_timings *dp) -{ - int ret = 0, code; - - memset(hdmi.edid, 0, HDMI_EDID_MAX_LENGTH); + int i, j; + char *edid = (char *) hdmi.edid; + memset(specs, 0x0, sizeof(*specs)); if (!hdmi.edid_set) - ret = read_edid(hdmi.edid, HDMI_EDID_MAX_LENGTH); + return; - if (!ret) { - if (!memcmp(hdmi.edid, edid_header, sizeof(edid_header))) { - /* search for timings of default resolution */ - get_edid_timing_data(hdmi.edid); - hdmi.edid_set = true; - } - } else { - DSSWARN("failed to read E-EDID\n"); - } + fb_edid_to_monspecs(edid, specs); + if (specs->modedb == NULL) + return; - if (!hdmi.edid_set) { - DSSINFO("fallback to VGA\n"); - hdmi.code = 4; /* setting default value of 640 480 VGA */ - hdmi.mode = HDMI_DVI; + for (i = 1; i <= edid[0x7e] && i * 128 < HDMI_EDID_MAX_LENGTH; i++) { + if (edid[i * 128] == 0x2) + fb_edid_add_monspecs(edid + i * 128, specs); } - code = get_timings_index(); - - *dp = cea_vesa_timings[code].timings; -} - -static void hdmi_core_init(struct hdmi_core_video_config *video_cfg, - struct hdmi_core_infoframe_avi *avi_cfg, - struct hdmi_core_packet_enable_repeat *repeat_cfg) -{ - DSSDBG("Enter hdmi_core_init\n"); - - /* video core */ - video_cfg->ip_bus_width = HDMI_INPUT_8BIT; - video_cfg->op_dither_truc = HDMI_OUTPUTTRUNCATION_8BIT; - video_cfg->deep_color_pkt = HDMI_DEEPCOLORPACKECTDISABLE; - video_cfg->pkt_mode = HDMI_PACKETMODERESERVEDVALUE; - video_cfg->hdmi_dvi = HDMI_DVI; - video_cfg->tclk_sel_clkmult = HDMI_FPLL10IDCK; - - /* info frame */ - avi_cfg->db1_format = 0; - avi_cfg->db1_active_info = 0; - avi_cfg->db1_bar_info_dv = 0; - avi_cfg->db1_scan_info = 0; - avi_cfg->db2_colorimetry = 0; - avi_cfg->db2_aspect_ratio = 0; - avi_cfg->db2_active_fmt_ar = 0; - avi_cfg->db3_itc = 0; - avi_cfg->db3_ec = 0; - avi_cfg->db3_q_range = 0; - avi_cfg->db3_nup_scaling = 0; - avi_cfg->db4_videocode = 0; - avi_cfg->db5_pixel_repeat = 0; - avi_cfg->db6_7_line_eoftop = 0 ; - avi_cfg->db8_9_line_sofbottom = 0; - avi_cfg->db10_11_pixel_eofleft = 0; - avi_cfg->db12_13_pixel_sofright = 0; - - /* packet enable and repeat */ - repeat_cfg->audio_pkt = 0; - repeat_cfg->audio_pkt_repeat = 0; - repeat_cfg->avi_infoframe = 0; - repeat_cfg->avi_infoframe_repeat = 0; - repeat_cfg->gen_cntrl_pkt = 0; - repeat_cfg->gen_cntrl_pkt_repeat = 0; - repeat_cfg->generic_pkt = 0; - repeat_cfg->generic_pkt_repeat = 0; -} + hdmi.can_do_hdmi = specs->misc & FB_MISC_HDMI; -static void hdmi_core_powerdown_disable(void) -{ - DSSDBG("Enter hdmi_core_powerdown_disable\n"); - REG_FLD_MOD(HDMI_CORE_CTRL1, 0x0, 0, 0); -} + /* filter out resolutions we don't support */ + for (i = j = 0; i < specs->modedb_len; i++) { + u32 max_pclk = hdmi.dssdev->clocks.hdmi.max_pixclk_khz; + if (!hdmi_set_timings(&specs->modedb[i], true)) + continue; -static void hdmi_core_swreset_release(void) -{ - DSSDBG("Enter hdmi_core_swreset_release\n"); - REG_FLD_MOD(HDMI_CORE_SYS_SRST, 0x0, 0, 0); -} + if (max_pclk && + max_pclk < PICOS2KHZ(specs->modedb[i].pixclock)) + continue; -static void hdmi_core_swreset_assert(void) -{ - DSSDBG("Enter hdmi_core_swreset_assert\n"); - REG_FLD_MOD(HDMI_CORE_SYS_SRST, 0x1, 0, 0); -} + if (specs->modedb[i].flag & FB_FLAG_PIXEL_REPEAT) + continue; -/* DSS_HDMI_CORE_VIDEO_CONFIG */ -static void hdmi_core_video_config(struct hdmi_core_video_config *cfg) -{ - u32 r = 0; - - /* sys_ctrl1 default configuration not tunable */ - r = hdmi_read_reg(HDMI_CORE_CTRL1); - r = FLD_MOD(r, HDMI_CORE_CTRL1_VEN_FOLLOWVSYNC, 5, 5); - r = FLD_MOD(r, HDMI_CORE_CTRL1_HEN_FOLLOWHSYNC, 4, 4); - r = FLD_MOD(r, HDMI_CORE_CTRL1_BSEL_24BITBUS, 2, 2); - r = FLD_MOD(r, HDMI_CORE_CTRL1_EDGE_RISINGEDGE, 1, 1); - hdmi_write_reg(HDMI_CORE_CTRL1, r); - - REG_FLD_MOD(HDMI_CORE_SYS_VID_ACEN, cfg->ip_bus_width, 7, 6); - - /* Vid_Mode */ - r = hdmi_read_reg(HDMI_CORE_SYS_VID_MODE); - - /* dither truncation configuration */ - if (cfg->op_dither_truc > HDMI_OUTPUTTRUNCATION_12BIT) { - r = FLD_MOD(r, cfg->op_dither_truc - 3, 7, 6); - r = FLD_MOD(r, 1, 5, 5); - } else { - r = FLD_MOD(r, cfg->op_dither_truc, 7, 6); - r = FLD_MOD(r, 0, 5, 5); + specs->modedb[j++] = specs->modedb[i]; } - hdmi_write_reg(HDMI_CORE_SYS_VID_MODE, r); - - /* HDMI_Ctrl */ - r = hdmi_read_reg(HDMI_CORE_AV_HDMI_CTRL); - r = FLD_MOD(r, cfg->deep_color_pkt, 6, 6); - r = FLD_MOD(r, cfg->pkt_mode, 5, 3); - r = FLD_MOD(r, cfg->hdmi_dvi, 0, 0); - hdmi_write_reg(HDMI_CORE_AV_HDMI_CTRL, r); - - /* TMDS_CTRL */ - REG_FLD_MOD(HDMI_CORE_SYS_TMDS_CTRL, - cfg->tclk_sel_clkmult, 6, 5); -} - -static void hdmi_core_aux_infoframe_avi_config( - struct hdmi_core_infoframe_avi info_avi) -{ - u32 val; - char sum = 0, checksum = 0; - - sum += 0x82 + 0x002 + 0x00D; - hdmi_write_reg(HDMI_CORE_AV_AVI_TYPE, 0x082); - hdmi_write_reg(HDMI_CORE_AV_AVI_VERS, 0x002); - hdmi_write_reg(HDMI_CORE_AV_AVI_LEN, 0x00D); - - val = (info_avi.db1_format << 5) | - (info_avi.db1_active_info << 4) | - (info_avi.db1_bar_info_dv << 2) | - (info_avi.db1_scan_info); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(0), val); - sum += val; - - val = (info_avi.db2_colorimetry << 6) | - (info_avi.db2_aspect_ratio << 4) | - (info_avi.db2_active_fmt_ar); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(1), val); - sum += val; - - val = (info_avi.db3_itc << 7) | - (info_avi.db3_ec << 4) | - (info_avi.db3_q_range << 2) | - (info_avi.db3_nup_scaling); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(2), val); - sum += val; - - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(3), info_avi.db4_videocode); - sum += info_avi.db4_videocode; - - val = info_avi.db5_pixel_repeat; - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(4), val); - sum += val; - - val = info_avi.db6_7_line_eoftop & 0x00FF; - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(5), val); - sum += val; - - val = ((info_avi.db6_7_line_eoftop >> 8) & 0x00FF); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(6), val); - sum += val; - - val = info_avi.db8_9_line_sofbottom & 0x00FF; - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(7), val); - sum += val; - - val = ((info_avi.db8_9_line_sofbottom >> 8) & 0x00FF); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(8), val); - sum += val; - - val = info_avi.db10_11_pixel_eofleft & 0x00FF; - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(9), val); - sum += val; - - val = ((info_avi.db10_11_pixel_eofleft >> 8) & 0x00FF); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(10), val); - sum += val; - - val = info_avi.db12_13_pixel_sofright & 0x00FF; - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(11), val); - sum += val; - - val = ((info_avi.db12_13_pixel_sofright >> 8) & 0x00FF); - hdmi_write_reg(HDMI_CORE_AV_AVI_DBYTE(12), val); - sum += val; - - checksum = 0x100 - sum; - hdmi_write_reg(HDMI_CORE_AV_AVI_CHSUM, checksum); -} - -static void hdmi_core_av_packet_config( - struct hdmi_core_packet_enable_repeat repeat_cfg) -{ - /* enable/repeat the infoframe */ - hdmi_write_reg(HDMI_CORE_AV_PB_CTRL1, - (repeat_cfg.audio_pkt << 5) | - (repeat_cfg.audio_pkt_repeat << 4) | - (repeat_cfg.avi_infoframe << 1) | - (repeat_cfg.avi_infoframe_repeat)); - - /* enable/repeat the packet */ - hdmi_write_reg(HDMI_CORE_AV_PB_CTRL2, - (repeat_cfg.gen_cntrl_pkt << 3) | - (repeat_cfg.gen_cntrl_pkt_repeat << 2) | - (repeat_cfg.generic_pkt << 1) | - (repeat_cfg.generic_pkt_repeat)); -} - -static void hdmi_wp_init(struct omap_video_timings *timings, - struct hdmi_video_format *video_fmt, - struct hdmi_video_interface *video_int) -{ - DSSDBG("Enter hdmi_wp_init\n"); - - timings->hbp = 0; - timings->hfp = 0; - timings->hsw = 0; - timings->vbp = 0; - timings->vfp = 0; - timings->vsw = 0; - - video_fmt->packing_mode = HDMI_PACK_10b_RGB_YUV444; - video_fmt->y_res = 0; - video_fmt->x_res = 0; - - video_int->vsp = 0; - video_int->hsp = 0; - - video_int->interlacing = 0; - video_int->tm = 0; /* HDMI_TIMING_SLAVE */ - + specs->modedb_len = j; } -static void hdmi_wp_video_start(bool start) +u8 *hdmi_read_edid(struct omap_video_timings *dp) { - REG_FLD_MOD(HDMI_WP_VIDEO_CFG, start, 31, 31); -} + int ret = 0, i; -static void hdmi_wp_video_init_format(struct hdmi_video_format *video_fmt, - struct omap_video_timings *timings, struct hdmi_config *param) -{ - DSSDBG("Enter hdmi_wp_video_init_format\n"); - - video_fmt->y_res = param->timings.timings.y_res; - video_fmt->x_res = param->timings.timings.x_res; + if (hdmi.edid_set) + return hdmi.edid; - timings->hbp = param->timings.timings.hbp; - timings->hfp = param->timings.timings.hfp; - timings->hsw = param->timings.timings.hsw; - timings->vbp = param->timings.timings.vbp; - timings->vfp = param->timings.timings.vfp; - timings->vsw = param->timings.timings.vsw; -} - -static void hdmi_wp_video_config_format( - struct hdmi_video_format *video_fmt) -{ - u32 l = 0; - - REG_FLD_MOD(HDMI_WP_VIDEO_CFG, video_fmt->packing_mode, 10, 8); - - l |= FLD_VAL(video_fmt->y_res, 31, 16); - l |= FLD_VAL(video_fmt->x_res, 15, 0); - hdmi_write_reg(HDMI_WP_VIDEO_SIZE, l); -} - -static void hdmi_wp_video_config_interface( - struct hdmi_video_interface *video_int) -{ - u32 r; - DSSDBG("Enter hdmi_wp_video_config_interface\n"); - - r = hdmi_read_reg(HDMI_WP_VIDEO_CFG); - r = FLD_MOD(r, video_int->vsp, 7, 7); - r = FLD_MOD(r, video_int->hsp, 6, 6); - r = FLD_MOD(r, video_int->interlacing, 3, 3); - r = FLD_MOD(r, video_int->tm, 1, 0); - hdmi_write_reg(HDMI_WP_VIDEO_CFG, r); -} - -static void hdmi_wp_video_config_timing( - struct omap_video_timings *timings) -{ - u32 timing_h = 0; - u32 timing_v = 0; - - DSSDBG("Enter hdmi_wp_video_config_timing\n"); - - timing_h |= FLD_VAL(timings->hbp, 31, 20); - timing_h |= FLD_VAL(timings->hfp, 19, 8); - timing_h |= FLD_VAL(timings->hsw, 7, 0); - hdmi_write_reg(HDMI_WP_VIDEO_TIMING_H, timing_h); - - timing_v |= FLD_VAL(timings->vbp, 31, 20); - timing_v |= FLD_VAL(timings->vfp, 19, 8); - timing_v |= FLD_VAL(timings->vsw, 7, 0); - hdmi_write_reg(HDMI_WP_VIDEO_TIMING_V, timing_v); -} - -static void hdmi_basic_configure(struct hdmi_config *cfg) -{ - /* HDMI */ - struct omap_video_timings video_timing; - struct hdmi_video_format video_format; - struct hdmi_video_interface video_interface; - /* HDMI core */ - struct hdmi_core_infoframe_avi avi_cfg; - struct hdmi_core_video_config v_core_cfg; - struct hdmi_core_packet_enable_repeat repeat_cfg; - - hdmi_wp_init(&video_timing, &video_format, - &video_interface); - - hdmi_core_init(&v_core_cfg, - &avi_cfg, - &repeat_cfg); - - hdmi_wp_video_init_format(&video_format, - &video_timing, cfg); - - hdmi_wp_video_config_timing(&video_timing); - - /* video config */ - video_format.packing_mode = HDMI_PACK_24b_RGB_YUV444_YUV422; - - hdmi_wp_video_config_format(&video_format); - - video_interface.vsp = cfg->timings.vsync_pol; - video_interface.hsp = cfg->timings.hsync_pol; - video_interface.interlacing = cfg->interlace; - video_interface.tm = 1 ; /* HDMI_TIMING_MASTER_24BIT */ - - hdmi_wp_video_config_interface(&video_interface); - - /* - * configure core video part - * set software reset in the core - */ - hdmi_core_swreset_assert(); - - /* power down off */ - hdmi_core_powerdown_disable(); + memset(hdmi.edid, 0, HDMI_EDID_MAX_LENGTH); - v_core_cfg.pkt_mode = HDMI_PACKETMODE24BITPERPIXEL; - v_core_cfg.hdmi_dvi = cfg->cm.mode; + ret = read_ti_4xxx_edid(&hdmi.hdmi_data, hdmi.edid, + HDMI_EDID_MAX_LENGTH); - hdmi_core_video_config(&v_core_cfg); + for (i = 0; i < HDMI_EDID_MAX_LENGTH; i += 16) + pr_info("edid[%03x] = %02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", i, + hdmi.edid[i], hdmi.edid[i + 1], hdmi.edid[i + 2], + hdmi.edid[i + 3], hdmi.edid[i + 4], hdmi.edid[i + 5], + hdmi.edid[i + 6], hdmi.edid[i + 7], hdmi.edid[i + 8], + hdmi.edid[i + 9], hdmi.edid[i + 10], hdmi.edid[i + 11], + hdmi.edid[i + 12], hdmi.edid[i + 13], hdmi.edid[i + 14], + hdmi.edid[i + 15]); - /* release software reset in the core */ - hdmi_core_swreset_release(); + if (ret) { + DSSWARN("failed to read E-EDID\n"); + return NULL; + } - /* - * configure packet - * info frame video see doc CEA861-D page 65 - */ - avi_cfg.db1_format = HDMI_INFOFRAME_AVI_DB1Y_RGB; - avi_cfg.db1_active_info = - HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_OFF; - avi_cfg.db1_bar_info_dv = HDMI_INFOFRAME_AVI_DB1B_NO; - avi_cfg.db1_scan_info = HDMI_INFOFRAME_AVI_DB1S_0; - avi_cfg.db2_colorimetry = HDMI_INFOFRAME_AVI_DB2C_NO; - avi_cfg.db2_aspect_ratio = HDMI_INFOFRAME_AVI_DB2M_NO; - avi_cfg.db2_active_fmt_ar = HDMI_INFOFRAME_AVI_DB2R_SAME; - avi_cfg.db3_itc = HDMI_INFOFRAME_AVI_DB3ITC_NO; - avi_cfg.db3_ec = HDMI_INFOFRAME_AVI_DB3EC_XVYUV601; - avi_cfg.db3_q_range = HDMI_INFOFRAME_AVI_DB3Q_DEFAULT; - avi_cfg.db3_nup_scaling = HDMI_INFOFRAME_AVI_DB3SC_NO; - avi_cfg.db4_videocode = cfg->cm.code; - avi_cfg.db5_pixel_repeat = HDMI_INFOFRAME_AVI_DB5PR_NO; - avi_cfg.db6_7_line_eoftop = 0; - avi_cfg.db8_9_line_sofbottom = 0; - avi_cfg.db10_11_pixel_eofleft = 0; - avi_cfg.db12_13_pixel_sofright = 0; - - hdmi_core_aux_infoframe_avi_config(avi_cfg); - - /* enable/repeat the infoframe */ - repeat_cfg.avi_infoframe = HDMI_PACKETENABLE; - repeat_cfg.avi_infoframe_repeat = HDMI_PACKETREPEATON; - /* wakeup */ - repeat_cfg.audio_pkt = HDMI_PACKETENABLE; - repeat_cfg.audio_pkt_repeat = HDMI_PACKETREPEATON; - hdmi_core_av_packet_config(repeat_cfg); -} + if (memcmp(hdmi.edid, edid_header, sizeof(edid_header))) + return NULL; -static void update_hdmi_timings(struct hdmi_config *cfg, - struct omap_video_timings *timings, int code) -{ - cfg->timings.timings.x_res = timings->x_res; - cfg->timings.timings.y_res = timings->y_res; - cfg->timings.timings.hbp = timings->hbp; - cfg->timings.timings.hfp = timings->hfp; - cfg->timings.timings.hsw = timings->hsw; - cfg->timings.timings.vbp = timings->vbp; - cfg->timings.timings.vfp = timings->vfp; - cfg->timings.timings.vsw = timings->vsw; - cfg->timings.timings.pixel_clock = timings->pixel_clock; - cfg->timings.vsync_pol = cea_vesa_timings[code].vsync_pol; - cfg->timings.hsync_pol = cea_vesa_timings[code].hsync_pol; + hdmi.edid_set = true; + return hdmi.edid; } static void hdmi_compute_pll(struct omap_dss_device *dssdev, int phy, @@ -1127,16 +296,12 @@ static void hdmi_compute_pll(struct omap_dss_device *dssdev, int phy, unsigned long clkin, refclk; u32 mf; - clkin = dss_clk_get_rate(DSS_CLK_SYSCK) / 10000; + clkin = clk_get_rate(hdmi.sys_clk) / 10000; /* * Input clock is predivided by N + 1 * out put of which is reference clk */ - if (dssdev->clocks.hdmi.regn == 0) - pi->regn = HDMI_DEFAULT_REGN; - else - pi->regn = dssdev->clocks.hdmi.regn; - + pi->regn = dssdev->clocks.hdmi.regn; refclk = clkin / (pi->regn + 1); /* @@ -1144,11 +309,7 @@ static void hdmi_compute_pll(struct omap_dss_device *dssdev, int phy, * Multiplying by 100 to avoid fractional part removal */ pi->regm = (phy * 100 / (refclk)) / 100; - - if (dssdev->clocks.hdmi.regm2 == 0) - pi->regm2 = HDMI_DEFAULT_REGM2; - else - pi->regm2 = dssdev->clocks.hdmi.regm2; + pi->regm2 = dssdev->clocks.hdmi.regm2; /* * fractional multiplier is remainder of the difference between @@ -1169,26 +330,59 @@ static void hdmi_compute_pll(struct omap_dss_device *dssdev, int phy, DSSDBG("range = %d sd = %d\n", pi->dcofreq, pi->regsd); } -static void hdmi_enable_clocks(int enable) -{ - if (enable) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK | - DSS_CLK_SYSCK | DSS_CLK_VIDFCK); - else - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK | - DSS_CLK_SYSCK | DSS_CLK_VIDFCK); +static void hdmi_load_hdcp_keys(struct omap_dss_device *dssdev) +{ + int aksv; + int retries = 5; + DSSDBG("hdmi_load_hdcp_keys\n"); + /* load the keys and reset the wrapper to populate the AKSV registers*/ + if (hdmi.hdmi_power_on_cb) { + aksv = hdmi_ti_4xx_check_aksv_data(&hdmi.hdmi_data); + if ((aksv == HDMI_AKSV_ZERO) && + hdmi.custom_set && + hdmi.hdmi_power_on_cb()) { + hdmi_ti_4xxx_set_wait_soft_reset(&hdmi.hdmi_data); + + while (retries) { + aksv = hdmi_ti_4xx_check_aksv_data(&hdmi.hdmi_data); + if (aksv == HDMI_AKSV_VALID) + break; + msleep(50); + retries--; + } + + hdmi.wp_reset_done = (aksv == HDMI_AKSV_VALID) ? + true : false; + DSSINFO("HDMI_WRAPPER RESET DONE\n"); + } else if (aksv == HDMI_AKSV_VALID) + hdmi.wp_reset_done = true; + else if (aksv == HDMI_AKSV_ERROR) + hdmi.wp_reset_done = false; + + if (!hdmi.wp_reset_done) + DSSERR("*** INVALID AKSV: " + "Do not perform HDCP AUTHENTICATION\n"); + } + } static int hdmi_power_on(struct omap_dss_device *dssdev) { - int r, code = 0; + int r; struct hdmi_pll_info pll_data; struct omap_video_timings *p; unsigned long phy; - hdmi_enable_clocks(1); + r = hdmi_runtime_get(); + if (r) + return r; + + /* Load the HDCP keys if not already loaded*/ + hdmi_load_hdcp_keys(dssdev); - dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, 0); + hdmi_ti_4xxx_wp_video_start(&hdmi.hdmi_data, 0); + + dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, dssdev->type, 0); p = &dssdev->panel.timings; @@ -1197,35 +391,53 @@ static int hdmi_power_on(struct omap_dss_device *dssdev) dssdev->panel.timings.y_res); if (!hdmi.custom_set) { - DSSDBG("Read EDID as no EDID is not set on poweron\n"); - hdmi_read_edid(p); + struct fb_videomode vesa_vga = vesa_modes[4]; + hdmi_set_timings(&vesa_vga, false); } - code = get_timings_index(); - dssdev->panel.timings = cea_vesa_timings[code].timings; - update_hdmi_timings(&hdmi.cfg, p, code); + + omapfb_fb2dss_timings(&hdmi.cfg.timings, &dssdev->panel.timings); phy = p->pixel_clock; - hdmi_compute_pll(dssdev, phy, &pll_data); + switch (hdmi.deep_color) { + case HDMI_DEEP_COLOR_30BIT: + phy = (p->pixel_clock * 125) / 100 ; + hdmi.cfg.deep_color = HDMI_DEEP_COLOR_30BIT; + break; + case HDMI_DEEP_COLOR_36BIT: + if (p->pixel_clock == 148500) { + printk(KERN_ERR "36 bit deep color not supported"); + goto err; + } - hdmi_wp_video_start(0); + phy = (p->pixel_clock * 150) / 100; + hdmi.cfg.deep_color = HDMI_DEEP_COLOR_36BIT; + break; + case HDMI_DEEP_COLOR_24BIT: + default: + phy = p->pixel_clock; + hdmi.cfg.deep_color = HDMI_DEEP_COLOR_24BIT; + break; + } - /* config the PLL and PHY first */ - r = hdmi_pll_program(&pll_data); + hdmi_compute_pll(dssdev, phy, &pll_data); + + /* config the PLL and PHY hdmi_set_pll_pwrfirst */ + r = hdmi_ti_4xxx_pll_program(&hdmi.hdmi_data, &pll_data); if (r) { DSSDBG("Failed to lock PLL\n"); goto err; } - r = hdmi_phy_init(); + r = hdmi_ti_4xxx_phy_init(&hdmi.hdmi_data); if (r) { DSSDBG("Failed to start PHY\n"); goto err; } - hdmi.cfg.cm.mode = hdmi.mode; + hdmi.cfg.cm.mode = hdmi.can_do_hdmi ? hdmi.mode : HDMI_DVI; hdmi.cfg.cm.code = hdmi.code; - hdmi_basic_configure(&hdmi.cfg); + hdmi_ti_4xxx_basic_configure(&hdmi.hdmi_data, &hdmi.cfg); /* Make selection of HDMI in DSS */ dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK); @@ -1245,65 +457,154 @@ static int hdmi_power_on(struct omap_dss_device *dssdev) dispc_set_digit_size(dssdev->panel.timings.x_res, dssdev->panel.timings.y_res); - dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, 1); + dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, dssdev->type, 1); + + hdmi_ti_4xxx_wp_video_start(&hdmi.hdmi_data, 1); - hdmi_wp_video_start(1); + if (hdmi.hdmi_start_frame_cb && + hdmi.custom_set && + hdmi.wp_reset_done) + (*hdmi.hdmi_start_frame_cb)(); return 0; err: - hdmi_enable_clocks(0); + hdmi_runtime_put(); return -EIO; } static void hdmi_power_off(struct omap_dss_device *dssdev) { - dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, 0); + if (hdmi.hdmi_irq_cb) + hdmi.hdmi_irq_cb(HDMI_HPD_LOW); + + hdmi_ti_4xxx_wp_video_start(&hdmi.hdmi_data, 0); + + dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, dssdev->type, 0); + hdmi_ti_4xxx_phy_off(&hdmi.hdmi_data, hdmi.set_mode); + hdmi_ti_4xxx_set_pll_pwr(&hdmi.hdmi_data, HDMI_PLLPWRCMD_ALLOFF); + hdmi_runtime_put(); + hdmi.deep_color = HDMI_DEEP_COLOR_24BIT; +} - hdmi_wp_video_start(0); - hdmi_phy_off(); - hdmi_set_pll_pwr(HDMI_PLLPWRCMD_ALLOFF); - hdmi_enable_clocks(0); +int omapdss_hdmi_get_pixel_clock(void) +{ + return PICOS2KHZ(hdmi.cfg.timings.pixclock); +} - hdmi.edid_set = 0; +int omapdss_hdmi_get_mode(void) +{ + return hdmi.mode; +} + +int omapdss_hdmi_register_hdcp_callbacks(void (*hdmi_start_frame_cb)(void), + void (*hdmi_irq_cb)(int status), + bool (*hdmi_power_on_cb)(void)) +{ + hdmi.hdmi_start_frame_cb = hdmi_start_frame_cb; + hdmi.hdmi_irq_cb = hdmi_irq_cb; + hdmi.hdmi_power_on_cb = hdmi_power_on_cb; + + return hdmi_ti_4xxx_wp_get_video_state(&hdmi.hdmi_data); +} +EXPORT_SYMBOL(omapdss_hdmi_register_hdcp_callbacks); + +void omapdss_hdmi_set_deepcolor(int val) +{ + hdmi.deep_color = val; +} + +int omapdss_hdmi_get_deepcolor(void) +{ + return hdmi.deep_color; +} + +int hdmi_get_current_hpd() +{ + return gpio_get_value(hdmi.dssdev->hpd_gpio); +} + +static irqreturn_t hpd_irq_handler(int irq, void *ptr) +{ + int hpd = hdmi_get_current_hpd(); + pr_info("hpd %d\n", hpd); + + hdmi_panel_hpd_handler(hpd); + + return IRQ_HANDLED; +} + +static irqreturn_t hdmi_irq_handler(int irq, void *arg) +{ + int r = 0; + + r = hdmi_ti_4xxx_irq_handler(&hdmi.hdmi_data); + + DSSDBG("Received HDMI IRQ = %08x\n", r); + + if (hdmi.hdmi_irq_cb) + hdmi.hdmi_irq_cb(r); + + return IRQ_HANDLED; } int omapdss_hdmi_display_check_timing(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { - struct hdmi_cm cm; + struct fb_videomode t; - cm = hdmi_get_code(timings); - if (cm.code == -1) { - DSSERR("Invalid timing entered\n"); - return -EINVAL; - } + omapfb_dss2fb_timings(timings, &t); + /* also check interlaced timings */ + if (!hdmi_set_timings(&t, true)) { + t.yres *= 2; + t.vmode |= FB_VMODE_INTERLACED; + } + if (!hdmi_set_timings(&t, true)) + return -EINVAL; return 0; +} +int omapdss_hdmi_display_set_mode(struct omap_dss_device *dssdev, + struct fb_videomode *vm) +{ + int r1, r2; + DSSINFO("Enter omapdss_hdmi_display_set_mode\n"); + /* turn the hdmi off and on to get new timings to use */ + hdmi.set_mode = true; + dssdev->driver->disable(dssdev); + hdmi.set_mode = false; + r1 = hdmi_set_timings(vm, false) ? 0 : -EINVAL; + hdmi.custom_set = 1; + hdmi.code = hdmi.cfg.cm.code; + hdmi.mode = hdmi.cfg.cm.mode; + r2 = dssdev->driver->enable(dssdev); + return r1 ? : r2; } void omapdss_hdmi_display_set_timing(struct omap_dss_device *dssdev) { - struct hdmi_cm cm; + struct fb_videomode t; - hdmi.custom_set = 1; - cm = hdmi_get_code(&dssdev->panel.timings); - hdmi.code = cm.code; - hdmi.mode = cm.mode; - omapdss_hdmi_display_enable(dssdev); - hdmi.custom_set = 0; + omapfb_dss2fb_timings(&dssdev->panel.timings, &t); + /* also check interlaced timings */ + if (!hdmi_set_timings(&t, true)) { + t.yres *= 2; + t.vmode |= FB_VMODE_INTERLACED; + } + + omapdss_hdmi_display_set_mode(dssdev, &t); } int omapdss_hdmi_display_enable(struct omap_dss_device *dssdev) { - struct omap_dss_hdmi_data *priv = dssdev->data; int r = 0; - DSSDBG("ENTER hdmi_display_enable\n"); + DSSINFO("ENTER hdmi_display_enable\n"); mutex_lock(&hdmi.lock); - hdmi.hpd_gpio = priv->hpd_gpio; + if (hdmi.enabled) + goto err0; r = omap_dss_start_device(dssdev); if (r) { @@ -1319,15 +620,34 @@ int omapdss_hdmi_display_enable(struct omap_dss_device *dssdev) } } + hdmi.hdmi_reg = regulator_get(NULL, "hdmi_vref"); + if (IS_ERR_OR_NULL(hdmi.hdmi_reg)) { + DSSERR("Failed to get hdmi_vref regulator\n"); + r = PTR_ERR(hdmi.hdmi_reg) ? : -ENODEV; + goto err2; + } + + r = regulator_enable(hdmi.hdmi_reg); + if (r) { + DSSERR("failed to enable hdmi_vref regulator\n"); + goto err3; + } + r = hdmi_power_on(dssdev); if (r) { DSSERR("failed to power on device\n"); - goto err2; + goto err4; } + hdmi.enabled = true; + mutex_unlock(&hdmi.lock); return 0; +err4: + regulator_disable(hdmi.hdmi_reg); +err3: + regulator_put(hdmi.hdmi_reg); err2: if (dssdev->platform_disable) dssdev->platform_disable(dssdev); @@ -1340,440 +660,91 @@ err0: void omapdss_hdmi_display_disable(struct omap_dss_device *dssdev) { - DSSDBG("Enter hdmi_display_disable\n"); + DSSINFO("Enter hdmi_display_disable\n"); mutex_lock(&hdmi.lock); + if (!hdmi.enabled) + goto done; + + hdmi.enabled = false; + hdmi.wp_reset_done = false; + hdmi_power_off(dssdev); + if (dssdev->sync_lost_error == 0) + if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) { + /* clear EDID and mode on disable only */ + hdmi.edid_set = false; + hdmi.custom_set = 0; + pr_info("hdmi: clearing EDID info\n"); + } + regulator_disable(hdmi.hdmi_reg); + + regulator_put(hdmi.hdmi_reg); if (dssdev->platform_disable) dssdev->platform_disable(dssdev); omap_dss_stop_device(dssdev); - +done: mutex_unlock(&hdmi.lock); } -#if defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) || \ - defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI_MODULE) -static void hdmi_wp_audio_config_format( - struct hdmi_audio_format *aud_fmt) -{ - u32 r; - - DSSDBG("Enter hdmi_wp_audio_config_format\n"); - - r = hdmi_read_reg(HDMI_WP_AUDIO_CFG); - r = FLD_MOD(r, aud_fmt->stereo_channels, 26, 24); - r = FLD_MOD(r, aud_fmt->active_chnnls_msk, 23, 16); - r = FLD_MOD(r, aud_fmt->en_sig_blk_strt_end, 5, 5); - r = FLD_MOD(r, aud_fmt->type, 4, 4); - r = FLD_MOD(r, aud_fmt->justification, 3, 3); - r = FLD_MOD(r, aud_fmt->sample_order, 2, 2); - r = FLD_MOD(r, aud_fmt->samples_per_word, 1, 1); - r = FLD_MOD(r, aud_fmt->sample_size, 0, 0); - hdmi_write_reg(HDMI_WP_AUDIO_CFG, r); -} - -static void hdmi_wp_audio_config_dma(struct hdmi_audio_dma *aud_dma) +static int hdmi_get_clocks(struct platform_device *pdev) { - u32 r; - - DSSDBG("Enter hdmi_wp_audio_config_dma\n"); - - r = hdmi_read_reg(HDMI_WP_AUDIO_CFG2); - r = FLD_MOD(r, aud_dma->transfer_size, 15, 8); - r = FLD_MOD(r, aud_dma->block_size, 7, 0); - hdmi_write_reg(HDMI_WP_AUDIO_CFG2, r); + struct clk *clk; - r = hdmi_read_reg(HDMI_WP_AUDIO_CTRL); - r = FLD_MOD(r, aud_dma->mode, 9, 9); - r = FLD_MOD(r, aud_dma->fifo_threshold, 8, 0); - hdmi_write_reg(HDMI_WP_AUDIO_CTRL, r); -} - -static void hdmi_core_audio_config(struct hdmi_core_audio_config *cfg) -{ - u32 r; - - /* audio clock recovery parameters */ - r = hdmi_read_reg(HDMI_CORE_AV_ACR_CTRL); - r = FLD_MOD(r, cfg->use_mclk, 2, 2); - r = FLD_MOD(r, cfg->en_acr_pkt, 1, 1); - r = FLD_MOD(r, cfg->cts_mode, 0, 0); - hdmi_write_reg(HDMI_CORE_AV_ACR_CTRL, r); - - REG_FLD_MOD(HDMI_CORE_AV_N_SVAL1, cfg->n, 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_N_SVAL2, cfg->n >> 8, 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_N_SVAL3, cfg->n >> 16, 7, 0); - - if (cfg->cts_mode == HDMI_AUDIO_CTS_MODE_SW) { - REG_FLD_MOD(HDMI_CORE_AV_CTS_SVAL1, cfg->cts, 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_CTS_SVAL2, cfg->cts >> 8, 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_CTS_SVAL3, cfg->cts >> 16, 7, 0); - } else { - /* - * HDMI IP uses this configuration to divide the MCLK to - * update CTS value. - */ - REG_FLD_MOD(HDMI_CORE_AV_FREQ_SVAL, cfg->mclk_mode, 2, 0); - - /* Configure clock for audio packets */ - REG_FLD_MOD(HDMI_CORE_AV_AUD_PAR_BUSCLK_1, - cfg->aud_par_busclk, 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_AUD_PAR_BUSCLK_2, - (cfg->aud_par_busclk >> 8), 7, 0); - REG_FLD_MOD(HDMI_CORE_AV_AUD_PAR_BUSCLK_3, - (cfg->aud_par_busclk >> 16), 7, 0); + clk = clk_get(&pdev->dev, "sys_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get sys_clk\n"); + return PTR_ERR(clk); } - /* Override of SPDIF sample frequency with value in I2S_CHST4 */ - REG_FLD_MOD(HDMI_CORE_AV_SPDIF_CTRL, cfg->fs_override, 1, 1); - - /* I2S parameters */ - REG_FLD_MOD(HDMI_CORE_AV_I2S_CHST4, cfg->freq_sample, 3, 0); - - r = hdmi_read_reg(HDMI_CORE_AV_I2S_IN_CTRL); - r = FLD_MOD(r, cfg->i2s_cfg.en_high_bitrate_aud, 7, 7); - r = FLD_MOD(r, cfg->i2s_cfg.sck_edge_mode, 6, 6); - r = FLD_MOD(r, cfg->i2s_cfg.cbit_order, 5, 5); - r = FLD_MOD(r, cfg->i2s_cfg.vbit, 4, 4); - r = FLD_MOD(r, cfg->i2s_cfg.ws_polarity, 3, 3); - r = FLD_MOD(r, cfg->i2s_cfg.justification, 2, 2); - r = FLD_MOD(r, cfg->i2s_cfg.direction, 1, 1); - r = FLD_MOD(r, cfg->i2s_cfg.shift, 0, 0); - hdmi_write_reg(HDMI_CORE_AV_I2S_IN_CTRL, r); - - r = hdmi_read_reg(HDMI_CORE_AV_I2S_CHST5); - r = FLD_MOD(r, cfg->freq_sample, 7, 4); - r = FLD_MOD(r, cfg->i2s_cfg.word_length, 3, 1); - r = FLD_MOD(r, cfg->i2s_cfg.word_max_length, 0, 0); - hdmi_write_reg(HDMI_CORE_AV_I2S_CHST5, r); - - REG_FLD_MOD(HDMI_CORE_AV_I2S_IN_LEN, cfg->i2s_cfg.in_length_bits, 3, 0); - - /* Audio channels and mode parameters */ - REG_FLD_MOD(HDMI_CORE_AV_HDMI_CTRL, cfg->layout, 2, 1); - r = hdmi_read_reg(HDMI_CORE_AV_AUD_MODE); - r = FLD_MOD(r, cfg->i2s_cfg.active_sds, 7, 4); - r = FLD_MOD(r, cfg->en_dsd_audio, 3, 3); - r = FLD_MOD(r, cfg->en_parallel_aud_input, 2, 2); - r = FLD_MOD(r, cfg->en_spdif, 1, 1); - hdmi_write_reg(HDMI_CORE_AV_AUD_MODE, r); -} - -static void hdmi_core_audio_infoframe_config( - struct hdmi_core_infoframe_audio *info_aud) -{ - u8 val; - u8 sum = 0, checksum = 0; - - /* - * Set audio info frame type, version and length as - * described in HDMI 1.4a Section 8.2.2 specification. - * Checksum calculation is defined in Section 5.3.5. - */ - hdmi_write_reg(HDMI_CORE_AV_AUDIO_TYPE, 0x84); - hdmi_write_reg(HDMI_CORE_AV_AUDIO_VERS, 0x01); - hdmi_write_reg(HDMI_CORE_AV_AUDIO_LEN, 0x0a); - sum += 0x84 + 0x001 + 0x00a; - - val = (info_aud->db1_coding_type << 4) - | (info_aud->db1_channel_count - 1); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(0), val); - sum += val; - - val = (info_aud->db2_sample_freq << 2) | info_aud->db2_sample_size; - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(1), val); - sum += val; - - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(2), 0x00); - - val = info_aud->db4_channel_alloc; - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(3), val); - sum += val; - - val = (info_aud->db5_downmix_inh << 7) | (info_aud->db5_lsv << 3); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(4), val); - sum += val; - - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(5), 0x00); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(6), 0x00); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(7), 0x00); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(8), 0x00); - hdmi_write_reg(HDMI_CORE_AV_AUD_DBYTE(9), 0x00); - - checksum = 0x100 - sum; - hdmi_write_reg(HDMI_CORE_AV_AUDIO_CHSUM, checksum); - - /* - * TODO: Add MPEG and SPD enable and repeat cfg when EDID parsing - * is available. - */ -} - -static int hdmi_config_audio_acr(u32 sample_freq, u32 *n, u32 *cts) -{ - u32 r; - u32 deep_color = 0; - u32 pclk = hdmi.cfg.timings.timings.pixel_clock; - - if (n == NULL || cts == NULL) - return -EINVAL; - /* - * Obtain current deep color configuration. This needed - * to calculate the TMDS clock based on the pixel clock. - */ - r = REG_GET(HDMI_WP_VIDEO_CFG, 1, 0); - switch (r) { - case 1: /* No deep color selected */ - deep_color = 100; - break; - case 2: /* 10-bit deep color selected */ - deep_color = 125; - break; - case 3: /* 12-bit deep color selected */ - deep_color = 150; - break; - default: - return -EINVAL; - } - - switch (sample_freq) { - case 32000: - if ((deep_color == 125) && ((pclk == 54054) - || (pclk == 74250))) - *n = 8192; - else - *n = 4096; - break; - case 44100: - *n = 6272; - break; - case 48000: - if ((deep_color == 125) && ((pclk == 54054) - || (pclk == 74250))) - *n = 8192; - else - *n = 6144; - break; - default: - *n = 0; - return -EINVAL; - } - - /* Calculate CTS. See HDMI 1.3a or 1.4a specifications */ - *cts = pclk * (*n / 128) * deep_color / (sample_freq / 10); - - return 0; -} - -static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct hdmi_audio_format audio_format; - struct hdmi_audio_dma audio_dma; - struct hdmi_core_audio_config core_cfg; - struct hdmi_core_infoframe_audio aud_if_cfg; - int err, n, cts; - enum hdmi_core_audio_sample_freq sample_freq; - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - core_cfg.i2s_cfg.word_max_length = - HDMI_AUDIO_I2S_MAX_WORD_20BITS; - core_cfg.i2s_cfg.word_length = HDMI_AUDIO_I2S_CHST_WORD_16_BITS; - core_cfg.i2s_cfg.in_length_bits = - HDMI_AUDIO_I2S_INPUT_LENGTH_16; - core_cfg.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_LEFT; - audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_TWOSAMPLES; - audio_format.sample_size = HDMI_AUDIO_SAMPLE_16BITS; - audio_format.justification = HDMI_AUDIO_JUSTIFY_LEFT; - audio_dma.transfer_size = 0x10; - break; - case SNDRV_PCM_FORMAT_S24_LE: - core_cfg.i2s_cfg.word_max_length = - HDMI_AUDIO_I2S_MAX_WORD_24BITS; - core_cfg.i2s_cfg.word_length = HDMI_AUDIO_I2S_CHST_WORD_24_BITS; - core_cfg.i2s_cfg.in_length_bits = - HDMI_AUDIO_I2S_INPUT_LENGTH_24; - audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_ONESAMPLE; - audio_format.sample_size = HDMI_AUDIO_SAMPLE_24BITS; - audio_format.justification = HDMI_AUDIO_JUSTIFY_RIGHT; - core_cfg.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_RIGHT; - audio_dma.transfer_size = 0x20; - break; - default: - return -EINVAL; - } + hdmi.sys_clk = clk; - switch (params_rate(params)) { - case 32000: - sample_freq = HDMI_AUDIO_FS_32000; - break; - case 44100: - sample_freq = HDMI_AUDIO_FS_44100; - break; - case 48000: - sample_freq = HDMI_AUDIO_FS_48000; - break; - default: - return -EINVAL; + clk = clk_get(&pdev->dev, "hdmi_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get hdmi_clk\n"); + clk_put(hdmi.sys_clk); + return PTR_ERR(clk); } - err = hdmi_config_audio_acr(params_rate(params), &n, &cts); - if (err < 0) - return err; - - /* Audio wrapper config */ - audio_format.stereo_channels = HDMI_AUDIO_STEREO_ONECHANNEL; - audio_format.active_chnnls_msk = 0x03; - audio_format.type = HDMI_AUDIO_TYPE_LPCM; - audio_format.sample_order = HDMI_AUDIO_SAMPLE_LEFT_FIRST; - /* Disable start/stop signals of IEC 60958 blocks */ - audio_format.en_sig_blk_strt_end = HDMI_AUDIO_BLOCK_SIG_STARTEND_OFF; - - audio_dma.block_size = 0xC0; - audio_dma.mode = HDMI_AUDIO_TRANSF_DMA; - audio_dma.fifo_threshold = 0x20; /* in number of samples */ + hdmi.hdmi_clk = clk; - hdmi_wp_audio_config_dma(&audio_dma); - hdmi_wp_audio_config_format(&audio_format); - - /* - * I2S config - */ - core_cfg.i2s_cfg.en_high_bitrate_aud = false; - /* Only used with high bitrate audio */ - core_cfg.i2s_cfg.cbit_order = false; - /* Serial data and word select should change on sck rising edge */ - core_cfg.i2s_cfg.sck_edge_mode = HDMI_AUDIO_I2S_SCK_EDGE_RISING; - core_cfg.i2s_cfg.vbit = HDMI_AUDIO_I2S_VBIT_FOR_PCM; - /* Set I2S word select polarity */ - core_cfg.i2s_cfg.ws_polarity = HDMI_AUDIO_I2S_WS_POLARITY_LOW_IS_LEFT; - core_cfg.i2s_cfg.direction = HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST; - /* Set serial data to word select shift. See Phillips spec. */ - core_cfg.i2s_cfg.shift = HDMI_AUDIO_I2S_FIRST_BIT_SHIFT; - /* Enable one of the four available serial data channels */ - core_cfg.i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN; - - /* Core audio config */ - core_cfg.freq_sample = sample_freq; - core_cfg.n = n; - core_cfg.cts = cts; - if (dss_has_feature(FEAT_HDMI_CTS_SWMODE)) { - core_cfg.aud_par_busclk = 0; - core_cfg.cts_mode = HDMI_AUDIO_CTS_MODE_SW; - core_cfg.use_mclk = false; - } else { - core_cfg.aud_par_busclk = (((128 * 31) - 1) << 8); - core_cfg.cts_mode = HDMI_AUDIO_CTS_MODE_HW; - core_cfg.use_mclk = true; - core_cfg.mclk_mode = HDMI_AUDIO_MCLK_128FS; - } - core_cfg.layout = HDMI_AUDIO_LAYOUT_2CH; - core_cfg.en_spdif = false; - /* Use sample frequency from channel status word */ - core_cfg.fs_override = true; - /* Enable ACR packets */ - core_cfg.en_acr_pkt = true; - /* Disable direct streaming digital audio */ - core_cfg.en_dsd_audio = false; - /* Use parallel audio interface */ - core_cfg.en_parallel_aud_input = true; - - hdmi_core_audio_config(&core_cfg); - - /* - * Configure packet - * info frame audio see doc CEA861-D page 74 - */ - aud_if_cfg.db1_coding_type = HDMI_INFOFRAME_AUDIO_DB1CT_FROM_STREAM; - aud_if_cfg.db1_channel_count = 2; - aud_if_cfg.db2_sample_freq = HDMI_INFOFRAME_AUDIO_DB2SF_FROM_STREAM; - aud_if_cfg.db2_sample_size = HDMI_INFOFRAME_AUDIO_DB2SS_FROM_STREAM; - aud_if_cfg.db4_channel_alloc = 0x00; - aud_if_cfg.db5_downmix_inh = false; - aud_if_cfg.db5_lsv = 0; - - hdmi_core_audio_infoframe_config(&aud_if_cfg); return 0; } -static int hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) +static void hdmi_put_clocks(void) { - int err = 0; - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - REG_FLD_MOD(HDMI_CORE_AV_AUD_MODE, 1, 0, 0); - REG_FLD_MOD(HDMI_WP_AUDIO_CTRL, 1, 31, 31); - REG_FLD_MOD(HDMI_WP_AUDIO_CTRL, 1, 30, 30); - break; - - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - REG_FLD_MOD(HDMI_CORE_AV_AUD_MODE, 0, 0, 0); - REG_FLD_MOD(HDMI_WP_AUDIO_CTRL, 0, 30, 30); - REG_FLD_MOD(HDMI_WP_AUDIO_CTRL, 0, 31, 31); - break; - default: - err = -EINVAL; - } - return err; -} - -static int hdmi_audio_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - if (!hdmi.mode) { - pr_err("Current video settings do not support audio.\n"); - return -EIO; - } - return 0; + if (hdmi.sys_clk) + clk_put(hdmi.sys_clk); + if (hdmi.hdmi_clk) + clk_put(hdmi.hdmi_clk); } -static struct snd_soc_codec_driver hdmi_audio_codec_drv = { -}; - -static struct snd_soc_dai_ops hdmi_audio_codec_ops = { - .hw_params = hdmi_audio_hw_params, - .trigger = hdmi_audio_trigger, - .startup = hdmi_audio_startup, -}; - -static struct snd_soc_dai_driver hdmi_codec_dai_drv = { - .name = "hdmi-audio-codec", - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - }, - .ops = &hdmi_audio_codec_ops, -}; -#endif - /* HDMI HW IP initialisation */ static int omapdss_hdmihw_probe(struct platform_device *pdev) { struct resource *hdmi_mem; -#if defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) || \ - defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI_MODULE) - int ret; -#endif + struct omap_dss_board_info *board_data; + int r; hdmi.pdata = pdev->dev.platform_data; hdmi.pdev = pdev; mutex_init(&hdmi.lock); + /* save reference to HDMI device */ + board_data = hdmi.pdata->board_data; + for (r = 0; r < board_data->num_devices; r++) { + if (board_data->devices[r]->type == OMAP_DISPLAY_TYPE_HDMI) + hdmi.dssdev = board_data->devices[r]; + } + if (!hdmi.dssdev) { + DSSERR("can't get HDMI device\n"); + return -EINVAL; + } + hdmi_mem = platform_get_resource(hdmi.pdev, IORESOURCE_MEM, 0); if (!hdmi_mem) { DSSERR("can't get IORESOURCE_MEM HDMI\n"); @@ -1781,25 +752,47 @@ static int omapdss_hdmihw_probe(struct platform_device *pdev) } /* Base address taken from platform */ - hdmi.base_wp = ioremap(hdmi_mem->start, resource_size(hdmi_mem)); - if (!hdmi.base_wp) { + hdmi.hdmi_data.base_wp = ioremap(hdmi_mem->start, + resource_size(hdmi_mem)); + if (!hdmi.hdmi_data.base_wp) { DSSERR("can't ioremap WP\n"); return -ENOMEM; } - hdmi_panel_init(); + r = hdmi_get_clocks(pdev); + if (r) { + iounmap(hdmi.hdmi_data.base_wp); + return r; + } -#if defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) || \ - defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI_MODULE) + pm_runtime_enable(&pdev->dev); - /* Register ASoC codec DAI */ - ret = snd_soc_register_codec(&pdev->dev, &hdmi_audio_codec_drv, - &hdmi_codec_dai_drv, 1); - if (ret) { - DSSERR("can't register ASoC HDMI audio codec\n"); - return ret; + r = request_irq(gpio_to_irq(hdmi.dssdev->hpd_gpio), hpd_irq_handler, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "hpd", NULL); + if (r < 0) { + pr_err("hdmi: request_irq %d failed\n", + gpio_to_irq(hdmi.dssdev->hpd_gpio)); + return -EINVAL; + } + + hdmi.hdmi_irq = platform_get_irq(pdev, 0); + + r = request_irq(hdmi.hdmi_irq, hdmi_irq_handler, 0, "OMAP HDMI", NULL); + if (r < 0) { + pr_err("hdmi: request_irq %s failed\n", + pdev->name); + return -EINVAL; } -#endif + + hdmi.hdmi_data.hdmi_core_sys_offset = HDMI_CORE_SYS; + hdmi.hdmi_data.hdmi_core_av_offset = HDMI_CORE_AV; + hdmi.hdmi_data.hdmi_pll_offset = HDMI_PLLCTRL; + hdmi.hdmi_data.hdmi_phy_offset = HDMI_PHY; + hdmi.wp_reset_done = false; + + hdmi_panel_init(); + return 0; } @@ -1807,12 +800,15 @@ static int omapdss_hdmihw_remove(struct platform_device *pdev) { hdmi_panel_exit(); -#if defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) || \ - defined(CONFIG_SND_OMAP_SOC_OMAP4_HDMI_MODULE) - snd_soc_unregister_codec(&pdev->dev); -#endif + if (hdmi.dssdev) + free_irq(gpio_to_irq(hdmi.dssdev->hpd_gpio), hpd_irq_handler); + hdmi.dssdev = NULL; - iounmap(hdmi.base_wp); + pm_runtime_disable(&pdev->dev); + + hdmi_put_clocks(); + + iounmap(hdmi.hdmi_data.base_wp); return 0; } @@ -1835,3 +831,13 @@ void hdmi_uninit_platform_driver(void) { return platform_driver_unregister(&omapdss_hdmihw_driver); } + +void hdmi_dump_regs(struct seq_file *s) +{ + if (hdmi_runtime_get()) + return; + + hdmi_ti_4xxx_dump_regs(&hdmi.hdmi_data, s); + + hdmi_runtime_put(); +} diff --git a/drivers/video/omap2/dss/hdmi.h b/drivers/video/omap2/dss/hdmi.h deleted file mode 100644 index c885f9c..0000000 --- a/drivers/video/omap2/dss/hdmi.h +++ /dev/null @@ -1,631 +0,0 @@ -/* - * hdmi.h - * - * HDMI driver definition for TI OMAP4 processors. - * - * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef _OMAP4_DSS_HDMI_H_ -#define _OMAP4_DSS_HDMI_H_ - -#include <linux/string.h> -#include <video/omapdss.h> - -#define HDMI_WP 0x0 -#define HDMI_CORE_SYS 0x400 -#define HDMI_CORE_AV 0x900 -#define HDMI_PLLCTRL 0x200 -#define HDMI_PHY 0x300 - -struct hdmi_reg { u16 idx; }; - -#define HDMI_REG(idx) ((const struct hdmi_reg) { idx }) - -/* HDMI Wrapper */ -#define HDMI_WP_REG(idx) HDMI_REG(HDMI_WP + idx) - -#define HDMI_WP_REVISION HDMI_WP_REG(0x0) -#define HDMI_WP_SYSCONFIG HDMI_WP_REG(0x10) -#define HDMI_WP_IRQSTATUS_RAW HDMI_WP_REG(0x24) -#define HDMI_WP_IRQSTATUS HDMI_WP_REG(0x28) -#define HDMI_WP_PWR_CTRL HDMI_WP_REG(0x40) -#define HDMI_WP_IRQENABLE_SET HDMI_WP_REG(0x2C) -#define HDMI_WP_VIDEO_CFG HDMI_WP_REG(0x50) -#define HDMI_WP_VIDEO_SIZE HDMI_WP_REG(0x60) -#define HDMI_WP_VIDEO_TIMING_H HDMI_WP_REG(0x68) -#define HDMI_WP_VIDEO_TIMING_V HDMI_WP_REG(0x6C) -#define HDMI_WP_WP_CLK HDMI_WP_REG(0x70) -#define HDMI_WP_AUDIO_CFG HDMI_WP_REG(0x80) -#define HDMI_WP_AUDIO_CFG2 HDMI_WP_REG(0x84) -#define HDMI_WP_AUDIO_CTRL HDMI_WP_REG(0x88) -#define HDMI_WP_AUDIO_DATA HDMI_WP_REG(0x8C) - -/* HDMI IP Core System */ -#define HDMI_CORE_SYS_REG(idx) HDMI_REG(HDMI_CORE_SYS + idx) - -#define HDMI_CORE_SYS_VND_IDL HDMI_CORE_SYS_REG(0x0) -#define HDMI_CORE_SYS_DEV_IDL HDMI_CORE_SYS_REG(0x8) -#define HDMI_CORE_SYS_DEV_IDH HDMI_CORE_SYS_REG(0xC) -#define HDMI_CORE_SYS_DEV_REV HDMI_CORE_SYS_REG(0x10) -#define HDMI_CORE_SYS_SRST HDMI_CORE_SYS_REG(0x14) -#define HDMI_CORE_CTRL1 HDMI_CORE_SYS_REG(0x20) -#define HDMI_CORE_SYS_SYS_STAT HDMI_CORE_SYS_REG(0x24) -#define HDMI_CORE_SYS_VID_ACEN HDMI_CORE_SYS_REG(0x124) -#define HDMI_CORE_SYS_VID_MODE HDMI_CORE_SYS_REG(0x128) -#define HDMI_CORE_SYS_INTR_STATE HDMI_CORE_SYS_REG(0x1C0) -#define HDMI_CORE_SYS_INTR1 HDMI_CORE_SYS_REG(0x1C4) -#define HDMI_CORE_SYS_INTR2 HDMI_CORE_SYS_REG(0x1C8) -#define HDMI_CORE_SYS_INTR3 HDMI_CORE_SYS_REG(0x1CC) -#define HDMI_CORE_SYS_INTR4 HDMI_CORE_SYS_REG(0x1D0) -#define HDMI_CORE_SYS_UMASK1 HDMI_CORE_SYS_REG(0x1D4) -#define HDMI_CORE_SYS_TMDS_CTRL HDMI_CORE_SYS_REG(0x208) -#define HDMI_CORE_SYS_DE_DLY HDMI_CORE_SYS_REG(0xC8) -#define HDMI_CORE_SYS_DE_CTRL HDMI_CORE_SYS_REG(0xCC) -#define HDMI_CORE_SYS_DE_TOP HDMI_CORE_SYS_REG(0xD0) -#define HDMI_CORE_SYS_DE_CNTL HDMI_CORE_SYS_REG(0xD8) -#define HDMI_CORE_SYS_DE_CNTH HDMI_CORE_SYS_REG(0xDC) -#define HDMI_CORE_SYS_DE_LINL HDMI_CORE_SYS_REG(0xE0) -#define HDMI_CORE_SYS_DE_LINH_1 HDMI_CORE_SYS_REG(0xE4) -#define HDMI_CORE_CTRL1_VEN_FOLLOWVSYNC 0x1 -#define HDMI_CORE_CTRL1_HEN_FOLLOWHSYNC 0x1 -#define HDMI_CORE_CTRL1_BSEL_24BITBUS 0x1 -#define HDMI_CORE_CTRL1_EDGE_RISINGEDGE 0x1 - -/* HDMI DDC E-DID */ -#define HDMI_CORE_DDC_CMD HDMI_CORE_SYS_REG(0x3CC) -#define HDMI_CORE_DDC_STATUS HDMI_CORE_SYS_REG(0x3C8) -#define HDMI_CORE_DDC_ADDR HDMI_CORE_SYS_REG(0x3B4) -#define HDMI_CORE_DDC_OFFSET HDMI_CORE_SYS_REG(0x3BC) -#define HDMI_CORE_DDC_COUNT1 HDMI_CORE_SYS_REG(0x3C0) -#define HDMI_CORE_DDC_COUNT2 HDMI_CORE_SYS_REG(0x3C4) -#define HDMI_CORE_DDC_DATA HDMI_CORE_SYS_REG(0x3D0) -#define HDMI_CORE_DDC_SEGM HDMI_CORE_SYS_REG(0x3B8) - -/* HDMI IP Core Audio Video */ -#define HDMI_CORE_AV_REG(idx) HDMI_REG(HDMI_CORE_AV + idx) - -#define HDMI_CORE_AV_HDMI_CTRL HDMI_CORE_AV_REG(0xBC) -#define HDMI_CORE_AV_DPD HDMI_CORE_AV_REG(0xF4) -#define HDMI_CORE_AV_PB_CTRL1 HDMI_CORE_AV_REG(0xF8) -#define HDMI_CORE_AV_PB_CTRL2 HDMI_CORE_AV_REG(0xFC) -#define HDMI_CORE_AV_AVI_TYPE HDMI_CORE_AV_REG(0x100) -#define HDMI_CORE_AV_AVI_VERS HDMI_CORE_AV_REG(0x104) -#define HDMI_CORE_AV_AVI_LEN HDMI_CORE_AV_REG(0x108) -#define HDMI_CORE_AV_AVI_CHSUM HDMI_CORE_AV_REG(0x10C) -#define HDMI_CORE_AV_AVI_DBYTE(n) HDMI_CORE_AV_REG(n * 4 + 0x110) -#define HDMI_CORE_AV_AVI_DBYTE_NELEMS HDMI_CORE_AV_REG(15) -#define HDMI_CORE_AV_SPD_DBYTE HDMI_CORE_AV_REG(0x190) -#define HDMI_CORE_AV_SPD_DBYTE_NELEMS HDMI_CORE_AV_REG(27) -#define HDMI_CORE_AV_AUD_DBYTE(n) HDMI_CORE_AV_REG(n * 4 + 0x210) -#define HDMI_CORE_AV_AUD_DBYTE_NELEMS HDMI_CORE_AV_REG(10) -#define HDMI_CORE_AV_MPEG_DBYTE HDMI_CORE_AV_REG(0x290) -#define HDMI_CORE_AV_MPEG_DBYTE_NELEMS HDMI_CORE_AV_REG(27) -#define HDMI_CORE_AV_GEN_DBYTE HDMI_CORE_AV_REG(0x300) -#define HDMI_CORE_AV_GEN_DBYTE_NELEMS HDMI_CORE_AV_REG(31) -#define HDMI_CORE_AV_GEN2_DBYTE HDMI_CORE_AV_REG(0x380) -#define HDMI_CORE_AV_GEN2_DBYTE_NELEMS HDMI_CORE_AV_REG(31) -#define HDMI_CORE_AV_ACR_CTRL HDMI_CORE_AV_REG(0x4) -#define HDMI_CORE_AV_FREQ_SVAL HDMI_CORE_AV_REG(0x8) -#define HDMI_CORE_AV_N_SVAL1 HDMI_CORE_AV_REG(0xC) -#define HDMI_CORE_AV_N_SVAL2 HDMI_CORE_AV_REG(0x10) -#define HDMI_CORE_AV_N_SVAL3 HDMI_CORE_AV_REG(0x14) -#define HDMI_CORE_AV_CTS_SVAL1 HDMI_CORE_AV_REG(0x18) -#define HDMI_CORE_AV_CTS_SVAL2 HDMI_CORE_AV_REG(0x1C) -#define HDMI_CORE_AV_CTS_SVAL3 HDMI_CORE_AV_REG(0x20) -#define HDMI_CORE_AV_CTS_HVAL1 HDMI_CORE_AV_REG(0x24) -#define HDMI_CORE_AV_CTS_HVAL2 HDMI_CORE_AV_REG(0x28) -#define HDMI_CORE_AV_CTS_HVAL3 HDMI_CORE_AV_REG(0x2C) -#define HDMI_CORE_AV_AUD_MODE HDMI_CORE_AV_REG(0x50) -#define HDMI_CORE_AV_SPDIF_CTRL HDMI_CORE_AV_REG(0x54) -#define HDMI_CORE_AV_HW_SPDIF_FS HDMI_CORE_AV_REG(0x60) -#define HDMI_CORE_AV_SWAP_I2S HDMI_CORE_AV_REG(0x64) -#define HDMI_CORE_AV_SPDIF_ERTH HDMI_CORE_AV_REG(0x6C) -#define HDMI_CORE_AV_I2S_IN_MAP HDMI_CORE_AV_REG(0x70) -#define HDMI_CORE_AV_I2S_IN_CTRL HDMI_CORE_AV_REG(0x74) -#define HDMI_CORE_AV_I2S_CHST0 HDMI_CORE_AV_REG(0x78) -#define HDMI_CORE_AV_I2S_CHST1 HDMI_CORE_AV_REG(0x7C) -#define HDMI_CORE_AV_I2S_CHST2 HDMI_CORE_AV_REG(0x80) -#define HDMI_CORE_AV_I2S_CHST4 HDMI_CORE_AV_REG(0x84) -#define HDMI_CORE_AV_I2S_CHST5 HDMI_CORE_AV_REG(0x88) -#define HDMI_CORE_AV_ASRC HDMI_CORE_AV_REG(0x8C) -#define HDMI_CORE_AV_I2S_IN_LEN HDMI_CORE_AV_REG(0x90) -#define HDMI_CORE_AV_HDMI_CTRL HDMI_CORE_AV_REG(0xBC) -#define HDMI_CORE_AV_AUDO_TXSTAT HDMI_CORE_AV_REG(0xC0) -#define HDMI_CORE_AV_AUD_PAR_BUSCLK_1 HDMI_CORE_AV_REG(0xCC) -#define HDMI_CORE_AV_AUD_PAR_BUSCLK_2 HDMI_CORE_AV_REG(0xD0) -#define HDMI_CORE_AV_AUD_PAR_BUSCLK_3 HDMI_CORE_AV_REG(0xD4) -#define HDMI_CORE_AV_TEST_TXCTRL HDMI_CORE_AV_REG(0xF0) -#define HDMI_CORE_AV_DPD HDMI_CORE_AV_REG(0xF4) -#define HDMI_CORE_AV_PB_CTRL1 HDMI_CORE_AV_REG(0xF8) -#define HDMI_CORE_AV_PB_CTRL2 HDMI_CORE_AV_REG(0xFC) -#define HDMI_CORE_AV_AVI_TYPE HDMI_CORE_AV_REG(0x100) -#define HDMI_CORE_AV_AVI_VERS HDMI_CORE_AV_REG(0x104) -#define HDMI_CORE_AV_AVI_LEN HDMI_CORE_AV_REG(0x108) -#define HDMI_CORE_AV_AVI_CHSUM HDMI_CORE_AV_REG(0x10C) -#define HDMI_CORE_AV_SPD_TYPE HDMI_CORE_AV_REG(0x180) -#define HDMI_CORE_AV_SPD_VERS HDMI_CORE_AV_REG(0x184) -#define HDMI_CORE_AV_SPD_LEN HDMI_CORE_AV_REG(0x188) -#define HDMI_CORE_AV_SPD_CHSUM HDMI_CORE_AV_REG(0x18C) -#define HDMI_CORE_AV_AUDIO_TYPE HDMI_CORE_AV_REG(0x200) -#define HDMI_CORE_AV_AUDIO_VERS HDMI_CORE_AV_REG(0x204) -#define HDMI_CORE_AV_AUDIO_LEN HDMI_CORE_AV_REG(0x208) -#define HDMI_CORE_AV_AUDIO_CHSUM HDMI_CORE_AV_REG(0x20C) -#define HDMI_CORE_AV_MPEG_TYPE HDMI_CORE_AV_REG(0x280) -#define HDMI_CORE_AV_MPEG_VERS HDMI_CORE_AV_REG(0x284) -#define HDMI_CORE_AV_MPEG_LEN HDMI_CORE_AV_REG(0x288) -#define HDMI_CORE_AV_MPEG_CHSUM HDMI_CORE_AV_REG(0x28C) -#define HDMI_CORE_AV_CP_BYTE1 HDMI_CORE_AV_REG(0x37C) -#define HDMI_CORE_AV_CEC_ADDR_ID HDMI_CORE_AV_REG(0x3FC) -#define HDMI_CORE_AV_SPD_DBYTE_ELSIZE 0x4 -#define HDMI_CORE_AV_GEN2_DBYTE_ELSIZE 0x4 -#define HDMI_CORE_AV_MPEG_DBYTE_ELSIZE 0x4 -#define HDMI_CORE_AV_GEN_DBYTE_ELSIZE 0x4 - -/* PLL */ -#define HDMI_PLL_REG(idx) HDMI_REG(HDMI_PLLCTRL + idx) - -#define PLLCTRL_PLL_CONTROL HDMI_PLL_REG(0x0) -#define PLLCTRL_PLL_STATUS HDMI_PLL_REG(0x4) -#define PLLCTRL_PLL_GO HDMI_PLL_REG(0x8) -#define PLLCTRL_CFG1 HDMI_PLL_REG(0xC) -#define PLLCTRL_CFG2 HDMI_PLL_REG(0x10) -#define PLLCTRL_CFG3 HDMI_PLL_REG(0x14) -#define PLLCTRL_CFG4 HDMI_PLL_REG(0x20) - -/* HDMI PHY */ -#define HDMI_PHY_REG(idx) HDMI_REG(HDMI_PHY + idx) - -#define HDMI_TXPHY_TX_CTRL HDMI_PHY_REG(0x0) -#define HDMI_TXPHY_DIGITAL_CTRL HDMI_PHY_REG(0x4) -#define HDMI_TXPHY_POWER_CTRL HDMI_PHY_REG(0x8) -#define HDMI_TXPHY_PAD_CFG_CTRL HDMI_PHY_REG(0xC) - -/* HDMI EDID Length */ -#define HDMI_EDID_MAX_LENGTH 256 -#define EDID_TIMING_DESCRIPTOR_SIZE 0x12 -#define EDID_DESCRIPTOR_BLOCK0_ADDRESS 0x36 -#define EDID_DESCRIPTOR_BLOCK1_ADDRESS 0x80 -#define EDID_SIZE_BLOCK0_TIMING_DESCRIPTOR 4 -#define EDID_SIZE_BLOCK1_TIMING_DESCRIPTOR 4 - -#define OMAP_HDMI_TIMINGS_NB 34 - -#define REG_FLD_MOD(idx, val, start, end) \ - hdmi_write_reg(idx, FLD_MOD(hdmi_read_reg(idx), val, start, end)) -#define REG_GET(idx, start, end) \ - FLD_GET(hdmi_read_reg(idx), start, end) - -/* HDMI timing structure */ -struct hdmi_timings { - struct omap_video_timings timings; - int vsync_pol; - int hsync_pol; -}; - -enum hdmi_phy_pwr { - HDMI_PHYPWRCMD_OFF = 0, - HDMI_PHYPWRCMD_LDOON = 1, - HDMI_PHYPWRCMD_TXON = 2 -}; - -enum hdmi_pll_pwr { - HDMI_PLLPWRCMD_ALLOFF = 0, - HDMI_PLLPWRCMD_PLLONLY = 1, - HDMI_PLLPWRCMD_BOTHON_ALLCLKS = 2, - HDMI_PLLPWRCMD_BOTHON_NOPHYCLK = 3 -}; - -enum hdmi_clk_refsel { - HDMI_REFSEL_PCLK = 0, - HDMI_REFSEL_REF1 = 1, - HDMI_REFSEL_REF2 = 2, - HDMI_REFSEL_SYSCLK = 3 -}; - -enum hdmi_core_inputbus_width { - HDMI_INPUT_8BIT = 0, - HDMI_INPUT_10BIT = 1, - HDMI_INPUT_12BIT = 2 -}; - -enum hdmi_core_dither_trunc { - HDMI_OUTPUTTRUNCATION_8BIT = 0, - HDMI_OUTPUTTRUNCATION_10BIT = 1, - HDMI_OUTPUTTRUNCATION_12BIT = 2, - HDMI_OUTPUTDITHER_8BIT = 3, - HDMI_OUTPUTDITHER_10BIT = 4, - HDMI_OUTPUTDITHER_12BIT = 5 -}; - -enum hdmi_core_deepcolor_ed { - HDMI_DEEPCOLORPACKECTDISABLE = 0, - HDMI_DEEPCOLORPACKECTENABLE = 1 -}; - -enum hdmi_core_packet_mode { - HDMI_PACKETMODERESERVEDVALUE = 0, - HDMI_PACKETMODE24BITPERPIXEL = 4, - HDMI_PACKETMODE30BITPERPIXEL = 5, - HDMI_PACKETMODE36BITPERPIXEL = 6, - HDMI_PACKETMODE48BITPERPIXEL = 7 -}; - -enum hdmi_core_hdmi_dvi { - HDMI_DVI = 0, - HDMI_HDMI = 1 -}; - -enum hdmi_core_tclkselclkmult { - HDMI_FPLL05IDCK = 0, - HDMI_FPLL10IDCK = 1, - HDMI_FPLL20IDCK = 2, - HDMI_FPLL40IDCK = 3 -}; - -enum hdmi_core_packet_ctrl { - HDMI_PACKETENABLE = 1, - HDMI_PACKETDISABLE = 0, - HDMI_PACKETREPEATON = 1, - HDMI_PACKETREPEATOFF = 0 -}; - -/* INFOFRAME_AVI_ and INFOFRAME_AUDIO_ definitions */ -enum hdmi_core_infoframe { - HDMI_INFOFRAME_AVI_DB1Y_RGB = 0, - HDMI_INFOFRAME_AVI_DB1Y_YUV422 = 1, - HDMI_INFOFRAME_AVI_DB1Y_YUV444 = 2, - HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_OFF = 0, - HDMI_INFOFRAME_AVI_DB1A_ACTIVE_FORMAT_ON = 1, - HDMI_INFOFRAME_AVI_DB1B_NO = 0, - HDMI_INFOFRAME_AVI_DB1B_VERT = 1, - HDMI_INFOFRAME_AVI_DB1B_HORI = 2, - HDMI_INFOFRAME_AVI_DB1B_VERTHORI = 3, - HDMI_INFOFRAME_AVI_DB1S_0 = 0, - HDMI_INFOFRAME_AVI_DB1S_1 = 1, - HDMI_INFOFRAME_AVI_DB1S_2 = 2, - HDMI_INFOFRAME_AVI_DB2C_NO = 0, - HDMI_INFOFRAME_AVI_DB2C_ITU601 = 1, - HDMI_INFOFRAME_AVI_DB2C_ITU709 = 2, - HDMI_INFOFRAME_AVI_DB2C_EC_EXTENDED = 3, - HDMI_INFOFRAME_AVI_DB2M_NO = 0, - HDMI_INFOFRAME_AVI_DB2M_43 = 1, - HDMI_INFOFRAME_AVI_DB2M_169 = 2, - HDMI_INFOFRAME_AVI_DB2R_SAME = 8, - HDMI_INFOFRAME_AVI_DB2R_43 = 9, - HDMI_INFOFRAME_AVI_DB2R_169 = 10, - HDMI_INFOFRAME_AVI_DB2R_149 = 11, - HDMI_INFOFRAME_AVI_DB3ITC_NO = 0, - HDMI_INFOFRAME_AVI_DB3ITC_YES = 1, - HDMI_INFOFRAME_AVI_DB3EC_XVYUV601 = 0, - HDMI_INFOFRAME_AVI_DB3EC_XVYUV709 = 1, - HDMI_INFOFRAME_AVI_DB3Q_DEFAULT = 0, - HDMI_INFOFRAME_AVI_DB3Q_LR = 1, - HDMI_INFOFRAME_AVI_DB3Q_FR = 2, - HDMI_INFOFRAME_AVI_DB3SC_NO = 0, - HDMI_INFOFRAME_AVI_DB3SC_HORI = 1, - HDMI_INFOFRAME_AVI_DB3SC_VERT = 2, - HDMI_INFOFRAME_AVI_DB3SC_HORIVERT = 3, - HDMI_INFOFRAME_AVI_DB5PR_NO = 0, - HDMI_INFOFRAME_AVI_DB5PR_2 = 1, - HDMI_INFOFRAME_AVI_DB5PR_3 = 2, - HDMI_INFOFRAME_AVI_DB5PR_4 = 3, - HDMI_INFOFRAME_AVI_DB5PR_5 = 4, - HDMI_INFOFRAME_AVI_DB5PR_6 = 5, - HDMI_INFOFRAME_AVI_DB5PR_7 = 6, - HDMI_INFOFRAME_AVI_DB5PR_8 = 7, - HDMI_INFOFRAME_AVI_DB5PR_9 = 8, - HDMI_INFOFRAME_AVI_DB5PR_10 = 9, - HDMI_INFOFRAME_AUDIO_DB1CT_FROM_STREAM = 0, - HDMI_INFOFRAME_AUDIO_DB1CT_IEC60958 = 1, - HDMI_INFOFRAME_AUDIO_DB1CT_AC3 = 2, - HDMI_INFOFRAME_AUDIO_DB1CT_MPEG1 = 3, - HDMI_INFOFRAME_AUDIO_DB1CT_MP3 = 4, - HDMI_INFOFRAME_AUDIO_DB1CT_MPEG2_MULTICH = 5, - HDMI_INFOFRAME_AUDIO_DB1CT_AAC = 6, - HDMI_INFOFRAME_AUDIO_DB1CT_DTS = 7, - HDMI_INFOFRAME_AUDIO_DB1CT_ATRAC = 8, - HDMI_INFOFRAME_AUDIO_DB1CT_ONEBIT = 9, - HDMI_INFOFRAME_AUDIO_DB1CT_DOLBY_DIGITAL_PLUS = 10, - HDMI_INFOFRAME_AUDIO_DB1CT_DTS_HD = 11, - HDMI_INFOFRAME_AUDIO_DB1CT_MAT = 12, - HDMI_INFOFRAME_AUDIO_DB1CT_DST = 13, - HDMI_INFOFRAME_AUDIO_DB1CT_WMA_PRO = 14, - HDMI_INFOFRAME_AUDIO_DB2SF_FROM_STREAM = 0, - HDMI_INFOFRAME_AUDIO_DB2SF_32000 = 1, - HDMI_INFOFRAME_AUDIO_DB2SF_44100 = 2, - HDMI_INFOFRAME_AUDIO_DB2SF_48000 = 3, - HDMI_INFOFRAME_AUDIO_DB2SF_88200 = 4, - HDMI_INFOFRAME_AUDIO_DB2SF_96000 = 5, - HDMI_INFOFRAME_AUDIO_DB2SF_176400 = 6, - HDMI_INFOFRAME_AUDIO_DB2SF_192000 = 7, - HDMI_INFOFRAME_AUDIO_DB2SS_FROM_STREAM = 0, - HDMI_INFOFRAME_AUDIO_DB2SS_16BIT = 1, - HDMI_INFOFRAME_AUDIO_DB2SS_20BIT = 2, - HDMI_INFOFRAME_AUDIO_DB2SS_24BIT = 3, - HDMI_INFOFRAME_AUDIO_DB5_DM_INH_PERMITTED = 0, - HDMI_INFOFRAME_AUDIO_DB5_DM_INH_PROHIBITED = 1 -}; - -enum hdmi_packing_mode { - HDMI_PACK_10b_RGB_YUV444 = 0, - HDMI_PACK_24b_RGB_YUV444_YUV422 = 1, - HDMI_PACK_20b_YUV422 = 2, - HDMI_PACK_ALREADYPACKED = 7 -}; - -enum hdmi_core_audio_sample_freq { - HDMI_AUDIO_FS_32000 = 0x3, - HDMI_AUDIO_FS_44100 = 0x0, - HDMI_AUDIO_FS_48000 = 0x2, - HDMI_AUDIO_FS_88200 = 0x8, - HDMI_AUDIO_FS_96000 = 0xA, - HDMI_AUDIO_FS_176400 = 0xC, - HDMI_AUDIO_FS_192000 = 0xE, - HDMI_AUDIO_FS_NOT_INDICATED = 0x1 -}; - -enum hdmi_core_audio_layout { - HDMI_AUDIO_LAYOUT_2CH = 0, - HDMI_AUDIO_LAYOUT_8CH = 1 -}; - -enum hdmi_core_cts_mode { - HDMI_AUDIO_CTS_MODE_HW = 0, - HDMI_AUDIO_CTS_MODE_SW = 1 -}; - -enum hdmi_stereo_channels { - HDMI_AUDIO_STEREO_NOCHANNELS = 0, - HDMI_AUDIO_STEREO_ONECHANNEL = 1, - HDMI_AUDIO_STEREO_TWOCHANNELS = 2, - HDMI_AUDIO_STEREO_THREECHANNELS = 3, - HDMI_AUDIO_STEREO_FOURCHANNELS = 4 -}; - -enum hdmi_audio_type { - HDMI_AUDIO_TYPE_LPCM = 0, - HDMI_AUDIO_TYPE_IEC = 1 -}; - -enum hdmi_audio_justify { - HDMI_AUDIO_JUSTIFY_LEFT = 0, - HDMI_AUDIO_JUSTIFY_RIGHT = 1 -}; - -enum hdmi_audio_sample_order { - HDMI_AUDIO_SAMPLE_RIGHT_FIRST = 0, - HDMI_AUDIO_SAMPLE_LEFT_FIRST = 1 -}; - -enum hdmi_audio_samples_perword { - HDMI_AUDIO_ONEWORD_ONESAMPLE = 0, - HDMI_AUDIO_ONEWORD_TWOSAMPLES = 1 -}; - -enum hdmi_audio_sample_size { - HDMI_AUDIO_SAMPLE_16BITS = 0, - HDMI_AUDIO_SAMPLE_24BITS = 1 -}; - -enum hdmi_audio_transf_mode { - HDMI_AUDIO_TRANSF_DMA = 0, - HDMI_AUDIO_TRANSF_IRQ = 1 -}; - -enum hdmi_audio_blk_strt_end_sig { - HDMI_AUDIO_BLOCK_SIG_STARTEND_ON = 0, - HDMI_AUDIO_BLOCK_SIG_STARTEND_OFF = 1 -}; - -enum hdmi_audio_i2s_config { - HDMI_AUDIO_I2S_WS_POLARITY_LOW_IS_LEFT = 0, - HDMI_AUDIO_I2S_WS_POLARIT_YLOW_IS_RIGHT = 1, - HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST = 0, - HDMI_AUDIO_I2S_LSB_SHIFTED_FIRST = 1, - HDMI_AUDIO_I2S_MAX_WORD_20BITS = 0, - HDMI_AUDIO_I2S_MAX_WORD_24BITS = 1, - HDMI_AUDIO_I2S_CHST_WORD_NOT_SPECIFIED = 0, - HDMI_AUDIO_I2S_CHST_WORD_16_BITS = 1, - HDMI_AUDIO_I2S_CHST_WORD_17_BITS = 6, - HDMI_AUDIO_I2S_CHST_WORD_18_BITS = 2, - HDMI_AUDIO_I2S_CHST_WORD_19_BITS = 4, - HDMI_AUDIO_I2S_CHST_WORD_20_BITS_20MAX = 5, - HDMI_AUDIO_I2S_CHST_WORD_20_BITS_24MAX = 1, - HDMI_AUDIO_I2S_CHST_WORD_21_BITS = 6, - HDMI_AUDIO_I2S_CHST_WORD_22_BITS = 2, - HDMI_AUDIO_I2S_CHST_WORD_23_BITS = 4, - HDMI_AUDIO_I2S_CHST_WORD_24_BITS = 5, - HDMI_AUDIO_I2S_SCK_EDGE_FALLING = 0, - HDMI_AUDIO_I2S_SCK_EDGE_RISING = 1, - HDMI_AUDIO_I2S_VBIT_FOR_PCM = 0, - HDMI_AUDIO_I2S_VBIT_FOR_COMPRESSED = 1, - HDMI_AUDIO_I2S_INPUT_LENGTH_NA = 0, - HDMI_AUDIO_I2S_INPUT_LENGTH_16 = 2, - HDMI_AUDIO_I2S_INPUT_LENGTH_17 = 12, - HDMI_AUDIO_I2S_INPUT_LENGTH_18 = 4, - HDMI_AUDIO_I2S_INPUT_LENGTH_19 = 8, - HDMI_AUDIO_I2S_INPUT_LENGTH_20 = 10, - HDMI_AUDIO_I2S_INPUT_LENGTH_21 = 13, - HDMI_AUDIO_I2S_INPUT_LENGTH_22 = 5, - HDMI_AUDIO_I2S_INPUT_LENGTH_23 = 9, - HDMI_AUDIO_I2S_INPUT_LENGTH_24 = 11, - HDMI_AUDIO_I2S_FIRST_BIT_SHIFT = 0, - HDMI_AUDIO_I2S_FIRST_BIT_NO_SHIFT = 1, - HDMI_AUDIO_I2S_SD0_EN = 1, - HDMI_AUDIO_I2S_SD1_EN = 1 << 1, - HDMI_AUDIO_I2S_SD2_EN = 1 << 2, - HDMI_AUDIO_I2S_SD3_EN = 1 << 3, -}; - -enum hdmi_audio_mclk_mode { - HDMI_AUDIO_MCLK_128FS = 0, - HDMI_AUDIO_MCLK_256FS = 1, - HDMI_AUDIO_MCLK_384FS = 2, - HDMI_AUDIO_MCLK_512FS = 3, - HDMI_AUDIO_MCLK_768FS = 4, - HDMI_AUDIO_MCLK_1024FS = 5, - HDMI_AUDIO_MCLK_1152FS = 6, - HDMI_AUDIO_MCLK_192FS = 7 -}; - -struct hdmi_core_video_config { - enum hdmi_core_inputbus_width ip_bus_width; - enum hdmi_core_dither_trunc op_dither_truc; - enum hdmi_core_deepcolor_ed deep_color_pkt; - enum hdmi_core_packet_mode pkt_mode; - enum hdmi_core_hdmi_dvi hdmi_dvi; - enum hdmi_core_tclkselclkmult tclk_sel_clkmult; -}; - -/* - * Refer to section 8.2 in HDMI 1.3 specification for - * details about infoframe databytes - */ -struct hdmi_core_infoframe_avi { - u8 db1_format; - /* Y0, Y1 rgb,yCbCr */ - u8 db1_active_info; - /* A0 Active information Present */ - u8 db1_bar_info_dv; - /* B0, B1 Bar info data valid */ - u8 db1_scan_info; - /* S0, S1 scan information */ - u8 db2_colorimetry; - /* C0, C1 colorimetry */ - u8 db2_aspect_ratio; - /* M0, M1 Aspect ratio (4:3, 16:9) */ - u8 db2_active_fmt_ar; - /* R0...R3 Active format aspect ratio */ - u8 db3_itc; - /* ITC IT content. */ - u8 db3_ec; - /* EC0, EC1, EC2 Extended colorimetry */ - u8 db3_q_range; - /* Q1, Q0 Quantization range */ - u8 db3_nup_scaling; - /* SC1, SC0 Non-uniform picture scaling */ - u8 db4_videocode; - /* VIC0..6 Video format identification */ - u8 db5_pixel_repeat; - /* PR0..PR3 Pixel repetition factor */ - u16 db6_7_line_eoftop; - /* Line number end of top bar */ - u16 db8_9_line_sofbottom; - /* Line number start of bottom bar */ - u16 db10_11_pixel_eofleft; - /* Pixel number end of left bar */ - u16 db12_13_pixel_sofright; - /* Pixel number start of right bar */ -}; -/* - * Refer to section 8.2 in HDMI 1.3 specification for - * details about infoframe databytes - */ -struct hdmi_core_infoframe_audio { - u8 db1_coding_type; - u8 db1_channel_count; - u8 db2_sample_freq; - u8 db2_sample_size; - u8 db4_channel_alloc; - bool db5_downmix_inh; - u8 db5_lsv; /* Level shift values for downmix */ -}; - -struct hdmi_core_packet_enable_repeat { - u32 audio_pkt; - u32 audio_pkt_repeat; - u32 avi_infoframe; - u32 avi_infoframe_repeat; - u32 gen_cntrl_pkt; - u32 gen_cntrl_pkt_repeat; - u32 generic_pkt; - u32 generic_pkt_repeat; -}; - -struct hdmi_video_format { - enum hdmi_packing_mode packing_mode; - u32 y_res; /* Line per panel */ - u32 x_res; /* pixel per line */ -}; - -struct hdmi_video_interface { - int vsp; /* Vsync polarity */ - int hsp; /* Hsync polarity */ - int interlacing; - int tm; /* Timing mode */ -}; - -struct hdmi_cm { - int code; - int mode; -}; - -struct hdmi_config { - struct hdmi_timings timings; - u16 interlace; - struct hdmi_cm cm; -}; - -struct hdmi_audio_format { - enum hdmi_stereo_channels stereo_channels; - u8 active_chnnls_msk; - enum hdmi_audio_type type; - enum hdmi_audio_justify justification; - enum hdmi_audio_sample_order sample_order; - enum hdmi_audio_samples_perword samples_per_word; - enum hdmi_audio_sample_size sample_size; - enum hdmi_audio_blk_strt_end_sig en_sig_blk_strt_end; -}; - -struct hdmi_audio_dma { - u8 transfer_size; - u8 block_size; - enum hdmi_audio_transf_mode mode; - u16 fifo_threshold; -}; - -struct hdmi_core_audio_i2s_config { - u8 word_max_length; - u8 word_length; - u8 in_length_bits; - u8 justification; - u8 en_high_bitrate_aud; - u8 sck_edge_mode; - u8 cbit_order; - u8 vbit; - u8 ws_polarity; - u8 direction; - u8 shift; - u8 active_sds; -}; - -struct hdmi_core_audio_config { - struct hdmi_core_audio_i2s_config i2s_cfg; - enum hdmi_core_audio_sample_freq freq_sample; - bool fs_override; - u32 n; - u32 cts; - u32 aud_par_busclk; - enum hdmi_core_audio_layout layout; - enum hdmi_core_cts_mode cts_mode; - bool use_mclk; - enum hdmi_audio_mclk_mode mclk_mode; - bool en_acr_pkt; - bool en_dsd_audio; - bool en_parallel_aud_input; - bool en_spdif; -}; -#endif diff --git a/drivers/video/omap2/dss/hdmi_omap4_panel.c b/drivers/video/omap2/dss/hdmi_panel.c index 7d4f2bd..0fa5dea 100644 --- a/drivers/video/omap2/dss/hdmi_omap4_panel.c +++ b/drivers/video/omap2/dss/hdmi_panel.c @@ -1,5 +1,5 @@ /* - * hdmi_omap4_panel.c + * hdmi_panel.c * * HDMI library support functions for TI OMAP4 processors. * @@ -25,13 +25,39 @@ #include <linux/mutex.h> #include <linux/module.h> #include <video/omapdss.h> +#include <linux/switch.h> #include "dss.h" +#include <video/hdmi_ti_4xxx_ip.h> + static struct { struct mutex hdmi_lock; + struct switch_dev hpd_switch; } hdmi; +static ssize_t hdmi_deepcolor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int r; + r = omapdss_hdmi_get_deepcolor(); + return snprintf(buf, PAGE_SIZE, "%d\n", r); +} + +static ssize_t hdmi_deepcolor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long deep_color; + int r = kstrtoul(buf, 0, &deep_color); + if (r || deep_color > 2) + return -EINVAL; + omapdss_hdmi_set_deepcolor(deep_color); + return size; +} + +static DEVICE_ATTR(deepcolor, S_IRUGO | S_IWUSR, hdmi_deepcolor_show, + hdmi_deepcolor_store); static int hdmi_panel_probe(struct omap_dss_device *dssdev) { @@ -48,6 +74,10 @@ static int hdmi_panel_probe(struct omap_dss_device *dssdev) dssdev->panel.timings.x_res = 640; dssdev->panel.timings.y_res = 480; + /* sysfs entry to provide user space control to set deepcolor mode */ + if (device_create_file(&dssdev->dev, &dev_attr_deepcolor)) + DSSERR("failed to create sysfs file\n"); + DSSDBG("hdmi_panel_probe x_res= %d y_res = %d\n", dssdev->panel.timings.x_res, dssdev->panel.timings.y_res); @@ -56,7 +86,7 @@ static int hdmi_panel_probe(struct omap_dss_device *dssdev) static void hdmi_panel_remove(struct omap_dss_device *dssdev) { - + device_remove_file(&dssdev->dev, &dev_attr_deepcolor); } static int hdmi_panel_enable(struct omap_dss_device *dssdev) @@ -78,7 +108,6 @@ static int hdmi_panel_enable(struct omap_dss_device *dssdev) } dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; - err: mutex_unlock(&hdmi.hdmi_lock); @@ -110,8 +139,9 @@ static int hdmi_panel_suspend(struct omap_dss_device *dssdev) dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; - omapdss_hdmi_display_disable(dssdev); + hdmi_panel_hpd_handler(0); + omapdss_hdmi_display_disable(dssdev); err: mutex_unlock(&hdmi.hdmi_lock); @@ -129,18 +159,90 @@ static int hdmi_panel_resume(struct omap_dss_device *dssdev) goto err; } - r = omapdss_hdmi_display_enable(dssdev); - if (r) { - DSSERR("failed to power on\n"); - goto err; + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +err: + mutex_unlock(&hdmi.hdmi_lock); + + hdmi_panel_hpd_handler(hdmi_get_current_hpd()); + + return r; +} + +enum { + HPD_STATE_OFF, + HPD_STATE_START, + HPD_STATE_EDID_TRYLAST = HPD_STATE_START + 5, +}; + +static struct hpd_worker_data { + struct delayed_work dwork; + atomic_t state; +} hpd_work; +static struct workqueue_struct *my_workq; + +static void hdmi_hotplug_detect_worker(struct work_struct *work) +{ + struct hpd_worker_data *d = container_of(work, typeof(*d), dwork.work); + struct omap_dss_device *dssdev = NULL; + int state = atomic_read(&d->state); + + int match(struct omap_dss_device *dssdev, void *arg) + { + return sysfs_streq(dssdev->name , "hdmi"); } + dssdev = omap_dss_find_device(NULL, match); - dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + pr_err("in hpd work %d, state=%d\n", state, dssdev->state); + if (dssdev == NULL) + return; -err: + mutex_lock(&hdmi.hdmi_lock); + if (state == HPD_STATE_OFF) { + switch_set_state(&hdmi.hpd_switch, 0); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + mutex_unlock(&hdmi.hdmi_lock); + dssdev->driver->disable(dssdev); + mutex_lock(&hdmi.hdmi_lock); + } + goto done; + } else { + if (state == HPD_STATE_START) { + mutex_unlock(&hdmi.hdmi_lock); + dssdev->driver->enable(dssdev); + mutex_lock(&hdmi.hdmi_lock); + } else if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || + hdmi.hpd_switch.state) { + /* powered down after enable - skip EDID read */ + goto done; + } else if (hdmi_read_edid(&dssdev->panel.timings)) { + /* get monspecs from edid */ + hdmi_get_monspecs(&dssdev->panel.monspecs); + pr_info("panel size %d by %d\n", + dssdev->panel.monspecs.max_x, + dssdev->panel.monspecs.max_y); + dssdev->panel.width_in_um = + dssdev->panel.monspecs.max_x * 10000; + dssdev->panel.height_in_um = + dssdev->panel.monspecs.max_y * 10000; + switch_set_state(&hdmi.hpd_switch, 1); + goto done; + } else if (state == HPD_STATE_EDID_TRYLAST){ + pr_info("Failed to read EDID after %d times. Giving up.", state - HPD_STATE_START); + goto done; + } + if (atomic_add_unless(&d->state, 1, HPD_STATE_OFF)) + queue_delayed_work(my_workq, &d->dwork, msecs_to_jiffies(60)); + } +done: mutex_unlock(&hdmi.hdmi_lock); +} - return r; +int hdmi_panel_hpd_handler(int hpd) +{ + __cancel_delayed_work(&hpd_work.dwork); + atomic_set(&hpd_work.state, hpd ? HPD_STATE_START : HPD_STATE_OFF); + queue_delayed_work(my_workq, &hpd_work.dwork, msecs_to_jiffies(hpd ? 40 : 30)); + return 0; } static void hdmi_get_timings(struct omap_dss_device *dssdev, @@ -162,11 +264,8 @@ static void hdmi_set_timings(struct omap_dss_device *dssdev, dssdev->panel.timings = *timings; - if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { - /* turn the hdmi off and on to get new timings to use */ - omapdss_hdmi_display_disable(dssdev); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) omapdss_hdmi_display_set_timing(dssdev); - } mutex_unlock(&hdmi.hdmi_lock); } @@ -190,6 +289,16 @@ err: return r; } +static int hdmi_get_modedb(struct omap_dss_device *dssdev, + struct fb_videomode *modedb, int modedb_len) +{ + struct fb_monspecs *specs = &dssdev->panel.monspecs; + if (specs->modedb_len < modedb_len) + modedb_len = specs->modedb_len; + memcpy(modedb, specs->modedb, sizeof(*modedb) * modedb_len); + return modedb_len; +} + static struct omap_dss_driver hdmi_driver = { .probe = hdmi_panel_probe, .remove = hdmi_panel_remove, @@ -200,6 +309,8 @@ static struct omap_dss_driver hdmi_driver = { .get_timings = hdmi_get_timings, .set_timings = hdmi_set_timings, .check_timings = hdmi_check_timings, + .get_modedb = hdmi_get_modedb, + .set_mode = omapdss_hdmi_display_set_mode, .driver = { .name = "hdmi_panel", .owner = THIS_MODULE, @@ -209,7 +320,11 @@ static struct omap_dss_driver hdmi_driver = { int hdmi_panel_init(void) { mutex_init(&hdmi.hdmi_lock); + hdmi.hpd_switch.name = "hdmi"; + switch_dev_register(&hdmi.hpd_switch); + my_workq = create_singlethread_workqueue("hdmi_hotplug"); + INIT_DELAYED_WORK(&hpd_work.dwork, hdmi_hotplug_detect_worker); omap_dss_register_driver(&hdmi_driver); return 0; @@ -217,6 +332,8 @@ int hdmi_panel_init(void) void hdmi_panel_exit(void) { + destroy_workqueue(my_workq); omap_dss_unregister_driver(&hdmi_driver); + switch_dev_unregister(&hdmi.hpd_switch); } diff --git a/drivers/video/omap2/dss/manager.c b/drivers/video/omap2/dss/manager.c index 9aeea50..5888688 100644 --- a/drivers/video/omap2/dss/manager.c +++ b/drivers/video/omap2/dss/manager.c @@ -28,6 +28,8 @@ #include <linux/platform_device.h> #include <linux/spinlock.h> #include <linux/jiffies.h> +#include <linux/ratelimit.h> +#include <linux/seq_file.h> #include <video/omapdss.h> #include <plat/cpu.h> @@ -37,6 +39,7 @@ static int num_managers; static struct list_head manager_list; +static struct omap_overlay_manager *mgrs[MAX_DSS_MANAGERS]; static ssize_t manager_name_show(struct omap_overlay_manager *mgr, char *buf) { @@ -260,6 +263,10 @@ static ssize_t manager_alpha_blending_enabled_store( if (sscanf(buf, "%d", &enable) != 1) return -EINVAL; + /* if we have OMAP3 alpha compatibility, alpha blending is always on */ + if (dss_has_feature(FEAT_ALPHA_OMAP3_COMPAT) && !enable) + return -EINVAL; + mgr->get_manager_info(mgr, &info); info.alpha_enabled = enable ? true : false; @@ -275,6 +282,108 @@ static ssize_t manager_alpha_blending_enabled_store( return size; } +static ssize_t manager_cpr_enable_show(struct omap_overlay_manager *mgr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", mgr->info.cpr_enable); +} + +static ssize_t manager_cpr_enable_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + int v; + int r; + bool enable; + + if (!dss_has_feature(FEAT_CPR)) + return -ENODEV; + + r = kstrtoint(buf, 0, &v); + if (r) + return r; + + enable = !!v; + + mgr->get_manager_info(mgr, &info); + + if (info.cpr_enable == enable) + return size; + + info.cpr_enable = enable; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + +static ssize_t manager_cpr_coef_show(struct omap_overlay_manager *mgr, + char *buf) +{ + struct omap_overlay_manager_info info; + + mgr->get_manager_info(mgr, &info); + + return snprintf(buf, PAGE_SIZE, + "%d %d %d %d %d %d %d %d %d\n", + info.cpr_coefs.rr, + info.cpr_coefs.rg, + info.cpr_coefs.rb, + info.cpr_coefs.gr, + info.cpr_coefs.gg, + info.cpr_coefs.gb, + info.cpr_coefs.br, + info.cpr_coefs.bg, + info.cpr_coefs.bb); +} + +static ssize_t manager_cpr_coef_store(struct omap_overlay_manager *mgr, + const char *buf, size_t size) +{ + struct omap_overlay_manager_info info; + struct omap_dss_cpr_coefs coefs; + int r, i; + s16 *arr; + + if (!dss_has_feature(FEAT_CPR)) + return -ENODEV; + + if (sscanf(buf, "%hd %hd %hd %hd %hd %hd %hd %hd %hd", + &coefs.rr, &coefs.rg, &coefs.rb, + &coefs.gr, &coefs.gg, &coefs.gb, + &coefs.br, &coefs.bg, &coefs.bb) != 9) + return -EINVAL; + + arr = (s16[]){ coefs.rr, coefs.rg, coefs.rb, + coefs.gr, coefs.gg, coefs.gb, + coefs.br, coefs.bg, coefs.bb }; + + for (i = 0; i < 9; ++i) { + if (arr[i] < -512 || arr[i] > 511) + return -EINVAL; + } + + mgr->get_manager_info(mgr, &info); + + info.cpr_coefs = coefs; + + r = mgr->set_manager_info(mgr, &info); + if (r) + return r; + + r = mgr->apply(mgr); + if (r) + return r; + + return size; +} + struct manager_attribute { struct attribute attr; ssize_t (*show)(struct omap_overlay_manager *, char *); @@ -300,6 +409,12 @@ static MANAGER_ATTR(trans_key_enabled, S_IRUGO|S_IWUSR, static MANAGER_ATTR(alpha_blending_enabled, S_IRUGO|S_IWUSR, manager_alpha_blending_enabled_show, manager_alpha_blending_enabled_store); +static MANAGER_ATTR(cpr_enable, S_IRUGO|S_IWUSR, + manager_cpr_enable_show, + manager_cpr_enable_store); +static MANAGER_ATTR(cpr_coef, S_IRUGO|S_IWUSR, + manager_cpr_coef_show, + manager_cpr_coef_store); static struct attribute *manager_sysfs_attrs[] = { @@ -310,6 +425,8 @@ static struct attribute *manager_sysfs_attrs[] = { &manager_attr_trans_key_value.attr, &manager_attr_trans_key_enabled.attr, &manager_attr_alpha_blending_enabled.attr, + &manager_attr_cpr_enable.attr, + &manager_attr_cpr_coef.attr, NULL }; @@ -353,6 +470,20 @@ static struct kobj_type manager_ktype = { .default_attrs = manager_sysfs_attrs, }; +struct callback_states { + /* + * Keep track of callbacks at the last 3 levels of pipeline: + * cache, shadow registers and in DISPC registers. + * + * Note: We zero the function pointer when moving from one level to + * another to avoid checking for dirty and shadow_dirty fields that + * are not common between overlay and manager cache structures. + */ + struct omapdss_ovl_cb cache, shadow, dispc; + bool dispc_displayed; + bool shadow_enabled; +}; + /* * We have 4 levels of cache for the dispc settings. First two are in SW and * the latter two in HW. @@ -409,15 +540,21 @@ struct overlay_cache_data { u8 global_alpha; u8 pre_mult_alpha; + struct callback_states cb; /* callback data for the last 3 states */ + int dispc_channel; /* overlay's channel in DISPC */ + enum omap_channel channel; bool replication; bool ilace; + u16 min_x_decim, max_x_decim, min_y_decim, max_y_decim; enum omap_burst_size burst_size; u32 fifo_low; u32 fifo_high; bool manual_update; + enum omap_overlay_zorder zorder; + struct omap_dss_cconv_coefs cconv; }; struct manager_cache_data { @@ -447,6 +584,12 @@ struct manager_cache_data { /* enlarge the update area if the update area contains scaled * overlays */ bool enlarge_update_area; + + struct callback_states cb; /* callback data for the last 3 states */ + + bool cpr_enable; + struct omap_dss_cpr_coefs cpr_coefs; + bool skip_init; }; static struct { @@ -455,9 +598,43 @@ static struct { struct manager_cache_data manager_cache[MAX_DSS_MANAGERS]; bool irq_enabled; + u32 comp_irq_enabled; } dss_cache; +/* propagating callback info between states */ +static inline void +dss_ovl_configure_cb(struct callback_states *st, int i, bool enabled) +{ + /* complete info in shadow */ + dss_ovl_cb(&st->shadow, i, DSS_COMPLETION_ECLIPSED_SHADOW); + + /* propagate cache to shadow */ + st->shadow = st->cache; + st->shadow_enabled = enabled; + st->cache.fn = NULL; /* info traveled to shadow */ +} +static inline void +dss_ovl_program_cb(struct callback_states *st, int i) +{ + /* mark previous programming as completed */ + dss_ovl_cb(&st->dispc, i, st->dispc_displayed ? + DSS_COMPLETION_RELEASED : DSS_COMPLETION_TORN); + + /* mark shadow info as programmed, not yet displayed */ + dss_ovl_cb(&st->shadow, i, DSS_COMPLETION_PROGRAMMED); + + /* if overlay/manager is not enabled, we are done now */ + if (!st->shadow_enabled) { + dss_ovl_cb(&st->shadow, i, DSS_COMPLETION_RELEASED); + st->shadow.fn = NULL; + } + + /* propagate shadow to dispc */ + st->dispc = st->shadow; + st->shadow.fn = NULL; + st->dispc_displayed = false; +} static int omap_dss_set_device(struct omap_overlay_manager *mgr, struct omap_dss_device *dssdev) @@ -492,6 +669,12 @@ static int omap_dss_set_device(struct omap_overlay_manager *mgr, mgr->device = dssdev; mgr->device_changed = true; + if (dssdev->type == OMAP_DISPLAY_TYPE_DSI && + !(dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE)) + omap_dispc_set_irq_type(mgr->id, OMAP_DISPC_IRQ_TYPE_VSYNC); + else + omap_dispc_set_irq_type(mgr->id, OMAP_DISPC_IRQ_TYPE_FRAMEDONE); + return 0; } @@ -513,6 +696,7 @@ static int dss_mgr_wait_for_vsync(struct omap_overlay_manager *mgr) { unsigned long timeout = msecs_to_jiffies(500); u32 irq; + int r; if (mgr->device->type == OMAP_DISPLAY_TYPE_VENC) { irq = DISPC_IRQ_EVSYNC_ODD; @@ -524,7 +708,11 @@ static int dss_mgr_wait_for_vsync(struct omap_overlay_manager *mgr) else irq = DISPC_IRQ_VSYNC2; } - return omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + if (!r) + mgr->device->first_vsync = true; + + return r; } static int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr) @@ -541,7 +729,8 @@ static int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr) if (dssdev->type == OMAP_DISPLAY_TYPE_VENC || dssdev->type == OMAP_DISPLAY_TYPE_HDMI) { - irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN; + irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN + | DISPC_IRQ_FRAMEDONETV; } else { if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { enum omap_dss_update_mode mode; @@ -588,6 +777,8 @@ static int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr) } r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + if (!r) + mgr->device->first_vsync = true; if (r == -ERESTARTSYS) break; @@ -619,7 +810,8 @@ int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl) if (dssdev->type == OMAP_DISPLAY_TYPE_VENC || dssdev->type == OMAP_DISPLAY_TYPE_HDMI) { - irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN; + irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN + | DISPC_IRQ_FRAMEDONETV; } else { if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { enum omap_dss_update_mode mode; @@ -737,6 +929,8 @@ static int configure_overlay(enum omap_plane plane) u16 x, y, w, h; u32 paddr; int r; + u16 x_decim, y_decim; + bool five_taps; u16 orig_w, orig_h, orig_outw, orig_outh; DSSDBGF("%d", plane); @@ -779,11 +973,17 @@ static int configure_overlay(enum omap_plane plane) case OMAP_DSS_COLOR_NV12: bpp = 8; break; + + case OMAP_DSS_COLOR_CLUT1: + case OMAP_DSS_COLOR_CLUT2: + case OMAP_DSS_COLOR_CLUT4: + case OMAP_DSS_COLOR_CLUT8: case OMAP_DSS_COLOR_RGB16: case OMAP_DSS_COLOR_ARGB16: case OMAP_DSS_COLOR_YUV2: case OMAP_DSS_COLOR_UYVY: case OMAP_DSS_COLOR_RGBA16: + case OMAP_DSS_COLOR_RGB12U: case OMAP_DSS_COLOR_RGBX16: case OMAP_DSS_COLOR_ARGB16_1555: case OMAP_DSS_COLOR_XRGB16_1555: @@ -849,14 +1049,20 @@ static int configure_overlay(enum omap_plane plane) } } - r = dispc_setup_plane(plane, + r = dispc_scaling_decision(w, h, outw, outh, + plane, c->color_mode, c->channel, + c->rotation, c->rotation_type, + c->min_x_decim, c->max_x_decim, + c->min_y_decim, c->max_y_decim, + &x_decim, &y_decim, &five_taps); + r = r ? : dispc_setup_plane(plane, paddr, c->screen_width, x, y, w, h, outw, outh, c->color_mode, - c->ilace, + c->ilace, x_decim, y_decim, five_taps, c->rotation_type, c->rotation, c->mirror, @@ -875,7 +1081,12 @@ static int configure_overlay(enum omap_plane plane) dispc_enable_replication(plane, c->replication); dispc_set_burst_size(plane, c->burst_size); - dispc_setup_plane_fifo(plane, c->fifo_low, c->fifo_high); + dispc_set_zorder(plane, c->zorder); + dispc_enable_zorder(plane, 1); + if (!cpu_is_omap44xx()) + dispc_setup_plane_fifo(plane, c->fifo_low, c->fifo_high); + if (plane != OMAP_DSS_GFX) + _dispc_setup_color_conv_coef(plane, &c->cconv); dispc_enable_plane(plane, 1); @@ -893,7 +1104,19 @@ static void configure_manager(enum omap_channel channel) dispc_set_default_color(channel, c->default_color); dispc_set_trans_key(channel, c->trans_key_type, c->trans_key); dispc_enable_trans_key(channel, c->trans_enabled); - dispc_enable_alpha_blending(channel, c->alpha_enabled); + + /* if we have OMAP3 alpha compatibility, alpha blending is always on */ + if (dss_has_feature(FEAT_ALPHA_OMAP3_COMPAT)) { + /* and alpha_blending bit enables OMAP3 compatibility mode */ + dispc_enable_alpha_blending(channel, false); + c->alpha_enabled = true; + } else { + dispc_enable_alpha_blending(channel, c->alpha_enabled); + } + if (dss_has_feature(FEAT_CPR)) { + dispc_enable_cpr(channel, c->cpr_enable); + dispc_set_cpr_coef(channel, &c->cpr_coefs); + } } /* configure_dispc() tries to write values from cache to shadow registers. @@ -908,6 +1131,7 @@ static int configure_dispc(void) const int num_mgrs = dss_feat_get_num_mgrs(); int i; int r; + int used_ovls, j; bool mgr_busy[MAX_DSS_MANAGERS]; bool mgr_go[MAX_DSS_MANAGERS]; bool busy; @@ -940,6 +1164,8 @@ static int configure_dispc(void) if (r) DSSERR("configure_overlay %d failed\n", i); + dss_ovl_configure_cb(&oc->cb, i, oc->enabled); + oc->dirty = false; oc->shadow_dirty = true; mgr_go[oc->channel] = true; @@ -960,7 +1186,16 @@ static int configure_dispc(void) continue; } + for (j = used_ovls = 0; j < num_ovls; j++) { + oc = &dss_cache.overlay_cache[j]; + if (oc->channel == i && oc->enabled) + used_ovls++; + } + configure_manager(i); + + dss_ovl_configure_cb(&mc->cb, i, used_ovls); + mc->dirty = false; mc->shadow_dirty = true; mgr_go[i] = true; @@ -976,8 +1211,12 @@ static int configure_dispc(void) /* We don't need GO with manual update display. LCD iface will * always be turned off after frame, and new settings will be * taken in to use at next update */ - if (!mc->manual_upd_display) - dispc_go(i); + if (!mc->manual_upd_display){ + if(mc->skip_init) + mc->skip_init = false; + else + dispc_go(i); + } } if (busy) @@ -1139,6 +1378,89 @@ void dss_setup_partial_planes(struct omap_dss_device *dssdev, *hi = h; } +static void schedule_completion_irq(void); + +static void dss_completion_irq_handler(void *data, u32 mask) +{ + struct manager_cache_data *mc; + struct overlay_cache_data *oc; + const int num_ovls = ARRAY_SIZE(dss_cache.overlay_cache); + const int num_mgrs = MAX_DSS_MANAGERS; + const u32 masks[] = { + DISPC_IRQ_FRAMEDONE | DISPC_IRQ_VSYNC, + DISPC_IRQ_FRAMEDONE2 | DISPC_IRQ_VSYNC2, + DISPC_IRQ_FRAMEDONETV | DISPC_IRQ_EVSYNC_EVEN | + DISPC_IRQ_EVSYNC_ODD + }; + int i; + + spin_lock(&dss_cache.lock); + + for (i = 0; i < num_mgrs; i++) { + mc = &dss_cache.manager_cache[i]; + if (mask & masks[i]) { + if (mgrs[i] && mgrs[i]->device) + mgrs[i]->device->first_vsync = true; + dss_ovl_cb(&mc->cb.dispc, i, DSS_COMPLETION_DISPLAYED); + mc->cb.dispc_displayed = true; + } + } + + /* notify all overlays on that manager */ + for (i = 0; i < num_ovls; i++) { + oc = &dss_cache.overlay_cache[i]; + if (mask & masks[oc->channel]) { + dss_ovl_cb(&oc->cb.dispc, i, DSS_COMPLETION_DISPLAYED); + oc->cb.dispc_displayed = true; + } + } + + schedule_completion_irq(); + + spin_unlock(&dss_cache.lock); +} + +static void schedule_completion_irq(void) +{ + struct manager_cache_data *mc; + struct overlay_cache_data *oc; + const int num_ovls = ARRAY_SIZE(dss_cache.overlay_cache); + const int num_mgrs = MAX_DSS_MANAGERS; + const u32 masks[] = { + DISPC_IRQ_FRAMEDONE | DISPC_IRQ_VSYNC, + DISPC_IRQ_FRAMEDONE2 | DISPC_IRQ_VSYNC2, + DISPC_IRQ_FRAMEDONETV | DISPC_IRQ_EVSYNC_EVEN | + DISPC_IRQ_EVSYNC_ODD + }; + u32 mask = 0; + int i; + + for (i = 0; i < num_mgrs; i++) { + mc = &dss_cache.manager_cache[i]; + if (mc->cb.dispc.fn && + (mc->cb.dispc.mask & DSS_COMPLETION_DISPLAYED)) + mask |= masks[i]; + } + + /* notify all overlays on that manager */ + for (i = 0; i < num_ovls; i++) { + oc = &dss_cache.overlay_cache[i]; + if (oc->cb.dispc.fn && oc->enabled && + (oc->cb.dispc.mask & DSS_COMPLETION_DISPLAYED)) + mask |= masks[oc->channel]; + } + + if (mask != dss_cache.comp_irq_enabled) { + if (dss_cache.comp_irq_enabled) + omap_dispc_unregister_isr(dss_completion_irq_handler, + NULL, dss_cache.comp_irq_enabled); + if (mask) + omap_dispc_register_isr(dss_completion_irq_handler, + NULL, mask); + dss_cache.comp_irq_enabled = mask; + } +} + void dss_start_update(struct omap_dss_device *dssdev) { struct manager_cache_data *mc; @@ -1147,14 +1469,20 @@ void dss_start_update(struct omap_dss_device *dssdev) const int num_mgrs = dss_feat_get_num_mgrs(); struct omap_overlay_manager *mgr; int i; + unsigned long flags; mgr = dssdev->manager; + spin_lock_irqsave(&dss_cache.lock, flags); for (i = 0; i < num_ovls; ++i) { oc = &dss_cache.overlay_cache[i]; if (oc->channel != mgr->id) continue; + if (oc->shadow_dirty) { + dss_ovl_program_cb(&oc->cb, i); + oc->dispc_channel = oc->channel; + } oc->shadow_dirty = false; } @@ -1163,9 +1491,14 @@ void dss_start_update(struct omap_dss_device *dssdev) if (mgr->id != i) continue; + if (mc->shadow_dirty) + dss_ovl_program_cb(&mc->cb, i); mc->shadow_dirty = false; } + schedule_completion_irq(); + spin_unlock_irqrestore(&dss_cache.lock, flags); + dssdev->manager->enable(dssdev->manager); } @@ -1179,30 +1512,52 @@ static void dss_apply_irq_handler(void *data, u32 mask) bool mgr_busy[MAX_DSS_MANAGERS]; u32 irq_mask; + spin_lock(&dss_cache.lock); + for (i = 0; i < num_mgrs; i++) mgr_busy[i] = dispc_go_busy(i); - spin_lock(&dss_cache.lock); - for (i = 0; i < num_ovls; ++i) { oc = &dss_cache.overlay_cache[i]; - if (!mgr_busy[oc->channel]) + if (!mgr_busy[oc->channel] && oc->shadow_dirty) { + dss_ovl_program_cb(&oc->cb, i); + oc->dispc_channel = oc->channel; oc->shadow_dirty = false; + } } for (i = 0; i < num_mgrs; ++i) { mc = &dss_cache.manager_cache[i]; - if (!mgr_busy[i]) + if (!mgr_busy[i] && mc->shadow_dirty) { + if (mgrs[i] && mgrs[i]->device) + mgrs[i]->device->first_vsync = true; + dss_ovl_program_cb(&mc->cb, i); mc->shadow_dirty = false; + } } + schedule_completion_irq(); + r = configure_dispc(); if (r == 1) goto end; + /* + * FIXME Sometimes when handling an interrupt for a manager, the + * manager is still busy at the beginning of the interrupt handler. + * Later it becomes idle, so we unregister the interrupt. This + * leaves the shadow_dirty flag in an incorrect true state, and also + * misses the 'programmed' callback. + * + * For now, we do not unregister the interrupt if any manager + * was busy at the first read of the GO bits. A better fix would be + * to keep the first read busy state in the cache, so we do not operate + * on instantaneous reads of the GO bit. + */ + /* re-read busy flags */ for (i = 0; i < num_mgrs; i++) - mgr_busy[i] = dispc_go_busy(i); + mgr_busy[i] |= dispc_go_busy(i); /* keep running as long as there are busy managers, so that * we can collect overlay-applied information */ @@ -1223,10 +1578,133 @@ end: spin_unlock(&dss_cache.lock); } +static int omap_dss_mgr_blank(struct omap_overlay_manager *mgr, + bool wait_for_go) +{ + struct overlay_cache_data *oc; + struct manager_cache_data *mc; + unsigned long flags; + int r, r_get, i; + + DSSDBG("omap_dss_mgr_blank(%s,wait=%d)\n", mgr->name, wait_for_go); + + r_get = r = dispc_runtime_get(); + /* still clear cache even if failed to get clocks, just don't config */ + + spin_lock_irqsave(&dss_cache.lock, flags); + + /* disable overlays in overlay info structs and in cache */ + for (i = 0; i < omap_dss_get_num_overlays(); i++) { + struct omap_overlay_info oi = { .enabled = false }; + struct omap_overlay *ovl; + + ovl = omap_dss_get_overlay(i); + + if (!(ovl->caps & OMAP_DSS_OVL_CAP_DISPC) || + ovl->manager != mgr) + continue; + + oc = &dss_cache.overlay_cache[ovl->id]; + + /* complete unconfigured info in cache */ + dss_ovl_cb(&oc->cb.cache, i, DSS_COMPLETION_ECLIPSED_CACHE); + oc->cb.cache.fn = NULL; + + ovl->info = oi; + ovl->info_dirty = false; + oc->dirty = true; + oc->enabled = false; + } + + /* dirty manager */ + mc = &dss_cache.manager_cache[mgr->id]; + dss_ovl_cb(&mc->cb.cache, i, DSS_COMPLETION_ECLIPSED_CACHE); + mc->cb.cache.fn = NULL; + mgr->info.cb.fn = NULL; + mc->dirty = true; + mgr->info_dirty = false; + + /* + * TRICKY: Enable apply irq even if not waiting for vsync, so that + * DISPC programming takes place in case GO bit was on. + */ + if (!dss_cache.irq_enabled) { + u32 mask; + + mask = DISPC_IRQ_VSYNC | DISPC_IRQ_EVSYNC_ODD | + DISPC_IRQ_EVSYNC_EVEN; + if (dss_has_feature(FEAT_MGR_LCD2)) + mask |= DISPC_IRQ_VSYNC2; + + r = omap_dispc_register_isr(dss_apply_irq_handler, NULL, mask); + dss_cache.irq_enabled = true; + } + + if (!r_get) { + r = configure_dispc(); + if (r) + pr_info("mgr_blank while GO is set"); + } + + if (r_get || !wait_for_go) { + /* pretend that programming has happened */ + for (i = 0; i < omap_dss_get_num_overlays(); ++i) { + oc = &dss_cache.overlay_cache[i]; + if (oc->channel != mgr->id) + continue; + if (r && oc->dirty) + dss_ovl_configure_cb(&oc->cb, i, false); + if (oc->shadow_dirty) { + dss_ovl_program_cb(&oc->cb, i); + oc->dispc_channel = oc->channel; + oc->shadow_dirty = false; + } else { + pr_warn("ovl%d-shadow is not dirty\n", i); + } + } + + if (r && mc->dirty) + dss_ovl_configure_cb(&mc->cb, i, false); + if (mc->shadow_dirty) { + dss_ovl_program_cb(&mc->cb, i); + mc->shadow_dirty = false; + } else { + pr_warn("mgr%d-shadow is not dirty\n", mgr->id); + } + } + + spin_unlock_irqrestore(&dss_cache.lock, flags); + + if (wait_for_go) + mgr->wait_for_go(mgr); + + if (!r_get) + dispc_runtime_put(); + + return r; +} + +int omap_dss_manager_unregister_callback(struct omap_overlay_manager *mgr, + struct omapdss_ovl_cb *cb) +{ + unsigned long flags; + int r = 0; + spin_lock_irqsave(&dss_cache.lock, flags); + if (mgr->info_dirty && + mgr->info.cb.fn == cb->fn && + mgr->info.cb.data == cb->data) + mgr->info.cb.fn = NULL; + else + r = -EPERM; + spin_unlock_irqrestore(&dss_cache.lock, flags); + return r; +} + static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) { struct overlay_cache_data *oc; struct manager_cache_data *mc; + struct omap_dss_device *dssdev; int i; struct omap_overlay *ovl; int num_planes_enabled = 0; @@ -1236,8 +1714,19 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) DSSDBG("omap_dss_mgr_apply(%s)\n", mgr->name); + r = dispc_runtime_get(); + if (r) + return r; + spin_lock_irqsave(&dss_cache.lock, flags); + if (!mgr->device || mgr->device->state != OMAP_DSS_DISPLAY_ACTIVE) { + pr_info_ratelimited("cannot apply mgr(%s) on inactive device\n", + mgr->name); + r = -ENODEV; + goto done; + } + /* Configure overlays */ for (i = 0; i < omap_dss_get_num_overlays(); ++i) { struct omap_dss_device *dssdev; @@ -1247,34 +1736,39 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) if (!(ovl->caps & OMAP_DSS_OVL_CAP_DISPC)) continue; - oc = &dss_cache.overlay_cache[ovl->id]; - - if (!overlay_enabled(ovl)) { - if (oc->enabled) { - oc->enabled = false; - oc->dirty = true; - } + if (ovl->manager != mgr) continue; - } - if (!ovl->info_dirty) { + oc = &dss_cache.overlay_cache[ovl->id]; + dssdev = mgr->device; + + if (!overlay_enabled(ovl) || !dssdev) { + ovl->info.enabled = false; + } else if (!ovl->info_dirty) { if (oc->enabled) ++num_planes_enabled; continue; + } else if (dss_check_overlay(ovl, dssdev)) { + ovl->info.enabled = false; } - dssdev = ovl->manager->device; - - if (dss_check_overlay(ovl, dssdev)) { - if (oc->enabled) { - oc->enabled = false; - oc->dirty = true; - } - continue; - } + /* complete unconfigured info in cache */ + dss_ovl_cb(&oc->cb.cache, i, +#if 0 + (oc->cb.cache.fn == ovl->info.cb.fn && + oc->cb.cache.data == ovl->info.cb.data) ? + DSS_COMPLETION_CHANGED_CACHE : +#endif + DSS_COMPLETION_ECLIPSED_CACHE); + oc->cb.cache = ovl->info.cb; + ovl->info.cb.fn = NULL; ovl->info_dirty = false; - oc->dirty = true; + if (ovl->info.enabled || oc->enabled) + oc->dirty = true; + oc->enabled = ovl->info.enabled; + if (!oc->enabled) + continue; oc->paddr = ovl->info.paddr; oc->vaddr = ovl->info.vaddr; @@ -1292,15 +1786,19 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) oc->out_height = ovl->info.out_height; oc->global_alpha = ovl->info.global_alpha; oc->pre_mult_alpha = ovl->info.pre_mult_alpha; + oc->zorder = ovl->info.zorder; + oc->min_x_decim = ovl->info.min_x_decim; + oc->max_x_decim = ovl->info.max_x_decim; + oc->min_y_decim = ovl->info.min_y_decim; + oc->max_y_decim = ovl->info.max_y_decim; + oc->cconv = ovl->info.cconv; oc->replication = dss_use_replication(dssdev, ovl->info.color_mode); oc->ilace = dssdev->type == OMAP_DISPLAY_TYPE_VENC; - oc->channel = ovl->manager->id; - - oc->enabled = true; + oc->channel = mgr->id; oc->manual_update = dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE && @@ -1310,45 +1808,58 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) ++num_planes_enabled; } - /* Configure managers */ - list_for_each_entry(mgr, &manager_list, list) { - struct omap_dss_device *dssdev; + /* configure manager */ + if (!(mgr->caps & OMAP_DSS_OVL_MGR_CAP_DISPC)) + goto skip_mgr; - if (!(mgr->caps & OMAP_DSS_OVL_MGR_CAP_DISPC)) - continue; + mc = &dss_cache.manager_cache[mgr->id]; - mc = &dss_cache.manager_cache[mgr->id]; + if (mgr->device_changed) { + mgr->device_changed = false; + mgr->info_dirty = true; + } - if (mgr->device_changed) { - mgr->device_changed = false; - mgr->info_dirty = true; - } + if (!mgr->info_dirty) + goto skip_mgr; - if (!mgr->info_dirty) - continue; + if (!mgr->device) + goto skip_mgr; - if (!mgr->device) - continue; + dssdev = mgr->device; - dssdev = mgr->device; + /* complete unconfigured info in cache */ + dss_ovl_cb(&mc->cb.cache, mgr->id, +#if 0 + (mc->cb.cache.fn == mgr->info.cb.fn && + mc->cb.cache.data == mgr->info.cb.data) ? + DSS_COMPLETION_CHANGED_CACHE : +#endif + DSS_COMPLETION_ECLIPSED_CACHE); + mc->cb.cache = mgr->info.cb; + mgr->info.cb.fn = NULL; - mgr->info_dirty = false; - mc->dirty = true; + mgr->info_dirty = false; + mc->dirty = true; - mc->default_color = mgr->info.default_color; - mc->trans_key_type = mgr->info.trans_key_type; - mc->trans_key = mgr->info.trans_key; - mc->trans_enabled = mgr->info.trans_enabled; - mc->alpha_enabled = mgr->info.alpha_enabled; + mc->default_color = mgr->info.default_color; + mc->trans_key_type = mgr->info.trans_key_type; + mc->trans_key = mgr->info.trans_key; + mc->trans_enabled = mgr->info.trans_enabled; + mc->alpha_enabled = mgr->info.alpha_enabled; + mc->cpr_coefs = mgr->info.cpr_coefs; + mc->cpr_enable = mgr->info.cpr_enable; - mc->manual_upd_display = - dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE; + mc->manual_upd_display = + dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE; - mc->manual_update = - dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE && - dssdev->driver->get_update_mode(dssdev) != - OMAP_DSS_UPDATE_AUTO; - } + mc->manual_update = + dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE && + dssdev->driver->get_update_mode(dssdev) != + OMAP_DSS_UPDATE_AUTO; + + mc->skip_init = dssdev->skip_init; + +skip_mgr: /* XXX TODO: Try to get fifomerge working. The problem is that it * affects both managers, not individually but at the same time. This @@ -1381,6 +1892,8 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) continue; dssdev = ovl->manager->device; + if (!dssdev) + continue; size = dispc_get_plane_fifo_size(ovl->id); if (use_fifomerge) @@ -1409,7 +1922,6 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) } r = 0; - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); if (!dss_cache.irq_enabled) { u32 mask; @@ -1422,41 +1934,139 @@ static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) dss_cache.irq_enabled = true; } configure_dispc(); - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); +done: spin_unlock_irqrestore(&dss_cache.lock, flags); + dispc_runtime_put(); + return r; } -static int dss_check_manager(struct omap_overlay_manager *mgr) +#ifdef CONFIG_DEBUG_FS +static void seq_print_cb(struct seq_file *s, struct omapdss_ovl_cb *cb) { - /* OMAP supports only graphics source transparency color key and alpha - * blending simultaneously. See TRM 15.4.2.4.2.2 Alpha Mode */ + if (!cb->fn) { + seq_printf(s, "(none)\n"); + return; + } + + seq_printf(s, "mask=%c%c%c%c [%p] %pf\n", + (cb->mask & DSS_COMPLETION_CHANGED) ? 'C' : '-', + (cb->mask & DSS_COMPLETION_PROGRAMMED) ? 'P' : '-', + (cb->mask & DSS_COMPLETION_DISPLAYED) ? 'D' : '-', + (cb->mask & DSS_COMPLETION_RELEASED) ? 'R' : '-', + cb->data, + cb->fn); +} +#endif + +static void seq_print_cbs(struct omap_overlay_manager *mgr, struct seq_file *s) +{ +#ifdef CONFIG_DEBUG_FS + struct manager_cache_data *mc; + unsigned long flags; + + spin_lock_irqsave(&dss_cache.lock, flags); + + mc = &dss_cache.manager_cache[mgr->id]; + + seq_printf(s, " DISPC pipeline:\n\n" + " info:%13s ", mgr->info_dirty ? "DIRTY" : "clean"); + seq_print_cb(s, &mgr->info.cb); + seq_printf(s, " cache:%12s ", mc->dirty ? "DIRTY" : "clean"); + seq_print_cb(s, &mc->cb.cache); + seq_printf(s, " shadow: %s %s ", + mc->cb.shadow_enabled ? "ACT" : "off", + mc->shadow_dirty ? "DIRTY" : "clean"); + seq_print_cb(s, &mc->cb.shadow); + seq_printf(s, " dispc:%12s ", + mc->cb.dispc_displayed ? "DISPLAYED" : ""); + seq_print_cb(s, &mc->cb.dispc); + seq_printf(s, "\n"); + + spin_unlock_irqrestore(&dss_cache.lock, flags); +#endif +} - if (mgr->info.alpha_enabled && mgr->info.trans_enabled && +static int dss_check_manager(struct omap_overlay_manager *mgr) +{ + /* if we have OMAP3 alpha compatibility, alpha blending is always on */ + if (dss_has_feature(FEAT_ALPHA_OMAP3_COMPAT)) { + if (!mgr->info.alpha_enabled) + return -EINVAL; + } else { + /* + * OMAP3- supports only graphics destination transparency + * color key and alpha blending simultaneously. + * See TRM 15.4.2.4.2.2 Alpha Mode. + */ + if (mgr->info.alpha_enabled && mgr->info.trans_enabled && mgr->info.trans_key_type != OMAP_DSS_COLOR_KEY_GFX_DST) - return -EINVAL; + return -EINVAL; + } + + return 0; +} + +int omap_dss_ovl_set_info(struct omap_overlay *ovl, + struct omap_overlay_info *info) +{ + int r; + struct omap_overlay_info old_info; + unsigned long flags; + + spin_lock_irqsave(&dss_cache.lock, flags); + old_info = ovl->info; + ovl->info = *info; + + if (ovl->manager) { + r = dss_check_overlay(ovl, ovl->manager->device); + if (r) { + ovl->info = old_info; + spin_unlock_irqrestore(&dss_cache.lock, flags); + return r; + } + } + + /* complete previous settings */ + if (ovl->info_dirty) + dss_ovl_cb(&old_info.cb, ovl->id, + (info->cb.fn == old_info.cb.fn && + info->cb.data == old_info.cb.data) ? + DSS_COMPLETION_CHANGED_SET : + DSS_COMPLETION_ECLIPSED_SET); + + ovl->info_dirty = true; + spin_unlock_irqrestore(&dss_cache.lock, flags); return 0; } + static int omap_dss_mgr_set_info(struct omap_overlay_manager *mgr, struct omap_overlay_manager_info *info) { int r; struct omap_overlay_manager_info old_info; + unsigned long flags; + spin_lock_irqsave(&dss_cache.lock, flags); old_info = mgr->info; mgr->info = *info; r = dss_check_manager(mgr); if (r) { mgr->info = old_info; + spin_unlock_irqrestore(&dss_cache.lock, flags); return r; } + if (mgr->info_dirty) + dss_ovl_cb(&old_info.cb, mgr->id, DSS_COMPLETION_ECLIPSED_SET); + mgr->info_dirty = true; + spin_unlock_irqrestore(&dss_cache.lock, flags); return 0; } @@ -1469,13 +2079,13 @@ static void omap_dss_mgr_get_info(struct omap_overlay_manager *mgr, static int dss_mgr_enable(struct omap_overlay_manager *mgr) { - dispc_enable_channel(mgr->id, 1); + dispc_enable_channel(mgr->id, mgr->device->type, 1); return 0; } static int dss_mgr_disable(struct omap_overlay_manager *mgr) { - dispc_enable_channel(mgr->id, 0); + dispc_enable_channel(mgr->id, mgr->device->type, 0); return 0; } @@ -1483,6 +2093,8 @@ static void omap_dss_add_overlay_manager(struct omap_overlay_manager *manager) { ++num_managers; list_add_tail(&manager->list, &manager_list); + if (manager->id < ARRAY_SIZE(mgrs)) + mgrs[manager->id] = manager; } int dss_init_overlay_managers(struct platform_device *pdev) @@ -1501,6 +2113,10 @@ int dss_init_overlay_managers(struct platform_device *pdev) BUG_ON(mgr == NULL); + /* alpha blending always on with OMAP3 alpha compatibility */ + if (dss_has_feature(FEAT_ALPHA_OMAP3_COMPAT)) + mgr->info.alpha_enabled = true; + switch (i) { case 0: mgr->name = "lcd"; @@ -1523,6 +2139,8 @@ int dss_init_overlay_managers(struct platform_device *pdev) mgr->get_manager_info = &omap_dss_mgr_get_info; mgr->wait_for_go = &dss_mgr_wait_for_go; mgr->wait_for_vsync = &dss_mgr_wait_for_vsync; + mgr->blank = &omap_dss_mgr_blank; + mgr->dump_cb = &seq_print_cbs; mgr->enable = &dss_mgr_enable; mgr->disable = &dss_mgr_disable; diff --git a/drivers/video/omap2/dss/overlay.c b/drivers/video/omap2/dss/overlay.c index 0f08025..e9d31c2 100644 --- a/drivers/video/omap2/dss/overlay.c +++ b/drivers/video/omap2/dss/overlay.c @@ -84,32 +84,42 @@ static ssize_t overlay_manager_store(struct omap_overlay *ovl, const char *buf, old_mgr = ovl->manager; + r = dispc_runtime_get(); + if (r) + return r; + /* detach old manager */ if (old_mgr) { r = ovl->unset_manager(ovl); if (r) { DSSERR("detach failed\n"); - return r; + goto err; } r = old_mgr->apply(old_mgr); if (r) - return r; + goto err; } if (mgr) { r = ovl->set_manager(ovl, mgr); if (r) { DSSERR("Failed to attach overlay\n"); - return r; + goto err; } r = mgr->apply(mgr); if (r) - return r; + goto err; } + dispc_runtime_put(); + return size; + +err: + dispc_runtime_put(); + return r; } static ssize_t overlay_input_size_show(struct omap_overlay *ovl, char *buf) @@ -238,6 +248,9 @@ static ssize_t overlay_global_alpha_store(struct omap_overlay *ovl, u8 alpha; struct omap_overlay_info info; + if (!dss_has_feature(FEAT_GLOBAL_ALPHA)) + return -ENODEV; + r = kstrtou8(buf, 0, &alpha); if (r) return r; @@ -308,6 +321,118 @@ static ssize_t overlay_pre_mult_alpha_store(struct omap_overlay *ovl, return size; } +static ssize_t overlay_zorder_show(struct omap_overlay *ovl, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", + ovl->info.zorder); +} + +static ssize_t overlay_zorder_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + struct omap_overlay_info info; + + if (!dss_has_feature(FEAT_OVL_ZORDER)) + return size; + + ovl->get_overlay_info(ovl, &info); + + info.zorder = simple_strtoul(buf, NULL, 10); + + r = ovl->set_overlay_info(ovl, &info); + if (r) + return r; + + if (ovl->manager) { + r = ovl->manager->apply(ovl->manager); + if (r) + return r; + } + + return size; +} + +static ssize_t overlay_decim_show(u16 min, u16 max, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d..%d\n", min, max); +} + +static ssize_t overlay_x_decim_show(struct omap_overlay *ovl, char *buf) +{ + return overlay_decim_show(ovl->info.min_x_decim, ovl->info.max_x_decim, + buf); +} + +static ssize_t overlay_y_decim_show(struct omap_overlay *ovl, char *buf) +{ + return overlay_decim_show(ovl->info.min_y_decim, ovl->info.max_y_decim, + buf); +} + +static ssize_t overlay_decim_store(u16 *min, u16 *max, + const char *buf, size_t size) +{ + char *last; + + *min = *max = simple_strtoul(buf, &last, 10); + if (last < buf + size && *last == '.') { + /* check for .. separator */ + if (last + 2 >= buf + size || last[1] != '.') + return -EINVAL; + + *max = simple_strtoul(last + 2, &last, 10); + + /* fix order */ + if (*max < *min) + swap(*min, *max); + } + + /* decimation must be positive */ + if (*min == 0) + return -EINVAL; + + return 0; +} + +static ssize_t overlay_x_decim_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + r = overlay_decim_store(&info.min_x_decim, &info.max_x_decim, + buf, size); + + r = r ? : ovl->set_overlay_info(ovl, &info); + + if (!r && ovl->manager) + r = ovl->manager->apply(ovl->manager); + + return r ? : size; +} + +static ssize_t overlay_y_decim_store(struct omap_overlay *ovl, + const char *buf, size_t size) +{ + int r; + struct omap_overlay_info info; + + ovl->get_overlay_info(ovl, &info); + + r = overlay_decim_store(&info.min_y_decim, &info.max_y_decim, + buf, size); + + r = r ? : ovl->set_overlay_info(ovl, &info); + + if (!r && ovl->manager) + r = ovl->manager->apply(ovl->manager); + + return r ? : size; +} + struct overlay_attribute { struct attribute attr; ssize_t (*show)(struct omap_overlay *, char *); @@ -334,6 +459,12 @@ static OVERLAY_ATTR(global_alpha, S_IRUGO|S_IWUSR, static OVERLAY_ATTR(pre_mult_alpha, S_IRUGO|S_IWUSR, overlay_pre_mult_alpha_show, overlay_pre_mult_alpha_store); +static OVERLAY_ATTR(x_decim, S_IRUGO|S_IWUSR, + overlay_x_decim_show, overlay_x_decim_store); +static OVERLAY_ATTR(y_decim, S_IRUGO|S_IWUSR, + overlay_y_decim_show, overlay_y_decim_store); +static OVERLAY_ATTR(zorder, S_IRUGO|S_IWUSR, + overlay_zorder_show, overlay_zorder_store); static struct attribute *overlay_sysfs_attrs[] = { &overlay_attr_name.attr, @@ -345,6 +476,9 @@ static struct attribute *overlay_sysfs_attrs[] = { &overlay_attr_enabled.attr, &overlay_attr_global_alpha.attr, &overlay_attr_pre_mult_alpha.attr, + &overlay_attr_zorder.attr, + &overlay_attr_x_decim.attr, + &overlay_attr_y_decim.attr, NULL }; @@ -449,28 +583,12 @@ int dss_check_overlay(struct omap_overlay *ovl, struct omap_dss_device *dssdev) return -EINVAL; } - return 0; -} - -static int dss_ovl_set_overlay_info(struct omap_overlay *ovl, - struct omap_overlay_info *info) -{ - int r; - struct omap_overlay_info old_info; - - old_info = ovl->info; - ovl->info = *info; - - if (ovl->manager) { - r = dss_check_overlay(ovl, ovl->manager->device); - if (r) { - ovl->info = old_info; - return r; - } + if ((info->zorder < OMAP_DSS_OVL_ZORDER_0) || + (info->zorder > OMAP_DSS_OVL_ZORDER_3)) { + DSSERR("overlay doesn't support zorder %d\n", info->zorder); + return -EINVAL; } - ovl->info_dirty = true; - return 0; } @@ -504,7 +622,6 @@ static int omap_dss_set_manager(struct omap_overlay *ovl, ovl->manager = mgr; - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); /* XXX: When there is an overlay on a DSI manual update display, and * the overlay is first disabled, then moved to tv, and enabled, we * seem to get SYNC_LOST_DIGIT error. @@ -518,7 +635,6 @@ static int omap_dss_set_manager(struct omap_overlay *ovl, * the overlay, but before moving the overlay to TV. */ dispc_set_channel_out(ovl->id, mgr->id); - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); return 0; } @@ -592,6 +708,9 @@ void dss_overlay_setup_l4_manager(struct omap_overlay_manager *mgr) void dss_init_overlays(struct platform_device *pdev) { int i, r; + const struct omap_dss_cconv_coefs ctbl_bt601_5 = { + 298, 409, 0, 298, -208, -100, 298, 0, 517, 0, + }; INIT_LIST_HEAD(&overlay_list); @@ -609,6 +728,7 @@ void dss_init_overlays(struct platform_device *pdev) ovl->id = OMAP_DSS_GFX; ovl->caps = OMAP_DSS_OVL_CAP_DISPC; ovl->info.global_alpha = 255; + ovl->info.zorder = OMAP_DSS_OVL_ZORDER_0; break; case 1: ovl->name = "vid1"; @@ -616,6 +736,9 @@ void dss_init_overlays(struct platform_device *pdev) ovl->caps = OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_DISPC; ovl->info.global_alpha = 255; + ovl->info.zorder = dss_has_feature(FEAT_OVL_ZORDER) ? + OMAP_DSS_OVL_ZORDER_3 : + OMAP_DSS_OVL_ZORDER_0; break; case 2: ovl->name = "vid2"; @@ -623,12 +746,31 @@ void dss_init_overlays(struct platform_device *pdev) ovl->caps = OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_DISPC; ovl->info.global_alpha = 255; + ovl->info.zorder = dss_has_feature(FEAT_OVL_ZORDER) ? + OMAP_DSS_OVL_ZORDER_2 : + OMAP_DSS_OVL_ZORDER_0; + break; + case 3: + ovl->name = "vid3"; + ovl->id = OMAP_DSS_VIDEO3; + ovl->caps = OMAP_DSS_OVL_CAP_SCALE | + OMAP_DSS_OVL_CAP_DISPC; + ovl->info.global_alpha = 255; + ovl->info.zorder = dss_has_feature(FEAT_OVL_ZORDER) ? + OMAP_DSS_OVL_ZORDER_1 : + OMAP_DSS_OVL_ZORDER_0; break; + } + ovl->info.min_x_decim = ovl->info.min_y_decim = 1; + ovl->info.max_x_decim = ovl->info.max_y_decim = + cpu_is_omap44xx() ? 16 : 1; + ovl->info.cconv = ctbl_bt601_5; + ovl->set_manager = &omap_dss_set_manager; ovl->unset_manager = &omap_dss_unset_manager; - ovl->set_overlay_info = &dss_ovl_set_overlay_info; + ovl->set_overlay_info = &omap_dss_ovl_set_info; ovl->get_overlay_info = &dss_ovl_get_overlay_info; ovl->wait_for_go = &dss_ovl_wait_for_go; @@ -718,16 +860,18 @@ void dss_recheck_connections(struct omap_dss_device *dssdev, bool force) } } - if (mgr) { + if (mgr && force) { + dispc_runtime_get(); + for (i = 0; i < dss_feat_get_num_ovls(); i++) { struct omap_overlay *ovl; ovl = omap_dss_get_overlay(i); - if (!ovl->manager || force) { - if (ovl->manager) - omap_dss_unset_manager(ovl); - omap_dss_set_manager(ovl, mgr); - } + if (ovl->manager) + omap_dss_unset_manager(ovl); + omap_dss_set_manager(ovl, mgr); } + + dispc_runtime_put(); } } diff --git a/drivers/video/omap2/dss/rfbi.c b/drivers/video/omap2/dss/rfbi.c index c06fbe0..3e2f5cd 100644 --- a/drivers/video/omap2/dss/rfbi.c +++ b/drivers/video/omap2/dss/rfbi.c @@ -33,6 +33,8 @@ #include <linux/hrtimer.h> #include <linux/seq_file.h> #include <linux/semaphore.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <video/omapdss.h> #include "dss.h" @@ -120,12 +122,46 @@ static inline u32 rfbi_read_reg(const struct rfbi_reg idx) return __raw_readl(rfbi.base + idx.idx); } -static void rfbi_enable_clocks(bool enable) +static int rfbi_runtime_get(void) { - if (enable) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); - else - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + int r; + + DSSDBG("rfbi_runtime_get\n"); + + r = dss_runtime_get(); + if (r) + goto err_get_dss; + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; + + r = pm_runtime_get_sync(&rfbi.pdev->dev); + WARN_ON(r); + if (r < 0) + goto err_runtime_get; + + return 0; + +err_runtime_get: + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: + return r; +} + +static void rfbi_runtime_put(void) +{ + int r; + + DSSDBG("rfbi_runtime_put\n"); + + r = pm_runtime_put_sync(&rfbi.pdev->dev); + WARN_ON(r); + + dispc_runtime_put(); + dss_runtime_put(); } void rfbi_bus_lock(void) @@ -805,7 +841,8 @@ void rfbi_dump_regs(struct seq_file *s) { #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, rfbi_read_reg(r)) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + if (rfbi_runtime_get()) + return; DUMPREG(RFBI_REVISION); DUMPREG(RFBI_SYSCONFIG); @@ -836,7 +873,7 @@ void rfbi_dump_regs(struct seq_file *s) DUMPREG(RFBI_VSYNC_WIDTH); DUMPREG(RFBI_HSYNC_WIDTH); - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + rfbi_runtime_put(); #undef DUMPREG } @@ -844,7 +881,9 @@ int omapdss_rfbi_display_enable(struct omap_dss_device *dssdev) { int r; - rfbi_enable_clocks(1); + r = rfbi_runtime_get(); + if (r) + return r; r = omap_dss_start_device(dssdev); if (r) { @@ -879,6 +918,7 @@ int omapdss_rfbi_display_enable(struct omap_dss_device *dssdev) err1: omap_dss_stop_device(dssdev); err0: + rfbi_runtime_put(); return r; } EXPORT_SYMBOL(omapdss_rfbi_display_enable); @@ -889,7 +929,7 @@ void omapdss_rfbi_display_disable(struct omap_dss_device *dssdev) DISPC_IRQ_FRAMEDONE); omap_dss_stop_device(dssdev); - rfbi_enable_clocks(0); + rfbi_runtime_put(); } EXPORT_SYMBOL(omapdss_rfbi_display_disable); @@ -904,8 +944,9 @@ int rfbi_init_display(struct omap_dss_device *dssdev) static int omap_rfbihw_probe(struct platform_device *pdev) { u32 rev; - u32 l; struct resource *rfbi_mem; + struct clk *clk; + int r; rfbi.pdev = pdev; @@ -914,36 +955,55 @@ static int omap_rfbihw_probe(struct platform_device *pdev) rfbi_mem = platform_get_resource(rfbi.pdev, IORESOURCE_MEM, 0); if (!rfbi_mem) { DSSERR("can't get IORESOURCE_MEM RFBI\n"); - return -EINVAL; + r = -EINVAL; + goto err_ioremap; } rfbi.base = ioremap(rfbi_mem->start, resource_size(rfbi_mem)); if (!rfbi.base) { DSSERR("can't ioremap RFBI\n"); - return -ENOMEM; + r = -ENOMEM; + goto err_ioremap; } - rfbi_enable_clocks(1); + pm_runtime_enable(&pdev->dev); + + r = rfbi_runtime_get(); + if (r) + goto err_get_rfbi; msleep(10); - rfbi.l4_khz = dss_clk_get_rate(DSS_CLK_ICK) / 1000; + clk = clk_get(&pdev->dev, "rfbi_iclk"); + if (IS_ERR(clk)) { + DSSERR("can't get rfbi_iclk\n"); + r = PTR_ERR(clk); + goto err_get_ick; + } + + rfbi.l4_khz = clk_get_rate(clk) / 1000; - /* Enable autoidle and smart-idle */ - l = rfbi_read_reg(RFBI_SYSCONFIG); - l |= (1 << 0) | (2 << 3); - rfbi_write_reg(RFBI_SYSCONFIG, l); + clk_put(clk); rev = rfbi_read_reg(RFBI_REVISION); dev_dbg(&pdev->dev, "OMAP RFBI rev %d.%d\n", FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); - rfbi_enable_clocks(0); + rfbi_runtime_put(); return 0; + +err_get_ick: + rfbi_runtime_put(); +err_get_rfbi: + pm_runtime_disable(&pdev->dev); + iounmap(rfbi.base); +err_ioremap: + return r; } static int omap_rfbihw_remove(struct platform_device *pdev) { + pm_runtime_disable(&pdev->dev); iounmap(rfbi.base); return 0; } diff --git a/drivers/video/omap2/dss/sdi.c b/drivers/video/omap2/dss/sdi.c index 0bd4b03..3a688c8 100644 --- a/drivers/video/omap2/dss/sdi.c +++ b/drivers/video/omap2/dss/sdi.c @@ -20,13 +20,11 @@ #define DSS_SUBSYS_NAME "SDI" #include <linux/kernel.h> -#include <linux/clk.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/regulator/consumer.h> #include <video/omapdss.h> -#include <plat/cpu.h> #include "dss.h" static struct { @@ -60,14 +58,20 @@ int omapdss_sdi_display_enable(struct omap_dss_device *dssdev) r = omap_dss_start_device(dssdev); if (r) { DSSERR("failed to start device\n"); - goto err0; + goto err_start_dev; } r = regulator_enable(sdi.vdds_sdi_reg); if (r) - goto err1; + goto err_reg_enable; - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK); + r = dss_runtime_get(); + if (r) + goto err_get_dss; + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; sdi_basic_init(dssdev); @@ -80,7 +84,7 @@ int omapdss_sdi_display_enable(struct omap_dss_device *dssdev) r = dss_calc_clock_div(1, t->pixel_clock * 1000, &dss_cinfo, &dispc_cinfo); if (r) - goto err2; + goto err_calc_clock_div; fck = dss_cinfo.fck; lck_div = dispc_cinfo.lck_div; @@ -101,27 +105,34 @@ int omapdss_sdi_display_enable(struct omap_dss_device *dssdev) r = dss_set_clock_div(&dss_cinfo); if (r) - goto err2; + goto err_set_dss_clock_div; r = dispc_set_clock_div(dssdev->manager->id, &dispc_cinfo); if (r) - goto err2; + goto err_set_dispc_clock_div; dss_sdi_init(dssdev->phy.sdi.datapairs); r = dss_sdi_enable(); if (r) - goto err1; + goto err_sdi_enable; mdelay(2); dssdev->manager->enable(dssdev->manager); return 0; -err2: - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + +err_sdi_enable: +err_set_dispc_clock_div: +err_set_dss_clock_div: +err_calc_clock_div: + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: regulator_disable(sdi.vdds_sdi_reg); -err1: +err_reg_enable: omap_dss_stop_device(dssdev); -err0: +err_start_dev: return r; } EXPORT_SYMBOL(omapdss_sdi_display_enable); @@ -132,7 +143,8 @@ void omapdss_sdi_display_disable(struct omap_dss_device *dssdev) dss_sdi_disable(); - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK); + dispc_runtime_put(); + dss_runtime_put(); regulator_disable(sdi.vdds_sdi_reg); diff --git a/drivers/video/omap2/dss/venc.c b/drivers/video/omap2/dss/venc.c index 980f919..4491983 100644 --- a/drivers/video/omap2/dss/venc.c +++ b/drivers/video/omap2/dss/venc.c @@ -33,11 +33,13 @@ #include <linux/seq_file.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> #include <video/omapdss.h> #include <plat/cpu.h> #include "dss.h" +#include "dss_features.h" /* Venc registers */ #define VENC_REV_ID 0x00 @@ -292,6 +294,12 @@ static struct { struct mutex venc_lock; u32 wss_data; struct regulator *vdda_dac_reg; + + struct mutex runtime_lock; + int runtime_count; + + struct clk *tv_clk; + struct clk *tv_dac_clk; } venc; static inline void venc_write_reg(int idx, u32 val) @@ -380,14 +388,71 @@ static void venc_reset(void) #endif } -static void venc_enable_clocks(int enable) +static int venc_runtime_get(void) +{ + int r; + + mutex_lock(&venc.runtime_lock); + + if (venc.runtime_count++ == 0) { + DSSDBG("venc_runtime_get\n"); + + r = dss_runtime_get(); + if (r) + goto err_get_dss; + + r = dispc_runtime_get(); + if (r) + goto err_get_dispc; + + clk_enable(venc.tv_clk); + if (venc.tv_dac_clk) + clk_enable(venc.tv_dac_clk); + + r = pm_runtime_get_sync(&venc.pdev->dev); + WARN_ON(r); + if (r < 0) + goto err_runtime_get; + } + + mutex_unlock(&venc.runtime_lock); + + return 0; + +err_runtime_get: + clk_disable(venc.tv_clk); + if (venc.tv_dac_clk) + clk_disable(venc.tv_dac_clk); + dispc_runtime_put(); +err_get_dispc: + dss_runtime_put(); +err_get_dss: + mutex_unlock(&venc.runtime_lock); + + return r; +} + +static void venc_runtime_put(void) { - if (enable) - dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK | DSS_CLK_TVFCK | - DSS_CLK_VIDFCK); - else - dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK | DSS_CLK_TVFCK | - DSS_CLK_VIDFCK); + mutex_lock(&venc.runtime_lock); + + if (--venc.runtime_count == 0) { + int r; + + DSSDBG("venc_runtime_put\n"); + + r = pm_runtime_put_sync(&venc.pdev->dev); + WARN_ON(r); + + clk_disable(venc.tv_clk); + if (venc.tv_dac_clk) + clk_disable(venc.tv_dac_clk); + + dispc_runtime_put(); + dss_runtime_put(); + } + + mutex_unlock(&venc.runtime_lock); } static const struct venc_config *venc_timings_to_config( @@ -406,8 +471,6 @@ static void venc_power_on(struct omap_dss_device *dssdev) { u32 l; - venc_enable_clocks(1); - venc_reset(); venc_write_config(venc_timings_to_config(&dssdev->panel.timings)); @@ -448,8 +511,6 @@ static void venc_power_off(struct omap_dss_device *dssdev) dssdev->platform_disable(dssdev); regulator_disable(venc.vdda_dac_reg); - - venc_enable_clocks(0); } @@ -487,6 +548,10 @@ static int venc_panel_enable(struct omap_dss_device *dssdev) goto err1; } + r = venc_runtime_get(); + if (r) + goto err1; + venc_power_on(dssdev); venc.wss_data = 0; @@ -520,6 +585,8 @@ static void venc_panel_disable(struct omap_dss_device *dssdev) venc_power_off(dssdev); + venc_runtime_put(); + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; omap_dss_stop_device(dssdev); @@ -598,6 +665,7 @@ static u32 venc_get_wss(struct omap_dss_device *dssdev) static int venc_set_wss(struct omap_dss_device *dssdev, u32 wss) { const struct venc_config *config; + int r; DSSDBG("venc_set_wss\n"); @@ -608,16 +676,19 @@ static int venc_set_wss(struct omap_dss_device *dssdev, u32 wss) /* Invert due to VENC_L21_WC_CTL:INV=1 */ venc.wss_data = (wss ^ 0xfffff) << 8; - venc_enable_clocks(1); + r = venc_runtime_get(); + if (r) + goto err; venc_write_reg(VENC_BSTAMP_WSS_DATA, config->bstamp_wss_data | venc.wss_data); - venc_enable_clocks(0); + venc_runtime_put(); +err: mutex_unlock(&venc.venc_lock); - return 0; + return r; } static struct omap_dss_driver venc_driver = { @@ -673,7 +744,8 @@ void venc_dump_regs(struct seq_file *s) { #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, venc_read_reg(r)) - venc_enable_clocks(1); + if (venc_runtime_get()) + return; DUMPREG(VENC_F_CONTROL); DUMPREG(VENC_VIDOUT_CTRL); @@ -717,16 +789,53 @@ void venc_dump_regs(struct seq_file *s) DUMPREG(VENC_OUTPUT_CONTROL); DUMPREG(VENC_OUTPUT_TEST); - venc_enable_clocks(0); + venc_runtime_put(); #undef DUMPREG } +static int venc_get_clocks(struct platform_device *pdev) +{ + struct clk *clk; + + clk = clk_get(&pdev->dev, "tv_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get tv_clk\n"); + return PTR_ERR(clk); + } + + venc.tv_clk = clk; + + if (dss_has_feature(FEAT_VENC_REQUIRES_TV_DAC_CLK)) { + clk = clk_get(&pdev->dev, "tv_dac_clk"); + if (IS_ERR(clk)) { + DSSERR("can't get tv_dac_clk\n"); + clk_put(venc.tv_clk); + return PTR_ERR(clk); + } + } else { + clk = NULL; + } + + venc.tv_dac_clk = clk; + + return 0; +} + +static void venc_put_clocks(void) +{ + if (venc.tv_clk) + clk_put(venc.tv_clk); + if (venc.tv_dac_clk) + clk_put(venc.tv_dac_clk); +} + /* VENC HW IP initialisation */ static int omap_venchw_probe(struct platform_device *pdev) { u8 rev_id; struct resource *venc_mem; + int r; venc.pdev = pdev; @@ -737,22 +846,41 @@ static int omap_venchw_probe(struct platform_device *pdev) venc_mem = platform_get_resource(venc.pdev, IORESOURCE_MEM, 0); if (!venc_mem) { DSSERR("can't get IORESOURCE_MEM VENC\n"); - return -EINVAL; + r = -EINVAL; + goto err_ioremap; } venc.base = ioremap(venc_mem->start, resource_size(venc_mem)); if (!venc.base) { DSSERR("can't ioremap VENC\n"); - return -ENOMEM; + r = -ENOMEM; + goto err_ioremap; } - venc_enable_clocks(1); + r = venc_get_clocks(pdev); + if (r) + goto err_get_clk; + + mutex_init(&venc.runtime_lock); + pm_runtime_enable(&pdev->dev); + + r = venc_runtime_get(); + if (r) + goto err_get_venc; rev_id = (u8)(venc_read_reg(VENC_REV_ID) & 0xff); dev_dbg(&pdev->dev, "OMAP VENC rev %d\n", rev_id); - venc_enable_clocks(0); + venc_runtime_put(); return omap_dss_register_driver(&venc_driver); + +err_get_venc: + pm_runtime_disable(&pdev->dev); + venc_put_clocks(); +err_get_clk: + iounmap(venc.base); +err_ioremap: + return r; } static int omap_venchw_remove(struct platform_device *pdev) @@ -763,6 +891,9 @@ static int omap_venchw_remove(struct platform_device *pdev) } omap_dss_unregister_driver(&venc_driver); + pm_runtime_disable(&pdev->dev); + venc_put_clocks(); + iounmap(venc.base); return 0; } diff --git a/drivers/video/omap2/dsscomp/Kconfig b/drivers/video/omap2/dsscomp/Kconfig new file mode 100644 index 0000000..0c2a749 --- /dev/null +++ b/drivers/video/omap2/dsscomp/Kconfig @@ -0,0 +1,20 @@ +menuconfig DSSCOMP + tristate "OMAP DSS Composition support (EXPERIMENTAL)" + depends on OMAP2_DSS && PVR_SGX + default y + + help + Frame composition driver using OMAP DSS2. Allows using all + DSS2 resources in a unified configuration. Should not be used + together with other DSS2 devices, such as V4L2 or framebuffer. + +config DSSCOMP_DEBUG_LOG + bool "Log event timestamps in debugfs" + default y + depends on DEBUG_FS + + help + Takes timestamp for each callback and state transition, and + logs the last 128 entries (last few frames' worth) in a + log buffer. This is a separate menuconfig in case this is + deemed an overhead. diff --git a/drivers/video/omap2/dsscomp/Makefile b/drivers/video/omap2/dsscomp/Makefile new file mode 100644 index 0000000..8a67933 --- /dev/null +++ b/drivers/video/omap2/dsscomp/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DSSCOMP) += dsscomp.o +dsscomp-y := device.o base.o queue.o +dsscomp-y += gralloc.o diff --git a/drivers/video/omap2/dsscomp/base.c b/drivers/video/omap2/dsscomp/base.c new file mode 100644 index 0000000..ad7ade6 --- /dev/null +++ b/drivers/video/omap2/dsscomp/base.c @@ -0,0 +1,504 @@ +/* + * linux/drivers/video/omap2/dsscomp/base.c + * + * DSS Composition basic operation support + * + * Copyright (C) 2011 Texas Instruments, Inc + * Author: Lajos Molnar <molnar@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> + +#include <linux/notifier.h> +#include <mach/tiler.h> + +#include <video/omapdss.h> +#include <video/dsscomp.h> +#include <plat/dsscomp.h> + +#include "dsscomp.h" + +int debug; +module_param(debug, int, 0644); + +/* color formats supported - bitfield info is used for truncation logic */ +static const struct color_info { + int a_ix, a_bt; /* bitfields */ + int r_ix, r_bt; + int g_ix, g_bt; + int b_ix, b_bt; + int x_bt; + enum omap_color_mode mode; + const char *name; +} fmts[2][16] = { { + { 0, 0, 0, 0, 0, 0, 0, 0, 1, OMAP_DSS_COLOR_CLUT1, "BITMAP1" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 2, OMAP_DSS_COLOR_CLUT2, "BITMAP2" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 4, OMAP_DSS_COLOR_CLUT4, "BITMAP4" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 8, OMAP_DSS_COLOR_CLUT8, "BITMAP8" }, + { 0, 0, 8, 4, 4, 4, 0, 4, 4, OMAP_DSS_COLOR_RGB12U, "xRGB12-4444" }, + { 12, 4, 8, 4, 4, 4, 0, 4, 0, OMAP_DSS_COLOR_ARGB16, "ARGB16-4444" }, + { 0, 0, 11, 5, 5, 6, 0, 5, 0, OMAP_DSS_COLOR_RGB16, "RGB16-565" }, + { 15, 1, 10, 5, 5, 5, 0, 5, 0, OMAP_DSS_COLOR_ARGB16_1555, + "ARGB16-1555" }, + { 0, 0, 16, 8, 8, 8, 0, 8, 8, OMAP_DSS_COLOR_RGB24U, "xRGB24-8888" }, + { 0, 0, 16, 8, 8, 8, 0, 8, 0, OMAP_DSS_COLOR_RGB24P, "RGB24-888" }, + { 0, 0, 12, 4, 8, 4, 4, 4, 4, OMAP_DSS_COLOR_RGBX16, "RGBx12-4444" }, + { 0, 4, 12, 4, 8, 4, 4, 4, 0, OMAP_DSS_COLOR_RGBA16, "RGBA16-4444" }, + { 24, 8, 16, 8, 8, 8, 0, 8, 0, OMAP_DSS_COLOR_ARGB32, "ARGB32-8888" }, + { 0, 8, 24, 8, 16, 8, 8, 8, 0, OMAP_DSS_COLOR_RGBA32, "RGBA32-8888" }, + { 0, 0, 24, 8, 16, 8, 8, 8, 8, OMAP_DSS_COLOR_RGBX32, "RGBx24-8888" }, + { 0, 0, 10, 5, 5, 5, 0, 5, 1, OMAP_DSS_COLOR_XRGB16_1555, + "xRGB15-1555" }, +}, { + { 0, 0, 0, 0, 0, 0, 0, 0, 12, OMAP_DSS_COLOR_NV12, "NV12" }, + { 0, 0, 12, 4, 8, 4, 4, 4, 4, OMAP_DSS_COLOR_RGBX16, "RGBx12-4444" }, + { 0, 4, 12, 4, 8, 4, 4, 4, 0, OMAP_DSS_COLOR_RGBA16, "RGBA16-4444" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "invalid" }, + { 0, 0, 8, 4, 4, 4, 0, 4, 4, OMAP_DSS_COLOR_RGB12U, "xRGB12-4444" }, + { 12, 4, 8, 4, 4, 4, 0, 4, 0, OMAP_DSS_COLOR_ARGB16, "ARGB16-4444" }, + { 0, 0, 11, 5, 5, 6, 0, 5, 0, OMAP_DSS_COLOR_RGB16, "RGB16-565" }, + { 15, 1, 10, 5, 5, 5, 0, 5, 0, OMAP_DSS_COLOR_ARGB16_1555, + "ARGB16-1555" }, + { 0, 0, 16, 8, 8, 8, 0, 8, 8, OMAP_DSS_COLOR_RGB24U, "xRGB24-8888" }, + { 0, 0, 16, 8, 8, 8, 0, 8, 0, OMAP_DSS_COLOR_RGB24P, "RGB24-888" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 16, OMAP_DSS_COLOR_YUV2, "YUYV" }, + { 0, 0, 0, 0, 0, 0, 0, 0, 16, OMAP_DSS_COLOR_UYVY, "UYVY" }, + { 24, 8, 16, 8, 8, 8, 0, 8, 0, OMAP_DSS_COLOR_ARGB32, "ARGB32-8888" }, + { 0, 8, 24, 8, 16, 8, 8, 8, 0, OMAP_DSS_COLOR_RGBA32, "RGBA32-8888" }, + { 0, 0, 24, 8, 16, 8, 8, 8, 8, OMAP_DSS_COLOR_RGBX32, "RGBx24-8888" }, + { 0, 0, 10, 5, 5, 5, 0, 5, 1, OMAP_DSS_COLOR_XRGB16_1555, + "xRGB15-1555" }, +} }; + +static const struct color_info *get_color_info(enum omap_color_mode mode) +{ + int i; + for (i = 0; i < sizeof(fmts) / sizeof(fmts[0][0]); i++) + if (fmts[0][i].mode == mode) + return fmts[0] + i; + return NULL; +} + +static int color_mode_to_bpp(enum omap_color_mode color_mode) +{ + const struct color_info *ci = get_color_info(color_mode); + BUG_ON(!ci); + + return ci->a_bt + ci->r_bt + ci->g_bt + ci->b_bt + ci->x_bt; +} + +#ifdef CONFIG_DEBUG_FS +const char *dsscomp_get_color_name(enum omap_color_mode m) +{ + const struct color_info *ci = get_color_info(m); + return ci ? ci->name : NULL; +} +#endif + +union rect { + struct { + s32 x; + s32 y; + s32 w; + s32 h; + }; + struct { + s32 xy[2]; + s32 wh[2]; + }; + struct dss2_rect_t r; +}; + +int crop_to_rect(union rect *crop, union rect *win, union rect *vis, + int rotation, int mirror) +{ + int c, swap = rotation & 1; + + /* align crop window with display coordinates */ + if (swap) + crop->y -= (crop->h = -crop->h); + if (rotation & 2) + crop->xy[!swap] -= (crop->wh[!swap] = -crop->wh[!swap]); + if ((!mirror) ^ !(rotation & 2)) + crop->xy[swap] -= (crop->wh[swap] = -crop->wh[swap]); + + for (c = 0; c < 2; c++) { + /* see if complete buffer is outside the vis or it is + fully cropped or scaled to 0 */ + if (win->wh[c] <= 0 || vis->wh[c] <= 0 || + win->xy[c] + win->wh[c] <= vis->xy[c] || + win->xy[c] >= vis->xy[c] + vis->wh[c] || + !crop->wh[c ^ swap]) + return -ENOENT; + + /* crop left/top */ + if (win->xy[c] < vis->xy[c]) { + /* correction term */ + int a = (vis->xy[c] - win->xy[c]) * + crop->wh[c ^ swap] / win->wh[c]; + crop->xy[c ^ swap] += a; + crop->wh[c ^ swap] -= a; + win->wh[c] -= vis->xy[c] - win->xy[c]; + win->xy[c] = vis->xy[c]; + } + /* crop right/bottom */ + if (win->xy[c] + win->wh[c] > vis->xy[c] + vis->wh[c]) { + crop->wh[c ^ swap] = crop->wh[c ^ swap] * + (vis->xy[c] + vis->wh[c] - win->xy[c]) / + win->wh[c]; + win->wh[c] = vis->xy[c] + vis->wh[c] - win->xy[c]; + } + + if (!crop->wh[c ^ swap] || !win->wh[c]) + return -ENOENT; + } + + /* realign crop window to buffer coordinates */ + if (rotation & 2) + crop->xy[!swap] -= (crop->wh[!swap] = -crop->wh[!swap]); + if ((!mirror) ^ !(rotation & 2)) + crop->xy[swap] -= (crop->wh[swap] = -crop->wh[swap]); + if (swap) + crop->y -= (crop->h = -crop->h); + return 0; +} + +int set_dss_ovl_info(struct dss2_ovl_info *oi) +{ + struct omap_overlay_info info; + struct omap_overlay *ovl; + struct dss2_ovl_cfg *cfg; + union rect crop, win, vis; + int c; + + /* check overlay number */ + if (!oi || oi->cfg.ix >= omap_dss_get_num_overlays()) + return -EINVAL; + cfg = &oi->cfg; + ovl = omap_dss_get_overlay(cfg->ix); + + /* just in case there are new fields, we get the current info */ + ovl->get_overlay_info(ovl, &info); + + info.enabled = cfg->enabled; + if (!cfg->enabled) + goto done; + + /* copied params */ + info.zorder = cfg->zorder; + + if (cfg->zonly) + goto done; + + info.global_alpha = cfg->global_alpha; + info.pre_mult_alpha = cfg->pre_mult_alpha; + info.rotation = cfg->rotation; + info.mirror = cfg->mirror; + info.color_mode = cfg->color_mode; + + /* crop to screen */ + crop.r = cfg->crop; + win.r = cfg->win; + vis.x = vis.y = 0; + vis.w = ovl->manager->device->panel.timings.x_res; + vis.h = ovl->manager->device->panel.timings.y_res; + + if (crop_to_rect(&crop, &win, &vis, cfg->rotation, cfg->mirror) || + vis.w < 2) { + info.enabled = false; + goto done; + } + + /* adjust crop to UV pixel boundaries */ + for (c = 0; c < (cfg->color_mode == OMAP_DSS_COLOR_NV12 ? 2 : + (cfg->color_mode & + (OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY)) ? 1 : 0); c++) { + /* keep the output window to avoid trembling edges */ + crop.wh[c] += crop.xy[c] & 1; /* round down start */ + crop.xy[c] &= ~1; + crop.wh[c] += crop.wh[c] & 1; /* round up end */ + + /* + * Buffer is aligned on UV pixel boundaries, so no + * worries about extending crop region. + */ + } + + info.width = crop.w; + info.height = crop.h; + if (cfg->rotation & 1) + /* DISPC uses swapped height/width for 90/270 degrees */ + swap(info.width, info.height); + info.pos_x = win.x; + info.pos_y = win.y; + info.out_width = win.w; + info.out_height = win.h; + + /* calculate addresses and cropping */ + info.paddr = oi->ba; + info.p_uv_addr = (info.color_mode == OMAP_DSS_COLOR_NV12) ? oi->uv : 0; + info.vaddr = NULL; + + /* check for TILER 2D buffer */ + if (info.paddr >= 0x60000000 && info.paddr < 0x78000000) { + int bpp = 1 << ((info.paddr >> 27) & 3); + struct tiler_view_t t; + + /* crop to top-left */ + + /* + * DSS supports YUV422 on 32-bit mode, but its technically + * 2 bytes-per-pixel. + * Also RGB24-888 is 3 bytes-per-pixel even though no + * tiler pixel format matches this. + */ + if (cfg->color_mode & + (OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY)) + bpp = 2; + else if (cfg->color_mode == OMAP_DSS_COLOR_RGB24P) + bpp = 3; + + tilview_create(&t, info.paddr, cfg->width, cfg->height); + info.paddr -= t.tsptr; + tilview_crop(&t, 0, crop.y, cfg->width, crop.h); + info.paddr += t.tsptr + bpp * crop.x; + + info.rotation_type = OMAP_DSS_ROT_TILER; + info.screen_width = 0; + + /* for NV12 format also crop NV12 */ + if (info.color_mode == OMAP_DSS_COLOR_NV12) { + tilview_create(&t, info.p_uv_addr, + cfg->width >> 1, cfg->height >> 1); + info.p_uv_addr -= t.tsptr; + tilview_crop(&t, 0, crop.y >> 1, cfg->width >> 1, + crop.h >> 1); + info.p_uv_addr += t.tsptr + bpp * crop.x; + } + } else { + /* program tiler 1D as SDMA */ + + int bpp = color_mode_to_bpp(cfg->color_mode); + info.screen_width = cfg->stride * 8 / (bpp == 12 ? 8 : bpp); + info.paddr += crop.x * (bpp / 8) + crop.y * cfg->stride; + + /* for NV12 format also crop NV12 */ + if (info.color_mode == OMAP_DSS_COLOR_NV12) + info.p_uv_addr += crop.x * (bpp / 8) + + (crop.y >> 1) * cfg->stride; + + /* no rotation on DMA buffer */ + if (cfg->rotation & 3 || cfg->mirror) + return -EINVAL; + + info.rotation_type = OMAP_DSS_ROT_DMA; + } + + info.max_x_decim = cfg->decim.max_x ? : 255; + info.max_y_decim = cfg->decim.max_y ? : 255; + info.min_x_decim = cfg->decim.min_x ? : 1; + info.min_y_decim = cfg->decim.min_y ? : 1; +#if 0 + info.pic_height = cfg->height; + + info.field = 0; + if (cfg->ilace & OMAP_DSS_ILACE_SEQ) + info.field |= OMAP_FLAG_IBUF; + if (cfg->ilace & OMAP_DSS_ILACE_SWAP) + info.field |= OMAP_FLAG_ISWAP; + /* + * Ignore OMAP_DSS_ILACE as there is no real support yet for + * interlaced interleaved vs progressive buffers + */ + if (ovl->manager && + ovl->manager->device && + !strcmp(ovl->manager->device->name, "hdmi") && + is_hdmi_interlaced()) + info.field |= OMAP_FLAG_IDEV; + + info.out_wb = 0; +#endif + + info.cconv = cfg->cconv; + +done: +#if 0 + pr_debug("ovl%d: en=%d %x/%x (%dx%d|%d) => (%dx%d) @ (%d,%d) rot=%d " + "mir=%d col=%x z=%d al=%02x prem=%d pich=%d ilace=%d\n", + ovl->id, info.enabled, info.paddr, info.p_uv_addr, info.width, + info.height, info.screen_width, info.out_width, info.out_height, + info.pos_x, info.pos_y, info.rotation, info.mirror, + info.color_mode, info.zorder, info.global_alpha, + info.pre_mult_alpha, info.pic_height, info.field); +#else + pr_debug("ovl%d: en=%d %x/%x (%dx%d|%d) => (%dx%d) @ (%d,%d) rot=%d " + "mir=%d col=%x z=%d al=%02x prem=%d\n", + ovl->id, info.enabled, info.paddr, info.p_uv_addr, info.width, + info.height, info.screen_width, info.out_width, info.out_height, + info.pos_x, info.pos_y, info.rotation, info.mirror, + info.color_mode, info.zorder, info.global_alpha, + info.pre_mult_alpha); +#endif + /* set overlay info */ + return ovl->set_overlay_info(ovl, &info); +} + +void swap_rb_in_ovl_info(struct dss2_ovl_info *oi) +{ + /* we need to swap YUV color matrix if we are swapping R and B */ + if (oi->cfg.color_mode & + (OMAP_DSS_COLOR_NV12 | OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY)) { + swap(oi->cfg.cconv.ry, oi->cfg.cconv.by); + swap(oi->cfg.cconv.rcr, oi->cfg.cconv.bcr); + swap(oi->cfg.cconv.rcb, oi->cfg.cconv.bcb); + } +} + +struct omap_overlay_manager *find_dss_mgr(int display_ix) +{ + struct omap_overlay_manager *mgr; + char name[32]; + int i; + + sprintf(name, "display%d", display_ix); + + for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { + mgr = omap_dss_get_overlay_manager(i); + if (mgr->device && !strcmp(name, dev_name(&mgr->device->dev))) + return mgr; + } + return NULL; +} + +int set_dss_mgr_info(struct dss2_mgr_info *mi, struct omapdss_ovl_cb *cb) +{ + struct omap_overlay_manager_info info; + struct omap_overlay_manager *mgr; + + if (!mi) + return -EINVAL; + mgr = find_dss_mgr(mi->ix); + if (!mgr) + return -EINVAL; + + /* just in case there are new fields, we get the current info */ + mgr->get_manager_info(mgr, &info); + + info.alpha_enabled = mi->alpha_blending; + info.default_color = mi->default_color; + info.trans_enabled = mi->trans_enabled && !mi->alpha_blending; + info.trans_key = mi->trans_key; + info.trans_key_type = mi->trans_key_type; + + info.cpr_coefs = mi->cpr_coefs; + info.cpr_enable = mi->cpr_enabled; + info.cb = *cb; + + return mgr->set_manager_info(mgr, &info); +} + +void swap_rb_in_mgr_info(struct dss2_mgr_info *mi) +{ + const struct omap_dss_cpr_coefs c = { 256, 0, 0, 0, 256, 0, 0, 0, 256 }; + + /* set default CPR */ + if (!mi->cpr_enabled) + mi->cpr_coefs = c; + mi->cpr_enabled = true; + + /* swap red and blue */ + swap(mi->cpr_coefs.rr, mi->cpr_coefs.br); + swap(mi->cpr_coefs.rg, mi->cpr_coefs.bg); + swap(mi->cpr_coefs.rb, mi->cpr_coefs.bb); +} + +/* + * =========================================================================== + * DEBUG METHODS + * =========================================================================== + */ +void dump_ovl_info(struct dsscomp_dev *cdev, struct dss2_ovl_info *oi) +{ + struct dss2_ovl_cfg *c = &oi->cfg; + const struct color_info *ci; + + if (!(debug & DEBUG_OVERLAYS) || + !(debug & DEBUG_COMPOSITIONS)) + return; + + ci = get_color_info(c->color_mode); + if (c->zonly) { + dev_info(DEV(cdev), "ovl%d(%s z%d)\n", + c->ix, c->enabled ? "ON" : "off", c->zorder); + return; + } + dev_info(DEV(cdev), "ovl%d(%s z%d %s%s *%d%% %d*%d:%d,%d+%d,%d rot%d%s" + " => %d,%d+%d,%d %p/%p|%d)\n", + c->ix, c->enabled ? "ON" : "off", c->zorder, + ci->name ? : "(none)", + c->pre_mult_alpha ? " premult" : "", + (c->global_alpha * 100 + 128) / 255, + c->width, c->height, c->crop.x, c->crop.y, + c->crop.w, c->crop.h, + c->rotation, c->mirror ? "+mir" : "", + c->win.x, c->win.y, c->win.w, c->win.h, + (void *) oi->ba, (void *) oi->uv, c->stride); +} + +static void print_mgr_info(struct dsscomp_dev *cdev, + struct dss2_mgr_info *mi) +{ + printk("(dis%d(%s) alpha=%d col=%08x ilace=%d) ", + mi->ix, + (mi->ix < cdev->num_displays && cdev->displays[mi->ix]) ? + cdev->displays[mi->ix]->name : "NONE", + mi->alpha_blending, mi->default_color, + mi->interlaced); +} + +void dump_comp_info(struct dsscomp_dev *cdev, struct dsscomp_setup_mgr_data *d, + const char *phase) +{ + if (!(debug & DEBUG_COMPOSITIONS)) + return; + + dev_info(DEV(cdev), "[%p] %s: %c%c%c ", + *phase == 'q' ? (void *) d->sync_id : d, phase, + (d->mode & DSSCOMP_SETUP_MODE_APPLY) ? 'A' : '-', + (d->mode & DSSCOMP_SETUP_MODE_DISPLAY) ? 'D' : '-', + (d->mode & DSSCOMP_SETUP_MODE_CAPTURE) ? 'C' : '-'); + print_mgr_info(cdev, &d->mgr); + printk("n=%d\n", d->num_ovls); +} + +void dump_total_comp_info(struct dsscomp_dev *cdev, + struct dsscomp_setup_dispc_data *d, + const char *phase) +{ + int i; + + if (!(debug & DEBUG_COMPOSITIONS)) + return; + + dev_info(DEV(cdev), "[%p] %s: %c%c%c ", + *phase == 'q' ? (void *) d->sync_id : d, phase, + (d->mode & DSSCOMP_SETUP_MODE_APPLY) ? 'A' : '-', + (d->mode & DSSCOMP_SETUP_MODE_DISPLAY) ? 'D' : '-', + (d->mode & DSSCOMP_SETUP_MODE_CAPTURE) ? 'C' : '-'); + + for (i = 0; i < d->num_mgrs && i < ARRAY_SIZE(d->mgrs); i++) + print_mgr_info(cdev, d->mgrs + i); + printk("n=%d\n", d->num_ovls); +} diff --git a/drivers/video/omap2/dsscomp/device.c b/drivers/video/omap2/dsscomp/device.c new file mode 100644 index 0000000..80cc21b --- /dev/null +++ b/drivers/video/omap2/dsscomp/device.c @@ -0,0 +1,634 @@ +/* + * linux/drivers/video/omap2/dsscomp/device.c + * + * DSS Composition file device and ioctl support + * + * Copyright (C) 2011 Texas Instruments, Inc + * Author: Lajos Molnar <molnar@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define DEBUG + +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/anon_inodes.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/sched.h> +#include <linux/syscalls.h> + +#define MODULE_NAME "dsscomp" + +#include <video/omapdss.h> +#include <video/dsscomp.h> +#include <plat/dsscomp.h> +#include "dsscomp.h" + +#include <linux/debugfs.h> + +static DECLARE_WAIT_QUEUE_HEAD(waitq); +static DEFINE_MUTEX(wait_mtx); + +static u32 hwc_virt_to_phys(u32 arg) +{ + pmd_t *pmd; + pte_t *ptep; + + pgd_t *pgd = pgd_offset(current->mm, arg); + if (pgd_none(*pgd) || pgd_bad(*pgd)) + return 0; + + pmd = pmd_offset(pgd, arg); + if (pmd_none(*pmd) || pmd_bad(*pmd)) + return 0; + + ptep = pte_offset_map(pmd, arg); + if (ptep && pte_present(*ptep)) + return (PAGE_MASK & *ptep) | (~PAGE_MASK & arg); + + return 0; +} + +/* + * =========================================================================== + * WAIT OPERATIONS + * =========================================================================== + */ + +static void sync_drop(struct dsscomp_sync_obj *sync) +{ + if (sync && atomic_dec_and_test(&sync->refs)) { + if (debug & DEBUG_WAITS) + pr_info("free sync [%p]\n", sync); + + kfree(sync); + } +} + +static int sync_setup(const char *name, const struct file_operations *fops, + struct dsscomp_sync_obj *sync, int flags) +{ + if (!sync) + return -ENOMEM; + + sync->refs.counter = 1; + sync->fd = anon_inode_getfd(name, fops, sync, flags); + return sync->fd < 0 ? sync->fd : 0; +} + +static int sync_finalize(struct dsscomp_sync_obj *sync, int r) +{ + if (sync) { + if (r < 0) + /* delete sync object on failure */ + sys_close(sync->fd); + else + /* return file descriptor on success */ + r = sync->fd; + } + return r; +} + +/* wait for programming or release of a composition */ +int dsscomp_wait(struct dsscomp_sync_obj *sync, enum dsscomp_wait_phase phase, + int timeout) +{ + mutex_lock(&wait_mtx); + if (debug & DEBUG_WAITS) + pr_info("wait %s on [%p]\n", + phase == DSSCOMP_WAIT_DISPLAYED ? "display" : + phase == DSSCOMP_WAIT_PROGRAMMED ? "program" : + "release", sync); + + if (sync->state < phase) { + mutex_unlock(&wait_mtx); + + timeout = wait_event_interruptible_timeout(waitq, + sync->state >= phase, timeout); + if (debug & DEBUG_WAITS) + pr_info("wait over [%p]: %s %d\n", sync, + timeout < 0 ? "signal" : + timeout > 0 ? "ok" : "timeout", + timeout); + if (timeout <= 0) + return timeout ? : -ETIME; + + mutex_lock(&wait_mtx); + } + mutex_unlock(&wait_mtx); + + return 0; +} +EXPORT_SYMBOL(dsscomp_wait); + +static void dsscomp_queue_cb(void *data, int status) +{ + struct dsscomp_sync_obj *sync = data; + enum dsscomp_wait_phase phase = + status == DSS_COMPLETION_PROGRAMMED ? DSSCOMP_WAIT_PROGRAMMED : + status == DSS_COMPLETION_DISPLAYED ? DSSCOMP_WAIT_DISPLAYED : + DSSCOMP_WAIT_RELEASED, old_phase; + + mutex_lock(&wait_mtx); + old_phase = sync->state; + if (old_phase < phase) + sync->state = phase; + mutex_unlock(&wait_mtx); + + if (status & DSS_COMPLETION_RELEASED) + sync_drop(sync); + if (old_phase < phase) + wake_up_interruptible_sync(&waitq); +} + +static int sync_release(struct inode *inode, struct file *filp) +{ + struct dsscomp_sync_obj *sync = filp->private_data; + sync_drop(sync); + return 0; +} + +static long sync_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int r = 0; + struct dsscomp_sync_obj *sync = filp->private_data; + void __user *ptr = (void __user *)arg; + + switch (cmd) { + case DSSCIOC_WAIT: + { + struct dsscomp_wait_data wd; + r = copy_from_user(&wd, ptr, sizeof(wd)) ? : + dsscomp_wait(sync, wd.phase, + usecs_to_jiffies(wd.timeout_us)); + break; + } + default: + r = -EINVAL; + } + return r; +} + +static const struct file_operations sync_fops = { + .owner = THIS_MODULE, + .release = sync_release, + .unlocked_ioctl = sync_ioctl, +}; + +static long setup_mgr(struct dsscomp_dev *cdev, + struct dsscomp_setup_mgr_data *d) +{ + int i, r; + struct omap_dss_device *dev; + struct omap_overlay_manager *mgr; + dsscomp_t comp; + struct dsscomp_sync_obj *sync = NULL; + + dump_comp_info(cdev, d, "queue"); + for (i = 0; i < d->num_ovls; i++) + dump_ovl_info(cdev, d->ovls + i); + + /* verify display is valid and connected */ + if (d->mgr.ix >= cdev->num_displays) + return -EINVAL; + dev = cdev->displays[d->mgr.ix]; + if (!dev) + return -EINVAL; + mgr = dev->manager; + if (!mgr) + return -ENODEV; + + comp = dsscomp_new(mgr); + if (IS_ERR(comp)) + return PTR_ERR(comp); + + /* swap red & blue if requested */ + if (d->mgr.swap_rb) { + swap_rb_in_mgr_info(&d->mgr); + for (i = 0; i < d->num_ovls; i++) + swap_rb_in_ovl_info(d->ovls + i); + } + + r = dsscomp_set_mgr(comp, &d->mgr); + + for (i = 0; i < d->num_ovls; i++) { + struct dss2_ovl_info *oi = d->ovls + i; + u32 addr = (u32) oi->address; + + /* convert addresses to user space */ + if (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12) + oi->uv = hwc_virt_to_phys(addr + + oi->cfg.height * oi->cfg.stride); + oi->ba = hwc_virt_to_phys(addr); + + r = r ? : dsscomp_set_ovl(comp, oi); + } + + r = r ? : dsscomp_setup(comp, d->mode, d->win); + + /* create sync object */ + if (d->get_sync_obj) { + sync = kzalloc(sizeof(*sync), GFP_KERNEL); + r = sync_setup("dsscomp_sync", &sync_fops, sync, O_RDONLY); + if (sync && (debug & DEBUG_WAITS)) + dev_info(DEV(cdev), "new sync [%p] on #%d\n", sync, + sync->fd); + if (r) + sync_drop(sync); + } + + /* drop composition if failed to create */ + if (r) { + dsscomp_drop(comp); + return r; + } + + if (sync) { + sync->refs.counter++; + comp->extra_cb = dsscomp_queue_cb; + comp->extra_cb_data = sync; + } + if (d->mode & DSSCOMP_SETUP_APPLY) + r = dsscomp_delayed_apply(comp); + + /* delete sync object if failed to apply or create file */ + if (sync) { + r = sync_finalize(sync, r); + if (r < 0) + sync_drop(sync); + } + return r; +} + +static long query_display(struct dsscomp_dev *cdev, + struct dsscomp_display_info *dis) +{ + struct omap_dss_device *dev; + struct omap_overlay_manager *mgr; + int i; + + /* get display */ + if (dis->ix >= cdev->num_displays) + return -EINVAL; + dev = cdev->displays[dis->ix]; + if (!dev) + return -EINVAL; + mgr = dev->manager; + + /* fill out display information */ + dis->channel = dev->channel; + dis->enabled = (dev->state == OMAP_DSS_DISPLAY_SUSPENDED) ? + dev->activate_after_resume : + (dev->state == OMAP_DSS_DISPLAY_ACTIVE); + dis->overlays_available = 0; + dis->overlays_owned = 0; +#if 0 + dis->s3d_info = dev->panel.s3d_info; +#endif + dis->state = dev->state; + dis->timings = dev->panel.timings; + + dis->width_in_mm = DIV_ROUND_CLOSEST(dev->panel.width_in_um, 1000); + dis->height_in_mm = DIV_ROUND_CLOSEST(dev->panel.height_in_um, 1000); + + /* find all overlays available for/owned by this display */ + for (i = 0; i < cdev->num_ovls && dis->enabled; i++) { + if (cdev->ovls[i]->manager == mgr) + dis->overlays_owned |= 1 << i; + else if (!cdev->ovls[i]->info.enabled) + dis->overlays_available |= 1 << i; + } + dis->overlays_available |= dis->overlays_owned; + + /* fill out manager information */ + if (mgr) { + dis->mgr.alpha_blending = mgr->info.alpha_enabled; + dis->mgr.default_color = mgr->info.default_color; +#if 0 + dis->mgr.interlaced = !strcmp(dev->name, "hdmi") && + is_hdmi_interlaced() +#else + dis->mgr.interlaced = 0; +#endif + dis->mgr.trans_enabled = mgr->info.trans_enabled; + dis->mgr.trans_key = mgr->info.trans_key; + dis->mgr.trans_key_type = mgr->info.trans_key_type; + } else { + /* display is disabled if it has no manager */ + memset(&dis->mgr, 0, sizeof(dis->mgr)); + } + dis->mgr.ix = dis->ix; + + if (dis->modedb_len && dev->driver->get_modedb) + dis->modedb_len = dev->driver->get_modedb(dev, + (struct fb_videomode *) dis->modedb, dis->modedb_len); + return 0; +} + +static long check_ovl(struct dsscomp_dev *cdev, + struct dsscomp_check_ovl_data *chk) +{ + /* for now return all overlays as possible */ + return (1 << cdev->num_ovls) - 1; +} + +static long setup_display(struct dsscomp_dev *cdev, + struct dsscomp_setup_display_data *dis) +{ + struct omap_dss_device *dev; + + /* get display */ + if (dis->ix >= cdev->num_displays) + return -EINVAL; + dev = cdev->displays[dis->ix]; + if (!dev) + return -EINVAL; + + if (dev->driver->set_mode) + return dev->driver->set_mode(dev, + (struct fb_videomode *) &dis->mode); + else + return 0; +} + +static void fill_cache(struct dsscomp_dev *cdev) +{ + unsigned long i; + struct omap_dss_device *dssdev = NULL; + + cdev->num_ovls = min(omap_dss_get_num_overlays(), MAX_OVERLAYS); + for (i = 0; i < cdev->num_ovls; i++) + cdev->ovls[i] = omap_dss_get_overlay(i); + + cdev->num_mgrs = min(omap_dss_get_num_overlay_managers(), MAX_MANAGERS); + for (i = 0; i < cdev->num_mgrs; i++) + cdev->mgrs[i] = omap_dss_get_overlay_manager(i); + + for_each_dss_dev(dssdev) { + const char *name = dev_name(&dssdev->dev); + if (strncmp(name, "display", 7) || + strict_strtoul(name + 7, 10, &i) || + i >= MAX_DISPLAYS) + continue; + + if (cdev->num_displays <= i) + cdev->num_displays = i + 1; + + cdev->displays[i] = dssdev; + dev_dbg(DEV(cdev), "display%lu=%s\n", i, dssdev->driver_name); + + cdev->state_notifiers[i].notifier_call = dsscomp_state_notifier; + blocking_notifier_chain_register(&dssdev->state_notifiers, + cdev->state_notifiers + i); + } + dev_info(DEV(cdev), "found %d displays and %d overlays\n", + cdev->num_displays, cdev->num_ovls); +} + +static long comp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int r = 0; + struct miscdevice *dev = filp->private_data; + struct dsscomp_dev *cdev = container_of(dev, struct dsscomp_dev, dev); + void __user *ptr = (void __user *)arg; + + union { + struct { + struct dsscomp_setup_mgr_data set; + struct dss2_ovl_info ovl[MAX_OVERLAYS]; + } m; + struct dsscomp_setup_dispc_data dispc; + struct dsscomp_display_info dis; + struct dsscomp_check_ovl_data chk; + struct dsscomp_setup_display_data sdis; + } u; + + dsscomp_gralloc_init(cdev); + + switch (cmd) { + case DSSCIOC_SETUP_MGR: + { + r = copy_from_user(&u.m.set, ptr, sizeof(u.m.set)) ? : + u.m.set.num_ovls >= ARRAY_SIZE(u.m.ovl) ? -EINVAL : + copy_from_user(&u.m.ovl, + (void __user *)arg + sizeof(u.m.set), + sizeof(*u.m.ovl) * u.m.set.num_ovls) ? : + setup_mgr(cdev, &u.m.set); + break; + } + case DSSCIOC_SETUP_DISPC: + { + r = copy_from_user(&u.dispc, ptr, sizeof(u.dispc)) ? : + dsscomp_gralloc_queue_ioctl(&u.dispc); + break; + } + case DSSCIOC_QUERY_DISPLAY: + { + struct dsscomp_display_info *dis = NULL; + r = copy_from_user(&u.dis, ptr, sizeof(u.dis)); + if (!r) { + /* impose a safe limit on modedb_len to prevent + * wrap around/overflow calculation of the alloced + * size that would make it smaller than + * struct dsscomp_display_info and cause heap + * corruption. + */ + u.dis.modedb_len = clamp_val(u.dis.modedb_len, 0, 256); + + dis = kzalloc(sizeof(*dis->modedb) * u.dis.modedb_len + + sizeof(*dis), GFP_KERNEL); + } + if (dis) { + *dis = u.dis; + r = query_display(cdev, dis) ? : + copy_to_user(ptr, dis, sizeof(*dis) + + sizeof(*dis->modedb) * dis->modedb_len); + kfree(dis); + } else { + r = r ? : -ENOMEM; + } + break; + } + case DSSCIOC_CHECK_OVL: + { + r = copy_from_user(&u.chk, ptr, sizeof(u.chk)) ? : + check_ovl(cdev, &u.chk); + break; + } + case DSSCIOC_SETUP_DISPLAY: + { + r = copy_from_user(&u.sdis, ptr, sizeof(u.sdis)) ? : + setup_display(cdev, &u.sdis); + } + default: + r = -EINVAL; + } + return r; +} + +/* must implement open for filp->private_data to be filled */ +static int comp_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static const struct file_operations comp_fops = { + .owner = THIS_MODULE, + .open = comp_open, + .unlocked_ioctl = comp_ioctl, +}; + +static int dsscomp_debug_show(struct seq_file *s, void *unused) +{ + void (*fn)(struct seq_file *s) = s->private; + fn(s); + return 0; +} + +static int dsscomp_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, dsscomp_debug_show, inode->i_private); +} + +static const struct file_operations dsscomp_debug_fops = { + .open = dsscomp_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dsscomp_probe(struct platform_device *pdev) +{ + int ret; + struct dsscomp_dev *cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) { + pr_err("dsscomp: failed to allocate device.\n"); + return -ENOMEM; + } + cdev->dev.minor = MISC_DYNAMIC_MINOR; + cdev->dev.name = "dsscomp"; + cdev->dev.mode = 0666; + cdev->dev.fops = &comp_fops; + + ret = misc_register(&cdev->dev); + if (ret) { + pr_err("dsscomp: failed to register misc device.\n"); + return ret; + } + cdev->dbgfs = debugfs_create_dir("dsscomp", NULL); + if (IS_ERR_OR_NULL(cdev->dbgfs)) + dev_warn(DEV(cdev), "failed to create debug files.\n"); + else { + debugfs_create_file("comps", S_IRUGO, + cdev->dbgfs, dsscomp_dbg_comps, &dsscomp_debug_fops); + debugfs_create_file("gralloc", S_IRUGO, + cdev->dbgfs, dsscomp_dbg_gralloc, &dsscomp_debug_fops); +#ifdef CONFIG_DSSCOMP_DEBUG_LOG + debugfs_create_file("log", S_IRUGO, + cdev->dbgfs, dsscomp_dbg_events, &dsscomp_debug_fops); +#endif + } + + platform_set_drvdata(pdev, cdev); + + pr_info("dsscomp: initializing.\n"); + + fill_cache(cdev); + + /* initialize queues */ + dsscomp_queue_init(cdev); + dsscomp_gralloc_init(cdev); + + return 0; +} + +static int dsscomp_remove(struct platform_device *pdev) +{ + struct dsscomp_dev *cdev = platform_get_drvdata(pdev); + misc_deregister(&cdev->dev); + debugfs_remove_recursive(cdev->dbgfs); + dsscomp_queue_exit(); + dsscomp_gralloc_exit(); + kfree(cdev); + + return 0; +} + +static struct platform_driver dsscomp_pdriver = { + .probe = dsscomp_probe, + .remove = dsscomp_remove, + .driver = { .name = MODULE_NAME, .owner = THIS_MODULE } +}; + +static struct platform_device dsscomp_pdev = { + .name = MODULE_NAME, + .id = -1 +}; + +static int __init dsscomp_init(void) +{ + int err = platform_driver_register(&dsscomp_pdriver); + if (err) + return err; + + err = platform_device_register(&dsscomp_pdev); + if (err) + platform_driver_unregister(&dsscomp_pdriver); + return err; +} + +static void __exit dsscomp_exit(void) +{ + platform_device_unregister(&dsscomp_pdev); + platform_driver_unregister(&dsscomp_pdriver); +} + +#define DUMP_CHUNK 256 +static char dump_buf[64 * 1024]; +void dsscomp_kdump(void) +{ + struct seq_file s = { + .buf = dump_buf, + .size = sizeof(dump_buf) - 1, + }; + int i; + + dsscomp_dbg_events(&s); + dsscomp_dbg_comps(&s); + dsscomp_dbg_gralloc(&s); + + for (i = 0; i < s.count; i += DUMP_CHUNK) { + if ((s.count - i) > DUMP_CHUNK) { + char c = s.buf[i + DUMP_CHUNK]; + s.buf[i + DUMP_CHUNK] = 0; + pr_cont("%s", s.buf + i); + s.buf[i + DUMP_CHUNK] = c; + } else { + s.buf[s.count] = 0; + pr_cont("%s", s.buf + i); + } + } +} +EXPORT_SYMBOL(dsscomp_kdump); + +MODULE_LICENSE("GPL v2"); +module_init(dsscomp_init); +module_exit(dsscomp_exit); diff --git a/drivers/video/omap2/dsscomp/dsscomp.h b/drivers/video/omap2/dsscomp/dsscomp.h new file mode 100644 index 0000000..8edfaa7 --- /dev/null +++ b/drivers/video/omap2/dsscomp/dsscomp.h @@ -0,0 +1,208 @@ +/* + * linux/drivers/video/omap2/dsscomp/base.c + * + * DSS Composition basic operation support + * + * Copyright (C) 2011 Texas Instruments, Inc + * Author: Lajos Molnar <molnar@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _DSSCOMP_H +#define _DSSCOMP_H + +#include <linux/miscdevice.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#ifdef CONFIG_DSSCOMP_DEBUG_LOG +#include <linux/hrtimer.h> +#endif + +#define MAX_OVERLAYS 5 +#define MAX_MANAGERS 3 +#define MAX_DISPLAYS 4 + +#define DEBUG_OVERLAYS (1 << 0) +#define DEBUG_COMPOSITIONS (1 << 1) +#define DEBUG_PHASES (1 << 2) +#define DEBUG_WAITS (1 << 3) +#define DEBUG_GRALLOC_PHASES (1 << 4) + +/* + * Utility macros + */ +#define ZERO(c) memset(&c, 0, sizeof(c)) +#define ZEROn(c, n) memset(c, 0, sizeof(*c) * n) +#define DEV(c) (c->dev.this_device) + +/** + * DSS Composition Device Driver + * + * @dev: misc device base + * @dbgfs: debugfs hook + */ +struct dsscomp_dev { + struct miscdevice dev; + struct dentry *dbgfs; + + /* cached DSS objects */ + u32 num_ovls; + struct omap_overlay *ovls[MAX_OVERLAYS]; + u32 num_mgrs; + struct omap_overlay_manager *mgrs[MAX_MANAGERS]; + u32 num_displays; + struct omap_dss_device *displays[MAX_DISPLAYS]; + struct notifier_block state_notifiers[MAX_DISPLAYS]; +}; + +extern int debug; + +#ifdef CONFIG_DEBUG_FS +extern struct mutex dbg_mtx; +extern struct list_head dbg_comps; +#define DO_IF_DEBUG_FS(cmd) { \ + mutex_lock(&dbg_mtx); \ + cmd; \ + mutex_unlock(&dbg_mtx); \ +} +#else +#define DO_IF_DEBUG_FS(cmd) +#endif + +enum dsscomp_state { + DSSCOMP_STATE_ACTIVE = 0xAC54156E, + DSSCOMP_STATE_APPLYING = 0xB554C591, + DSSCOMP_STATE_APPLIED = 0xB60504C1, + DSSCOMP_STATE_PROGRAMMED = 0xC0520652, + DSSCOMP_STATE_DISPLAYED = 0xD15504CA, +}; + +struct dsscomp_data { + enum dsscomp_state state; + /* + * :TRICKY: before applying, overlays used in a composition are stored + * in ovl_mask and the other masks are empty. Once composition is + * applied, blank is set to see if all overlays are to be disabled on + * this composition, any disabled overlays in the composition are set in + * ovl_dmask, and ovl_mask is updated to include ALL overlays that are + * actually on the display - even if they are not part of the + * composition. The reason: we use ovl_mask to see if an overlay is used + * or planned to be used on a manager. We update ovl_mask when + * composition is programmed (removing the disabled overlays). + */ + bool blank; /* true if all overlays are to be disabled */ + u32 ovl_mask; /* overlays used on this frame */ + u32 ovl_dmask; /* overlays disabled on this frame */ + u32 ix; /* manager index that this frame is on */ + struct dsscomp_setup_mgr_data frm; + struct dss2_ovl_info ovls[5]; + void (*extra_cb)(void *data, int status); + void *extra_cb_data; + bool must_apply; /* whether composition must be applied */ + +#ifdef CONFIG_DEBUG_FS + struct list_head dbg_q; + u32 dbg_used; + struct { + u32 t, state; + } dbg_log[8]; +#endif +}; + +struct dsscomp_sync_obj { + int state; + int fd; + atomic_t refs; +}; + +/* + * Kernel interface + */ +int dsscomp_queue_init(struct dsscomp_dev *cdev); +void dsscomp_queue_exit(void); +void dsscomp_gralloc_init(struct dsscomp_dev *cdev); +void dsscomp_gralloc_exit(void); +int dsscomp_gralloc_queue_ioctl(struct dsscomp_setup_dispc_data *d); +int dsscomp_wait(struct dsscomp_sync_obj *sync, enum dsscomp_wait_phase phase, + int timeout); +int dsscomp_state_notifier(struct notifier_block *nb, + unsigned long arg, void *ptr); + +/* basic operation - if not using queues */ +int set_dss_ovl_info(struct dss2_ovl_info *oi); +int set_dss_mgr_info(struct dss2_mgr_info *mi, struct omapdss_ovl_cb *cb); +struct omap_overlay_manager *find_dss_mgr(int display_ix); +void swap_rb_in_ovl_info(struct dss2_ovl_info *oi); +void swap_rb_in_mgr_info(struct dss2_mgr_info *mi); + +/* + * Debug functions + */ +void dump_ovl_info(struct dsscomp_dev *cdev, struct dss2_ovl_info *oi); +void dump_comp_info(struct dsscomp_dev *cdev, struct dsscomp_setup_mgr_data *d, + const char *phase); +void dump_total_comp_info(struct dsscomp_dev *cdev, + struct dsscomp_setup_dispc_data *d, + const char *phase); +const char *dsscomp_get_color_name(enum omap_color_mode m); + +void dsscomp_dbg_comps(struct seq_file *s); +void dsscomp_dbg_gralloc(struct seq_file *s); + +#define log_state_str(s) (\ + (s) == DSSCOMP_STATE_ACTIVE ? "ACTIVE" : \ + (s) == DSSCOMP_STATE_APPLYING ? "APPLY'N" : \ + (s) == DSSCOMP_STATE_APPLIED ? "APPLIED" : \ + (s) == DSSCOMP_STATE_PROGRAMMED ? "PROGR'D" : \ + (s) == DSSCOMP_STATE_DISPLAYED ? "DISPL'D" : "INVALID") + +#define log_status_str(ev) ( \ + ((ev) & DSS_COMPLETION_CHANGED) ? "CHANGED" : \ + (ev) == DSS_COMPLETION_DISPLAYED ? "DISPLAYED" : \ + (ev) == DSS_COMPLETION_PROGRAMMED ? "PROGRAMMED" : \ + (ev) == DSS_COMPLETION_TORN ? "TORN" : \ + (ev) == DSS_COMPLETION_RELEASED ? "RELEASED" : \ + ((ev) & DSS_COMPLETION_RELEASED) ? "ECLIPSED" : "???") + +#ifdef CONFIG_DSSCOMP_DEBUG_LOG +extern struct dbg_event_t { + u32 ms, a1, a2, ix; + void *data; + const char *fmt; +} dbg_events[128]; +extern u32 dbg_event_ix; + +void dsscomp_dbg_events(struct seq_file *s); +#endif + +static inline +void __log_event(u32 ix, u32 ms, void *data, const char *fmt, u32 a1, u32 a2) +{ +#ifdef CONFIG_DSSCOMP_DEBUG_LOG + if (!ms) + ms = ktime_to_ms(ktime_get()); + dbg_events[dbg_event_ix].ms = ms; + dbg_events[dbg_event_ix].data = data; + dbg_events[dbg_event_ix].fmt = fmt; + dbg_events[dbg_event_ix].a1 = a1; + dbg_events[dbg_event_ix].a2 = a2; + dbg_events[dbg_event_ix].ix = ix; + dbg_event_ix = (dbg_event_ix + 1) % ARRAY_SIZE(dbg_events); +#endif +} + +#define log_event(ix, ms, data, fmt, a1, a2) \ + DO_IF_DEBUG_FS(__log_event(ix, ms, data, fmt, a1, a2)) + +#endif diff --git a/drivers/video/omap2/dsscomp/gralloc.c b/drivers/video/omap2/dsscomp/gralloc.c new file mode 100644 index 0000000..b75dfd9 --- /dev/null +++ b/drivers/video/omap2/dsscomp/gralloc.c @@ -0,0 +1,607 @@ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <mach/tiler.h> +#include <video/dsscomp.h> +#include <plat/dsscomp.h> +#include "dsscomp.h" + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif +static bool blanked; + +#define NUM_TILER1D_SLOTS 2 +#define TILER1D_SLOT_SIZE (16 << 20) + +static struct tiler1d_slot { + struct list_head q; + tiler_blk_handle slot; + u32 phys; + u32 size; + u32 *page_map; +} slots[NUM_TILER1D_SLOTS]; +static struct list_head free_slots; +static struct dsscomp_dev *cdev; +static DEFINE_MUTEX(mtx); +static struct semaphore free_slots_sem = + __SEMAPHORE_INITIALIZER(free_slots_sem, 0); + +/* gralloc composition sync object */ +struct dsscomp_gralloc_t { + void (*cb_fn)(void *, int); + void *cb_arg; + struct list_head q; + struct list_head slots; + atomic_t refs; + bool early_callback; + bool programmed; +}; + +/* queued gralloc compositions */ +static LIST_HEAD(flip_queue); + +static u32 ovl_use_mask[MAX_MANAGERS]; + +static void unpin_tiler_blocks(struct list_head *slots) +{ + struct tiler1d_slot *slot; + + /* unpin any tiler memory */ + list_for_each_entry(slot, slots, q) { + tiler_unpin_block(slot->slot); + up(&free_slots_sem); + } + + /* free tiler slots */ + list_splice_init(slots, &free_slots); +} + +static void dsscomp_gralloc_cb(void *data, int status) +{ + struct dsscomp_gralloc_t *gsync = data, *gsync_; + bool early_cbs = true; + LIST_HEAD(done); + + mutex_lock(&mtx); + if (gsync->early_callback && status == DSS_COMPLETION_PROGRAMMED) + gsync->programmed = true; + + if (status & DSS_COMPLETION_RELEASED) { + if (atomic_dec_and_test(&gsync->refs)) + unpin_tiler_blocks(&gsync->slots); + + log_event(0, 0, gsync, "--refs=%d on %s", + atomic_read(&gsync->refs), + (u32) log_status_str(status)); + } + + /* get completed list items in order, if any */ + list_for_each_entry_safe(gsync, gsync_, &flip_queue, q) { + if (gsync->cb_fn) { + early_cbs &= gsync->early_callback && gsync->programmed; + if (early_cbs) { + gsync->cb_fn(gsync->cb_arg, 1); + gsync->cb_fn = NULL; + } + } + if (gsync->refs.counter && gsync->cb_fn) + break; + if (gsync->refs.counter == 0) + list_move_tail(&gsync->q, &done); + } + mutex_unlock(&mtx); + + /* call back for completed composition with mutex unlocked */ + list_for_each_entry_safe(gsync, gsync_, &done, q) { + if (debug & DEBUG_GRALLOC_PHASES) + dev_info(DEV(cdev), "[%p] completed flip\n", gsync); + + log_event(0, 0, gsync, "calling %pf [%p]", + (u32) gsync->cb_fn, (u32) gsync->cb_arg); + + if (gsync->cb_fn) + gsync->cb_fn(gsync->cb_arg, 1); + kfree(gsync); + } +} + +/* This is just test code for now that does the setup + apply. + It still uses userspace virtual addresses, but maps non + TILER buffers into 1D */ +int dsscomp_gralloc_queue_ioctl(struct dsscomp_setup_dispc_data *d) +{ + struct tiler_pa_info *pas[MAX_OVERLAYS]; + s32 ret; + u32 i; + + if (d->num_ovls > MAX_OVERLAYS) + return -EINVAL; + + /* convert virtual addresses to physical and get tiler pa infos */ + for (i = 0; i < d->num_ovls; i++) { + struct dss2_ovl_info *oi = d->ovls + i; + u32 addr = (u32) oi->address; + + pas[i] = NULL; + + /* assume virtual NV12 for now */ + if (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12) + oi->uv = tiler_virt2phys(addr + + oi->cfg.height * oi->cfg.stride); + else + oi->uv = 0; + oi->ba = tiler_virt2phys(addr); + + /* map non-TILER buffers to 1D */ + if ((oi->ba < 0x60000000 || oi->ba >= 0x80000000) && oi->ba) + pas[i] = user_block_to_pa(addr & PAGE_MASK, + PAGE_ALIGN(oi->cfg.height * oi->cfg.stride + + (addr & ~PAGE_MASK)) >> PAGE_SHIFT); + } + ret = dsscomp_gralloc_queue(d, pas, false, NULL, NULL); + for (i = 0; i < d->num_ovls; i++) + tiler_pa_free(pas[i]); + return ret; +} + +int dsscomp_gralloc_queue(struct dsscomp_setup_dispc_data *d, + struct tiler_pa_info **pas, + bool early_callback, + void (*cb_fn)(void *, int), void *cb_arg) +{ + u32 i; + int r = 0; + struct omap_dss_device *dev; + struct omap_overlay_manager *mgr; + static DEFINE_MUTEX(local_mtx); + dsscomp_t comp[MAX_MANAGERS]; + u32 ovl_new_use_mask[MAX_MANAGERS]; + u32 mgr_set_mask = 0; + u32 ovl_set_mask = 0; + struct tiler1d_slot *slot = NULL; + u32 slot_used = 0; +#ifdef CONFIG_DEBUG_FS + u32 ms = ktime_to_ms(ktime_get()); +#endif + u32 channels[ARRAY_SIZE(d->mgrs)], ch; + int skip; + struct dsscomp_gralloc_t *gsync; + struct dss2_rect_t win = { .w = 0 }; + + /* reserve tiler areas if not already done so */ + dsscomp_gralloc_init(cdev); + + dump_total_comp_info(cdev, d, "queue"); + for (i = 0; i < d->num_ovls; i++) + dump_ovl_info(cdev, d->ovls + i); + + mutex_lock(&local_mtx); + + mutex_lock(&mtx); + + /* create sync object with 1 temporary ref */ + gsync = kzalloc(sizeof(*gsync), GFP_KERNEL); + gsync->cb_arg = cb_arg; + gsync->cb_fn = cb_fn; + gsync->refs.counter = 1; + gsync->early_callback = early_callback; + INIT_LIST_HEAD(&gsync->slots); + list_add_tail(&gsync->q, &flip_queue); + if (debug & DEBUG_GRALLOC_PHASES) + dev_info(DEV(cdev), "[%p] queuing flip\n", gsync); + + log_event(0, ms, gsync, "new in %pf (refs=1)", + (u32) dsscomp_gralloc_queue, 0); + + /* ignore frames while we are blanked */ + skip = blanked; + if (skip && (debug & DEBUG_PHASES)) + dev_info(DEV(cdev), "[%p,%08x] ignored\n", gsync, d->sync_id); + + /* mark blank frame by NULL tiler pa pointer */ + if (!skip && pas == NULL) + blanked = true; + + mutex_unlock(&mtx); + + d->num_mgrs = min(d->num_mgrs, (u16) ARRAY_SIZE(d->mgrs)); + d->num_ovls = min(d->num_ovls, (u16) ARRAY_SIZE(d->ovls)); + + memset(comp, 0, sizeof(comp)); + memset(ovl_new_use_mask, 0, sizeof(ovl_new_use_mask)); + + if (skip) + goto skip_comp; + + d->mode = DSSCOMP_SETUP_DISPLAY; + + /* mark managers we are using */ + for (i = 0; i < d->num_mgrs; i++) { + /* verify display is valid & connected, ignore if not */ + if (d->mgrs[i].ix >= cdev->num_displays) + continue; + dev = cdev->displays[d->mgrs[i].ix]; + if (!dev) { + dev_warn(DEV(cdev), "failed to get display%d\n", + d->mgrs[i].ix); + continue; + } + mgr = dev->manager; + if (!mgr) { + dev_warn(DEV(cdev), "no manager for display%d\n", + d->mgrs[i].ix); + continue; + } + channels[i] = ch = mgr->id; + mgr_set_mask |= 1 << ch; + + /* swap red & blue if requested */ + if (d->mgrs[i].swap_rb) + swap_rb_in_mgr_info(d->mgrs + i); + } + + /* create dsscomp objects for set managers (including active ones) */ + for (ch = 0; ch < MAX_MANAGERS; ch++) { + if (!(mgr_set_mask & (1 << ch)) && !ovl_use_mask[ch]) + continue; + + mgr = cdev->mgrs[ch]; + + comp[ch] = dsscomp_new(mgr); + if (IS_ERR(comp[ch])) { + comp[ch] = NULL; + dev_warn(DEV(cdev), "failed to get composition on %s\n", + mgr->name); + continue; + } + + /* set basic manager information for blanked managers */ + if (!(mgr_set_mask & (1 << ch))) { + struct dss2_mgr_info mi = { + .alpha_blending = true, + .ix = comp[ch]->frm.mgr.ix, + }; + dsscomp_set_mgr(comp[ch], &mi); + } + + comp[ch]->must_apply = true; + r = dsscomp_setup(comp[ch], d->mode, win); + if (r) + dev_err(DEV(cdev), "failed to setup comp (%d)\n", r); + } + + /* configure manager data from gralloc composition */ + for (i = 0; i < d->num_mgrs; i++) { + ch = channels[i]; + r = dsscomp_set_mgr(comp[ch], d->mgrs + i); + if (r) + dev_err(DEV(cdev), "failed to set mgr%d (%d)\n", ch, r); + } + + /* NOTE: none of the dsscomp sets should fail as composition is new */ + for (i = 0; i < d->num_ovls; i++) { + struct dss2_ovl_info *oi = d->ovls + i; + u32 mgr_ix = oi->cfg.mgr_ix; + u32 size; + + /* verify manager index */ + if (mgr_ix >= d->num_mgrs) { + dev_err(DEV(cdev), "invalid manager for ovl%d\n", + oi->cfg.ix); + continue; + } + ch = channels[mgr_ix]; + + /* skip overlays on compositions we could not create */ + if (!comp[ch]) + continue; + + /* swap red & blue if requested */ + if (d->mgrs[mgr_ix].swap_rb) + swap_rb_in_ovl_info(d->ovls + i); + + /* copy prior overlay to avoid mapping layers twice to 1D */ + if (oi->addressing == OMAP_DSS_BUFADDR_OVL_IX) { + unsigned int j = oi->ba; + if (j >= i) { + WARN(1, "Invalid clone layer (%u)", j); + goto skip_buffer; + } + + oi->ba = d->ovls[j].ba; + oi->uv = d->ovls[j].uv; + goto skip_map1d; + } else if (oi->addressing == OMAP_DSS_BUFADDR_FB) { + /* get fb */ + int fb_ix = (oi->ba >> 28); + int fb_uv_ix = (oi->uv >> 28); + struct fb_info *fbi = NULL, *fbi_uv = NULL; + size_t size = oi->cfg.height * oi->cfg.stride; + if (fb_ix >= num_registered_fb || + (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12 && + fb_uv_ix >= num_registered_fb)) { + WARN(1, "display has no framebuffer"); + goto skip_buffer; + } + + fbi = fbi_uv = registered_fb[fb_ix]; + if (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12) + fbi_uv = registered_fb[fb_uv_ix]; + + if (size + oi->ba > fbi->fix.smem_len || + (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12 && + (size >> 1) + oi->uv > fbi_uv->fix.smem_len)) { + WARN(1, "image outside of framebuffer memory"); + goto skip_buffer; + } + + oi->ba += fbi->fix.smem_start; + oi->uv += fbi_uv->fix.smem_start; + goto skip_map1d; + } + + /* map non-TILER buffers to 1D */ + + /* skip 2D and disabled layers */ + if (!pas[i] || !oi->cfg.enabled) + goto skip_map1d; + + if (!slot) { + if (down_timeout(&free_slots_sem, + msecs_to_jiffies(100))) { + dev_warn(DEV(cdev), "could not obtain tiler slot"); + goto skip_buffer; + } + mutex_lock(&mtx); + slot = list_first_entry(&free_slots, typeof(*slot), q); + list_move(&slot->q, &gsync->slots); + mutex_unlock(&mtx); + } + + size = oi->cfg.stride * oi->cfg.height; + if (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12) + size += size >> 2; + size = DIV_ROUND_UP(size, PAGE_SIZE); + + if (slot_used + size > slot->size) { + dev_err(DEV(cdev), "tiler slot not big enough for frame %d + %d > %d", + slot_used, size, slot->size); + goto skip_buffer; + } + + /* "map" into TILER 1D - will happen after loop */ + oi->ba = slot->phys + (slot_used << PAGE_SHIFT) + + (oi->ba & ~PAGE_MASK); + memcpy(slot->page_map + slot_used, pas[i]->mem, + sizeof(*slot->page_map) * size); + slot_used += size; + goto skip_map1d; + +skip_buffer: + oi->cfg.enabled = false; +skip_map1d: + + if (oi->cfg.enabled) + ovl_new_use_mask[ch] |= 1 << oi->cfg.ix; + + r = dsscomp_set_ovl(comp[ch], oi); + if (r) + dev_err(DEV(cdev), "failed to set ovl%d (%d)\n", + oi->cfg.ix, r); + else + ovl_set_mask |= 1 << oi->cfg.ix; + } + + if (slot && slot_used) { + r = tiler_pin_block(slot->slot, slot->page_map, + slot_used); + if (r) + dev_err(DEV(cdev), "failed to pin %d pages into" + " %d-pg slots (%d)\n", slot_used, + TILER1D_SLOT_SIZE >> PAGE_SHIFT, r); + } + + for (ch = 0; ch < MAX_MANAGERS; ch++) { + /* disable all overlays not specifically set from prior frame */ + u32 mask = ovl_use_mask[ch] & ~ovl_set_mask; + + if (!comp[ch]) + continue; + + while (mask) { + struct dss2_ovl_info oi = { + .cfg.zonly = true, + .cfg.enabled = false, + .cfg.ix = fls(mask) - 1, + }; + dsscomp_set_ovl(comp[ch], &oi); + mask &= ~(1 << oi.cfg.ix); + } + + /* associate dsscomp objects with this gralloc composition */ + comp[ch]->extra_cb = dsscomp_gralloc_cb; + comp[ch]->extra_cb_data = gsync; + atomic_inc(&gsync->refs); + log_event(0, ms, gsync, "++refs=%d for [%p]", + atomic_read(&gsync->refs), (u32) comp[ch]); + + r = dsscomp_delayed_apply(comp[ch]); + if (r) + dev_err(DEV(cdev), "failed to apply comp (%d)\n", r); + else + ovl_use_mask[ch] = ovl_new_use_mask[ch]; + } +skip_comp: + /* release sync object ref - this completes unapplied compositions */ + dsscomp_gralloc_cb(gsync, DSS_COMPLETION_RELEASED); + + mutex_unlock(&local_mtx); + + return r; +} + +#ifdef CONFIG_EARLYSUSPEND +static int blank_complete; +static DECLARE_WAIT_QUEUE_HEAD(early_suspend_wq); + +static void dsscomp_early_suspend_cb(void *data, int status) +{ + blank_complete = true; + wake_up(&early_suspend_wq); +} + +static void dsscomp_early_suspend(struct early_suspend *h) +{ + struct dsscomp_setup_dispc_data d = { + .num_mgrs = 0, + }; + int err; + + pr_info("DSSCOMP: %s\n", __func__); + + /* use gralloc queue as we need to blank all screens */ + blank_complete = false; + dsscomp_gralloc_queue(&d, NULL, false, dsscomp_early_suspend_cb, NULL); + + /* wait until composition is displayed */ + err = wait_event_timeout(early_suspend_wq, blank_complete, + msecs_to_jiffies(500)); + if (err == 0) + pr_warn("DSSCOMP: timeout blanking screen\n"); + else + pr_info("DSSCOMP: blanked screen\n"); +} + +static void dsscomp_late_resume(struct early_suspend *h) +{ + pr_info("DSSCOMP: %s\n", __func__); + blanked = false; +} + +static struct early_suspend early_suspend_info = { + .suspend = dsscomp_early_suspend, + .resume = dsscomp_late_resume, + .level = EARLY_SUSPEND_LEVEL_DISABLE_FB, +}; +#endif + +void dsscomp_dbg_gralloc(struct seq_file *s) +{ +#ifdef CONFIG_DEBUG_FS + struct dsscomp_gralloc_t *g; + struct tiler1d_slot *t; + dsscomp_t c; + int i; + + mutex_lock(&dbg_mtx); + seq_printf(s, "ACTIVE GRALLOC FLIPS\n\n"); + list_for_each_entry(g, &flip_queue, q) { + char *sep = ""; + seq_printf(s, " [%p] (refs=%d)\n" + " slots=[", g, atomic_read(&g->refs)); + list_for_each_entry(t, &g->slots, q) { + seq_printf(s, "%s%08x", sep, t->phys); + sep = ", "; + } + seq_printf(s, "]\n cmdcb=[%08x] ", (u32) g->cb_arg); + if (g->cb_fn) + seq_printf(s, "%pf\n\n ", g->cb_fn); + else + seq_printf(s, "(called)\n\n "); + + list_for_each_entry(c, &dbg_comps, dbg_q) { + if (c->extra_cb && c->extra_cb_data == g) + seq_printf(s, "| %8s ", + cdev->mgrs[c->ix]->name); + } + seq_printf(s, "\n "); + list_for_each_entry(c, &dbg_comps, dbg_q) { + if (c->extra_cb && c->extra_cb_data == g) + seq_printf(s, "| [%08x] %7s ", (u32) c, + log_state_str(c->state)); + } +#ifdef CONFIG_DSSCOMP_DEBUG_LOG + for (i = 0; i < ARRAY_SIZE(c->dbg_log); i++) { + int go = false; + seq_printf(s, "\n "); + list_for_each_entry(c, &dbg_comps, dbg_q) { + if (!c->extra_cb || c->extra_cb_data != g) + continue; + if (i < c->dbg_used) { + u32 t = c->dbg_log[i].t; + u32 state = c->dbg_log[i].state; + seq_printf(s, "| % 6d.%03d %7s ", + t / 1000, t % 1000, + log_state_str(state)); + go |= c->dbg_used > i + 1; + } else { + seq_printf(s, "%-21s", "|"); + } + } + if (!go) + break; + } +#endif + seq_printf(s, "\n\n"); + } + seq_printf(s, "\n"); + mutex_unlock(&dbg_mtx); +#endif +} + +void dsscomp_gralloc_init(struct dsscomp_dev *cdev_) +{ + int i; + + /* save at least cdev pointer */ + if (!cdev && cdev_) { + cdev = cdev_; + +#ifdef CONFIG_HAS_EARLYSUSPEND + register_early_suspend(&early_suspend_info); +#endif + } + + if (!free_slots.next) { + INIT_LIST_HEAD(&free_slots); + for (i = 0; i < NUM_TILER1D_SLOTS; i++) { + u32 phys; + tiler_blk_handle slot = + tiler_alloc_block_area(TILFMT_PAGE, + TILER1D_SLOT_SIZE, 1, &phys, NULL); + if (IS_ERR_OR_NULL(slot)) { + pr_err("could not allocate slot"); + break; + } + slots[i].slot = slot; + slots[i].phys = phys; + slots[i].size = TILER1D_SLOT_SIZE >> PAGE_SHIFT; + slots[i].page_map = kmalloc(sizeof(*slots[i].page_map) * + slots[i].size, GFP_KERNEL); + if (!slots[i].page_map) { + pr_err("could not allocate page_map"); + tiler_free_block_area(slot); + break; + } + list_add(&slots[i].q, &free_slots); + up(&free_slots_sem); + } + /* reset free_slots if no TILER memory could be reserved */ + if (!i) + ZERO(free_slots); + } +} + +void dsscomp_gralloc_exit(void) +{ + struct tiler1d_slot *slot; + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&early_suspend_info); +#endif + + list_for_each_entry(slot, &free_slots, q) + tiler_free_block_area(slot->slot); + INIT_LIST_HEAD(&free_slots); +} diff --git a/drivers/video/omap2/dsscomp/queue.c b/drivers/video/omap2/dsscomp/queue.c new file mode 100644 index 0000000..c25d655 --- /dev/null +++ b/drivers/video/omap2/dsscomp/queue.c @@ -0,0 +1,807 @@ +/* + * linux/drivers/video/omap2/dsscomp/queue.c + * + * DSS Composition queueing support + * + * Copyright (C) 2011 Texas Instruments, Inc + * Author: Lajos Molnar <molnar@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/vmalloc.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ratelimit.h> + +#include <video/omapdss.h> +#include <video/dsscomp.h> +#include <plat/dsscomp.h> + +#include <linux/debugfs.h> + +#include "dsscomp.h" +/* queue state */ + +static DEFINE_MUTEX(mtx); + +/* free overlay structs */ +struct maskref { + u32 mask; + u32 refs[MAX_OVERLAYS]; +}; + +static struct { + struct workqueue_struct *apply_workq; + + u32 ovl_mask; /* overlays used on this display */ + struct maskref ovl_qmask; /* overlays queued to this display */ + bool blanking; +} mgrq[MAX_MANAGERS]; + +static struct workqueue_struct *cb_wkq; /* callback work queue */ +static struct dsscomp_dev *cdev; + +#ifdef CONFIG_DEBUG_FS +LIST_HEAD(dbg_comps); +DEFINE_MUTEX(dbg_mtx); +#endif + +#ifdef CONFIG_DSSCOMP_DEBUG_LOG +struct dbg_event_t dbg_events[128]; +u32 dbg_event_ix; +#endif + +static inline void __log_state(dsscomp_t c, void *fn, u32 ev) +{ +#ifdef CONFIG_DSSCOMP_DEBUG_LOG + if (c->dbg_used < ARRAY_SIZE(c->dbg_log)) { + u32 t = (u32) ktime_to_ms(ktime_get()); + c->dbg_log[c->dbg_used].t = t; + c->dbg_log[c->dbg_used++].state = c->state; + __log_event(20 * c->ix + 20, t, c, ev ? "%pf on %s" : "%pf", + (u32) fn, (u32) log_status_str(ev)); + } +#endif +} +#define log_state(c, fn, ev) DO_IF_DEBUG_FS(__log_state(c, fn, ev)) + +static inline void maskref_incbit(struct maskref *om, u32 ix) +{ + om->refs[ix]++; + om->mask |= 1 << ix; +} + +static void maskref_decmask(struct maskref *om, u32 mask) +{ + while (mask) { + u32 ix = fls(mask) - 1, m = 1 << ix; + if (!--om->refs[ix]) + om->mask &= ~m; + mask &= ~m; + } +} + +/* + * =========================================================================== + * EXIT + * =========================================================================== + */ + +/* Initialize queue structures, and set up state of the displays */ +int dsscomp_queue_init(struct dsscomp_dev *cdev_) +{ + u32 i, j; + cdev = cdev_; + + if (ARRAY_SIZE(mgrq) < cdev->num_mgrs) + return -EINVAL; + + ZERO(mgrq); + for (i = 0; i < cdev->num_mgrs; i++) { + struct omap_overlay_manager *mgr; + mgrq[i].apply_workq = create_singlethread_workqueue("dsscomp_apply"); + if (!mgrq[i].apply_workq) + goto error; + + /* record overlays on this display */ + mgr = cdev->mgrs[i]; + for (j = 0; j < cdev->num_ovls; j++) { + if (cdev->ovls[j]->info.enabled && + mgr && + cdev->ovls[j]->manager == mgr) + mgrq[i].ovl_mask |= 1 << j; + } + } + + cb_wkq = create_singlethread_workqueue("dsscomp_cb"); + if (!cb_wkq) + goto error; + + return 0; +error: + while (i--) + destroy_workqueue(mgrq[i].apply_workq); + return -ENOMEM; +} + +/* get display index from manager */ +static u32 get_display_ix(struct omap_overlay_manager *mgr) +{ + u32 i; + + /* handle if manager is not attached to a display */ + if (!mgr || !mgr->device) + return cdev->num_displays; + + /* find manager's display */ + for (i = 0; i < cdev->num_displays; i++) + if (cdev->displays[i] == mgr->device) + break; + + return i; +} + +/* + * =========================================================================== + * QUEUING SETUP OPERATIONS + * =========================================================================== + */ + +/* create a new composition for a display */ +dsscomp_t dsscomp_new(struct omap_overlay_manager *mgr) +{ + struct dsscomp_data *comp = NULL; + u32 display_ix = get_display_ix(mgr); + + /* check manager */ + u32 ix = mgr ? mgr->id : cdev->num_mgrs; + if (ix >= cdev->num_mgrs || display_ix >= cdev->num_displays) + return ERR_PTR(-EINVAL); + + /* allocate composition */ + comp = kzalloc(sizeof(*comp), GFP_KERNEL); + if (!comp) + return NULL; + + /* initialize new composition */ + comp->ix = ix; /* save where this composition came from */ + comp->ovl_mask = comp->ovl_dmask = 0; + comp->frm.sync_id = 0; + comp->frm.mgr.ix = display_ix; + comp->state = DSSCOMP_STATE_ACTIVE; + + DO_IF_DEBUG_FS({ + __log_state(comp, dsscomp_new, 0); + list_add(&comp->dbg_q, &dbg_comps); + }); + + return comp; +} +EXPORT_SYMBOL(dsscomp_new); + +/* returns overlays used in a composition */ +u32 dsscomp_get_ovls(dsscomp_t comp) +{ + u32 mask; + + mutex_lock(&mtx); + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + mask = comp->ovl_mask; + mutex_unlock(&mtx); + + return mask; +} +EXPORT_SYMBOL(dsscomp_get_ovls); + +/* set overlay info */ +int dsscomp_set_ovl(dsscomp_t comp, struct dss2_ovl_info *ovl) +{ + int r = -EBUSY; + u32 i, mask, oix, ix; + struct omap_overlay *o; + + mutex_lock(&mtx); + + BUG_ON(!ovl); + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + + ix = comp->ix; + + if (ovl->cfg.ix >= cdev->num_ovls) { + r = -EINVAL; + goto done; + } + + /* if overlay is already part of the composition */ + mask = 1 << ovl->cfg.ix; + if (mask & comp->ovl_mask) { + /* look up overlay */ + for (oix = 0; oix < comp->frm.num_ovls; oix++) { + if (comp->ovls[oix].cfg.ix == ovl->cfg.ix) + break; + } + BUG_ON(oix == comp->frm.num_ovls); + } else { + /* check if ovl is free to use */ + if (comp->frm.num_ovls >= ARRAY_SIZE(comp->ovls)) + goto done; + + /* not in any other displays queue */ + if (mask & ~mgrq[ix].ovl_qmask.mask) { + for (i = 0; i < cdev->num_mgrs; i++) { + if (i == ix) + continue; + if (mgrq[i].ovl_qmask.mask & mask) + goto done; + } + } + + /* and disabled (unless forced) if on another manager */ + o = cdev->ovls[ovl->cfg.ix]; + if (o->info.enabled && (!o->manager || o->manager->id != ix)) + goto done; + + /* add overlay to composition & display */ + comp->ovl_mask |= mask; + oix = comp->frm.num_ovls++; + maskref_incbit(&mgrq[ix].ovl_qmask, ovl->cfg.ix); + } + + comp->ovls[oix] = *ovl; + r = 0; +done: + mutex_unlock(&mtx); + + return r; +} +EXPORT_SYMBOL(dsscomp_set_ovl); + +/* get overlay info */ +int dsscomp_get_ovl(dsscomp_t comp, u32 ix, struct dss2_ovl_info *ovl) +{ + int r; + u32 oix; + + mutex_lock(&mtx); + + BUG_ON(!ovl); + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + + if (ix >= cdev->num_ovls) { + r = -EINVAL; + } else if (comp->ovl_mask & (1 << ix)) { + r = 0; + for (oix = 0; oix < comp->frm.num_ovls; oix++) + if (comp->ovls[oix].cfg.ix == ovl->cfg.ix) { + *ovl = comp->ovls[oix]; + break; + } + BUG_ON(oix == comp->frm.num_ovls); + } else { + r = -ENOENT; + } + + mutex_unlock(&mtx); + + return r; +} +EXPORT_SYMBOL(dsscomp_get_ovl); + +/* set manager info */ +int dsscomp_set_mgr(dsscomp_t comp, struct dss2_mgr_info *mgr) +{ + mutex_lock(&mtx); + + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + BUG_ON(mgr->ix != comp->frm.mgr.ix); + + comp->frm.mgr = *mgr; + + mutex_unlock(&mtx); + + return 0; +} +EXPORT_SYMBOL(dsscomp_set_mgr); + +/* get manager info */ +int dsscomp_get_mgr(dsscomp_t comp, struct dss2_mgr_info *mgr) +{ + mutex_lock(&mtx); + + BUG_ON(!mgr); + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + + *mgr = comp->frm.mgr; + + mutex_unlock(&mtx); + + return 0; +} +EXPORT_SYMBOL(dsscomp_get_mgr); + +/* get manager info */ +int dsscomp_setup(dsscomp_t comp, enum dsscomp_setup_mode mode, + struct dss2_rect_t win) +{ + mutex_lock(&mtx); + + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + + comp->frm.mode = mode; + comp->frm.win = win; + + mutex_unlock(&mtx); + + return 0; +} +EXPORT_SYMBOL(dsscomp_setup); + +/* + * =========================================================================== + * QUEUING COMMITTING OPERATIONS + * =========================================================================== + */ +void dsscomp_drop(dsscomp_t comp) +{ + /* decrement unprogrammed references */ + if (comp->state < DSSCOMP_STATE_PROGRAMMED) + maskref_decmask(&mgrq[comp->ix].ovl_qmask, comp->ovl_mask); + comp->state = 0; + + if (debug & DEBUG_COMPOSITIONS) + dev_info(DEV(cdev), "[%p] released\n", comp); + + DO_IF_DEBUG_FS(list_del(&comp->dbg_q)); + + kfree(comp); +} +EXPORT_SYMBOL(dsscomp_drop); + +struct dsscomp_cb_work { + struct work_struct work; + struct dsscomp_data *comp; + int status; +}; + +static void dsscomp_mgr_delayed_cb(struct work_struct *work) +{ + struct dsscomp_cb_work *wk = container_of(work, typeof(*wk), work); + struct dsscomp_data *comp = wk->comp; + int status = wk->status; + u32 ix; + + kfree(work); + + mutex_lock(&mtx); + + BUG_ON(comp->state == DSSCOMP_STATE_ACTIVE); + ix = comp->ix; + + /* call extra callbacks if requested */ + if (comp->extra_cb) + comp->extra_cb(comp->extra_cb_data, status); + + /* handle programming & release */ + if (status == DSS_COMPLETION_PROGRAMMED) { + comp->state = DSSCOMP_STATE_PROGRAMMED; + log_state(comp, dsscomp_mgr_delayed_cb, status); + + /* update used overlay mask */ + mgrq[ix].ovl_mask = comp->ovl_mask & ~comp->ovl_dmask; + maskref_decmask(&mgrq[ix].ovl_qmask, comp->ovl_mask); + + if (debug & DEBUG_PHASES) + dev_info(DEV(cdev), "[%p] programmed\n", comp); + } else if ((status == DSS_COMPLETION_DISPLAYED) && + comp->state == DSSCOMP_STATE_PROGRAMMED) { + /* composition is 1st displayed */ + comp->state = DSSCOMP_STATE_DISPLAYED; + log_state(comp, dsscomp_mgr_delayed_cb, status); + if (debug & DEBUG_PHASES) + dev_info(DEV(cdev), "[%p] displayed\n", comp); + } else if (status & DSS_COMPLETION_RELEASED) { + /* composition is no longer displayed */ + log_event(20 * comp->ix + 20, 0, comp, "%pf on %s", + (u32) dsscomp_mgr_delayed_cb, + (u32) log_status_str(status)); + dsscomp_drop(comp); + } + mutex_unlock(&mtx); +} + +static u32 dsscomp_mgr_callback(void *data, int id, int status) +{ + struct dsscomp_data *comp = data; + + if (status == DSS_COMPLETION_PROGRAMMED || + (status == DSS_COMPLETION_DISPLAYED && + comp->state != DSSCOMP_STATE_DISPLAYED) || + (status & DSS_COMPLETION_RELEASED)) { + struct dsscomp_cb_work *wk = kzalloc(sizeof(*wk), GFP_ATOMIC); + wk->comp = comp; + wk->status = status; + INIT_WORK(&wk->work, dsscomp_mgr_delayed_cb); + queue_work(cb_wkq, &wk->work); + } + + /* get each callback only once */ + return ~status; +} + +static inline bool dssdev_manually_updated(struct omap_dss_device *dev) +{ + return dev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE && + dev->driver->get_update_mode(dev) != OMAP_DSS_UPDATE_AUTO; +} + +/* apply composition */ +/* at this point the composition is not on any queue */ +static int dsscomp_apply(dsscomp_t comp) +{ + int i, r = -EFAULT; + u32 dmask, display_ix; + struct omap_dss_device *dssdev; + struct omap_dss_driver *drv; + struct omap_overlay_manager *mgr; + struct omap_overlay *ovl; + struct dsscomp_setup_mgr_data *d; + u32 oix; + bool cb_programmed = false; + + struct omapdss_ovl_cb cb = { + .fn = dsscomp_mgr_callback, + .data = comp, + .mask = DSS_COMPLETION_DISPLAYED | + DSS_COMPLETION_PROGRAMMED | DSS_COMPLETION_RELEASED, + }; + + BUG_ON(comp->state != DSSCOMP_STATE_APPLYING); + + /* check if the display is valid and used */ + r = -ENODEV; + d = &comp->frm; + display_ix = d->mgr.ix; + if (display_ix >= cdev->num_displays) + goto done; + dssdev = cdev->displays[display_ix]; + if (!dssdev) + goto done; + + drv = dssdev->driver; + mgr = dssdev->manager; + if (!mgr || !drv || mgr->id >= cdev->num_mgrs) + goto done; + + dump_comp_info(cdev, d, "apply"); + + r = 0; + dmask = 0; + for (oix = 0; oix < comp->frm.num_ovls; oix++) { + struct dss2_ovl_info *oi = comp->ovls + oix; + + /* keep track of disabled overlays */ + if (!oi->cfg.enabled) + dmask |= 1 << oi->cfg.ix; + + if (r && !comp->must_apply) + continue; + + dump_ovl_info(cdev, oi); + + if (oi->cfg.ix >= cdev->num_ovls) { + r = -EINVAL; + continue; + } + ovl = cdev->ovls[oi->cfg.ix]; + + /* set overlays' manager & info */ + if (ovl->info.enabled && ovl->manager != mgr) { + r = -EBUSY; + goto skip_ovl_set; + } + if (ovl->manager != mgr) { + /* + * Ideally, we should call ovl->unset_manager(ovl), + * but it may block on go even though the disabling + * of the overlay already went through. So instead, + * we are just clearing the manager. + */ + ovl->manager = NULL; + r = ovl->set_manager(ovl, mgr); + if (r) + goto skip_ovl_set; + } + + r = set_dss_ovl_info(oi); +skip_ovl_set: + if (r && comp->must_apply) { + dev_err(DEV(cdev), "[%p] set ovl%d failed %d", comp, + oi->cfg.ix, r); + oi->cfg.enabled = false; + dmask |= 1 << oi->cfg.ix; + set_dss_ovl_info(oi); + } + } + + /* + * set manager's info - this also sets the completion callback, + * so if it succeeds, we will use the callback to complete the + * composition. Otherwise, we can skip the composition now. + */ + if (!r || comp->must_apply) { + r = set_dss_mgr_info(&d->mgr, &cb); + cb_programmed = r == 0; + } + + if (r && !comp->must_apply) { + dev_err(DEV(cdev), "[%p] set failed %d\n", comp, r); + goto done; + } else { + if (r) + dev_warn(DEV(cdev), "[%p] ignoring set failure %d\n", + comp, r); + comp->blank = dmask == comp->ovl_mask; + comp->ovl_dmask = dmask; + + /* + * Check other overlays that may also use this display. + * NOTE: This is only needed in case someone changes + * overlays via sysfs. We use comp->ovl_mask to refresh + * the overlays actually used on a manager when the + * composition is programmed. + */ + for (i = 0; i < cdev->num_ovls; i++) { + u32 mask = 1 << i; + if ((~comp->ovl_mask & mask) && + cdev->ovls[i]->info.enabled && + cdev->ovls[i]->manager == mgr) { + mutex_lock(&mtx); + comp->ovl_mask |= mask; + maskref_incbit(&mgrq[comp->ix].ovl_qmask, i); + mutex_unlock(&mtx); + } + } + } + + /* apply changes and call update on manual panels */ + /* no need for mutex as no callbacks are scheduled yet */ + comp->state = DSSCOMP_STATE_APPLIED; + log_state(comp, dsscomp_apply, 0); + + if (!d->win.w && !d->win.x) + d->win.w = dssdev->panel.timings.x_res - d->win.x; + if (!d->win.h && !d->win.y) + d->win.h = dssdev->panel.timings.y_res - d->win.y; + + mutex_lock(&mtx); + if (mgrq[comp->ix].blanking) { + pr_info_ratelimited("ignoring apply mgr(%s) while blanking\n", + mgr->name); + r = -ENODEV; + } else { + r = mgr->apply(mgr); + if (r) + dev_err(DEV(cdev), "failed while applying %d", r); + /* keep error if set_mgr_info failed */ + if (!r && !cb_programmed) + r = -EINVAL; + } + mutex_unlock(&mtx); + + /* + * TRICKY: try to unregister callback to see if callbacks have + * been applied (moved into DSS2 pipeline). Unregistering also + * avoids having to unnecessarily kick out compositions (which + * would result in screen blinking). If callbacks failed to apply, + * (e.g. could not set them or apply them) we will need to call + * them ourselves (we note this by returning an error). + */ + if (cb_programmed && r) { + /* clear error if callback already registered */ + if (omap_dss_manager_unregister_callback(mgr, &cb)) + r = 0; + } + /* if failed to apply, kick out prior composition */ + if (comp->must_apply && r) + mgr->blank(mgr, true); + + if (!r && (d->mode & DSSCOMP_SETUP_MODE_DISPLAY)) { + /* cannot handle update errors, so ignore them */ + if (dssdev_manually_updated(dssdev) && drv->update) + drv->update(dssdev, d->win.x, + d->win.y, d->win.w, d->win.h); + else + /* wait for sync to do smooth animations */ + mgr->wait_for_vsync(mgr); + } + +done: + return r; +} + +struct dsscomp_apply_work { + struct work_struct work; + dsscomp_t comp; +}; + +int dsscomp_state_notifier(struct notifier_block *nb, + unsigned long arg, void *ptr) +{ + struct omap_dss_device *dssdev = ptr; + enum omap_dss_display_state state = arg; + struct omap_overlay_manager *mgr = dssdev->manager; + if (mgr) { + mutex_lock(&mtx); + if (state == OMAP_DSS_DISPLAY_DISABLED) { + mgr->blank(mgr, true); + mgrq[mgr->id].blanking = true; + } else if (state == OMAP_DSS_DISPLAY_ACTIVE) { + mgrq[mgr->id].blanking = false; + } + mutex_unlock(&mtx); + } + return 0; +} + + +static void dsscomp_do_apply(struct work_struct *work) +{ + struct dsscomp_apply_work *wk = container_of(work, typeof(*wk), work); + /* complete compositions that failed to apply */ + if (dsscomp_apply(wk->comp)) + dsscomp_mgr_callback(wk->comp, -1, DSS_COMPLETION_ECLIPSED_SET); + kfree(wk); +} + +int dsscomp_delayed_apply(dsscomp_t comp) +{ + /* don't block in case we are called from interrupt context */ + struct dsscomp_apply_work *wk = kzalloc(sizeof(*wk), GFP_NOWAIT); + if (!wk) + return -ENOMEM; + + mutex_lock(&mtx); + + BUG_ON(comp->state != DSSCOMP_STATE_ACTIVE); + comp->state = DSSCOMP_STATE_APPLYING; + log_state(comp, dsscomp_delayed_apply, 0); + + if (debug & DEBUG_PHASES) + dev_info(DEV(cdev), "[%p] applying\n", comp); + mutex_unlock(&mtx); + + wk->comp = comp; + INIT_WORK(&wk->work, dsscomp_do_apply); + return queue_work(mgrq[comp->ix].apply_workq, &wk->work) ? 0 : -EBUSY; +} +EXPORT_SYMBOL(dsscomp_delayed_apply); + +/* + * =========================================================================== + * DEBUGFS + * =========================================================================== + */ + +#ifdef CONFIG_DEBUG_FS +void seq_print_comp(struct seq_file *s, dsscomp_t c) +{ + struct dsscomp_setup_mgr_data *d = &c->frm; + int i; + + seq_printf(s, " [%p]: %s%s\n", c, c->blank ? "blank " : "", + c->state == DSSCOMP_STATE_ACTIVE ? "ACTIVE" : + c->state == DSSCOMP_STATE_APPLYING ? "APPLYING" : + c->state == DSSCOMP_STATE_APPLIED ? "APPLIED" : + c->state == DSSCOMP_STATE_PROGRAMMED ? "PROGRAMMED" : + c->state == DSSCOMP_STATE_DISPLAYED ? "DISPLAYED" : + "???"); + seq_printf(s, " sync_id=%x, flags=%c%c%c\n", + d->sync_id, + (d->mode & DSSCOMP_SETUP_MODE_APPLY) ? 'A' : '-', + (d->mode & DSSCOMP_SETUP_MODE_DISPLAY) ? 'D' : '-', + (d->mode & DSSCOMP_SETUP_MODE_CAPTURE) ? 'C' : '-'); + for (i = 0; i < d->num_ovls; i++) { + struct dss2_ovl_info *oi; + struct dss2_ovl_cfg *g; + oi = d->ovls + i; + g = &oi->cfg; + if (g->zonly) { + seq_printf(s, " ovl%d={%s z%d}\n", + g->ix, g->enabled ? "ON" : "off", g->zorder); + } else { + seq_printf(s, " ovl%d={%s z%d %s%s *%d%%" + " %d*%d:%d,%d+%d,%d rot%d%s" + " => %d,%d+%d,%d %p/%p|%d}\n", + g->ix, g->enabled ? "ON" : "off", g->zorder, + dsscomp_get_color_name(g->color_mode) ? : "N/A", + g->pre_mult_alpha ? " premult" : "", + (g->global_alpha * 100 + 128) / 255, + g->width, g->height, g->crop.x, g->crop.y, + g->crop.w, g->crop.h, + g->rotation, g->mirror ? "+mir" : "", + g->win.x, g->win.y, g->win.w, g->win.h, + (void *) oi->ba, (void *) oi->uv, g->stride); + } + } + if (c->extra_cb) + seq_printf(s, " gsync=[%p] %pf\n\n", c->extra_cb_data, + c->extra_cb); + else + seq_printf(s, " gsync=[%p] (called)\n\n", c->extra_cb_data); +} +#endif + +void dsscomp_dbg_comps(struct seq_file *s) +{ +#ifdef CONFIG_DEBUG_FS + dsscomp_t c; + u32 i; + + mutex_lock(&dbg_mtx); + for (i = 0; i < cdev->num_mgrs; i++) { + struct omap_overlay_manager *mgr = cdev->mgrs[i]; + seq_printf(s, "ACTIVE COMPOSITIONS on %s\n\n", mgr->name); + list_for_each_entry(c, &dbg_comps, dbg_q) { + struct dss2_mgr_info *mi = &c->frm.mgr; + if (mi->ix < cdev->num_displays && + cdev->displays[mi->ix]->manager == mgr) + seq_print_comp(s, c); + } + + /* print manager cache */ + mgr->dump_cb(mgr, s); + } + mutex_unlock(&dbg_mtx); +#endif +} + +void dsscomp_dbg_events(struct seq_file *s) +{ +#ifdef CONFIG_DSSCOMP_DEBUG_LOG + u32 i; + struct dbg_event_t *d; + + mutex_lock(&dbg_mtx); + for (i = dbg_event_ix; i < dbg_event_ix + ARRAY_SIZE(dbg_events); i++) { + d = dbg_events + (i % ARRAY_SIZE(dbg_events)); + if (!d->ms) + continue; + seq_printf(s, "[% 5d.%03d] %*s[%08x] ", + d->ms / 1000, d->ms % 1000, + d->ix + ((u32) d->data) % 7, + "", (u32) d->data); + seq_printf(s, d->fmt, d->a1, d->a2); + seq_printf(s, "\n"); + } + mutex_unlock(&dbg_mtx); +#endif +} + +/* + * =========================================================================== + * EXIT + * =========================================================================== + */ +void dsscomp_queue_exit(void) +{ + if (cdev) { + int i; + for (i = 0; i < cdev->num_displays; i++) + destroy_workqueue(mgrq[i].apply_workq); + destroy_workqueue(cb_wkq); + cdev = NULL; + } +} +EXPORT_SYMBOL(dsscomp_queue_exit); diff --git a/drivers/video/omap2/hdcp/Kconfig b/drivers/video/omap2/hdcp/Kconfig new file mode 100644 index 0000000..a18f183 --- /dev/null +++ b/drivers/video/omap2/hdcp/Kconfig @@ -0,0 +1,14 @@ +menuconfig OMAP4_HDCP + bool "OMAP4 HDCP support" + depends on OMAP2_DSS && OMAP4_DSS_HDMI + default n + help + HDCP Interface. This adds the High Definition Content Protection Interface. + See http://www.digital-cp.com/ for HDCP specification. + +config OMAP4_HDCP_DEBUG + bool "OMAP4 HDCP Debugging" + depends on OMAP4_HDCP + default n + help + Enableds verbose debugging the the HDCP drivers diff --git a/drivers/video/omap2/hdcp/Makefile b/drivers/video/omap2/hdcp/Makefile new file mode 100644 index 0000000..80b0c0c --- /dev/null +++ b/drivers/video/omap2/hdcp/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for HDCP linux kernel module. +# + +ccflags-$(CONFIG_OMAP4_HDCP_DEBUG) = -DDEBUG -DHDCP_DEBUG + +obj-$(CONFIG_OMAP4_HDCP) += hdcp.o +hdcp-y := hdcp_top.o hdcp_lib.o hdcp_ddc.o diff --git a/drivers/video/omap2/hdcp/hdcp.h b/drivers/video/omap2/hdcp/hdcp.h new file mode 100644 index 0000000..e22a588 --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp.h @@ -0,0 +1,394 @@ +/* + * hdcp.h + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HDCP_H_ +#define _HDCP_H_ + + +/********************************/ +/* Structures related to ioctl */ +/********************************/ + +/* HDCP key size in 32-bit words */ +#define DESHDCP_KEY_SIZE 160 + +/* HDCP ioctl */ +#include <linux/ioctl.h> +#include <linux/types.h> + +struct hdcp_encrypt_control { + uint32_t in_key[DESHDCP_KEY_SIZE]; + uint32_t *out_key; +}; + +struct hdcp_enable_control { + uint32_t key[DESHDCP_KEY_SIZE]; + int nb_retry; +}; + +#define MAX_SHA_DATA_SIZE 645 +#define MAX_SHA_VPRIME_SIZE 20 + +struct hdcp_sha_in { + uint8_t data[MAX_SHA_DATA_SIZE]; + uint32_t byte_counter; + uint8_t vprime[MAX_SHA_VPRIME_SIZE]; +}; + +struct hdcp_wait_control { + uint32_t event; + struct hdcp_sha_in *data; +}; + +/* HDCP ioctl */ +#define HDCP_IOCTL_MAGIC 'h' +#define HDCP_ENABLE _IOW(HDCP_IOCTL_MAGIC, 0, \ + struct hdcp_enable_control) +#define HDCP_DISABLE _IO(HDCP_IOCTL_MAGIC, 1) +#define HDCP_ENCRYPT_KEY _IOWR(HDCP_IOCTL_MAGIC, 2, \ + struct hdcp_encrypt_control) +#define HDCP_QUERY_STATUS _IOWR(HDCP_IOCTL_MAGIC, 3, uint32_t) +#define HDCP_WAIT_EVENT _IOWR(HDCP_IOCTL_MAGIC, 4, \ + struct hdcp_wait_control) +#define HDCP_DONE _IOW(HDCP_IOCTL_MAGIC, 5, uint32_t) + +/* HDCP state */ +#define HDCP_STATE_DISABLED 0 +#define HDCP_STATE_INIT 1 +#define HDCP_STATE_AUTH_1ST_STEP 2 +#define HDCP_STATE_AUTH_2ND_STEP 3 +#define HDCP_STATE_AUTH_3RD_STEP 4 +#define HDCP_STATE_AUTH_FAIL_RESTARTING 5 +#define HDCP_STATE_AUTH_FAILURE 6 + +/* HDCP events */ +#define HDCP_EVENT_STEP1 (1 << 0x0) +#define HDCP_EVENT_STEP2 (1 << 0x1) +#define HDCP_EVENT_EXIT (1 << 0x2) + +/* HDCP user space status */ +#define HDCP_US_NO_ERR (0 << 8) +#define HDCP_US_FAILURE (1 << 8) + +#ifdef __KERNEL__ + +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/fs.h> + +#define _9032_AUTO_RI_ /* Auto Ri mode */ +#define _9032_BCAP_ /* BCAP polling */ +#undef _9032_AN_STOP_FIX_ + +#ifdef DEBUG +#define DDC_DBG /* Log DDC data */ +#undef POWER_TRANSITION_DBG /* Add wait loops to allow testing DSS power + * transition during HDCP */ +#endif + +/***************************/ +/* HW specific definitions */ +/***************************/ + +/* DESHDCP base address */ +/*----------------------*/ + +#define DSS_SS_FROM_L3__DESHDCP 0x58007000 + +/* DESHDCP registers */ +#define DESHDCP__DHDCP_CTRL 0x020 +#define DESHDCP__DHDCP_DATA_L 0x024 +#define DESHDCP__DHDCP_DATA_H 0x028 + +/* DESHDCP CTRL bits */ +#define DESHDCP__DHDCP_CTRL__DIRECTION_POS_F 2 +#define DESHDCP__DHDCP_CTRL__DIRECTION_POS_L 2 + +#define DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F 0 +#define DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L 0 + +/* HDMI WP base address */ +/*----------------------*/ +#define HDMI_WP 0x58006000 + +/* HDMI CORE SYSTEM base address */ +/*-------------------------------*/ + +#define HDMI_IP_CORE_SYSTEM 0x400 + +/* HDMI CORE registers */ +#define HDMI_IP_CORE_SYSTEM__DCTL 0x034 + +#define HDMI_IP_CORE_SYSTEM__HDCP_CTRL 0x03C + +#define HDMI_IP_CORE_SYSTEM__BKSV0 0x040 + +#define HDMI_IP_CORE_SYSTEM__AN0 0x054 + +#define HDMI_IP_CORE_SYSTEM__AKSV0 0x074 + +#define HDMI_IP_CORE_SYSTEM__R1 0x088 +#define HDMI_IP_CORE_SYSTEM__R2 0x08C + +#define HDMI_IP_CORE_SYSTEM__RI_CMD 0x09C +#define HDMI_IP_CORE_SYSTEM__RI_STAT 0x098 + +#define HDMI_IP_CORE_SYSTEM__INTR2 0x1C8 +#define HDMI_IP_CORE_SYSTEM__INTR3 0x1CC + +#define HDMI_IP_CORE_SYSTEM__INT_UNMASK2 0x1D8 +#define HDMI_IP_CORE_SYSTEM__INT_UNMASK3 0x1DC + +#define HDMI_IP_CORE_SYSTEM__SHA_CTRL 0x330 + +#define HDMI_IP_CORE_SYSTEM__INTR2__BCAP 0x80 +#define HDMI_IP_CORE_SYSTEM__INTR3__RI_ERR 0xF0 + +enum hdcp_repeater { + HDCP_RECEIVER = 0, + HDCP_REPEATER = 1 +}; + +enum encryption_state { + HDCP_ENC_OFF = 0x0, + HDCP_ENC_ON = 0x1 +}; + +/* HDMI CORE AV base address */ +/*---------------------------*/ + +#define HDMI_CORE_AV_BASE 0x900 +#ifndef HDMI_CORE_AV_HDMI_CTRL +#define HDMI_CORE_AV_HDMI_CTRL 0x0BC +#define HDMI_CORE_AV_PB_CTRL2 0x0FC +#define HDMI_CORE_AV_CP_BYTE1 0x37C +#endif + +#define HDMI_CORE_AV_HDMI_CTRL__HDMI_MODE 0x01 + +enum av_mute { + AV_MUTE_SET = 0x01, + AV_MUTE_CLEAR = 0x10 +}; +/***********************/ +/* HDCP DDC addresses */ +/***********************/ + +#define DDC_BKSV_ADDR 0x00 +#define DDC_Ri_ADDR 0x08 +#define DDC_AKSV_ADDR 0x10 +#define DDC_AN_ADDR 0x18 +#define DDC_V_ADDR 0x20 +#define DDC_BCAPS_ADDR 0x40 +#define DDC_BSTATUS_ADDR 0x41 +#define DDC_KSV_FIFO_ADDR 0x43 + +#define DDC_BKSV_LEN 5 +#define DDC_Ri_LEN 2 +#define DDC_AKSV_LEN 5 +#define DDC_AN_LEN 8 +#define DDC_V_LEN 20 +#define DDC_BCAPS_LEN 1 +#define DDC_BSTATUS_LEN 2 + +#define DDC_BIT_REPEATER 6 + +#define DDC_BSTATUS0_MAX_DEVS 0x80 +#define DDC_BSTATUS0_DEV_COUNT 0x7F +#define DDC_BSTATUS1_MAX_CASC 0x08 + +/***************************/ +/* Definitions */ +/***************************/ + +/* Status / error codes */ +#define HDCP_OK 0 +#define HDCP_DDC_ERROR 1 +#define HDCP_AUTH_FAILURE 2 +#define HDCP_AKSV_ERROR 3 +#define HDCP_3DES_ERROR 4 +#define HDCP_SHA1_ERROR 5 +#define HDCP_DRIVER_ERROR 6 +#define HDCP_CANCELLED_AUTH 7 + +#define HDCP_INFINITE_REAUTH 0x100 +#define HDCP_MAX_DDC_ERR 5 + +/* FIXME: should be 300ms delay between HDMI start frame event and HDCP enable + * (to respect 7 VSYNC delay in 24 Hz) + */ +#define HDCP_ENABLE_DELAY 300 +#define HDCP_R0_DELAY 110 +#define HDCP_KSV_TIMEOUT_DELAY 5000 +#define HDCP_REAUTH_DELAY 100 + +/* DDC access timeout in ms */ +#define HDCP_DDC_TIMEOUT 500 +#define HDCP_STOP_FRAME_BLOCKING_TIMEOUT (2*HDCP_DDC_TIMEOUT) + +/* Event source */ +#define HDCP_SRC_SHIFT 8 +#define HDCP_IOCTL_SRC (0x1 << HDCP_SRC_SHIFT) +#define HDCP_HDMI_SRC (0x2 << HDCP_SRC_SHIFT) +#define HDCP_IRQ_SRC (0x4 << HDCP_SRC_SHIFT) +#define HDCP_WORKQUEUE_SRC (0x8 << HDCP_SRC_SHIFT) + +/* Workqueue events */ +/* Note: HDCP_ENABLE_CTL, HDCP_R0_EXP_EVENT, HDCP_KSV_TIMEOUT_EVENT and + * HDCP_AUTH_REATT_EVENT can be cancelled by HDCP disabling + */ +#define HDCP_ENABLE_CTL (HDCP_IOCTL_SRC | 0) +#define HDCP_DISABLE_CTL (HDCP_IOCTL_SRC | 1) +#define HDCP_START_FRAME_EVENT (HDCP_HDMI_SRC | 2) +#define HDCP_STOP_FRAME_EVENT (HDCP_HDMI_SRC | 3) +#define HDCP_HPD_LOW_EVENT (HDCP_IRQ_SRC | 4) +#define HDCP_RI_FAIL_EVENT (HDCP_IRQ_SRC | 5) +#define HDCP_KSV_LIST_RDY_EVENT (HDCP_IRQ_SRC | 6) +#define HDCP_R0_EXP_EVENT (HDCP_WORKQUEUE_SRC | 7) +#define HDCP_KSV_TIMEOUT_EVENT (HDCP_WORKQUEUE_SRC | 8) +#define HDCP_AUTH_REATT_EVENT (HDCP_WORKQUEUE_SRC | 9) + +/* IRQ status */ +#define HDCP_IRQ_RI_FAIL 0x01 +#define HDCP_IRQ_KSV_RDY 0x02 + +enum hdcp_states { + HDCP_DISABLED, + HDCP_ENABLE_PENDING, + HDCP_AUTHENTICATION_START, + HDCP_WAIT_R0_DELAY, + HDCP_WAIT_KSV_LIST, + HDCP_LINK_INTEGRITY_CHECK, + HDCP_KEY_ENCRYPTION_ONGOING +}; + +enum hdmi_states { + HDMI_STOPPED, + HDMI_STARTED +}; + +struct hdcp_delayed_work { + struct delayed_work work; + int event; +}; + +struct hdcp { + void __iomem *hdmi_wp_base_addr; + void __iomem *deshdcp_base_addr; + struct mutex lock; + struct hdcp_enable_control *en_ctrl; + dev_t dev_id; + struct class *hdcp_class; + enum hdmi_states hdmi_state; + enum hdcp_states hdcp_state; + int auth_state; + struct delayed_work *pending_start; + /* Following variable should store works submitted from workqueue + * context + * WARNING: only ONE work at a time can be stored (no conflict + * should happen). It is used to allow cancelled pending works when + * disabling HDCP + */ + struct delayed_work *pending_wq_event; + int retry_cnt; + int dss_state; + int pending_disable; + int hdmi_restart; + int hpd_low; + spinlock_t spinlock; + struct workqueue_struct *workqueue; + int hdcp_up_event; + int hdcp_down_event; + bool hdcp_keys_loaded; +}; + +extern struct hdcp hdcp; +extern struct hdcp_sha_in sha_input; + + +/***************************/ +/* Macros for accessing HW */ +/***************************/ + +#define WR_REG_32(base, offset, val) __raw_writel(val, base + offset) +#define RD_REG_32(base, offset) __raw_readl(base + offset) + + +#undef FLD_MASK +#define FLD_MASK(start, end) (((1 << (start - end + 1)) - 1) << (end)) +#undef FLD_VAL +#define FLD_VAL(val, start, end) (((val) << end) & FLD_MASK(start, end)) +#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end)) +#define FLD_MOD(orig, val, start, end) \ + (((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end)) + +#define WR_FIELD_32(base, offset, start, end, val) \ + WR_REG_32(base, offset, FLD_MOD(RD_REG_32(base, offset), val, \ + start, end)) + +#define RD_FIELD_32(base, offset, start, end) \ + ((RD_REG_32(base, offset) & FLD_MASK(start, end)) >> (end)) + + +#undef DBG + +#ifdef HDCP_DEBUG +#define DBG(format, ...) \ + printk(KERN_DEBUG "HDCP: " format "\n", ## __VA_ARGS__) +#else +#define DBG(format, ...) +#endif + +/***************************/ +/* Function prototypes */ +/***************************/ + +int hdcp_user_space_task(int flags); + +/* 3DES */ +int hdcp_3des_load_key(uint32_t *deshdcp_encrypted_key); +void hdcp_3des_encrypt_key(struct hdcp_encrypt_control *enc_ctrl, + uint32_t out_key[DESHDCP_KEY_SIZE]); + +/* IP control */ +int hdcp_lib_disable(void); +int hdcp_lib_step1_start(void); +int hdcp_lib_step1_r0_check(void); +int hdcp_lib_step2(void); +int hdcp_lib_irq(void); +void hdcp_lib_auto_ri_check(bool state); +void hdcp_lib_auto_bcaps_rdy_check(bool state); +void hdcp_lib_set_av_mute(enum av_mute av_mute_state); +void hdcp_lib_set_encryption(enum encryption_state enc_state); +u8 hdcp_lib_check_repeater_bit_in_tx(void); + +/* DDC */ +int hdcp_ddc_read(u16 no_bytes, u8 addr, u8 *pdata); +int hdcp_ddc_write(u16 no_bytes, u8 addr, u8 *pdata); +void hdcp_ddc_abort(void); + +#endif /* __KERNEL__ */ + +#endif /* _HDCP_H_ */ diff --git a/drivers/video/omap2/hdcp/hdcp_ddc.c b/drivers/video/omap2/hdcp/hdcp_ddc.c new file mode 100644 index 0000000..e5104fa --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_ddc.c @@ -0,0 +1,310 @@ +/* + * hdcp_ddc.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include "hdcp.h" +#include "hdcp_ddc.h" + +/*----------------------------------------------------------------------------- + * Function: hdcp_suspend_resume_auto_ri + *----------------------------------------------------------------------------- + */ +static int hdcp_suspend_resume_auto_ri(enum ri_suspend_resume state) +{ + static u8 OldRiStat, OldRiCommand; + u8 TimeOut = 10; + + /* Suspend Auto Ri in order to allow FW access MDDC bus. + * Poll 0x72:0x26[0] for MDDC bus availability or timeout + */ + + DBG("hdcp_suspend_resume_auto_ri() state=%s", + state == AUTO_RI_SUSPEND ? "SUSPEND" : "RESUME"); + + if (state == AUTO_RI_SUSPEND) { + /* Save original Auto Ri state */ + OldRiCommand = RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, 0, 0); + + /* Disable Auto Ri */ + hdcp_lib_auto_ri_check(false); + + /* Wait for HW to release MDDC bus */ + /* TODO: while loop / timeout to be enhanced */ + while (--TimeOut) { + if (!RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0)) + break; + } + + /* MDDC bus not relinquished */ + if (!TimeOut) { + printk(KERN_ERR "HDCP: Suspending Auto Ri failed !\n"); + return -HDCP_DDC_ERROR; + } + + OldRiStat = RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0); + } else { + /* If Auto Ri was enabled before it was suspended */ + if ((OldRiStat) && (OldRiCommand)) + /* Re-enable Auto Ri */ + hdcp_lib_auto_ri_check(false); + } + + return HDCP_OK; +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_start_ddc_transfer + *----------------------------------------------------------------------------- + */ +static int hdcp_start_ddc_transfer(mddc_type *mddc_cmd, u8 operation) +{ + u8 *cmd = (u8 *)mddc_cmd; + struct timeval t0, t1, t2; + u32 time_elapsed_ms = 0; + u32 i, size; + unsigned long flags; + +#ifdef _9032_AUTO_RI_ + if (hdcp_suspend_resume_auto_ri(AUTO_RI_SUSPEND)) + return -HDCP_DDC_ERROR; +#endif + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Abort Master DDC operation and Clear FIFO pointer */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + /* Sending DDC header, it'll clear DDC Status register too */ + for (i = 0; i < 7; i++) { + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_ADDR + i * sizeof(uint32_t), + cmd[i]); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_ADDR + + i * sizeof(uint32_t)); + } + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + do_gettimeofday(&t1); + memcpy(&t0, &t1, sizeof(t0)); + + i = 0; + size = mddc_cmd->nbytes_lsb + (mddc_cmd->nbytes_msb << 8); + + while ((i < size) && (hdcp.pending_disable == 0)) { + if (operation == DDC_WRITE) { + /* Write data to DDC FIFO as long as it is NOT full */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS, 3, 3) + == 0) { + WR_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_DATA, + mddc_cmd->pdata[i++]); + do_gettimeofday(&t1); + } + } else if (operation == DDC_READ) { + /* Read from DDC FIFO as long as it is NOT empty */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS, 2, 2) + == 0) { + mddc_cmd->pdata[i++] = + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_DATA); + do_gettimeofday(&t1); + } + } + + do_gettimeofday(&t2); + time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 + + (t2.tv_usec - t1.tv_usec) / 1000; + + if (time_elapsed_ms > HDCP_DDC_TIMEOUT) { + DBG("DDC timeout - no data during %d ms - " + "status=%02x %u", + HDCP_DDC_TIMEOUT, + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS), + jiffies_to_msecs(jiffies)); + goto ddc_error; + } + } + + if (hdcp.pending_disable) + goto ddc_abort; + + /* Wait for the FIFO to be empty (end of transfer) */ + while ((RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS) != 0x4) && + (hdcp.pending_disable == 0)) { + do_gettimeofday(&t2); + time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 + + (t2.tv_usec - t1.tv_usec) / 1000; + + if (time_elapsed_ms > HDCP_DDC_TIMEOUT) { + DBG("DDC timeout - FIFO not getting empty - " + "status=%02x", + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + goto ddc_error; + } + } + + if (hdcp.pending_disable) + goto ddc_abort; + + DBG("DDC transfer: bytes: %d time_us: %lu status: %x", + i, + (t2.tv_sec - t0.tv_sec) * 1000000 + (t2.tv_usec - t0.tv_usec), + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + +#ifdef DDC_DBG + { + int k; + for (k = 0; k < i; k++) + printk(KERN_DEBUG "%02x ", mddc_cmd->pdata[k]); + printk(KERN_DEBUG "\n"); + } +#endif + +#ifdef _9032_AUTO_RI_ + /* Re-enable Auto Ri */ + if (hdcp_suspend_resume_auto_ri(AUTO_RI_RESUME)) + return -HDCP_DDC_ERROR; +#endif + + return HDCP_OK; + +ddc_error: + hdcp_ddc_abort(); + return -HDCP_DDC_ERROR; + +ddc_abort: + DBG("DDC transfer aborted - status=%02x", + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_operation + *----------------------------------------------------------------------------- + */ +static int hdcp_ddc_operation(u16 no_bytes, u8 addr, u8 *pdata, + enum ddc_operation operation) +{ + mddc_type mddc; + + mddc.slaveAddr = HDCPRX_SLV; + mddc.offset = 0; + mddc.regAddr = addr; + mddc.nbytes_lsb = no_bytes & 0xFF; + mddc.nbytes_msb = (no_bytes & 0x300) >> 8; + mddc.dummy = 0; + mddc.pdata = pdata; + + if (operation == DDC_READ) + mddc.cmd = MASTER_CMD_SEQ_RD; + else + mddc.cmd = MASTER_CMD_SEQ_WR; + + DBG("DDC %s: offset=%02x len=%d %u", operation == DDC_READ ? + "READ" : "WRITE", + addr, no_bytes, + jiffies_to_msecs(jiffies)); + + return hdcp_start_ddc_transfer(&mddc, operation); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_read + *----------------------------------------------------------------------------- + */ +int hdcp_ddc_read(u16 no_bytes, u8 addr, u8 *pdata) +{ + return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_READ); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_write + *----------------------------------------------------------------------------- + */ +int hdcp_ddc_write(u16 no_bytes, u8 addr, u8 *pdata) +{ + return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_WRITE); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_abort + *----------------------------------------------------------------------------- + */ +void hdcp_ddc_abort(void) +{ + unsigned long flags; + + /* In case of I2C_NO_ACK error, do not abort DDC to avoid + * DDC lockup + */ + if (RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS) & 0x20) + return; + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Abort Master DDC operation and Clear FIFO pointer */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_ABORT); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} diff --git a/drivers/video/omap2/hdcp/hdcp_ddc.h b/drivers/video/omap2/hdcp/hdcp_ddc.h new file mode 100644 index 0000000..83bae23 --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_ddc.h @@ -0,0 +1,111 @@ +/* + * hdcp_ddc.h + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define HDCPRX_SLV 0x74 + +#define MASTER_BASE 0xEC +#define MDDC_MANUAL_ADDR 0xEC +#define MDDC_SLAVE_ADDR 0xED +#define MDDC_SEGMENT_ADDR 0xEE +#define MDDC_OFFSET_ADDR 0xEF +#define MDDC_DIN_CNT_LSB_ADDR 0xF0 +#define MDDC_DIN_CNT_MSB_ADDR 0xF1 +#define MDDC_STATUS_ADDR 0xF2 +#define MDDC_COMMAND_ADDR 0xF3 +#define MDDC_FIFO_ADDR 0xF4 +#define MDDC_FIFO_CNT_ADDR 0xF5 + +#define BIT_MDDC_ST_IN_PROGR 0x10 +#define BIT_MDDC_ST_I2C_LOW 0x40 +#define BIT_MDDC_ST_NO_ACK 0x20 + +/* DDC Command[3:0]: + * + * 1111 - Abort transaction + * 1001 - Clear FIFO + * 1010 - Clock SCL + * 0000 - Current address read with no ACK on last byte + * 0001 - Current address read with ACK on last byte + * 0010 - Sequential read with no ACK on last byte + * 0011 - Sequential read with ACK on last byte + * 0100 - Enhanced DDC read with no ACK on last byte + * 0101 - Enhanced DDC read with ACK on last byte + * 0110 - Sequential write ignoring ACK on last byte + * 0111 - Sequential write requiring ACK on last byte + */ + +#define MASTER_CMD_ABORT 0x0f +#define MASTER_CMD_CLEAR_FIFO 0x09 +#define MASTER_CMD_CLOCK 0x0a +#define MASTER_CMD_CUR_RD 0x00 +#define MASTER_CMD_SEQ_RD 0x02 +#define MASTER_CMD_ENH_RD 0x04 +#define MASTER_CMD_SEQ_WR 0x06 + +#define MASTER_FIFO_WR_USE 0x01 +#define MASTER_FIFO_RD_USE 0x02 +#define MASTER_FIFO_EMPTY 0x04 +#define MASTER_FIFO_FULL 0x08 +#define MASTER_DDC_BUSY 0x10 +#define MASTER_DDC_NOACK 0x20 +#define MASTER_DDC_STUCK 0x40 +#define MASTER_DDC_RSVD 0x80 + +/* OMAP 4 HDMI TRM: */ +#define HDMI_IP_CORE_SYSTEM__DDC_MAN 0x3B0 +#define HDMI_IP_CORE_SYSTEM__DDC_ADDR 0x3B4 +#define HDMI_IP_CORE_SYSTEM__DDC_SEGM 0x3B8 +#define HDMI_IP_CORE_SYSTEM__DDC_OFFSET 0x3BC +#define HDMI_IP_CORE_SYSTEM__DDC_COUNT1 0x3C0 +#define HDMI_IP_CORE_SYSTEM__DDC_COUNT2 0x3C4 +#define HDMI_IP_CORE_SYSTEM__DDC_STATUS 0x3C8 +#define HDMI_IP_CORE_SYSTEM__DDC_CMD 0x3CC +#define HDMI_IP_CORE_SYSTEM__DDC_DATA 0x3D0 +#define HDMI_IP_CORE_SYSTEM__DDC_FIFOCNT 0x3D4 + +#define IIC_OK 0 +#define _IIC_CAPTURED 1 +#define _IIC_NOACK 2 +#define _MDDC_CAPTURED 3 +#define _MDDC_NOACK 4 +#define _MDDC_FIFO_FULL 5 + +typedef struct { + u8 slaveAddr; + u8 offset; /* "offset = DDC_SEGM register" */ + u8 regAddr; + u8 nbytes_lsb; + u8 nbytes_msb; + u8 dummy; + u8 cmd; + u8 *pdata; + u8 data[6]; +} mddc_type; + +enum ddc_operation { + DDC_READ, + DDC_WRITE +}; + +enum ri_suspend_resume { + AUTO_RI_SUSPEND, + AUTO_RI_RESUME +}; diff --git a/drivers/video/omap2/hdcp/hdcp_lib.c b/drivers/video/omap2/hdcp/hdcp_lib.c new file mode 100644 index 0000000..6e7850c --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_lib.c @@ -0,0 +1,838 @@ +/* + * hdcp_lib.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include <mach/omap4-common.h> +#include <linux/dma-mapping.h> +#include "hdcp.h" + +static void hdcp_lib_read_an(u8 *an); +static void hdcp_lib_read_aksv(u8 *ksv_data); +static void hdcp_lib_write_bksv(u8 *ksv_data); +static void hdcp_lib_generate_an(u8 *an); +static int hdcp_lib_r0_check(void); +static int hdcp_lib_sha_bstatus(struct hdcp_sha_in *sha); +static void hdcp_lib_set_repeater_bit_in_tx(enum hdcp_repeater rx_mode); +static void hdcp_lib_toggle_repeater_bit_in_tx(void); +static int hdcp_lib_initiate_step1(void); +static int hdcp_lib_check_ksv(uint8_t ksv[5]); + +#define PPA_SERVICE_HDCP_READ_M0 0x30 +#define PPA_SERVICE_HDCP_CHECK_V 0x31 +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_read_an + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_read_an(u8 *an) +{ + u8 i; + + for (i = 0; i < 8; i++) { + an[i] = (RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__AN0 + + i * sizeof(uint32_t))) & 0xFF; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_read_aksv + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_read_aksv(u8 *ksv_data) +{ + u8 i; + for (i = 0; i < 5; i++) { + ksv_data[i] = RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__AKSV0 + + i * sizeof(uint32_t)); + + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_write_bksv + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_write_bksv(u8 *ksv_data) +{ + u8 i; + for (i = 0; i < 5; i++) { + WR_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, HDMI_IP_CORE_SYSTEM__BKSV0 + + i * sizeof(uint32_t), ksv_data[i]); + } +} +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_generate_an + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_generate_an(u8 *an) +{ + /* Generate An using HDCP HW */ + DBG("hdcp_lib_generate_an()"); + + /* Start AN Gen */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 0); + + /* Delay of 10 ms */ + mdelay(10); + + /* Stop AN Gen */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 1); + + /* Must set 0x72:0x0F[3] twice to guarantee that takes effect */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 1); + + hdcp_lib_read_an(an); + + DBG("AN: %x %x %x %x %x %x %x %x", an[0], an[1], an[2], an[3], + an[4], an[5], an[6], an[7]); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_r0_check + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_r0_check(void) +{ + u8 ro_rx[2], ro_tx[2]; + + DBG("hdcp_lib_r0_check()"); + + /* DDC: Read Ri' from RX */ + if (hdcp_ddc_read(DDC_Ri_LEN, DDC_Ri_ADDR , (u8 *)&ro_rx)) + return -HDCP_DDC_ERROR; + + /* Read Ri in HDCP IP */ + ro_tx[0] = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__R1) & 0xFF; + + ro_tx[1] = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__R2) & 0xFF; + + /* Compare values */ + DBG("ROTX: %x%x RORX:%x%x", ro_tx[0], ro_tx[1], ro_rx[0], ro_rx[1]); + + if ((ro_rx[0] == ro_tx[0]) && (ro_rx[1] == ro_tx[1])) + return HDCP_OK; + else + return -HDCP_AUTH_FAILURE; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_sha_bstatus + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_sha_bstatus(struct hdcp_sha_in *sha) +{ + u8 data[2]; + + if (hdcp_ddc_read(DDC_BSTATUS_LEN, DDC_BSTATUS_ADDR, data)) + return -HDCP_DDC_ERROR; + + sha->data[sha->byte_counter++] = data[0]; + sha->data[sha->byte_counter++] = data[1]; + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_set_repeater_bit_in_tx(enum hdcp_repeater rx_mode) +{ + DBG("hdcp_lib_set_repeater_bit_in_tx() value=%d", rx_mode); + + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 4, 4, rx_mode); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_toggle_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_toggle_repeater_bit_in_tx(void) +{ + if (hdcp_lib_check_repeater_bit_in_tx()) + hdcp_lib_set_repeater_bit_in_tx(HDCP_RECEIVER); + else + hdcp_lib_set_repeater_bit_in_tx(HDCP_REPEATER); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_initiate_step1 + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_initiate_step1(void) +{ + /* HDCP authentication steps: + * 1) Read Bksv - check validity (is HDMI Rx supporting HDCP ?) + * 2) Initializes HDCP (CP reset release) + * 3) Read Bcaps - is HDMI Rx a repeater ? + * *** First part authentication *** + * 4) Read Bksv - check validity (is HDMI Rx supporting HDCP ?) + * 5) Generates An + * 6) DDC: Writes An, Aksv + * 7) DDC: Write Bksv + */ + uint8_t an_ksv_data[8], an_bksv_data[8]; + uint8_t rx_type; + + DBG("hdcp_lib_initiate_step1()\n"); + + /* DDC: Read BKSV from RX */ + if (hdcp_ddc_read(DDC_BKSV_LEN, DDC_BKSV_ADDR , an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + DBG("BKSV: %02x %02x %02x %02x %02x", an_ksv_data[0], an_ksv_data[1], + an_ksv_data[2], an_ksv_data[3], + an_ksv_data[4]); + + if (hdcp_lib_check_ksv(an_ksv_data)) { + DBG("BKSV error (number of 0 and 1)"); + return -HDCP_AUTH_FAILURE; + } + + /* TODO: Need to confirm it is required */ +#ifndef _9032_AN_STOP_FIX_ + hdcp_lib_toggle_repeater_bit_in_tx(); +#endif + + /* Release CP reset bit */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 2, 2, 1); + + /* Read BCAPS to determine if HDCP RX is a repeater */ + if (hdcp_ddc_read(DDC_BCAPS_LEN, DDC_BCAPS_ADDR, &rx_type)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + rx_type = FLD_GET(rx_type, DDC_BIT_REPEATER, DDC_BIT_REPEATER); + + /* Set repeater bit in HDCP CTRL */ + if (rx_type == 1) { + hdcp_lib_set_repeater_bit_in_tx(HDCP_REPEATER); + DBG("HDCP RX is a repeater"); + } else { + hdcp_lib_set_repeater_bit_in_tx(HDCP_RECEIVER); + DBG("HDCP RX is a receiver"); + } + +/* Power debug code */ +#ifdef POWER_TRANSITION_DBG + printk(KERN_INFO "\n**************************\n" + "AUTHENTICATION: WAIT FOR DSS TRANSITION\n" + "*************************\n"); + mdelay(10000); + printk(KERN_INFO "\n**************************\n" + "DONE\n" + "*************************\n"); +#endif + /* DDC: Read BKSV from RX */ + if (hdcp_ddc_read(DDC_BKSV_LEN, DDC_BKSV_ADDR , an_bksv_data)) + return -HDCP_DDC_ERROR; + + /* Generate An */ + hdcp_lib_generate_an(an_ksv_data); + + /* Authentication 1st step initiated HERE */ + + /* DDC: Write An */ + if (hdcp_ddc_write(DDC_AN_LEN, DDC_AN_ADDR , an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Read AKSV from IP: (HDCP AKSV register) */ + hdcp_lib_read_aksv(an_ksv_data); + + DBG("AKSV: %02x %02x %02x %02x %02x", an_ksv_data[0], an_ksv_data[1], + an_ksv_data[2], an_ksv_data[3], + an_ksv_data[4]); + + if (hdcp_lib_check_ksv(an_ksv_data)) { + printk(KERN_INFO "HDCP: AKSV error (number of 0 and 1)\n"); + return -HDCP_AKSV_ERROR; + } + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* DDC: Write AKSV */ + if (hdcp_ddc_write(DDC_AKSV_LEN, DDC_AKSV_ADDR, an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Write Bksv to IP */ + hdcp_lib_write_bksv(an_bksv_data); + + /* Check IP BKSV error */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 5, 5)) + return -HDCP_AUTH_FAILURE; + + /* Here BSKV should be checked against revokation list */ + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_check_ksv + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_check_ksv(uint8_t ksv[5]) +{ + int i, j; + int zero = 0, one = 0; + + for (i = 0; i < 5; i++) { + /* Count number of zero / one */ + for (j = 0; j < 8; j++) { + if (ksv[i] & (0x01 << j)) + one++; + else + zero++; + } + } + + if (one == zero) + return 0; + else + return -1; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_load_key + *----------------------------------------------------------------------------- + */ +int hdcp_3des_load_key(uint32_t *deshdcp_encrypted_key) +{ + int counter = 0, status = HDCP_OK; + + DBG("Loading HDCP keys..."); + + /* Set decryption mode in DES control register */ + WR_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_F, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_L, + 0x0); + + /* Write encrypted data */ + while (counter < DESHDCP_KEY_SIZE) { + /* Fill Data registers */ + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L, + deshdcp_encrypted_key[counter]); + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H, + deshdcp_encrypted_key[counter + 1]); + + /* Wait for output bit at '1' */ + while (RD_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L + ) != 0x1) + ; + + /* Dummy read (indeed data are transfered directly into + * key memory) + */ + if (RD_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L) != + 0x0) { + status = -HDCP_3DES_ERROR; + printk(KERN_ERR "HDCP: DESHDCP dummy read error\n"); + } + if (RD_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H) != + 0x0) { + status = -HDCP_3DES_ERROR; + printk(KERN_ERR "HDCP: DESHDCP dummy read error\n"); + } + + counter += 2; + } + + return status; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_encrypt_key + *----------------------------------------------------------------------------- + */ +void hdcp_3des_encrypt_key(struct hdcp_encrypt_control *enc_ctrl, + uint32_t out_key[DESHDCP_KEY_SIZE]) +{ + int counter = 0; + + DBG("Encrypting HDCP keys..."); + + /* Reset encrypted key array */ + for (counter = 0; counter < DESHDCP_KEY_SIZE; counter++) + out_key[counter] = 0; + + /* Set encryption mode in DES control register */ + WR_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_F, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_L, + 0x1); + + /* Write raw data and read encrypted data */ + counter = 0; + +#ifdef POWER_TRANSITION_DBG + printk(KERN_ERR "\n**************************\n" + "ENCRYPTION: WAIT FOR DSS TRANSITION\n" + "*************************\n"); + mdelay(10000); + printk(KER_INFO "\n**************************\n" + "DONE\n" + "*************************\n"); +#endif + + while (counter < DESHDCP_KEY_SIZE) { + /* Fill Data registers */ + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L, + enc_ctrl->in_key[counter]); + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H, + enc_ctrl->in_key[counter + 1]); + + /* Wait for output bit at '1' */ + while (RD_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L + ) != 0x1) + ; + + /* Read enrypted data */ + out_key[counter] = RD_REG_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_DATA_L); + out_key[counter + 1] = RD_REG_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_DATA_H); + + counter += 2; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_disable + *----------------------------------------------------------------------------- + */ +int hdcp_lib_disable() +{ + DBG("hdcp_lib_disable() %u", jiffies_to_msecs(jiffies)); + + /* CP reset */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 2, 2, 0); + + /* Clear AV mute in case it was set */ + hdcp_lib_set_av_mute(AV_MUTE_CLEAR); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_encryption + *----------------------------------------------------------------------------- + */ +void hdcp_lib_set_encryption(enum encryption_state enc_state) +{ + unsigned long flags; + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* HDCP_CTRL::ENC_EN set/clear */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 0, 0, enc_state); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + pr_info("HDCP: Encryption state changed: %s hdcp_ctrl: %02x", + enc_state == HDCP_ENC_OFF ? "OFF" : "ON", + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL)); + +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_av_mute + *----------------------------------------------------------------------------- + */ +void hdcp_lib_set_av_mute(enum av_mute av_mute_state) +{ + unsigned long flags; + + DBG("hdcp_lib_set_av_mute() av_mute=%d", av_mute_state); + + + spin_lock_irqsave(&hdcp.spinlock, flags); + + { + u8 RegVal, TimeOutCount = 64; + + RegVal = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2); + + /* PRguide-GPC: To change the content of the CP_BYTE1 register, + * CP_EN must be zero + * set PB_CTRL2 :: CP_RPT = 0 + */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, 2, 2, 0); + + /* Set/clear AV mute state */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_CP_BYTE1, av_mute_state); + + /* FIXME: This loop should be removed */ + while (TimeOutCount--) { + /* Continue in this loop till CP_EN becomes 0, + * prior to TimeOutCount becoming 0 */ + if (!RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, 3, 3)) + break; + } + + DBG(" timeoutcount=%d", TimeOutCount); + + /* FIXME: why is this if condition required?, according to prg, + * this shall be unconditioanlly */ + if (TimeOutCount) { + /* set PB_CTRL2 :: CP_EN = 1 & CP_RPT = 1 */ + RegVal = FLD_MOD(RegVal, 0x3, 3, 2); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, RegVal); + } + } + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_check_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +u8 hdcp_lib_check_repeater_bit_in_tx(void) +{ + return RD_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 4, 4); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_auto_ri_check + *----------------------------------------------------------------------------- + */ +void hdcp_lib_auto_ri_check(bool state) +{ + u8 reg_val; + unsigned long flags; + + DBG("hdcp_lib_auto_ri_check() state=%s", + state == true ? "ON" : "OFF"); + + spin_lock_irqsave(&hdcp.spinlock, flags); + + reg_val = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK3); + + reg_val = (state == true) ? (reg_val | 0xB0) : (reg_val & ~0xB0); + + /* Turn on/off the following Auto Ri interrupts */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK3, reg_val); + + /* Enable/Disable Ri */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, 0, 0, + ((state == true) ? 1 : 0)); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_auto_bcaps_rdy_check + *----------------------------------------------------------------------------- + */ +void hdcp_lib_auto_bcaps_rdy_check(bool state) +{ + u8 reg_val; + unsigned long flags; + + DBG("hdcp_lib_auto_bcaps_rdy_check() state=%s", + state == true ? "ON" : "OFF"); + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Enable KSV_READY / BACP_DONE interrupt */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK2, 7, 7, + ((state == true) ? 1 : 0)); + + /* Enable/Disable Ri & Bcap */ + reg_val = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + /* Enable RI_EN & BCAP_EN OR disable BCAP_EN */ + reg_val = (state == true) ? (reg_val | 0x3) : (reg_val & ~0x2); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, reg_val); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + DBG("hdcp_lib_auto_bcaps_rdy_check() Done\n"); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step1_start + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step1_start(void) +{ + u8 hdmi_mode; + int status; + + DBG("hdcp_lib_step1_start() %u", jiffies_to_msecs(jiffies)); + + /* Check if mode is HDMI or DVI */ + hdmi_mode = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_HDMI_CTRL) & + HDMI_CORE_AV_HDMI_CTRL__HDMI_MODE; + + DBG("RX mode: %s", hdmi_mode ? "HDMI" : "DVI"); + + /* Set AV Mute */ + hdcp_lib_set_av_mute(AV_MUTE_SET); + + /* Must turn encryption off when AVMUTE */ + hdcp_lib_set_encryption(HDCP_ENC_OFF); + + status = hdcp_lib_initiate_step1(); + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + else + return status; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step1_r0_check + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step1_r0_check(void) +{ + int status = HDCP_OK; + + /* HDCP authentication steps: + * 1) DDC: Read M0' + * 2) Compare M0 and M0' + * if Rx is a receiver: switch to authentication step 3 + * 3) Enable encryption / auto Ri check / disable AV mute + * if Rx is a repeater: switch to authentication step 2 + * 3) Get M0 from HDMI IP and store it for further processing (V) + * 4) Enable encryption / auto Ri check / auto BCAPS RDY polling + * Disable AV mute + */ + + DBG("hdcp_lib_step1_r0_check() %u", jiffies_to_msecs(jiffies)); + + status = hdcp_lib_r0_check(); + if (status < 0) + return status; + + /* Authentication 1st step done */ + + /* Now prepare 2nd step authentication in case of RX repeater and + * enable encryption / Ri check + */ + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + if (hdcp_lib_check_repeater_bit_in_tx()) { + status = omap4_secure_dispatcher(PPA_SERVICE_HDCP_READ_M0, + FLAG_START_CRITICAL, + 0, 0, 0, 0, 0); + /* Wait for user space */ + if (status) { + printk(KERN_ERR "HDCP: omap4_secure_dispatcher M0 error " + "%d\n", status); + return -HDCP_AUTH_FAILURE; + } + + DBG("hdcp_lib_set_encryption() %u", jiffies_to_msecs(jiffies)); + + /* Enable encryption */ + hdcp_lib_set_encryption(HDCP_ENC_ON); + +#ifdef _9032_AUTO_RI_ + /* Enable Auto Ri */ + hdcp_lib_auto_ri_check(true); +#endif + +#ifdef _9032_BCAP_ + /* Enable automatic BCAPS polling */ + hdcp_lib_auto_bcaps_rdy_check(true); +#endif + + /* Now, IP waiting for BCAPS ready bit */ + } else { + /* Receiver: enable encryption and auto Ri check */ + hdcp_lib_set_encryption(HDCP_ENC_ON); + +#ifdef _9032_AUTO_RI_ + /* Enable Auto Ri */ + hdcp_lib_auto_ri_check(true); +#endif + + } + + /* Clear AV mute */ + hdcp_lib_set_av_mute(AV_MUTE_CLEAR); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step2 + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step2(void) +{ + /* HDCP authentication steps: + * 1) Disable auto Ri check + * 2) DDC: read BStatus (nb of devices, MAX_DEV + */ + + u8 bstatus[2]; + int status = HDCP_OK; + + DBG("hdcp_lib_step2() %u", jiffies_to_msecs(jiffies)); + +#ifdef _9032_AUTO_RI_ + /* Disable Auto Ri */ + hdcp_lib_auto_ri_check(false); +#endif + + /* DDC: Read Bstatus (1st byte) from Rx */ + if (hdcp_ddc_read(DDC_BSTATUS_LEN, DDC_BSTATUS_ADDR, bstatus)) + return -HDCP_DDC_ERROR; + + /* Get KSV list size */ + DBG("KSV list size: %d", bstatus[0] & DDC_BSTATUS0_DEV_COUNT); + sha_input.byte_counter = (bstatus[0] & DDC_BSTATUS0_DEV_COUNT) * 5; + + /* Check BStatus topology errors */ + if (bstatus[0] & DDC_BSTATUS0_MAX_DEVS) { + DBG("MAX_DEV_EXCEEDED set"); + return -HDCP_AUTH_FAILURE; + } + + if (bstatus[1] & DDC_BSTATUS1_MAX_CASC) { + DBG("MAX_CASCADE_EXCEEDED set"); + return -HDCP_AUTH_FAILURE; + } + + DBG("Retrieving KSV list..."); + + /* Clear all SHA input data */ + /* TODO: should be done earlier at HDCP init */ + memset(sha_input.data, 0, MAX_SHA_DATA_SIZE); + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* DDC: read KSV list */ + if (sha_input.byte_counter) { + if (hdcp_ddc_read(sha_input.byte_counter, DDC_KSV_FIFO_ADDR, + (u8 *)&sha_input.data)) + return -HDCP_DDC_ERROR; + } + + /* Read and add Bstatus */ + if (hdcp_lib_sha_bstatus(&sha_input)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Read V' */ + if (hdcp_ddc_read(DDC_V_LEN, DDC_V_ADDR, sha_input.vprime)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* clear sha_input values in cache*/ + dma_sync_single_for_device(NULL, + __pa((u32)(&sha_input)), + sizeof(struct hdcp_sha_in), + DMA_TO_DEVICE); + + status = omap4_secure_dispatcher(PPA_SERVICE_HDCP_CHECK_V, + FLAG_START_CRITICAL, + 1, __pa((u32)&sha_input), 0, 0, 0); + /* Wait for user space */ + if (status) { + printk(KERN_ERR "HDCP: omap4_secure_dispatcher CHECH_V error " + "%d\n", status); + return -HDCP_AUTH_FAILURE; + } + + if (status == HDCP_OK) { + /* Re-enable Ri check */ +#ifdef _9032_AUTO_RI_ + hdcp_lib_auto_ri_check(true); +#endif + } + + return status; +} diff --git a/drivers/video/omap2/hdcp/hdcp_top.c b/drivers/video/omap2/hdcp/hdcp_top.c new file mode 100644 index 0000000..1bb32f4 --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_top.c @@ -0,0 +1,1050 @@ +/* + * hdcp_top.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/completion.h> +#include <linux/miscdevice.h> +#include <linux/firmware.h> +#include "../../hdmi_ti_4xxx_ip.h" +#include "../dss/dss.h" +#include "hdcp.h" + +struct hdcp hdcp; +struct hdcp_sha_in sha_input; + +/* State machine / workqueue */ +static void hdcp_wq_disable(void); +static void hdcp_wq_start_authentication(void); +static void hdcp_wq_check_r0(void); +static void hdcp_wq_step2_authentication(void); +static void hdcp_wq_authentication_failure(void); +static void hdcp_work_queue(struct work_struct *work); +static struct delayed_work *hdcp_submit_work(int event, int delay); +static void hdcp_cancel_work(struct delayed_work **work); + +/* Callbacks */ +static void hdcp_start_frame_cb(void); +static void hdcp_irq_cb(int hpd_low); + +/* Control */ +static long hdcp_enable_ctl(void __user *argp); +static long hdcp_disable_ctl(void); +static long hdcp_query_status_ctl(void __user *argp); +static long hdcp_encrypt_key_ctl(void __user *argp); + +/* Driver */ +static int __init hdcp_init(void); +static void __exit hdcp_exit(void); + +struct completion hdcp_comp; +static DECLARE_WAIT_QUEUE_HEAD(hdcp_up_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(hdcp_down_wait_queue); + +#define DSS_POWER + +/*----------------------------------------------------------------------------- + * Function: hdcp_request_dss + *----------------------------------------------------------------------------- + */ +static void hdcp_request_dss(void) +{ +#ifdef DSS_POWER + hdcp.dss_state = dss_runtime_get(); +#endif +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_user_space_task + *----------------------------------------------------------------------------- + */ +int hdcp_user_space_task(int flags) +{ + int ret; + + DBG("Wait for user space task %x\n", flags); + hdcp.hdcp_up_event = flags & 0xFF; + hdcp.hdcp_down_event = flags & 0xFF; + wake_up_interruptible(&hdcp_up_wait_queue); + wait_event_interruptible(hdcp_down_wait_queue, + (hdcp.hdcp_down_event & 0xFF) == 0); + ret = (hdcp.hdcp_down_event & 0xFF00) >> 8; + + DBG("User space task done %x\n", hdcp.hdcp_down_event); + hdcp.hdcp_down_event = 0; + + return ret; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_release_dss + *----------------------------------------------------------------------------- + */ +static void hdcp_release_dss(void) +{ +#ifdef DSS_POWER + if (hdcp.dss_state == 0) + dss_runtime_put(); +#endif +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_disable + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_disable(void) +{ + printk(KERN_INFO "HDCP: disabled\n"); + + hdcp_cancel_work(&hdcp.pending_wq_event); + hdcp_lib_disable(); + hdcp.pending_disable = 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_start_authentication + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_start_authentication(void) +{ + int status = HDCP_OK; + + hdcp.hdcp_state = HDCP_AUTHENTICATION_START; + + printk(KERN_INFO "HDCP: authentication start\n"); + + /* Step 1 part 1 (until R0 calc delay) */ + status = hdcp_lib_step1_start(); + + if (status == -HDCP_AKSV_ERROR) { + hdcp_wq_authentication_failure(); + } else if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 1 cancelled."); + return; + } else if (status != HDCP_OK) { + hdcp_wq_authentication_failure(); + } else { + hdcp.hdcp_state = HDCP_WAIT_R0_DELAY; + hdcp.auth_state = HDCP_STATE_AUTH_1ST_STEP; + hdcp.pending_wq_event = hdcp_submit_work(HDCP_R0_EXP_EVENT, + HDCP_R0_DELAY); + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_check_r0 + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_check_r0(void) +{ + int status = hdcp_lib_step1_r0_check(); + + if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 1/R0 cancelled."); + return; + } else if (status < 0) + hdcp_wq_authentication_failure(); + else { + if (hdcp_lib_check_repeater_bit_in_tx()) { + /* Repeater */ + printk(KERN_INFO "HDCP: authentication step 1 " + "successful - Repeater\n"); + + hdcp.hdcp_state = HDCP_WAIT_KSV_LIST; + hdcp.auth_state = HDCP_STATE_AUTH_2ND_STEP; + + hdcp.pending_wq_event = + hdcp_submit_work(HDCP_KSV_TIMEOUT_EVENT, + HDCP_KSV_TIMEOUT_DELAY); + } else { + /* Receiver */ + printk(KERN_INFO "HDCP: authentication step 1 " + "successful - Receiver\n"); + + hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; + hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; + + /* Restore retry counter */ + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + } + } +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_step2_authentication + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_step2_authentication(void) +{ + int status = HDCP_OK; + + /* KSV list timeout is running and should be canceled */ + hdcp_cancel_work(&hdcp.pending_wq_event); + + status = hdcp_lib_step2(); + + if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 2 cancelled."); + return; + } else if (status < 0) + hdcp_wq_authentication_failure(); + else { + printk(KERN_INFO "HDCP: (Repeater) authentication step 2 " + "successful\n"); + + hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; + hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; + + /* Restore retry counter */ + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_authentication_failure + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_authentication_failure(void) +{ + if (hdcp.hdmi_state == HDMI_STOPPED) { + hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; + return; + } + + hdcp_lib_auto_ri_check(false); + hdcp_lib_auto_bcaps_rdy_check(false); + hdcp_lib_set_av_mute(AV_MUTE_SET); + hdcp_lib_set_encryption(HDCP_ENC_OFF); + + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp_lib_disable(); + hdcp.pending_disable = 0; + + if (hdcp.retry_cnt && (hdcp.hdmi_state != HDMI_STOPPED)) { + if (hdcp.retry_cnt < HDCP_INFINITE_REAUTH) { + hdcp.retry_cnt--; + printk(KERN_INFO "HDCP: authentication failed - " + "retrying, attempts=%d\n", + hdcp.retry_cnt); + } else + printk(KERN_INFO "HDCP: authentication failed - " + "retrying\n"); + + hdcp.hdcp_state = HDCP_AUTHENTICATION_START; + hdcp.auth_state = HDCP_STATE_AUTH_FAIL_RESTARTING; + + hdcp.pending_wq_event = hdcp_submit_work(HDCP_AUTH_REATT_EVENT, + HDCP_REAUTH_DELAY); + } else { + printk(KERN_INFO "HDCP: authentication failed - " + "HDCP disabled\n"); + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; + } + +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_work_queue + *----------------------------------------------------------------------------- + */ +static void hdcp_work_queue(struct work_struct *work) +{ + struct hdcp_delayed_work *hdcp_w = + container_of(work, struct hdcp_delayed_work, work.work); + int event = hdcp_w->event; + + mutex_lock(&hdcp.lock); + + DBG("hdcp_work_queue() - START - %u hdmi=%d hdcp=%d auth=%d evt= %x %d" + " hdcp_ctrl=%02x", + jiffies_to_msecs(jiffies), + hdcp.hdmi_state, + hdcp.hdcp_state, + hdcp.auth_state, + (event & 0xFF00) >> 8, + event & 0xFF, + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL)); + + /* Clear pending_wq_event + * In case a delayed work is scheduled from the state machine + * "pending_wq_event" is used to memorize pointer on the event to be + * able to cancel any pending work in case HDCP is disabled + */ + if (event & HDCP_WORKQUEUE_SRC) + hdcp.pending_wq_event = 0; + + /* First handle HDMI state */ + if (event == HDCP_START_FRAME_EVENT) { + hdcp.pending_start = 0; + hdcp.hdmi_state = HDMI_STARTED; + } + /**********************/ + /* HDCP state machine */ + /**********************/ + switch (hdcp.hdcp_state) { + + /* State */ + /*********/ + case HDCP_DISABLED: + /* HDCP enable control or re-authentication event */ + if (event == HDCP_ENABLE_CTL) { + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + + if (hdcp.hdmi_state == HDMI_STARTED) + hdcp_wq_start_authentication(); + else + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + } + + break; + + /* State */ + /*********/ + case HDCP_ENABLE_PENDING: + /* HDMI start frame event */ + if (event == HDCP_START_FRAME_EVENT) + hdcp_wq_start_authentication(); + + break; + + /* State */ + /*********/ + case HDCP_AUTHENTICATION_START: + /* Re-authentication */ + if (event == HDCP_AUTH_REATT_EVENT) + hdcp_wq_start_authentication(); + + break; + + /* State */ + /*********/ + case HDCP_WAIT_R0_DELAY: + /* R0 timer elapsed */ + if (event == HDCP_R0_EXP_EVENT) + hdcp_wq_check_r0(); + + break; + + /* State */ + /*********/ + case HDCP_WAIT_KSV_LIST: + /* Ri failure */ + if (event == HDCP_RI_FAIL_EVENT) { + printk(KERN_INFO "HDCP: Ri check failure\n"); + + hdcp_wq_authentication_failure(); + } + /* KSV list ready event */ + else if (event == HDCP_KSV_LIST_RDY_EVENT) + hdcp_wq_step2_authentication(); + /* Timeout */ + else if (event == HDCP_KSV_TIMEOUT_EVENT) { + printk(KERN_INFO "HDCP: BCAPS polling timeout\n"); + hdcp_wq_authentication_failure(); + } + break; + + /* State */ + /*********/ + case HDCP_LINK_INTEGRITY_CHECK: + /* Ri failure */ + if (event == HDCP_RI_FAIL_EVENT) { + printk(KERN_INFO "HDCP: Ri check failure\n"); + hdcp_wq_authentication_failure(); + } + break; + + default: + printk(KERN_WARNING "HDCP: error - unknow HDCP state\n"); + break; + } + + kfree(hdcp_w); + hdcp_w = 0; + if (event == HDCP_START_FRAME_EVENT) + hdcp.pending_start = 0; + if (event == HDCP_KSV_LIST_RDY_EVENT || + event == HDCP_R0_EXP_EVENT) { + hdcp.pending_wq_event = 0; + } + + DBG("hdcp_work_queue() - END - %u hdmi=%d hdcp=%d auth=%d evt=%x %d ", + jiffies_to_msecs(jiffies), + hdcp.hdmi_state, + hdcp.hdcp_state, + hdcp.auth_state, + (event & 0xFF00) >> 8, + event & 0xFF); + + mutex_unlock(&hdcp.lock); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_submit_work + *----------------------------------------------------------------------------- + */ +static struct delayed_work *hdcp_submit_work(int event, int delay) +{ + struct hdcp_delayed_work *work; + + work = kmalloc(sizeof(struct hdcp_delayed_work), GFP_ATOMIC); + + if (work) { + INIT_DELAYED_WORK(&work->work, hdcp_work_queue); + work->event = event; + queue_delayed_work(hdcp.workqueue, + &work->work, + msecs_to_jiffies(delay)); + } else { + printk(KERN_WARNING "HDCP: Cannot allocate memory to " + "create work\n"); + return 0; + } + + return &work->work; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_cancel_work + *----------------------------------------------------------------------------- + */ +static void hdcp_cancel_work(struct delayed_work **work) +{ + int ret = 0; + + if (*work) { + ret = cancel_delayed_work(*work); + if (ret != 1) { + ret = cancel_work_sync(&((*work)->work)); + printk(KERN_INFO "Canceling work failed - " + "cancel_work_sync done %d\n", ret); + } + kfree(*work); + *work = 0; + } +} + + +/****************************************************************************** + * HDCP callbacks + *****************************************************************************/ + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_cb + *----------------------------------------------------------------------------- + */ +static bool hdcp_3des_cb(void) +{ + DBG("hdcp_3des_cb() %u", jiffies_to_msecs(jiffies)); + + if (!hdcp.hdcp_keys_loaded) { + printk(KERN_ERR "%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return false; + } + + /* Load 3DES key */ + if (hdcp_3des_load_key(hdcp.en_ctrl->key) != HDCP_OK) { + printk(KERN_ERR "Error Loading HDCP keys\n"); + return false; + } + return true; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_start_frame_cb + *----------------------------------------------------------------------------- + */ +static void hdcp_start_frame_cb(void) +{ + DBG("hdcp_start_frame_cb() %u", jiffies_to_msecs(jiffies)); + + if (!hdcp.hdcp_keys_loaded) { + DBG("%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return; + } + + /* Cancel any pending work */ + if (hdcp.pending_start) + hdcp_cancel_work(&hdcp.pending_start); + if (hdcp.pending_wq_event) + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp.hpd_low = 0; + hdcp.pending_disable = 0; + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + hdcp.pending_start = hdcp_submit_work(HDCP_START_FRAME_EVENT, + HDCP_ENABLE_DELAY); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_irq_cb + *----------------------------------------------------------------------------- + */ +static void hdcp_irq_cb(int status) +{ + DBG("hdcp_irq_cb() status=%x", status); + + if (!hdcp.hdcp_keys_loaded) { + DBG("%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return; + } + + /* Disable auto Ri/BCAPS immediately */ + if (((status & HDMI_RI_ERR) || + (status & HDMI_BCAP) || + (status & HDMI_HPD_LOW)) && + (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { + hdcp_lib_auto_ri_check(false); + hdcp_lib_auto_bcaps_rdy_check(false); + } + + /* Work queue execution not required if HDCP is disabled */ + /* TODO: ignore interrupts if they are masked (cannnot access UMASK + * here so should use global variable + */ + if ((hdcp.hdcp_state != HDCP_DISABLED) && + (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { + if (status & HDMI_HPD_LOW) { + hdcp_lib_set_encryption(HDCP_ENC_OFF); + hdcp_ddc_abort(); + } + + if (status & HDMI_RI_ERR) { + hdcp_lib_set_av_mute(AV_MUTE_SET); + hdcp_lib_set_encryption(HDCP_ENC_OFF); + hdcp_submit_work(HDCP_RI_FAIL_EVENT, 0); + } + /* RI error takes precedence over BCAP */ + else if (status & HDMI_BCAP) + hdcp_submit_work(HDCP_KSV_LIST_RDY_EVENT, 0); + } + + if (status & HDMI_HPD_LOW) { + hdcp.pending_disable = 1; /* Used to exit on-going HDCP + * work */ + hdcp.hpd_low = 0; /* Used to cancel HDCP works */ + hdcp_lib_disable(); + /* In case of HDCP_STOP_FRAME_EVENT, HDCP stop + * frame callback is blocked and waiting for + * HDCP driver to finish accessing the HW + * before returning + * Reason is to avoid HDMI driver to shutdown + * DSS/HDMI power before HDCP work is finished + */ + hdcp.hdmi_state = HDMI_STOPPED; + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.auth_state = HDCP_STATE_DISABLED; + } +} + +/****************************************************************************** + * HDCP control from ioctl + *****************************************************************************/ + + +/*----------------------------------------------------------------------------- + * Function: hdcp_enable_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_enable_ctl(void __user *argp) +{ + DBG("hdcp_ioctl() - ENABLE %u", jiffies_to_msecs(jiffies)); + + if (hdcp.en_ctrl == 0) { + hdcp.en_ctrl = + kmalloc(sizeof(struct hdcp_enable_control), + GFP_KERNEL); + + if (hdcp.en_ctrl == 0) { + printk(KERN_WARNING + "HDCP: Cannot allocate memory for HDCP" + " enable control struct\n"); + return -EFAULT; + } + } + + if (copy_from_user(hdcp.en_ctrl, argp, + sizeof(struct hdcp_enable_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space " + "- enable ioctl\n"); + return -EFAULT; + } + + /* Post event to workqueue */ + if (hdcp_submit_work(HDCP_ENABLE_CTL, 0) == 0) + return -EFAULT; + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_disable_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_disable_ctl(void) +{ + DBG("hdcp_ioctl() - DISABLE %u", jiffies_to_msecs(jiffies)); + + hdcp_cancel_work(&hdcp.pending_start); + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp.pending_disable = 1; + /* Post event to workqueue */ + if (hdcp_submit_work(HDCP_DISABLE_CTL, 0) == 0) + return -EFAULT; + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_query_status_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_query_status_ctl(void __user *argp) +{ + uint32_t *status = (uint32_t *)argp; + + DBG("hdcp_ioctl() - QUERY %u", jiffies_to_msecs(jiffies)); + + *status = hdcp.auth_state; + + return 0; +} + +static int hdcp_wait_re_entrance; + +/*----------------------------------------------------------------------------- + * Function: hdcp_wait_event_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_wait_event_ctl(void __user *argp) +{ + struct hdcp_wait_control ctrl; + + DBG("hdcp_ioctl() - WAIT %u %d", jiffies_to_msecs(jiffies), + hdcp.hdcp_up_event); + + if (copy_from_user(&ctrl, argp, + sizeof(struct hdcp_wait_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space" + " - wait ioctl"); + return -EFAULT; + } + + if (hdcp_wait_re_entrance == 0) { + hdcp_wait_re_entrance = 1; + wait_event_interruptible(hdcp_up_wait_queue, + (hdcp.hdcp_up_event & 0xFF) != 0); + + ctrl.event = hdcp.hdcp_up_event; + + if ((ctrl.event & 0xFF) == HDCP_EVENT_STEP2) { + if (copy_to_user(ctrl.data, &sha_input, + sizeof(struct hdcp_sha_in))) { + printk(KERN_WARNING "HDCP: Error copying to " + "user space - wait ioctl"); + return -EFAULT; + } + } + + hdcp.hdcp_up_event = 0; + hdcp_wait_re_entrance = 0; + } else + ctrl.event = HDCP_EVENT_EXIT; + + /* Store output data to output pointer */ + if (copy_to_user(argp, &ctrl, + sizeof(struct hdcp_wait_control))) { + printk(KERN_WARNING "HDCP: Error copying to user space -" + " wait ioctl"); + return -EFAULT; + } + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_done_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_done_ctl(void __user *argp) +{ + uint32_t *status = (uint32_t *)argp; + + DBG("hdcp_ioctl() - DONE %u %d", jiffies_to_msecs(jiffies), *status); + + hdcp.hdcp_down_event &= ~(*status & 0xFF); + hdcp.hdcp_down_event |= *status & 0xFF00; + + wake_up_interruptible(&hdcp_down_wait_queue); + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_encrypt_key_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_encrypt_key_ctl(void __user *argp) +{ + struct hdcp_encrypt_control *ctrl; + uint32_t *out_key; + + DBG("hdcp_ioctl() - ENCRYPT KEY %u", jiffies_to_msecs(jiffies)); + + mutex_lock(&hdcp.lock); + + if (hdcp.hdcp_state != HDCP_DISABLED) { + printk(KERN_INFO "HDCP: Cannot encrypt keys while HDCP " + "is enabled\n"); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + hdcp.hdcp_state = HDCP_KEY_ENCRYPTION_ONGOING; + + /* Encryption happens in ioctl / user context */ + ctrl = kmalloc(sizeof(struct hdcp_encrypt_control), + GFP_KERNEL); + + if (ctrl == 0) { + printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP" + " encryption control struct\n"); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + out_key = kmalloc(sizeof(uint32_t) * + DESHDCP_KEY_SIZE, GFP_KERNEL); + + if (out_key == 0) { + printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP " + "encryption output key\n"); + kfree(ctrl); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + if (copy_from_user(ctrl, argp, + sizeof(struct hdcp_encrypt_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space" + " - encrypt ioctl\n"); + kfree(ctrl); + kfree(out_key); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + hdcp_request_dss(); + + /* Call encrypt function */ + hdcp_3des_encrypt_key(ctrl, out_key); + + hdcp_release_dss(); + + hdcp.hdcp_state = HDCP_DISABLED; + mutex_unlock(&hdcp.lock); + + /* Store output data to output pointer */ + if (copy_to_user(ctrl->out_key, out_key, + sizeof(uint32_t)*DESHDCP_KEY_SIZE)) { + printk(KERN_WARNING "HDCP: Error copying to user space -" + " encrypt ioctl\n"); + kfree(ctrl); + kfree(out_key); + return -EFAULT; + } + + kfree(ctrl); + kfree(out_key); + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ioctl + *----------------------------------------------------------------------------- + */ +long hdcp_ioctl(struct file *fd, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case HDCP_ENABLE: + return hdcp_enable_ctl(argp); + + case HDCP_DISABLE: + return hdcp_disable_ctl(); + + case HDCP_ENCRYPT_KEY: + return hdcp_encrypt_key_ctl(argp); + + case HDCP_QUERY_STATUS: + return hdcp_query_status_ctl(argp); + + case HDCP_WAIT_EVENT: + return hdcp_wait_event_ctl(argp); + + case HDCP_DONE: + return hdcp_done_ctl(argp); + + default: + return -ENOTTY; + } /* End switch */ +} + + +/****************************************************************************** + * HDCP driver init/exit + *****************************************************************************/ + +/*----------------------------------------------------------------------------- + * Function: hdcp_mmap + *----------------------------------------------------------------------------- + */ +static int hdcp_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int status; + + DBG("hdcp_mmap() %lx %lx %lx\n", vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start); + + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + status = remap_pfn_range(vma, vma->vm_start, + HDMI_WP >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + if (status) { + DBG("mmap error %d\n", status); + return -EAGAIN; + } + + DBG("mmap succesfull\n"); + return 0; +} + +static struct file_operations hdcp_fops = { + .owner = THIS_MODULE, + .mmap = hdcp_mmap, + .unlocked_ioctl = hdcp_ioctl, +}; + +struct miscdevice mdev; + +static void hdcp_load_keys_cb(const struct firmware *fw, void *context) +{ + struct hdcp_enable_control *en_ctrl; + + if (!fw) { + pr_err("HDCP: failed to load keys\n"); + return; + } + + if (fw->size != sizeof(en_ctrl->key)) { + pr_err("HDCP: encrypted key file wrong size %d\n", fw->size); + return; + } + + en_ctrl = kmalloc(sizeof(*en_ctrl), GFP_KERNEL); + if (!en_ctrl) { + pr_err("HDCP: can't allocated space for keys\n"); + return; + } + + memcpy(en_ctrl->key, fw->data, sizeof(en_ctrl->key)); + en_ctrl->nb_retry = 20; + + hdcp.en_ctrl = en_ctrl; + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.hdcp_keys_loaded = true; + pr_info("HDCP: loaded keys\n"); +} + +static int hdcp_load_keys(void) +{ + int ret; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "hdcp.keys", mdev.this_device, GFP_KERNEL, + &hdcp, hdcp_load_keys_cb); + if (ret < 0) { + pr_err("HDCP: request_firmware_nowait failed: %d\n", ret); + hdcp.hdcp_keys_loaded = false; + return ret; + } + + return 0; +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_init + *----------------------------------------------------------------------------- + */ +static int __init hdcp_init(void) +{ + DBG("hdcp_init() %u", jiffies_to_msecs(jiffies)); + + /* Map HDMI WP address */ + hdcp.hdmi_wp_base_addr = ioremap(HDMI_WP, 0x1000); + + if (!hdcp.hdmi_wp_base_addr) { + printk(KERN_ERR "HDCP: HDMI WP IOremap error\n"); + return -EFAULT; + } + + /* Map DESHDCP in kernel address space */ + hdcp.deshdcp_base_addr = ioremap(DSS_SS_FROM_L3__DESHDCP, 0x34); + + if (!hdcp.deshdcp_base_addr) { + printk(KERN_ERR "HDCP: DESHDCP IOremap error\n"); + goto err_map_deshdcp; + } + + mutex_init(&hdcp.lock); + + mdev.minor = MISC_DYNAMIC_MINOR; + mdev.name = "hdcp"; + mdev.mode = 0666; + mdev.fops = &hdcp_fops; + + if (misc_register(&mdev)) { + printk(KERN_ERR "HDCP: Could not add character driver\n"); + goto err_register; + } + + mutex_lock(&hdcp.lock); + + /* Variable init */ + hdcp.en_ctrl = 0; + hdcp.hdcp_state = HDCP_DISABLED; + hdcp.pending_start = 0; + hdcp.pending_wq_event = 0; + hdcp.retry_cnt = 0; + hdcp.auth_state = HDCP_STATE_DISABLED; + hdcp.pending_disable = 0; + hdcp.hdcp_up_event = 0; + hdcp.hdcp_down_event = 0; + hdcp_wait_re_entrance = 0; + hdcp.hpd_low = 0; + + spin_lock_init(&hdcp.spinlock); + + init_completion(&hdcp_comp); + + hdcp.workqueue = create_singlethread_workqueue("hdcp"); + if (hdcp.workqueue == NULL) + goto err_add_driver; + + hdcp_request_dss(); + + /* Register HDCP callbacks to HDMI library */ + if (omapdss_hdmi_register_hdcp_callbacks(&hdcp_start_frame_cb, + &hdcp_irq_cb, + &hdcp_3des_cb)) + hdcp.hdmi_state = HDMI_STARTED; + else + hdcp.hdmi_state = HDMI_STOPPED; + + hdcp_release_dss(); + + mutex_unlock(&hdcp.lock); + + hdcp_load_keys(); + + return 0; + +err_add_driver: + misc_deregister(&mdev); + +err_register: + mutex_destroy(&hdcp.lock); + + iounmap(hdcp.deshdcp_base_addr); + +err_map_deshdcp: + iounmap(hdcp.hdmi_wp_base_addr); + + return -EFAULT; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_exit + *----------------------------------------------------------------------------- + */ +static void __exit hdcp_exit(void) +{ + DBG("hdcp_exit() %u", jiffies_to_msecs(jiffies)); + + mutex_lock(&hdcp.lock); + + kfree(hdcp.en_ctrl); + + hdcp_request_dss(); + + /* Un-register HDCP callbacks to HDMI library */ + omapdss_hdmi_register_hdcp_callbacks(0, 0, 0); + + hdcp_release_dss(); + + misc_deregister(&mdev); + + /* Unmap HDMI WP / DESHDCP */ + iounmap(hdcp.hdmi_wp_base_addr); + iounmap(hdcp.deshdcp_base_addr); + + destroy_workqueue(hdcp.workqueue); + + mutex_unlock(&hdcp.lock); + + mutex_destroy(&hdcp.lock); +} + +/*----------------------------------------------------------------------------- + *----------------------------------------------------------------------------- + */ +module_init(hdcp_init); +module_exit(hdcp_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OMAP HDCP kernel module"); +MODULE_AUTHOR("Fabrice Olivero"); diff --git a/drivers/video/omap2/omapfb/Kconfig b/drivers/video/omap2/omapfb/Kconfig index aa33386..d15486e 100644 --- a/drivers/video/omap2/omapfb/Kconfig +++ b/drivers/video/omap2/omapfb/Kconfig @@ -7,6 +7,7 @@ menuconfig FB_OMAP2 select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS help Frame buffer driver for OMAP2+ based boards. diff --git a/drivers/video/omap2/omapfb/omapfb-ioctl.c b/drivers/video/omap2/omapfb/omapfb-ioctl.c index cff4503..8188e92 100644 --- a/drivers/video/omap2/omapfb/omapfb-ioctl.c +++ b/drivers/video/omap2/omapfb/omapfb-ioctl.c @@ -883,6 +883,7 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) case OMAPFB_GET_DISPLAY_INFO: { u16 xres, yres; + u32 w, h; DBG("ioctl GET_DISPLAY_INFO\n"); @@ -896,15 +897,9 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) p.display_info.xres = xres; p.display_info.yres = yres; - if (display->driver->get_dimensions) { - u32 w, h; - display->driver->get_dimensions(display, &w, &h); - p.display_info.width = w; - p.display_info.height = h; - } else { - p.display_info.width = 0; - p.display_info.height = 0; - } + omapdss_display_get_dimensions(display, &w, &h); + p.display_info.width = w; + p.display_info.height = h; if (copy_to_user((void __user *)arg, &p.display_info, sizeof(p.display_info))) @@ -912,6 +907,24 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) break; } + case OMAPFB_ENABLEVSYNC: + if (get_user(p.crt, (__u32 __user *)arg)) { + r = -EFAULT; + break; + } + + omapfb_lock(fbdev); + fbdev->vsync_active = !!p.crt; + + if (display->state == OMAP_DSS_DISPLAY_ACTIVE) { + if (p.crt) + omapfb_enable_vsync(fbdev); + else + omapfb_disable_vsync(fbdev); + } + omapfb_unlock(fbdev); + break; + default: dev_err(fbdev->dev, "Unknown ioctl 0x%x\n", cmd); r = -EINVAL; diff --git a/drivers/video/omap2/omapfb/omapfb-main.c b/drivers/video/omap2/omapfb/omapfb-main.c index 505bc12..319c2ac 100644 --- a/drivers/video/omap2/omapfb/omapfb-main.c +++ b/drivers/video/omap2/omapfb/omapfb-main.c @@ -29,6 +29,7 @@ #include <linux/device.h> #include <linux/platform_device.h> #include <linux/omapfb.h> +#include <linux/wait.h> #include <video/omapdss.h> #include <plat/vram.h> @@ -303,7 +304,7 @@ static void assign_colormode_to_var(struct fb_var_screeninfo *var, var->transp = color->transp; } -static int fb_mode_to_dss_mode(struct fb_var_screeninfo *var, +int omapfb_mode_to_dss_mode(struct fb_var_screeninfo *var, enum omap_color_mode *mode) { enum omap_color_mode dssmode; @@ -358,7 +359,7 @@ static int fb_mode_to_dss_mode(struct fb_var_screeninfo *var, dssmode = OMAP_DSS_COLOR_RGB24P; break; case 32: - dssmode = OMAP_DSS_COLOR_RGB24U; + dssmode = OMAP_DSS_COLOR_ARGB32; break; default: return -EINVAL; @@ -375,6 +376,7 @@ static int fb_mode_to_dss_mode(struct fb_var_screeninfo *var, return -EINVAL; } +EXPORT_SYMBOL(omapfb_mode_to_dss_mode); static int check_fb_res_bounds(struct fb_var_screeninfo *var) { @@ -512,7 +514,7 @@ static int setup_vrfb_rotation(struct fb_info *fbi) DBG("setup_vrfb_rotation\n"); - r = fb_mode_to_dss_mode(var, &mode); + r = omapfb_mode_to_dss_mode(var, &mode); if (r) return r; @@ -622,8 +624,9 @@ void set_fb_fix(struct fb_info *fbi) fix->smem_len = var->yres_virtual * fix->line_length; } else { - fix->line_length = - (var->xres_virtual * var->bits_per_pixel) >> 3; + /* SGX requires stride to be a multiple of 32 pixels */ + int xres_align = ALIGN(var->xres_virtual, 32); + fix->line_length = (xres_align * var->bits_per_pixel) >> 3; fix->smem_len = rg->size; } @@ -665,12 +668,13 @@ int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var) enum omap_color_mode mode = 0; int i; int r; + u32 w = 0, h = 0; DBG("check_fb_var %d\n", ofbi->id); WARN_ON(!atomic_read(&ofbi->region->lock_count)); - r = fb_mode_to_dss_mode(var, &mode); + r = omapfb_mode_to_dss_mode(var, &mode); if (r) { DBG("cannot convert var to omap dss mode\n"); return r; @@ -702,9 +706,10 @@ int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var) var->xres, var->yres, var->xres_virtual, var->yres_virtual); - if (display && display->driver->get_dimensions) { - u32 w, h; - display->driver->get_dimensions(display, &w, &h); + if (display) + omapdss_display_get_dimensions(display, &w, &h); + + if (w && h) { var->width = DIV_ROUND_CLOSEST(w, 1000); var->height = DIV_ROUND_CLOSEST(h, 1000); } else { @@ -757,6 +762,11 @@ static int omapfb_open(struct fb_info *fbi, int user) static int omapfb_release(struct fb_info *fbi, int user) { + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + + omapfb_disable_vsync(fbdev); + return 0; } @@ -879,9 +889,9 @@ int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, omapfb_calc_addr(ofbi, var, fix, rotation, &data_start_p, &data_start_v); - r = fb_mode_to_dss_mode(var, &mode); + r = omapfb_mode_to_dss_mode(var, &mode); if (r) { - DBG("fb_mode_to_dss_mode failed"); + DBG("omapfb_mode_to_dss_mode failed"); goto err; } @@ -1018,6 +1028,41 @@ static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) return r; } +void omapfb_fb2dss_timings(struct fb_videomode *fb_timings, + struct omap_video_timings *dss_timings) +{ + dss_timings->x_res = fb_timings->xres; + dss_timings->y_res = fb_timings->yres; + if (fb_timings->vmode & FB_VMODE_INTERLACED) + dss_timings->y_res /= 2; + dss_timings->pixel_clock = fb_timings->pixclock ? + PICOS2KHZ(fb_timings->pixclock) : 0; + dss_timings->hfp = fb_timings->right_margin; + dss_timings->hbp = fb_timings->left_margin; + dss_timings->hsw = fb_timings->hsync_len; + dss_timings->vfp = fb_timings->lower_margin; + dss_timings->vbp = fb_timings->upper_margin; + dss_timings->vsw = fb_timings->vsync_len; +} +EXPORT_SYMBOL(omapfb_fb2dss_timings); + +void omapfb_dss2fb_timings(struct omap_video_timings *dss_timings, + struct fb_videomode *fb_timings) +{ + memset(fb_timings, 0, sizeof(*fb_timings)); + fb_timings->xres = dss_timings->x_res; + fb_timings->yres = dss_timings->y_res; + fb_timings->pixclock = dss_timings->pixel_clock ? + KHZ2PICOS(dss_timings->pixel_clock) : 0; + fb_timings->right_margin = dss_timings->hfp; + fb_timings->left_margin = dss_timings->hbp; + fb_timings->hsync_len = dss_timings->hsw; + fb_timings->lower_margin = dss_timings->vfp; + fb_timings->upper_margin = dss_timings->vbp; + fb_timings->vsync_len = dss_timings->vsw; +} +EXPORT_SYMBOL(omapfb_dss2fb_timings); + /* set the video mode according to info->var */ static int omapfb_set_par(struct fb_info *fbi) { @@ -1251,11 +1296,16 @@ static int omapfb_blank(int blank, struct fb_info *fbi) switch (blank) { case FB_BLANK_UNBLANK: - if (display->state != OMAP_DSS_DISPLAY_SUSPENDED) - goto exit; + if (display->state == OMAP_DSS_DISPLAY_SUSPENDED) { + if (display->driver->resume) + r = display->driver->resume(display); + } else if (display->state == OMAP_DSS_DISPLAY_DISABLED) { + if (display->driver->enable) + r = display->driver->enable(display); + } - if (display->driver->resume) - r = display->driver->resume(display); + if (fbdev->vsync_active) + omapfb_enable_vsync(fbdev); break; @@ -1265,11 +1315,17 @@ static int omapfb_blank(int blank, struct fb_info *fbi) case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_POWERDOWN: + + if (fbdev->vsync_active) + omapfb_disable_vsync(fbdev); + if (display->state != OMAP_DSS_DISPLAY_ACTIVE) goto exit; if (display->driver->suspend) r = display->driver->suspend(display); + else if (display->driver->disable) + display->driver->disable(display); break; @@ -2233,6 +2289,39 @@ static int omapfb_init_display(struct omapfb2_device *fbdev, return 0; } +static void omapfb_send_vsync_work(struct work_struct *work) +{ + struct omapfb2_device *fbdev = + container_of(work, typeof(*fbdev), vsync_work); + char buf[64]; + char *envp[2]; + + snprintf(buf, sizeof(buf), "VSYNC=%llu", + ktime_to_ns(fbdev->vsync_timestamp)); + envp[0] = buf; + envp[1] = NULL; + kobject_uevent_env(&fbdev->dev->kobj, KOBJ_CHANGE, envp); +} +static void omapfb_vsync_isr(void *data, u32 mask) +{ + struct omapfb2_device *fbdev = data; + fbdev->vsync_timestamp = ktime_get(); + schedule_work(&fbdev->vsync_work); +} + +int omapfb_enable_vsync(struct omapfb2_device *fbdev) +{ + int r; + /* TODO: should determine correct IRQ like dss_mgr_wait_for_vsync does*/ + r = omap_dispc_register_isr(omapfb_vsync_isr, fbdev, DISPC_IRQ_VSYNC); + return r; +} + +void omapfb_disable_vsync(struct omapfb2_device *fbdev) +{ + omap_dispc_unregister_isr(omapfb_vsync_isr, fbdev, DISPC_IRQ_VSYNC); +} + static int omapfb_probe(struct platform_device *pdev) { struct omapfb2_device *fbdev = NULL; @@ -2348,6 +2437,7 @@ static int omapfb_probe(struct platform_device *pdev) goto cleanup; } + INIT_WORK(&fbdev->vsync_work, omapfb_send_vsync_work); return 0; cleanup: @@ -2362,6 +2452,7 @@ static int omapfb_remove(struct platform_device *pdev) struct omapfb2_device *fbdev = platform_get_drvdata(pdev); /* FIXME: wait till completion of pending events */ + /* TODO: terminate vsync thread */ omapfb_remove_sysfs(fbdev); diff --git a/drivers/video/omap2/omapfb/omapfb.h b/drivers/video/omap2/omapfb/omapfb.h index aa1b1d9..649388f 100644 --- a/drivers/video/omap2/omapfb/omapfb.h +++ b/drivers/video/omap2/omapfb/omapfb.h @@ -97,6 +97,10 @@ struct omapfb2_device { struct omap_dss_device *dssdev; u8 bpp; } bpp_overrides[10]; + + bool vsync_active; + ktime_t vsync_timestamp; + struct work_struct vsync_work; }; struct omapfb_colormode { @@ -128,6 +132,9 @@ int dss_mode_to_fb_mode(enum omap_color_mode dssmode, int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, u16 posx, u16 posy, u16 outw, u16 outh); +int omapfb_enable_vsync(struct omapfb2_device *fbdev); +void omapfb_disable_vsync(struct omapfb2_device *fbdev); + /* find the display connected to this fb, if any */ static inline struct omap_dss_device *fb2display(struct fb_info *fbi) { |