diff options
Diffstat (limited to 'drivers/video/fbdev/omap2/dss')
38 files changed, 28226 insertions, 0 deletions
diff --git a/drivers/video/fbdev/omap2/dss/Kconfig b/drivers/video/fbdev/omap2/dss/Kconfig new file mode 100644 index 00000000000..285bcd103dc --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/Kconfig @@ -0,0 +1,141 @@ +config OMAP2_DSS_INIT +	bool + +menuconfig OMAP2_DSS +        tristate "OMAP2+ Display Subsystem support" +	select VIDEOMODE_HELPERS +	select OMAP2_DSS_INIT +        help +	  OMAP2+ Display Subsystem support. + +if OMAP2_DSS + +config OMAP2_DSS_DEBUG +	bool "Debug support" +	default n +	help +	  This enables printing of debug messages. Alternatively, debug messages +	  can also be enabled by setting CONFIG_DYNAMIC_DEBUG and then setting +	  appropriate flags in <debugfs>/dynamic_debug/control. + +config OMAP2_DSS_DEBUGFS +	bool "Debugfs filesystem support" +	depends on DEBUG_FS +	default n +	help +	  This enables debugfs for OMAPDSS at <debugfs>/omapdss. This enables +	  querying about clock configuration and register configuration of dss, +	  dispc, dsi, hdmi and rfbi. + +config OMAP2_DSS_COLLECT_IRQ_STATS +	bool "Collect DSS IRQ statistics" +	depends on OMAP2_DSS_DEBUGFS +	default n +	help +	  Collect DSS IRQ statistics, printable via debugfs. + +	  The statistics can be found from +	  <debugfs>/omapdss/dispc_irq for DISPC interrupts, and +	  <debugfs>/omapdss/dsi_irq for DSI interrupts. + +config OMAP2_DSS_DPI +	bool "DPI support" +	default y +	help +	  DPI Interface. This is the Parallel Display Interface. + +config OMAP2_DSS_RFBI +	bool "RFBI support" +	depends on BROKEN +        default n +	help +	  MIPI DBI support (RFBI, Remote Framebuffer Interface, in Texas +	  Instrument's terminology). + +	  DBI is a bus between the host processor and a peripheral, +	  such as a display or a framebuffer chip. + +	  See http://www.mipi.org/ for DBI specifications. + +config OMAP2_DSS_VENC +	bool "VENC support" +        default y +	help +	  OMAP Video Encoder support for S-Video and composite TV-out. + +config OMAP2_DSS_HDMI_COMMON +	bool + +config OMAP4_DSS_HDMI +	bool "HDMI support for OMAP4" +        default y +	select OMAP2_DSS_HDMI_COMMON +	help +	  HDMI support for OMAP4 based SoCs. + +config OMAP4_DSS_HDMI_AUDIO +	bool + +config OMAP5_DSS_HDMI +	bool "HDMI support for OMAP5" +	default n +	select OMAP2_DSS_HDMI_COMMON +	help +	  HDMI Interface for OMAP5 and similar cores. This adds the High +	  Definition Multimedia Interface. See http://www.hdmi.org/ for HDMI +	  specification. + +config OMAP5_DSS_HDMI_AUDIO +	depends on OMAP5_DSS_HDMI +	bool + +config OMAP2_DSS_SDI +	bool "SDI support" +        default n +	help +	  SDI (Serial Display Interface) support. + +	  SDI is a high speed one-way display serial bus between the host +	  processor and a display. + +config OMAP2_DSS_DSI +	bool "DSI support" +        default n +	help +	  MIPI DSI (Display Serial Interface) support. + +	  DSI is a high speed half-duplex serial interface between the host +	  processor and a peripheral, such as a display or a framebuffer chip. + +	  See http://www.mipi.org/ for DSI specifications. + +config OMAP2_DSS_MIN_FCK_PER_PCK +	int "Minimum FCK/PCK ratio (for scaling)" +	range 0 32 +	default 0 +	help +	  This can be used to adjust the minimum FCK/PCK ratio. + +	  With this you can make sure that DISPC FCK is at least +	  n x PCK. Video plane scaling requires higher FCK than +	  normally. + +	  If this is set to 0, there's no extra constraint on the +	  DISPC FCK. However, the FCK will at minimum be +	  2xPCK (if active matrix) or 3xPCK (if passive matrix). + +	  Max FCK is 173MHz, so this doesn't work if your PCK +	  is very high. + +config OMAP2_DSS_SLEEP_AFTER_VENC_RESET +	bool "Sleep 20ms after VENC reset" +	default y +	help +	  There is a 20ms sleep after VENC reset which seemed to fix the +	  reset. The reason for the bug is unclear, and it's also unclear +	  on what platforms this happens. + +	  This option enables the sleep, and is enabled by default. You can +	  disable the sleep if it doesn't cause problems on your platform. + +endif diff --git a/drivers/video/fbdev/omap2/dss/Makefile b/drivers/video/fbdev/omap2/dss/Makefile new file mode 100644 index 00000000000..245f933060e --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/Makefile @@ -0,0 +1,18 @@ +obj-$(CONFIG_OMAP2_DSS_INIT) += omapdss-boot-init.o +obj-$(CONFIG_OMAP2_DSS) += omapdss.o +# Core DSS files +omapdss-y := core.o dss.o dss_features.o dispc.o dispc_coefs.o display.o \ +	output.o dss-of.o +# DSS compat layer files +omapdss-y += manager.o manager-sysfs.o overlay.o overlay-sysfs.o apply.o \ +	dispc-compat.o display-sysfs.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_OMAP2_DSS_HDMI_COMMON) += hdmi_common.o hdmi_wp.o hdmi_pll.o \ +	hdmi_phy.o +omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi4.o hdmi4_core.o +omapdss-$(CONFIG_OMAP5_DSS_HDMI) += hdmi5.o hdmi5_core.o +ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG diff --git a/drivers/video/fbdev/omap2/dss/apply.c b/drivers/video/fbdev/omap2/dss/apply.c new file mode 100644 index 00000000000..0a0b084ce65 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/apply.c @@ -0,0 +1,1700 @@ +/* + * Copyright (C) 2011 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@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 DSS_SUBSYS_NAME "APPLY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc-compat.h" + +/* + * We have 4 levels of cache for the dispc settings. First two are in SW and + * the latter two in HW. + * + *       set_info() + *          v + * +--------------------+ + * |     user_info      | + * +--------------------+ + *          v + *        apply() + *          v + * +--------------------+ + * |       info         | + * +--------------------+ + *          v + *      write_regs() + *          v + * +--------------------+ + * |  shadow registers  | + * +--------------------+ + *          v + * VFP or lcd/digit_enable + *          v + * +--------------------+ + * |      registers     | + * +--------------------+ + */ + +struct ovl_priv_data { + +	bool user_info_dirty; +	struct omap_overlay_info user_info; + +	bool info_dirty; +	struct omap_overlay_info info; + +	bool shadow_info_dirty; + +	bool extra_info_dirty; +	bool shadow_extra_info_dirty; + +	bool enabled; +	u32 fifo_low, fifo_high; + +	/* +	 * True if overlay is to be enabled. Used to check and calculate configs +	 * for the overlay before it is enabled in the HW. +	 */ +	bool enabling; +}; + +struct mgr_priv_data { + +	bool user_info_dirty; +	struct omap_overlay_manager_info user_info; + +	bool info_dirty; +	struct omap_overlay_manager_info info; + +	bool shadow_info_dirty; + +	/* If true, GO bit is up and shadow registers cannot be written. +	 * Never true for manual update displays */ +	bool busy; + +	/* If true, dispc output is enabled */ +	bool updating; + +	/* If true, a display is enabled using this manager */ +	bool enabled; + +	bool extra_info_dirty; +	bool shadow_extra_info_dirty; + +	struct omap_video_timings timings; +	struct dss_lcd_mgr_config lcd_config; + +	void (*framedone_handler)(void *); +	void *framedone_handler_data; +}; + +static struct { +	struct ovl_priv_data ovl_priv_data_array[MAX_DSS_OVERLAYS]; +	struct mgr_priv_data mgr_priv_data_array[MAX_DSS_MANAGERS]; + +	bool irq_enabled; +} dss_data; + +/* protects dss_data */ +static spinlock_t data_lock; +/* lock for blocking functions */ +static DEFINE_MUTEX(apply_lock); +static DECLARE_COMPLETION(extra_updated_completion); + +static void dss_register_vsync_isr(void); + +static struct ovl_priv_data *get_ovl_priv(struct omap_overlay *ovl) +{ +	return &dss_data.ovl_priv_data_array[ovl->id]; +} + +static struct mgr_priv_data *get_mgr_priv(struct omap_overlay_manager *mgr) +{ +	return &dss_data.mgr_priv_data_array[mgr->id]; +} + +static void apply_init_priv(void) +{ +	const int num_ovls = dss_feat_get_num_ovls(); +	struct mgr_priv_data *mp; +	int i; + +	spin_lock_init(&data_lock); + +	for (i = 0; i < num_ovls; ++i) { +		struct ovl_priv_data *op; + +		op = &dss_data.ovl_priv_data_array[i]; + +		op->info.color_mode = OMAP_DSS_COLOR_RGB16; +		op->info.rotation_type = OMAP_DSS_ROT_DMA; + +		op->info.global_alpha = 255; + +		switch (i) { +		case 0: +			op->info.zorder = 0; +			break; +		case 1: +			op->info.zorder = +				dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 3 : 0; +			break; +		case 2: +			op->info.zorder = +				dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 2 : 0; +			break; +		case 3: +			op->info.zorder = +				dss_has_feature(FEAT_ALPHA_FREE_ZORDER) ? 1 : 0; +			break; +		} + +		op->user_info = op->info; +	} + +	/* +	 * Initialize some of the lcd_config fields for TV manager, this lets +	 * us prevent checking if the manager is LCD or TV at some places +	 */ +	mp = &dss_data.mgr_priv_data_array[OMAP_DSS_CHANNEL_DIGIT]; + +	mp->lcd_config.video_port_width = 24; +	mp->lcd_config.clock_info.lck_div = 1; +	mp->lcd_config.clock_info.pck_div = 1; +} + +/* + * A LCD manager's stallmode decides whether it is in manual or auto update. TV + * manager is always auto update, stallmode field for TV manager is false by + * default + */ +static bool ovl_manual_update(struct omap_overlay *ovl) +{ +	struct mgr_priv_data *mp = get_mgr_priv(ovl->manager); + +	return mp->lcd_config.stallmode; +} + +static bool mgr_manual_update(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	return mp->lcd_config.stallmode; +} + +static int dss_check_settings_low(struct omap_overlay_manager *mgr, +		bool applying) +{ +	struct omap_overlay_info *oi; +	struct omap_overlay_manager_info *mi; +	struct omap_overlay *ovl; +	struct omap_overlay_info *ois[MAX_DSS_OVERLAYS]; +	struct ovl_priv_data *op; +	struct mgr_priv_data *mp; + +	mp = get_mgr_priv(mgr); + +	if (!mp->enabled) +		return 0; + +	if (applying && mp->user_info_dirty) +		mi = &mp->user_info; +	else +		mi = &mp->info; + +	/* collect the infos to be tested into the array */ +	list_for_each_entry(ovl, &mgr->overlays, list) { +		op = get_ovl_priv(ovl); + +		if (!op->enabled && !op->enabling) +			oi = NULL; +		else if (applying && op->user_info_dirty) +			oi = &op->user_info; +		else +			oi = &op->info; + +		ois[ovl->id] = oi; +	} + +	return dss_mgr_check(mgr, mi, &mp->timings, &mp->lcd_config, ois); +} + +/* + * check manager and overlay settings using overlay_info from data->info + */ +static int dss_check_settings(struct omap_overlay_manager *mgr) +{ +	return dss_check_settings_low(mgr, false); +} + +/* + * check manager and overlay settings using overlay_info from ovl->info if + * dirty and from data->info otherwise + */ +static int dss_check_settings_apply(struct omap_overlay_manager *mgr) +{ +	return dss_check_settings_low(mgr, true); +} + +static bool need_isr(void) +{ +	const int num_mgrs = dss_feat_get_num_mgrs(); +	int i; + +	for (i = 0; i < num_mgrs; ++i) { +		struct omap_overlay_manager *mgr; +		struct mgr_priv_data *mp; +		struct omap_overlay *ovl; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mp->enabled) +			continue; + +		if (mgr_manual_update(mgr)) { +			/* to catch FRAMEDONE */ +			if (mp->updating) +				return true; +		} else { +			/* to catch GO bit going down */ +			if (mp->busy) +				return true; + +			/* to write new values to registers */ +			if (mp->info_dirty) +				return true; + +			/* to set GO bit */ +			if (mp->shadow_info_dirty) +				return true; + +			/* +			 * NOTE: we don't check extra_info flags for disabled +			 * managers, once the manager is enabled, the extra_info +			 * related manager changes will be taken in by HW. +			 */ + +			/* to write new values to registers */ +			if (mp->extra_info_dirty) +				return true; + +			/* to set GO bit */ +			if (mp->shadow_extra_info_dirty) +				return true; + +			list_for_each_entry(ovl, &mgr->overlays, list) { +				struct ovl_priv_data *op; + +				op = get_ovl_priv(ovl); + +				/* +				 * NOTE: we check extra_info flags even for +				 * disabled overlays, as extra_infos need to be +				 * always written. +				 */ + +				/* to write new values to registers */ +				if (op->extra_info_dirty) +					return true; + +				/* to set GO bit */ +				if (op->shadow_extra_info_dirty) +					return true; + +				if (!op->enabled) +					continue; + +				/* to write new values to registers */ +				if (op->info_dirty) +					return true; + +				/* to set GO bit */ +				if (op->shadow_info_dirty) +					return true; +			} +		} +	} + +	return false; +} + +static bool need_go(struct omap_overlay_manager *mgr) +{ +	struct omap_overlay *ovl; +	struct mgr_priv_data *mp; +	struct ovl_priv_data *op; + +	mp = get_mgr_priv(mgr); + +	if (mp->shadow_info_dirty || mp->shadow_extra_info_dirty) +		return true; + +	list_for_each_entry(ovl, &mgr->overlays, list) { +		op = get_ovl_priv(ovl); +		if (op->shadow_info_dirty || op->shadow_extra_info_dirty) +			return true; +	} + +	return false; +} + +/* returns true if an extra_info field is currently being updated */ +static bool extra_info_update_ongoing(void) +{ +	const int num_mgrs = dss_feat_get_num_mgrs(); +	int i; + +	for (i = 0; i < num_mgrs; ++i) { +		struct omap_overlay_manager *mgr; +		struct omap_overlay *ovl; +		struct mgr_priv_data *mp; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mp->enabled) +			continue; + +		if (!mp->updating) +			continue; + +		if (mp->extra_info_dirty || mp->shadow_extra_info_dirty) +			return true; + +		list_for_each_entry(ovl, &mgr->overlays, list) { +			struct ovl_priv_data *op = get_ovl_priv(ovl); + +			if (op->extra_info_dirty || op->shadow_extra_info_dirty) +				return true; +		} +	} + +	return false; +} + +/* wait until no extra_info updates are pending */ +static void wait_pending_extra_info_updates(void) +{ +	bool updating; +	unsigned long flags; +	unsigned long t; +	int r; + +	spin_lock_irqsave(&data_lock, flags); + +	updating = extra_info_update_ongoing(); + +	if (!updating) { +		spin_unlock_irqrestore(&data_lock, flags); +		return; +	} + +	init_completion(&extra_updated_completion); + +	spin_unlock_irqrestore(&data_lock, flags); + +	t = msecs_to_jiffies(500); +	r = wait_for_completion_timeout(&extra_updated_completion, t); +	if (r == 0) +		DSSWARN("timeout in wait_pending_extra_info_updates\n"); +} + +static struct omap_dss_device *dss_mgr_get_device(struct omap_overlay_manager *mgr) +{ +	struct omap_dss_device *dssdev; + +	dssdev = mgr->output; +	if (dssdev == NULL) +		return NULL; + +	while (dssdev->dst) +		dssdev = dssdev->dst; + +	if (dssdev->driver) +		return dssdev; +	else +		return NULL; +} + +static struct omap_dss_device *dss_ovl_get_device(struct omap_overlay *ovl) +{ +	return ovl->manager ? dss_mgr_get_device(ovl->manager) : NULL; +} + +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->output == NULL) +		return -ENODEV; + +	r = dispc_runtime_get(); +	if (r) +		return r; + +	switch (mgr->output->id) { +	case OMAP_DSS_OUTPUT_VENC: +		irq = DISPC_IRQ_EVSYNC_ODD; +		break; +	case OMAP_DSS_OUTPUT_HDMI: +		irq = DISPC_IRQ_EVSYNC_EVEN; +		break; +	default: +		irq = dispc_mgr_get_vsync_irq(mgr->id); +		break; +	} + +	r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); + +	dispc_runtime_put(); + +	return r; +} + +static int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr) +{ +	unsigned long timeout = msecs_to_jiffies(500); +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	u32 irq; +	unsigned long flags; +	int r; +	int i; + +	spin_lock_irqsave(&data_lock, flags); + +	if (mgr_manual_update(mgr)) { +		spin_unlock_irqrestore(&data_lock, flags); +		return 0; +	} + +	if (!mp->enabled) { +		spin_unlock_irqrestore(&data_lock, flags); +		return 0; +	} + +	spin_unlock_irqrestore(&data_lock, flags); + +	r = dispc_runtime_get(); +	if (r) +		return r; + +	irq = dispc_mgr_get_vsync_irq(mgr->id); + +	i = 0; +	while (1) { +		bool shadow_dirty, dirty; + +		spin_lock_irqsave(&data_lock, flags); +		dirty = mp->info_dirty; +		shadow_dirty = mp->shadow_info_dirty; +		spin_unlock_irqrestore(&data_lock, flags); + +		if (!dirty && !shadow_dirty) { +			r = 0; +			break; +		} + +		/* 4 iterations is the worst case: +		 * 1 - initial iteration, dirty = true (between VFP and VSYNC) +		 * 2 - first VSYNC, dirty = true +		 * 3 - dirty = false, shadow_dirty = true +		 * 4 - shadow_dirty = false */ +		if (i++ == 3) { +			DSSERR("mgr(%d)->wait_for_go() not finishing\n", +					mgr->id); +			r = 0; +			break; +		} + +		r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); +		if (r == -ERESTARTSYS) +			break; + +		if (r) { +			DSSERR("mgr(%d)->wait_for_go() timeout\n", mgr->id); +			break; +		} +	} + +	dispc_runtime_put(); + +	return r; +} + +static int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl) +{ +	unsigned long timeout = msecs_to_jiffies(500); +	struct ovl_priv_data *op; +	struct mgr_priv_data *mp; +	u32 irq; +	unsigned long flags; +	int r; +	int i; + +	if (!ovl->manager) +		return 0; + +	mp = get_mgr_priv(ovl->manager); + +	spin_lock_irqsave(&data_lock, flags); + +	if (ovl_manual_update(ovl)) { +		spin_unlock_irqrestore(&data_lock, flags); +		return 0; +	} + +	if (!mp->enabled) { +		spin_unlock_irqrestore(&data_lock, flags); +		return 0; +	} + +	spin_unlock_irqrestore(&data_lock, flags); + +	r = dispc_runtime_get(); +	if (r) +		return r; + +	irq = dispc_mgr_get_vsync_irq(ovl->manager->id); + +	op = get_ovl_priv(ovl); +	i = 0; +	while (1) { +		bool shadow_dirty, dirty; + +		spin_lock_irqsave(&data_lock, flags); +		dirty = op->info_dirty; +		shadow_dirty = op->shadow_info_dirty; +		spin_unlock_irqrestore(&data_lock, flags); + +		if (!dirty && !shadow_dirty) { +			r = 0; +			break; +		} + +		/* 4 iterations is the worst case: +		 * 1 - initial iteration, dirty = true (between VFP and VSYNC) +		 * 2 - first VSYNC, dirty = true +		 * 3 - dirty = false, shadow_dirty = true +		 * 4 - shadow_dirty = false */ +		if (i++ == 3) { +			DSSERR("ovl(%d)->wait_for_go() not finishing\n", +					ovl->id); +			r = 0; +			break; +		} + +		r = omap_dispc_wait_for_irq_interruptible_timeout(irq, timeout); +		if (r == -ERESTARTSYS) +			break; + +		if (r) { +			DSSERR("ovl(%d)->wait_for_go() timeout\n", ovl->id); +			break; +		} +	} + +	dispc_runtime_put(); + +	return r; +} + +static void dss_ovl_write_regs(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	struct omap_overlay_info *oi; +	bool replication; +	struct mgr_priv_data *mp; +	int r; + +	DSSDBG("writing ovl %d regs\n", ovl->id); + +	if (!op->enabled || !op->info_dirty) +		return; + +	oi = &op->info; + +	mp = get_mgr_priv(ovl->manager); + +	replication = dss_ovl_use_replication(mp->lcd_config, oi->color_mode); + +	r = dispc_ovl_setup(ovl->id, oi, replication, &mp->timings, false); +	if (r) { +		/* +		 * We can't do much here, as this function can be called from +		 * vsync interrupt. +		 */ +		DSSERR("dispc_ovl_setup failed for ovl %d\n", ovl->id); + +		/* This will leave fifo configurations in a nonoptimal state */ +		op->enabled = false; +		dispc_ovl_enable(ovl->id, false); +		return; +	} + +	op->info_dirty = false; +	if (mp->updating) +		op->shadow_info_dirty = true; +} + +static void dss_ovl_write_regs_extra(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	struct mgr_priv_data *mp; + +	DSSDBG("writing ovl %d regs extra\n", ovl->id); + +	if (!op->extra_info_dirty) +		return; + +	/* note: write also when op->enabled == false, so that the ovl gets +	 * disabled */ + +	dispc_ovl_enable(ovl->id, op->enabled); +	dispc_ovl_set_fifo_threshold(ovl->id, op->fifo_low, op->fifo_high); + +	mp = get_mgr_priv(ovl->manager); + +	op->extra_info_dirty = false; +	if (mp->updating) +		op->shadow_extra_info_dirty = true; +} + +static void dss_mgr_write_regs(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	struct omap_overlay *ovl; + +	DSSDBG("writing mgr %d regs\n", mgr->id); + +	if (!mp->enabled) +		return; + +	WARN_ON(mp->busy); + +	/* Commit overlay settings */ +	list_for_each_entry(ovl, &mgr->overlays, list) { +		dss_ovl_write_regs(ovl); +		dss_ovl_write_regs_extra(ovl); +	} + +	if (mp->info_dirty) { +		dispc_mgr_setup(mgr->id, &mp->info); + +		mp->info_dirty = false; +		if (mp->updating) +			mp->shadow_info_dirty = true; +	} +} + +static void dss_mgr_write_regs_extra(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	DSSDBG("writing mgr %d regs extra\n", mgr->id); + +	if (!mp->extra_info_dirty) +		return; + +	dispc_mgr_set_timings(mgr->id, &mp->timings); + +	/* lcd_config parameters */ +	if (dss_mgr_is_lcd(mgr->id)) +		dispc_mgr_set_lcd_config(mgr->id, &mp->lcd_config); + +	mp->extra_info_dirty = false; +	if (mp->updating) +		mp->shadow_extra_info_dirty = true; +} + +static void dss_write_regs(void) +{ +	const int num_mgrs = omap_dss_get_num_overlay_managers(); +	int i; + +	for (i = 0; i < num_mgrs; ++i) { +		struct omap_overlay_manager *mgr; +		struct mgr_priv_data *mp; +		int r; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mp->enabled || mgr_manual_update(mgr) || mp->busy) +			continue; + +		r = dss_check_settings(mgr); +		if (r) { +			DSSERR("cannot write registers for manager %s: " +					"illegal configuration\n", mgr->name); +			continue; +		} + +		dss_mgr_write_regs(mgr); +		dss_mgr_write_regs_extra(mgr); +	} +} + +static void dss_set_go_bits(void) +{ +	const int num_mgrs = omap_dss_get_num_overlay_managers(); +	int i; + +	for (i = 0; i < num_mgrs; ++i) { +		struct omap_overlay_manager *mgr; +		struct mgr_priv_data *mp; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mp->enabled || mgr_manual_update(mgr) || mp->busy) +			continue; + +		if (!need_go(mgr)) +			continue; + +		mp->busy = true; + +		if (!dss_data.irq_enabled && need_isr()) +			dss_register_vsync_isr(); + +		dispc_mgr_go(mgr->id); +	} + +} + +static void mgr_clear_shadow_dirty(struct omap_overlay_manager *mgr) +{ +	struct omap_overlay *ovl; +	struct mgr_priv_data *mp; +	struct ovl_priv_data *op; + +	mp = get_mgr_priv(mgr); +	mp->shadow_info_dirty = false; +	mp->shadow_extra_info_dirty = false; + +	list_for_each_entry(ovl, &mgr->overlays, list) { +		op = get_ovl_priv(ovl); +		op->shadow_info_dirty = false; +		op->shadow_extra_info_dirty = false; +	} +} + +static int dss_mgr_connect_compat(struct omap_overlay_manager *mgr, +		struct omap_dss_device *dst) +{ +	return mgr->set_output(mgr, dst); +} + +static void dss_mgr_disconnect_compat(struct omap_overlay_manager *mgr, +		struct omap_dss_device *dst) +{ +	mgr->unset_output(mgr); +} + +static void dss_mgr_start_update_compat(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&data_lock, flags); + +	WARN_ON(mp->updating); + +	r = dss_check_settings(mgr); +	if (r) { +		DSSERR("cannot start manual update: illegal configuration\n"); +		spin_unlock_irqrestore(&data_lock, flags); +		return; +	} + +	dss_mgr_write_regs(mgr); +	dss_mgr_write_regs_extra(mgr); + +	mp->updating = true; + +	if (!dss_data.irq_enabled && need_isr()) +		dss_register_vsync_isr(); + +	dispc_mgr_enable_sync(mgr->id); + +	spin_unlock_irqrestore(&data_lock, flags); +} + +static void dss_apply_irq_handler(void *data, u32 mask); + +static void dss_register_vsync_isr(void) +{ +	const int num_mgrs = dss_feat_get_num_mgrs(); +	u32 mask; +	int r, i; + +	mask = 0; +	for (i = 0; i < num_mgrs; ++i) +		mask |= dispc_mgr_get_vsync_irq(i); + +	for (i = 0; i < num_mgrs; ++i) +		mask |= dispc_mgr_get_framedone_irq(i); + +	r = omap_dispc_register_isr(dss_apply_irq_handler, NULL, mask); +	WARN_ON(r); + +	dss_data.irq_enabled = true; +} + +static void dss_unregister_vsync_isr(void) +{ +	const int num_mgrs = dss_feat_get_num_mgrs(); +	u32 mask; +	int r, i; + +	mask = 0; +	for (i = 0; i < num_mgrs; ++i) +		mask |= dispc_mgr_get_vsync_irq(i); + +	for (i = 0; i < num_mgrs; ++i) +		mask |= dispc_mgr_get_framedone_irq(i); + +	r = omap_dispc_unregister_isr(dss_apply_irq_handler, NULL, mask); +	WARN_ON(r); + +	dss_data.irq_enabled = false; +} + +static void dss_apply_irq_handler(void *data, u32 mask) +{ +	const int num_mgrs = dss_feat_get_num_mgrs(); +	int i; +	bool extra_updating; + +	spin_lock(&data_lock); + +	/* clear busy, updating flags, shadow_dirty flags */ +	for (i = 0; i < num_mgrs; i++) { +		struct omap_overlay_manager *mgr; +		struct mgr_priv_data *mp; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mp->enabled) +			continue; + +		mp->updating = dispc_mgr_is_enabled(i); + +		if (!mgr_manual_update(mgr)) { +			bool was_busy = mp->busy; +			mp->busy = dispc_mgr_go_busy(i); + +			if (was_busy && !mp->busy) +				mgr_clear_shadow_dirty(mgr); +		} +	} + +	dss_write_regs(); +	dss_set_go_bits(); + +	extra_updating = extra_info_update_ongoing(); +	if (!extra_updating) +		complete_all(&extra_updated_completion); + +	/* call framedone handlers for manual update displays */ +	for (i = 0; i < num_mgrs; i++) { +		struct omap_overlay_manager *mgr; +		struct mgr_priv_data *mp; + +		mgr = omap_dss_get_overlay_manager(i); +		mp = get_mgr_priv(mgr); + +		if (!mgr_manual_update(mgr) || !mp->framedone_handler) +			continue; + +		if (mask & dispc_mgr_get_framedone_irq(i)) +			mp->framedone_handler(mp->framedone_handler_data); +	} + +	if (!need_isr()) +		dss_unregister_vsync_isr(); + +	spin_unlock(&data_lock); +} + +static void omap_dss_mgr_apply_ovl(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op; + +	op = get_ovl_priv(ovl); + +	if (!op->user_info_dirty) +		return; + +	op->user_info_dirty = false; +	op->info_dirty = true; +	op->info = op->user_info; +} + +static void omap_dss_mgr_apply_mgr(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp; + +	mp = get_mgr_priv(mgr); + +	if (!mp->user_info_dirty) +		return; + +	mp->user_info_dirty = false; +	mp->info_dirty = true; +	mp->info = mp->user_info; +} + +static int omap_dss_mgr_apply(struct omap_overlay_manager *mgr) +{ +	unsigned long flags; +	struct omap_overlay *ovl; +	int r; + +	DSSDBG("omap_dss_mgr_apply(%s)\n", mgr->name); + +	spin_lock_irqsave(&data_lock, flags); + +	r = dss_check_settings_apply(mgr); +	if (r) { +		spin_unlock_irqrestore(&data_lock, flags); +		DSSERR("failed to apply settings: illegal configuration.\n"); +		return r; +	} + +	/* Configure overlays */ +	list_for_each_entry(ovl, &mgr->overlays, list) +		omap_dss_mgr_apply_ovl(ovl); + +	/* Configure manager */ +	omap_dss_mgr_apply_mgr(mgr); + +	dss_write_regs(); +	dss_set_go_bits(); + +	spin_unlock_irqrestore(&data_lock, flags); + +	return 0; +} + +static void dss_apply_ovl_enable(struct omap_overlay *ovl, bool enable) +{ +	struct ovl_priv_data *op; + +	op = get_ovl_priv(ovl); + +	if (op->enabled == enable) +		return; + +	op->enabled = enable; +	op->extra_info_dirty = true; +} + +static void dss_apply_ovl_fifo_thresholds(struct omap_overlay *ovl, +		u32 fifo_low, u32 fifo_high) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); + +	if (op->fifo_low == fifo_low && op->fifo_high == fifo_high) +		return; + +	op->fifo_low = fifo_low; +	op->fifo_high = fifo_high; +	op->extra_info_dirty = true; +} + +static void dss_ovl_setup_fifo(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	u32 fifo_low, fifo_high; +	bool use_fifo_merge = false; + +	if (!op->enabled && !op->enabling) +		return; + +	dispc_ovl_compute_fifo_thresholds(ovl->id, &fifo_low, &fifo_high, +			use_fifo_merge, ovl_manual_update(ovl)); + +	dss_apply_ovl_fifo_thresholds(ovl, fifo_low, fifo_high); +} + +static void dss_mgr_setup_fifos(struct omap_overlay_manager *mgr) +{ +	struct omap_overlay *ovl; +	struct mgr_priv_data *mp; + +	mp = get_mgr_priv(mgr); + +	if (!mp->enabled) +		return; + +	list_for_each_entry(ovl, &mgr->overlays, list) +		dss_ovl_setup_fifo(ovl); +} + +static void dss_setup_fifos(void) +{ +	const int num_mgrs = omap_dss_get_num_overlay_managers(); +	struct omap_overlay_manager *mgr; +	int i; + +	for (i = 0; i < num_mgrs; ++i) { +		mgr = omap_dss_get_overlay_manager(i); +		dss_mgr_setup_fifos(mgr); +	} +} + +static int dss_mgr_enable_compat(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; +	int r; + +	mutex_lock(&apply_lock); + +	if (mp->enabled) +		goto out; + +	spin_lock_irqsave(&data_lock, flags); + +	mp->enabled = true; + +	r = dss_check_settings(mgr); +	if (r) { +		DSSERR("failed to enable manager %d: check_settings failed\n", +				mgr->id); +		goto err; +	} + +	dss_setup_fifos(); + +	dss_write_regs(); +	dss_set_go_bits(); + +	if (!mgr_manual_update(mgr)) +		mp->updating = true; + +	if (!dss_data.irq_enabled && need_isr()) +		dss_register_vsync_isr(); + +	spin_unlock_irqrestore(&data_lock, flags); + +	if (!mgr_manual_update(mgr)) +		dispc_mgr_enable_sync(mgr->id); + +out: +	mutex_unlock(&apply_lock); + +	return 0; + +err: +	mp->enabled = false; +	spin_unlock_irqrestore(&data_lock, flags); +	mutex_unlock(&apply_lock); +	return r; +} + +static void dss_mgr_disable_compat(struct omap_overlay_manager *mgr) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; + +	mutex_lock(&apply_lock); + +	if (!mp->enabled) +		goto out; + +	if (!mgr_manual_update(mgr)) +		dispc_mgr_disable_sync(mgr->id); + +	spin_lock_irqsave(&data_lock, flags); + +	mp->updating = false; +	mp->enabled = false; + +	spin_unlock_irqrestore(&data_lock, flags); + +out: +	mutex_unlock(&apply_lock); +} + +static int dss_mgr_set_info(struct omap_overlay_manager *mgr, +		struct omap_overlay_manager_info *info) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; +	int r; + +	r = dss_mgr_simple_check(mgr, info); +	if (r) +		return r; + +	spin_lock_irqsave(&data_lock, flags); + +	mp->user_info = *info; +	mp->user_info_dirty = true; + +	spin_unlock_irqrestore(&data_lock, flags); + +	return 0; +} + +static void dss_mgr_get_info(struct omap_overlay_manager *mgr, +		struct omap_overlay_manager_info *info) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; + +	spin_lock_irqsave(&data_lock, flags); + +	*info = mp->user_info; + +	spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_mgr_set_output(struct omap_overlay_manager *mgr, +		struct omap_dss_device *output) +{ +	int r; + +	mutex_lock(&apply_lock); + +	if (mgr->output) { +		DSSERR("manager %s is already connected to an output\n", +			mgr->name); +		r = -EINVAL; +		goto err; +	} + +	if ((mgr->supported_outputs & output->id) == 0) { +		DSSERR("output does not support manager %s\n", +			mgr->name); +		r = -EINVAL; +		goto err; +	} + +	output->manager = mgr; +	mgr->output = output; + +	mutex_unlock(&apply_lock); + +	return 0; +err: +	mutex_unlock(&apply_lock); +	return r; +} + +static int dss_mgr_unset_output(struct omap_overlay_manager *mgr) +{ +	int r; +	struct mgr_priv_data *mp = get_mgr_priv(mgr); +	unsigned long flags; + +	mutex_lock(&apply_lock); + +	if (!mgr->output) { +		DSSERR("failed to unset output, output not set\n"); +		r = -EINVAL; +		goto err; +	} + +	spin_lock_irqsave(&data_lock, flags); + +	if (mp->enabled) { +		DSSERR("output can't be unset when manager is enabled\n"); +		r = -EINVAL; +		goto err1; +	} + +	spin_unlock_irqrestore(&data_lock, flags); + +	mgr->output->manager = NULL; +	mgr->output = NULL; + +	mutex_unlock(&apply_lock); + +	return 0; +err1: +	spin_unlock_irqrestore(&data_lock, flags); +err: +	mutex_unlock(&apply_lock); + +	return r; +} + +static void dss_apply_mgr_timings(struct omap_overlay_manager *mgr, +		const struct omap_video_timings *timings) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	mp->timings = *timings; +	mp->extra_info_dirty = true; +} + +static void dss_mgr_set_timings_compat(struct omap_overlay_manager *mgr, +		const struct omap_video_timings *timings) +{ +	unsigned long flags; +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	spin_lock_irqsave(&data_lock, flags); + +	if (mp->updating) { +		DSSERR("cannot set timings for %s: manager needs to be disabled\n", +			mgr->name); +		goto out; +	} + +	dss_apply_mgr_timings(mgr, timings); +out: +	spin_unlock_irqrestore(&data_lock, flags); +} + +static void dss_apply_mgr_lcd_config(struct omap_overlay_manager *mgr, +		const struct dss_lcd_mgr_config *config) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	mp->lcd_config = *config; +	mp->extra_info_dirty = true; +} + +static void dss_mgr_set_lcd_config_compat(struct omap_overlay_manager *mgr, +		const struct dss_lcd_mgr_config *config) +{ +	unsigned long flags; +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	spin_lock_irqsave(&data_lock, flags); + +	if (mp->enabled) { +		DSSERR("cannot apply lcd config for %s: manager needs to be disabled\n", +			mgr->name); +		goto out; +	} + +	dss_apply_mgr_lcd_config(mgr, config); +out: +	spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_ovl_set_info(struct omap_overlay *ovl, +		struct omap_overlay_info *info) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	int r; + +	r = dss_ovl_simple_check(ovl, info); +	if (r) +		return r; + +	spin_lock_irqsave(&data_lock, flags); + +	op->user_info = *info; +	op->user_info_dirty = true; + +	spin_unlock_irqrestore(&data_lock, flags); + +	return 0; +} + +static void dss_ovl_get_info(struct omap_overlay *ovl, +		struct omap_overlay_info *info) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; + +	spin_lock_irqsave(&data_lock, flags); + +	*info = op->user_info; + +	spin_unlock_irqrestore(&data_lock, flags); +} + +static int dss_ovl_set_manager(struct omap_overlay *ovl, +		struct omap_overlay_manager *mgr) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	int r; + +	if (!mgr) +		return -EINVAL; + +	mutex_lock(&apply_lock); + +	if (ovl->manager) { +		DSSERR("overlay '%s' already has a manager '%s'\n", +				ovl->name, ovl->manager->name); +		r = -EINVAL; +		goto err; +	} + +	r = dispc_runtime_get(); +	if (r) +		goto err; + +	spin_lock_irqsave(&data_lock, flags); + +	if (op->enabled) { +		spin_unlock_irqrestore(&data_lock, flags); +		DSSERR("overlay has to be disabled to change the manager\n"); +		r = -EINVAL; +		goto err1; +	} + +	dispc_ovl_set_channel_out(ovl->id, mgr->id); + +	ovl->manager = mgr; +	list_add_tail(&ovl->list, &mgr->overlays); + +	spin_unlock_irqrestore(&data_lock, flags); + +	dispc_runtime_put(); + +	mutex_unlock(&apply_lock); + +	return 0; + +err1: +	dispc_runtime_put(); +err: +	mutex_unlock(&apply_lock); +	return r; +} + +static int dss_ovl_unset_manager(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	int r; + +	mutex_lock(&apply_lock); + +	if (!ovl->manager) { +		DSSERR("failed to detach overlay: manager not set\n"); +		r = -EINVAL; +		goto err; +	} + +	spin_lock_irqsave(&data_lock, flags); + +	if (op->enabled) { +		spin_unlock_irqrestore(&data_lock, flags); +		DSSERR("overlay has to be disabled to unset the manager\n"); +		r = -EINVAL; +		goto err; +	} + +	spin_unlock_irqrestore(&data_lock, flags); + +	/* wait for pending extra_info updates to ensure the ovl is disabled */ +	wait_pending_extra_info_updates(); + +	/* +	 * For a manual update display, there is no guarantee that the overlay +	 * is really disabled in HW, we may need an extra update from this +	 * manager before the configurations can go in. Return an error if the +	 * overlay needed an update from the manager. +	 * +	 * TODO: Instead of returning an error, try to do a dummy manager update +	 * here to disable the overlay in hardware. Use the *GATED fields in +	 * the DISPC_CONFIG registers to do a dummy update. +	 */ +	spin_lock_irqsave(&data_lock, flags); + +	if (ovl_manual_update(ovl) && op->extra_info_dirty) { +		spin_unlock_irqrestore(&data_lock, flags); +		DSSERR("need an update to change the manager\n"); +		r = -EINVAL; +		goto err; +	} + +	ovl->manager = NULL; +	list_del(&ovl->list); + +	spin_unlock_irqrestore(&data_lock, flags); + +	mutex_unlock(&apply_lock); + +	return 0; +err: +	mutex_unlock(&apply_lock); +	return r; +} + +static bool dss_ovl_is_enabled(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	bool e; + +	spin_lock_irqsave(&data_lock, flags); + +	e = op->enabled; + +	spin_unlock_irqrestore(&data_lock, flags); + +	return e; +} + +static int dss_ovl_enable(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	int r; + +	mutex_lock(&apply_lock); + +	if (op->enabled) { +		r = 0; +		goto err1; +	} + +	if (ovl->manager == NULL || ovl->manager->output == NULL) { +		r = -EINVAL; +		goto err1; +	} + +	spin_lock_irqsave(&data_lock, flags); + +	op->enabling = true; + +	r = dss_check_settings(ovl->manager); +	if (r) { +		DSSERR("failed to enable overlay %d: check_settings failed\n", +				ovl->id); +		goto err2; +	} + +	dss_setup_fifos(); + +	op->enabling = false; +	dss_apply_ovl_enable(ovl, true); + +	dss_write_regs(); +	dss_set_go_bits(); + +	spin_unlock_irqrestore(&data_lock, flags); + +	mutex_unlock(&apply_lock); + +	return 0; +err2: +	op->enabling = false; +	spin_unlock_irqrestore(&data_lock, flags); +err1: +	mutex_unlock(&apply_lock); +	return r; +} + +static int dss_ovl_disable(struct omap_overlay *ovl) +{ +	struct ovl_priv_data *op = get_ovl_priv(ovl); +	unsigned long flags; +	int r; + +	mutex_lock(&apply_lock); + +	if (!op->enabled) { +		r = 0; +		goto err; +	} + +	if (ovl->manager == NULL || ovl->manager->output == NULL) { +		r = -EINVAL; +		goto err; +	} + +	spin_lock_irqsave(&data_lock, flags); + +	dss_apply_ovl_enable(ovl, false); +	dss_write_regs(); +	dss_set_go_bits(); + +	spin_unlock_irqrestore(&data_lock, flags); + +	mutex_unlock(&apply_lock); + +	return 0; + +err: +	mutex_unlock(&apply_lock); +	return r; +} + +static int dss_mgr_register_framedone_handler_compat(struct omap_overlay_manager *mgr, +		void (*handler)(void *), void *data) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	if (mp->framedone_handler) +		return -EBUSY; + +	mp->framedone_handler = handler; +	mp->framedone_handler_data = data; + +	return 0; +} + +static void dss_mgr_unregister_framedone_handler_compat(struct omap_overlay_manager *mgr, +		void (*handler)(void *), void *data) +{ +	struct mgr_priv_data *mp = get_mgr_priv(mgr); + +	WARN_ON(mp->framedone_handler != handler || +			mp->framedone_handler_data != data); + +	mp->framedone_handler = NULL; +	mp->framedone_handler_data = NULL; +} + +static const struct dss_mgr_ops apply_mgr_ops = { +	.connect = dss_mgr_connect_compat, +	.disconnect = dss_mgr_disconnect_compat, +	.start_update = dss_mgr_start_update_compat, +	.enable = dss_mgr_enable_compat, +	.disable = dss_mgr_disable_compat, +	.set_timings = dss_mgr_set_timings_compat, +	.set_lcd_config = dss_mgr_set_lcd_config_compat, +	.register_framedone_handler = dss_mgr_register_framedone_handler_compat, +	.unregister_framedone_handler = dss_mgr_unregister_framedone_handler_compat, +}; + +static int compat_refcnt; +static DEFINE_MUTEX(compat_init_lock); + +int omapdss_compat_init(void) +{ +	struct platform_device *pdev = dss_get_core_pdev(); +	int i, r; + +	mutex_lock(&compat_init_lock); + +	if (compat_refcnt++ > 0) +		goto out; + +	apply_init_priv(); + +	dss_init_overlay_managers_sysfs(pdev); +	dss_init_overlays(pdev); + +	for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { +		struct omap_overlay_manager *mgr; + +		mgr = omap_dss_get_overlay_manager(i); + +		mgr->set_output = &dss_mgr_set_output; +		mgr->unset_output = &dss_mgr_unset_output; +		mgr->apply = &omap_dss_mgr_apply; +		mgr->set_manager_info = &dss_mgr_set_info; +		mgr->get_manager_info = &dss_mgr_get_info; +		mgr->wait_for_go = &dss_mgr_wait_for_go; +		mgr->wait_for_vsync = &dss_mgr_wait_for_vsync; +		mgr->get_device = &dss_mgr_get_device; +	} + +	for (i = 0; i < omap_dss_get_num_overlays(); i++) { +		struct omap_overlay *ovl = omap_dss_get_overlay(i); + +		ovl->is_enabled = &dss_ovl_is_enabled; +		ovl->enable = &dss_ovl_enable; +		ovl->disable = &dss_ovl_disable; +		ovl->set_manager = &dss_ovl_set_manager; +		ovl->unset_manager = &dss_ovl_unset_manager; +		ovl->set_overlay_info = &dss_ovl_set_info; +		ovl->get_overlay_info = &dss_ovl_get_info; +		ovl->wait_for_go = &dss_mgr_wait_for_go_ovl; +		ovl->get_device = &dss_ovl_get_device; +	} + +	r = dss_install_mgr_ops(&apply_mgr_ops); +	if (r) +		goto err_mgr_ops; + +	r = display_init_sysfs(pdev); +	if (r) +		goto err_disp_sysfs; + +	dispc_runtime_get(); + +	r = dss_dispc_initialize_irq(); +	if (r) +		goto err_init_irq; + +	dispc_runtime_put(); + +out: +	mutex_unlock(&compat_init_lock); + +	return 0; + +err_init_irq: +	dispc_runtime_put(); +	display_uninit_sysfs(pdev); + +err_disp_sysfs: +	dss_uninstall_mgr_ops(); + +err_mgr_ops: +	dss_uninit_overlay_managers_sysfs(pdev); +	dss_uninit_overlays(pdev); + +	compat_refcnt--; + +	mutex_unlock(&compat_init_lock); + +	return r; +} +EXPORT_SYMBOL(omapdss_compat_init); + +void omapdss_compat_uninit(void) +{ +	struct platform_device *pdev = dss_get_core_pdev(); + +	mutex_lock(&compat_init_lock); + +	if (--compat_refcnt > 0) +		goto out; + +	dss_dispc_uninitialize_irq(); + +	display_uninit_sysfs(pdev); + +	dss_uninstall_mgr_ops(); + +	dss_uninit_overlay_managers_sysfs(pdev); +	dss_uninit_overlays(pdev); +out: +	mutex_unlock(&compat_init_lock); +} +EXPORT_SYMBOL(omapdss_compat_uninit); diff --git a/drivers/video/fbdev/omap2/dss/core.c b/drivers/video/fbdev/omap2/dss/core.c new file mode 100644 index 00000000000..6b74f73fb52 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/core.c @@ -0,0 +1,366 @@ +/* + * linux/drivers/video/omap2/dss/core.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "CORE" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/regulator/consumer.h> +#include <linux/suspend.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static struct { +	struct platform_device *pdev; + +	const char *default_display_name; +} core; + +static char *def_disp_name; +module_param_named(def_disp, def_disp_name, charp, 0); +MODULE_PARM_DESC(def_disp, "default display name"); + +static bool dss_initialized; + +const char *omapdss_get_default_display_name(void) +{ +	return core.default_display_name; +} +EXPORT_SYMBOL(omapdss_get_default_display_name); + +enum omapdss_version omapdss_get_version(void) +{ +	struct omap_dss_board_info *pdata = core.pdev->dev.platform_data; +	return pdata->version; +} +EXPORT_SYMBOL(omapdss_get_version); + +bool omapdss_is_initialized(void) +{ +	return dss_initialized; +} +EXPORT_SYMBOL(omapdss_is_initialized); + +struct platform_device *dss_get_core_pdev(void) +{ +	return core.pdev; +} + +int dss_dsi_enable_pads(int dsi_id, unsigned lane_mask) +{ +	struct omap_dss_board_info *board_data = core.pdev->dev.platform_data; + +	if (!board_data->dsi_enable_pads) +		return -ENOENT; + +	return board_data->dsi_enable_pads(dsi_id, lane_mask); +} + +void dss_dsi_disable_pads(int dsi_id, unsigned lane_mask) +{ +	struct omap_dss_board_info *board_data = core.pdev->dev.platform_data; + +	if (!board_data->dsi_disable_pads) +		return; + +	return board_data->dsi_disable_pads(dsi_id, lane_mask); +} + +int dss_set_min_bus_tput(struct device *dev, unsigned long tput) +{ +	struct omap_dss_board_info *pdata = core.pdev->dev.platform_data; + +	if (pdata->set_min_bus_tput) +		return pdata->set_min_bus_tput(dev, tput); +	else +		return 0; +} + +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +static int dss_debug_show(struct seq_file *s, void *unused) +{ +	void (*func)(struct seq_file *) = s->private; +	func(s); +	return 0; +} + +static int dss_debug_open(struct inode *inode, struct file *file) +{ +	return single_open(file, dss_debug_show, inode->i_private); +} + +static const struct file_operations dss_debug_fops = { +	.open           = dss_debug_open, +	.read           = seq_read, +	.llseek         = seq_lseek, +	.release        = single_release, +}; + +static struct dentry *dss_debugfs_dir; + +static int dss_initialize_debugfs(void) +{ +	dss_debugfs_dir = debugfs_create_dir("omapdss", NULL); +	if (IS_ERR(dss_debugfs_dir)) { +		int err = PTR_ERR(dss_debugfs_dir); +		dss_debugfs_dir = NULL; +		return err; +	} + +	debugfs_create_file("clk", S_IRUGO, dss_debugfs_dir, +			&dss_debug_dump_clocks, &dss_debug_fops); + +	return 0; +} + +static void dss_uninitialize_debugfs(void) +{ +	if (dss_debugfs_dir) +		debugfs_remove_recursive(dss_debugfs_dir); +} + +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)) +{ +	struct dentry *d; + +	d = debugfs_create_file(name, S_IRUGO, dss_debugfs_dir, +			write, &dss_debug_fops); + +	return PTR_ERR_OR_ZERO(d); +} +#else /* CONFIG_OMAP2_DSS_DEBUGFS */ +static inline int dss_initialize_debugfs(void) +{ +	return 0; +} +static inline void dss_uninitialize_debugfs(void) +{ +} +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)) +{ +	return 0; +} +#endif /* CONFIG_OMAP2_DSS_DEBUGFS */ + +/* PLATFORM DEVICE */ +static int omap_dss_pm_notif(struct notifier_block *b, unsigned long v, void *d) +{ +	DSSDBG("pm notif %lu\n", v); + +	switch (v) { +	case PM_SUSPEND_PREPARE: +		DSSDBG("suspending displays\n"); +		return dss_suspend_all_devices(); + +	case PM_POST_SUSPEND: +		DSSDBG("resuming displays\n"); +		return dss_resume_all_devices(); + +	default: +		return 0; +	} +} + +static struct notifier_block omap_dss_pm_notif_block = { +	.notifier_call = omap_dss_pm_notif, +}; + +static int __init omap_dss_probe(struct platform_device *pdev) +{ +	struct omap_dss_board_info *pdata = pdev->dev.platform_data; +	int r; + +	core.pdev = pdev; + +	dss_features_init(omapdss_get_version()); + +	r = dss_initialize_debugfs(); +	if (r) +		goto err_debugfs; + +	if (def_disp_name) +		core.default_display_name = def_disp_name; +	else if (pdata->default_display_name) +		core.default_display_name = pdata->default_display_name; +	else if (pdata->default_device) +		core.default_display_name = pdata->default_device->name; + +	register_pm_notifier(&omap_dss_pm_notif_block); + +	return 0; + +err_debugfs: + +	return r; +} + +static int omap_dss_remove(struct platform_device *pdev) +{ +	unregister_pm_notifier(&omap_dss_pm_notif_block); + +	dss_uninitialize_debugfs(); + +	return 0; +} + +static void omap_dss_shutdown(struct platform_device *pdev) +{ +	DSSDBG("shutdown\n"); +	dss_disable_all_devices(); +} + +static struct platform_driver omap_dss_driver = { +	.remove         = omap_dss_remove, +	.shutdown	= omap_dss_shutdown, +	.driver         = { +		.name   = "omapdss", +		.owner  = THIS_MODULE, +	}, +}; + +/* INIT */ +static int (*dss_output_drv_reg_funcs[])(void) __initdata = { +#ifdef CONFIG_OMAP2_DSS_DSI +	dsi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_DPI +	dpi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_SDI +	sdi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_RFBI +	rfbi_init_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_VENC +	venc_init_platform_driver, +#endif +#ifdef CONFIG_OMAP4_DSS_HDMI +	hdmi4_init_platform_driver, +#endif +#ifdef CONFIG_OMAP5_DSS_HDMI +	hdmi5_init_platform_driver, +#endif +}; + +static void (*dss_output_drv_unreg_funcs[])(void) __exitdata = { +#ifdef CONFIG_OMAP2_DSS_DSI +	dsi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_DPI +	dpi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_SDI +	sdi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_RFBI +	rfbi_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP2_DSS_VENC +	venc_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP4_DSS_HDMI +	hdmi4_uninit_platform_driver, +#endif +#ifdef CONFIG_OMAP5_DSS_HDMI +	hdmi5_uninit_platform_driver, +#endif +}; + +static bool dss_output_drv_loaded[ARRAY_SIZE(dss_output_drv_reg_funcs)]; + +static int __init omap_dss_init(void) +{ +	int r; +	int i; + +	r = platform_driver_probe(&omap_dss_driver, omap_dss_probe); +	if (r) +		return r; + +	r = dss_init_platform_driver(); +	if (r) { +		DSSERR("Failed to initialize DSS platform driver\n"); +		goto err_dss; +	} + +	r = dispc_init_platform_driver(); +	if (r) { +		DSSERR("Failed to initialize dispc platform driver\n"); +		goto err_dispc; +	} + +	/* +	 * It's ok if the output-driver register fails. It happens, for example, +	 * when there is no output-device (e.g. SDI for OMAP4). +	 */ +	for (i = 0; i < ARRAY_SIZE(dss_output_drv_reg_funcs); ++i) { +		r = dss_output_drv_reg_funcs[i](); +		if (r == 0) +			dss_output_drv_loaded[i] = true; +	} + +	dss_initialized = true; + +	return 0; + +err_dispc: +	dss_uninit_platform_driver(); +err_dss: +	platform_driver_unregister(&omap_dss_driver); + +	return r; +} + +static void __exit omap_dss_exit(void) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(dss_output_drv_unreg_funcs); ++i) { +		if (dss_output_drv_loaded[i]) +			dss_output_drv_unreg_funcs[i](); +	} + +	dispc_uninit_platform_driver(); +	dss_uninit_platform_driver(); + +	platform_driver_unregister(&omap_dss_driver); +} + +module_init(omap_dss_init); +module_exit(omap_dss_exit); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); +MODULE_DESCRIPTION("OMAP2/3 Display Subsystem"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/video/fbdev/omap2/dss/dispc-compat.c b/drivers/video/fbdev/omap2/dss/dispc-compat.c new file mode 100644 index 00000000000..83779c2b292 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc-compat.c @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@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 DSS_SUBSYS_NAME "APPLY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/seq_file.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc-compat.h" + +#define DISPC_IRQ_MASK_ERROR            (DISPC_IRQ_GFX_FIFO_UNDERFLOW | \ +					 DISPC_IRQ_OCP_ERR | \ +					 DISPC_IRQ_VID1_FIFO_UNDERFLOW | \ +					 DISPC_IRQ_VID2_FIFO_UNDERFLOW | \ +					 DISPC_IRQ_SYNC_LOST | \ +					 DISPC_IRQ_SYNC_LOST_DIGIT) + +#define DISPC_MAX_NR_ISRS		8 + +struct omap_dispc_isr_data { +	omap_dispc_isr_t	isr; +	void			*arg; +	u32			mask; +}; + +struct dispc_irq_stats { +	unsigned long last_reset; +	unsigned irq_count; +	unsigned irqs[32]; +}; + +static struct { +	spinlock_t irq_lock; +	u32 irq_error_mask; +	struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS]; +	u32 error_irqs; +	struct work_struct error_work; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	spinlock_t irq_stats_lock; +	struct dispc_irq_stats irq_stats; +#endif +} dispc_compat; + + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dispc_dump_irqs(struct seq_file *s) +{ +	unsigned long flags; +	struct dispc_irq_stats stats; + +	spin_lock_irqsave(&dispc_compat.irq_stats_lock, flags); + +	stats = dispc_compat.irq_stats; +	memset(&dispc_compat.irq_stats, 0, sizeof(dispc_compat.irq_stats)); +	dispc_compat.irq_stats.last_reset = jiffies; + +	spin_unlock_irqrestore(&dispc_compat.irq_stats_lock, flags); + +	seq_printf(s, "period %u ms\n", +			jiffies_to_msecs(jiffies - stats.last_reset)); + +	seq_printf(s, "irqs %d\n", stats.irq_count); +#define PIS(x) \ +	seq_printf(s, "%-20s %10d\n", #x, stats.irqs[ffs(DISPC_IRQ_##x)-1]); + +	PIS(FRAMEDONE); +	PIS(VSYNC); +	PIS(EVSYNC_EVEN); +	PIS(EVSYNC_ODD); +	PIS(ACBIAS_COUNT_STAT); +	PIS(PROG_LINE_NUM); +	PIS(GFX_FIFO_UNDERFLOW); +	PIS(GFX_END_WIN); +	PIS(PAL_GAMMA_MASK); +	PIS(OCP_ERR); +	PIS(VID1_FIFO_UNDERFLOW); +	PIS(VID1_END_WIN); +	PIS(VID2_FIFO_UNDERFLOW); +	PIS(VID2_END_WIN); +	if (dss_feat_get_num_ovls() > 3) { +		PIS(VID3_FIFO_UNDERFLOW); +		PIS(VID3_END_WIN); +	} +	PIS(SYNC_LOST); +	PIS(SYNC_LOST_DIGIT); +	PIS(WAKEUP); +	if (dss_has_feature(FEAT_MGR_LCD2)) { +		PIS(FRAMEDONE2); +		PIS(VSYNC2); +		PIS(ACBIAS_COUNT_STAT2); +		PIS(SYNC_LOST2); +	} +	if (dss_has_feature(FEAT_MGR_LCD3)) { +		PIS(FRAMEDONE3); +		PIS(VSYNC3); +		PIS(ACBIAS_COUNT_STAT3); +		PIS(SYNC_LOST3); +	} +#undef PIS +} +#endif + +/* dispc.irq_lock has to be locked by the caller */ +static void _omap_dispc_set_irqs(void) +{ +	u32 mask; +	int i; +	struct omap_dispc_isr_data *isr_data; + +	mask = dispc_compat.irq_error_mask; + +	for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { +		isr_data = &dispc_compat.registered_isr[i]; + +		if (isr_data->isr == NULL) +			continue; + +		mask |= isr_data->mask; +	} + +	dispc_write_irqenable(mask); +} + +int omap_dispc_register_isr(omap_dispc_isr_t isr, void *arg, u32 mask) +{ +	int i; +	int ret; +	unsigned long flags; +	struct omap_dispc_isr_data *isr_data; + +	if (isr == NULL) +		return -EINVAL; + +	spin_lock_irqsave(&dispc_compat.irq_lock, flags); + +	/* check for duplicate entry */ +	for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { +		isr_data = &dispc_compat.registered_isr[i]; +		if (isr_data->isr == isr && isr_data->arg == arg && +				isr_data->mask == mask) { +			ret = -EINVAL; +			goto err; +		} +	} + +	isr_data = NULL; +	ret = -EBUSY; + +	for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { +		isr_data = &dispc_compat.registered_isr[i]; + +		if (isr_data->isr != NULL) +			continue; + +		isr_data->isr = isr; +		isr_data->arg = arg; +		isr_data->mask = mask; +		ret = 0; + +		break; +	} + +	if (ret) +		goto err; + +	_omap_dispc_set_irqs(); + +	spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + +	return 0; +err: +	spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + +	return ret; +} +EXPORT_SYMBOL(omap_dispc_register_isr); + +int omap_dispc_unregister_isr(omap_dispc_isr_t isr, void *arg, u32 mask) +{ +	int i; +	unsigned long flags; +	int ret = -EINVAL; +	struct omap_dispc_isr_data *isr_data; + +	spin_lock_irqsave(&dispc_compat.irq_lock, flags); + +	for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { +		isr_data = &dispc_compat.registered_isr[i]; +		if (isr_data->isr != isr || isr_data->arg != arg || +				isr_data->mask != mask) +			continue; + +		/* found the correct isr */ + +		isr_data->isr = NULL; +		isr_data->arg = NULL; +		isr_data->mask = 0; + +		ret = 0; +		break; +	} + +	if (ret == 0) +		_omap_dispc_set_irqs(); + +	spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + +	return ret; +} +EXPORT_SYMBOL(omap_dispc_unregister_isr); + +static void print_irq_status(u32 status) +{ +	if ((status & dispc_compat.irq_error_mask) == 0) +		return; + +#define PIS(x) (status & DISPC_IRQ_##x) ? (#x " ") : "" + +	pr_debug("DISPC IRQ: 0x%x: %s%s%s%s%s%s%s%s%s\n", +		status, +		PIS(OCP_ERR), +		PIS(GFX_FIFO_UNDERFLOW), +		PIS(VID1_FIFO_UNDERFLOW), +		PIS(VID2_FIFO_UNDERFLOW), +		dss_feat_get_num_ovls() > 3 ? PIS(VID3_FIFO_UNDERFLOW) : "", +		PIS(SYNC_LOST), +		PIS(SYNC_LOST_DIGIT), +		dss_has_feature(FEAT_MGR_LCD2) ? PIS(SYNC_LOST2) : "", +		dss_has_feature(FEAT_MGR_LCD3) ? PIS(SYNC_LOST3) : ""); +#undef PIS +} + +/* Called from dss.c. Note that we don't touch clocks here, + * but we presume they are on because we got an IRQ. However, + * an irq handler may turn the clocks off, so we may not have + * clock later in the function. */ +static irqreturn_t omap_dispc_irq_handler(int irq, void *arg) +{ +	int i; +	u32 irqstatus, irqenable; +	u32 handledirqs = 0; +	u32 unhandled_errors; +	struct omap_dispc_isr_data *isr_data; +	struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS]; + +	spin_lock(&dispc_compat.irq_lock); + +	irqstatus = dispc_read_irqstatus(); +	irqenable = dispc_read_irqenable(); + +	/* IRQ is not for us */ +	if (!(irqstatus & irqenable)) { +		spin_unlock(&dispc_compat.irq_lock); +		return IRQ_NONE; +	} + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	spin_lock(&dispc_compat.irq_stats_lock); +	dispc_compat.irq_stats.irq_count++; +	dss_collect_irq_stats(irqstatus, dispc_compat.irq_stats.irqs); +	spin_unlock(&dispc_compat.irq_stats_lock); +#endif + +	print_irq_status(irqstatus); + +	/* Ack the interrupt. Do it here before clocks are possibly turned +	 * off */ +	dispc_clear_irqstatus(irqstatus); +	/* flush posted write */ +	dispc_read_irqstatus(); + +	/* make a copy and unlock, so that isrs can unregister +	 * themselves */ +	memcpy(registered_isr, dispc_compat.registered_isr, +			sizeof(registered_isr)); + +	spin_unlock(&dispc_compat.irq_lock); + +	for (i = 0; i < DISPC_MAX_NR_ISRS; i++) { +		isr_data = ®istered_isr[i]; + +		if (!isr_data->isr) +			continue; + +		if (isr_data->mask & irqstatus) { +			isr_data->isr(isr_data->arg, irqstatus); +			handledirqs |= isr_data->mask; +		} +	} + +	spin_lock(&dispc_compat.irq_lock); + +	unhandled_errors = irqstatus & ~handledirqs & dispc_compat.irq_error_mask; + +	if (unhandled_errors) { +		dispc_compat.error_irqs |= unhandled_errors; + +		dispc_compat.irq_error_mask &= ~unhandled_errors; +		_omap_dispc_set_irqs(); + +		schedule_work(&dispc_compat.error_work); +	} + +	spin_unlock(&dispc_compat.irq_lock); + +	return IRQ_HANDLED; +} + +static void dispc_error_worker(struct work_struct *work) +{ +	int i; +	u32 errors; +	unsigned long flags; +	static const unsigned fifo_underflow_bits[] = { +		DISPC_IRQ_GFX_FIFO_UNDERFLOW, +		DISPC_IRQ_VID1_FIFO_UNDERFLOW, +		DISPC_IRQ_VID2_FIFO_UNDERFLOW, +		DISPC_IRQ_VID3_FIFO_UNDERFLOW, +	}; + +	spin_lock_irqsave(&dispc_compat.irq_lock, flags); +	errors = dispc_compat.error_irqs; +	dispc_compat.error_irqs = 0; +	spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + +	dispc_runtime_get(); + +	for (i = 0; i < omap_dss_get_num_overlays(); ++i) { +		struct omap_overlay *ovl; +		unsigned bit; + +		ovl = omap_dss_get_overlay(i); +		bit = fifo_underflow_bits[i]; + +		if (bit & errors) { +			DSSERR("FIFO UNDERFLOW on %s, disabling the overlay\n", +					ovl->name); +			ovl->disable(ovl); +			msleep(50); +		} +	} + +	for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { +		struct omap_overlay_manager *mgr; +		unsigned bit; + +		mgr = omap_dss_get_overlay_manager(i); +		bit = dispc_mgr_get_sync_lost_irq(i); + +		if (bit & errors) { +			int j; + +			DSSERR("SYNC_LOST on channel %s, restarting the output " +					"with video overlays disabled\n", +					mgr->name); + +			dss_mgr_disable(mgr); + +			for (j = 0; j < omap_dss_get_num_overlays(); ++j) { +				struct omap_overlay *ovl; +				ovl = omap_dss_get_overlay(j); + +				if (ovl->id != OMAP_DSS_GFX && +						ovl->manager == mgr) +					ovl->disable(ovl); +			} + +			dss_mgr_enable(mgr); +		} +	} + +	if (errors & DISPC_IRQ_OCP_ERR) { +		DSSERR("OCP_ERR\n"); +		for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { +			struct omap_overlay_manager *mgr; + +			mgr = omap_dss_get_overlay_manager(i); +			dss_mgr_disable(mgr); +		} +	} + +	spin_lock_irqsave(&dispc_compat.irq_lock, flags); +	dispc_compat.irq_error_mask |= errors; +	_omap_dispc_set_irqs(); +	spin_unlock_irqrestore(&dispc_compat.irq_lock, flags); + +	dispc_runtime_put(); +} + +int dss_dispc_initialize_irq(void) +{ +	int r; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	spin_lock_init(&dispc_compat.irq_stats_lock); +	dispc_compat.irq_stats.last_reset = jiffies; +	dss_debugfs_create_file("dispc_irq", dispc_dump_irqs); +#endif + +	spin_lock_init(&dispc_compat.irq_lock); + +	memset(dispc_compat.registered_isr, 0, +			sizeof(dispc_compat.registered_isr)); + +	dispc_compat.irq_error_mask = DISPC_IRQ_MASK_ERROR; +	if (dss_has_feature(FEAT_MGR_LCD2)) +		dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST2; +	if (dss_has_feature(FEAT_MGR_LCD3)) +		dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST3; +	if (dss_feat_get_num_ovls() > 3) +		dispc_compat.irq_error_mask |= DISPC_IRQ_VID3_FIFO_UNDERFLOW; + +	/* +	 * there's SYNC_LOST_DIGIT waiting after enabling the DSS, +	 * so clear it +	 */ +	dispc_clear_irqstatus(dispc_read_irqstatus()); + +	INIT_WORK(&dispc_compat.error_work, dispc_error_worker); + +	_omap_dispc_set_irqs(); + +	r = dispc_request_irq(omap_dispc_irq_handler, &dispc_compat); +	if (r) { +		DSSERR("dispc_request_irq failed\n"); +		return r; +	} + +	return 0; +} + +void dss_dispc_uninitialize_irq(void) +{ +	dispc_free_irq(&dispc_compat); +} + +static void dispc_mgr_disable_isr(void *data, u32 mask) +{ +	struct completion *compl = data; +	complete(compl); +} + +static void dispc_mgr_enable_lcd_out(enum omap_channel channel) +{ +	dispc_mgr_enable(channel, true); +} + +static void dispc_mgr_disable_lcd_out(enum omap_channel channel) +{ +	DECLARE_COMPLETION_ONSTACK(framedone_compl); +	int r; +	u32 irq; + +	if (dispc_mgr_is_enabled(channel) == false) +		return; + +	/* +	 * When we disable LCD output, we need to wait for FRAMEDONE to know +	 * that DISPC has finished with the LCD output. +	 */ + +	irq = dispc_mgr_get_framedone_irq(channel); + +	r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl, +			irq); +	if (r) +		DSSERR("failed to register FRAMEDONE isr\n"); + +	dispc_mgr_enable(channel, false); + +	/* if we couldn't register for framedone, just sleep and exit */ +	if (r) { +		msleep(100); +		return; +	} + +	if (!wait_for_completion_timeout(&framedone_compl, +				msecs_to_jiffies(100))) +		DSSERR("timeout waiting for FRAME DONE\n"); + +	r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl, +			irq); +	if (r) +		DSSERR("failed to unregister FRAMEDONE isr\n"); +} + +static void dispc_digit_out_enable_isr(void *data, u32 mask) +{ +	struct completion *compl = data; + +	/* ignore any sync lost interrupts */ +	if (mask & (DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD)) +		complete(compl); +} + +static void dispc_mgr_enable_digit_out(void) +{ +	DECLARE_COMPLETION_ONSTACK(vsync_compl); +	int r; +	u32 irq_mask; + +	if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == true) +		return; + +	/* +	 * Digit output produces some sync lost interrupts during the first +	 * frame when enabling. Those need to be ignored, so we register for the +	 * sync lost irq to prevent the error handler from triggering. +	 */ + +	irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT) | +		dispc_mgr_get_sync_lost_irq(OMAP_DSS_CHANNEL_DIGIT); + +	r = omap_dispc_register_isr(dispc_digit_out_enable_isr, &vsync_compl, +			irq_mask); +	if (r) { +		DSSERR("failed to register %x isr\n", irq_mask); +		return; +	} + +	dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, true); + +	/* wait for the first evsync */ +	if (!wait_for_completion_timeout(&vsync_compl, msecs_to_jiffies(100))) +		DSSERR("timeout waiting for digit out to start\n"); + +	r = omap_dispc_unregister_isr(dispc_digit_out_enable_isr, &vsync_compl, +			irq_mask); +	if (r) +		DSSERR("failed to unregister %x isr\n", irq_mask); +} + +static void dispc_mgr_disable_digit_out(void) +{ +	DECLARE_COMPLETION_ONSTACK(framedone_compl); +	int r, i; +	u32 irq_mask; +	int num_irqs; + +	if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == false) +		return; + +	/* +	 * When we disable the digit output, we need to wait for FRAMEDONE to +	 * know that DISPC has finished with the output. +	 */ + +	irq_mask = dispc_mgr_get_framedone_irq(OMAP_DSS_CHANNEL_DIGIT); +	num_irqs = 1; + +	if (!irq_mask) { +		/* +		 * omap 2/3 don't have framedone irq for TV, so we need to use +		 * vsyncs for this. +		 */ + +		irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT); +		/* +		 * We need to wait for both even and odd vsyncs. Note that this +		 * is not totally reliable, as we could get a vsync interrupt +		 * before we disable the output, which leads to timeout in the +		 * wait_for_completion. +		 */ +		num_irqs = 2; +	} + +	r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl, +			irq_mask); +	if (r) +		DSSERR("failed to register %x isr\n", irq_mask); + +	dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, false); + +	/* if we couldn't register the irq, just sleep and exit */ +	if (r) { +		msleep(100); +		return; +	} + +	for (i = 0; i < num_irqs; ++i) { +		if (!wait_for_completion_timeout(&framedone_compl, +					msecs_to_jiffies(100))) +			DSSERR("timeout waiting for digit out to stop\n"); +	} + +	r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl, +			irq_mask); +	if (r) +		DSSERR("failed to unregister %x isr\n", irq_mask); +} + +void dispc_mgr_enable_sync(enum omap_channel channel) +{ +	if (dss_mgr_is_lcd(channel)) +		dispc_mgr_enable_lcd_out(channel); +	else if (channel == OMAP_DSS_CHANNEL_DIGIT) +		dispc_mgr_enable_digit_out(); +	else +		WARN_ON(1); +} + +void dispc_mgr_disable_sync(enum omap_channel channel) +{ +	if (dss_mgr_is_lcd(channel)) +		dispc_mgr_disable_lcd_out(channel); +	else if (channel == OMAP_DSS_CHANNEL_DIGIT) +		dispc_mgr_disable_digit_out(); +	else +		WARN_ON(1); +} + +int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, +		unsigned long timeout) +{ +	void dispc_irq_wait_handler(void *data, u32 mask) +	{ +		complete((struct completion *)data); +	} + +	int r; +	DECLARE_COMPLETION_ONSTACK(completion); + +	r = omap_dispc_register_isr(dispc_irq_wait_handler, &completion, +			irqmask); + +	if (r) +		return r; + +	timeout = wait_for_completion_interruptible_timeout(&completion, +			timeout); + +	omap_dispc_unregister_isr(dispc_irq_wait_handler, &completion, irqmask); + +	if (timeout == 0) +		return -ETIMEDOUT; + +	if (timeout == -ERESTARTSYS) +		return -ERESTARTSYS; + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/dispc-compat.h b/drivers/video/fbdev/omap2/dss/dispc-compat.h new file mode 100644 index 00000000000..14a69b3d4fb --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc-compat.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@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 __OMAP2_DSS_DISPC_COMPAT_H +#define __OMAP2_DSS_DISPC_COMPAT_H + +void dispc_mgr_enable_sync(enum omap_channel channel); +void dispc_mgr_disable_sync(enum omap_channel channel); + +int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, +		unsigned long timeout); + +int dss_dispc_initialize_irq(void); +void dss_dispc_uninitialize_irq(void); + +#endif diff --git a/drivers/video/fbdev/omap2/dss/dispc.c b/drivers/video/fbdev/omap2/dss/dispc.c new file mode 100644 index 00000000000..7aa33b0f4a1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc.c @@ -0,0 +1,3855 @@ +/* + * linux/drivers/video/omap2/dss/dispc.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "DISPC" + +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/hardirq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/sizes.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" +#include "dispc.h" + +/* DISPC */ +#define DISPC_SZ_REGS			SZ_4K + +enum omap_burst_size { +	BURST_SIZE_X2 = 0, +	BURST_SIZE_X4 = 1, +	BURST_SIZE_X8 = 2, +}; + +#define REG_GET(idx, start, end) \ +	FLD_GET(dispc_read_reg(idx), start, end) + +#define REG_FLD_MOD(idx, val, start, end)				\ +	dispc_write_reg(idx, FLD_MOD(dispc_read_reg(idx), val, start, end)) + +struct dispc_features { +	u8 sw_start; +	u8 fp_start; +	u8 bp_start; +	u16 sw_max; +	u16 vp_max; +	u16 hp_max; +	u8 mgr_width_start; +	u8 mgr_height_start; +	u16 mgr_width_max; +	u16 mgr_height_max; +	unsigned long max_lcd_pclk; +	unsigned long max_tv_pclk; +	int (*calc_scaling) (unsigned long pclk, unsigned long lclk, +		const struct omap_video_timings *mgr_timings, +		u16 width, u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode, bool *five_taps, +		int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, +		u16 pos_x, unsigned long *core_clk, bool mem_to_mem); +	unsigned long (*calc_core_clk) (unsigned long pclk, +		u16 width, u16 height, u16 out_width, u16 out_height, +		bool mem_to_mem); +	u8 num_fifos; + +	/* swap GFX & WB fifos */ +	bool gfx_fifo_workaround:1; + +	/* no DISPC_IRQ_FRAMEDONETV on this SoC */ +	bool no_framedone_tv:1; + +	/* revert to the OMAP4 mechanism of DISPC Smart Standby operation */ +	bool mstandby_workaround:1; + +	bool set_max_preload:1; +}; + +#define DISPC_MAX_NR_FIFOS 5 + +static struct { +	struct platform_device *pdev; +	void __iomem    *base; + +	int irq; +	irq_handler_t user_handler; +	void *user_data; + +	unsigned long core_clk_rate; +	unsigned long tv_pclk_rate; + +	u32 fifo_size[DISPC_MAX_NR_FIFOS]; +	/* maps which plane is using a fifo. fifo-id -> plane-id */ +	int fifo_assignment[DISPC_MAX_NR_FIFOS]; + +	bool		ctx_valid; +	u32		ctx[DISPC_SZ_REGS / sizeof(u32)]; + +	const struct dispc_features *feat; + +	bool is_enabled; +} dispc; + +enum omap_color_component { +	/* used for all color formats for OMAP3 and earlier +	 * and for RGB and Y color component on OMAP4 +	 */ +	DISPC_COLOR_COMPONENT_RGB_Y		= 1 << 0, +	/* used for UV component for +	 * OMAP_DSS_COLOR_YUV2, OMAP_DSS_COLOR_UYVY, OMAP_DSS_COLOR_NV12 +	 * color formats on OMAP4 +	 */ +	DISPC_COLOR_COMPONENT_UV		= 1 << 1, +}; + +enum mgr_reg_fields { +	DISPC_MGR_FLD_ENABLE, +	DISPC_MGR_FLD_STNTFT, +	DISPC_MGR_FLD_GO, +	DISPC_MGR_FLD_TFTDATALINES, +	DISPC_MGR_FLD_STALLMODE, +	DISPC_MGR_FLD_TCKENABLE, +	DISPC_MGR_FLD_TCKSELECTION, +	DISPC_MGR_FLD_CPR, +	DISPC_MGR_FLD_FIFOHANDCHECK, +	/* used to maintain a count of the above fields */ +	DISPC_MGR_FLD_NUM, +}; + +struct dispc_reg_field { +	u16 reg; +	u8 high; +	u8 low; +}; + +static const struct { +	const char *name; +	u32 vsync_irq; +	u32 framedone_irq; +	u32 sync_lost_irq; +	struct dispc_reg_field reg_desc[DISPC_MGR_FLD_NUM]; +} mgr_desc[] = { +	[OMAP_DSS_CHANNEL_LCD] = { +		.name		= "LCD", +		.vsync_irq	= DISPC_IRQ_VSYNC, +		.framedone_irq	= DISPC_IRQ_FRAMEDONE, +		.sync_lost_irq	= DISPC_IRQ_SYNC_LOST, +		.reg_desc	= { +			[DISPC_MGR_FLD_ENABLE]		= { DISPC_CONTROL,  0,  0 }, +			[DISPC_MGR_FLD_STNTFT]		= { DISPC_CONTROL,  3,  3 }, +			[DISPC_MGR_FLD_GO]		= { DISPC_CONTROL,  5,  5 }, +			[DISPC_MGR_FLD_TFTDATALINES]	= { DISPC_CONTROL,  9,  8 }, +			[DISPC_MGR_FLD_STALLMODE]	= { DISPC_CONTROL, 11, 11 }, +			[DISPC_MGR_FLD_TCKENABLE]	= { DISPC_CONFIG,  10, 10 }, +			[DISPC_MGR_FLD_TCKSELECTION]	= { DISPC_CONFIG,  11, 11 }, +			[DISPC_MGR_FLD_CPR]		= { DISPC_CONFIG,  15, 15 }, +			[DISPC_MGR_FLD_FIFOHANDCHECK]	= { DISPC_CONFIG,  16, 16 }, +		}, +	}, +	[OMAP_DSS_CHANNEL_DIGIT] = { +		.name		= "DIGIT", +		.vsync_irq	= DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN, +		.framedone_irq	= DISPC_IRQ_FRAMEDONETV, +		.sync_lost_irq	= DISPC_IRQ_SYNC_LOST_DIGIT, +		.reg_desc	= { +			[DISPC_MGR_FLD_ENABLE]		= { DISPC_CONTROL,  1,  1 }, +			[DISPC_MGR_FLD_STNTFT]		= { }, +			[DISPC_MGR_FLD_GO]		= { DISPC_CONTROL,  6,  6 }, +			[DISPC_MGR_FLD_TFTDATALINES]	= { }, +			[DISPC_MGR_FLD_STALLMODE]	= { }, +			[DISPC_MGR_FLD_TCKENABLE]	= { DISPC_CONFIG,  12, 12 }, +			[DISPC_MGR_FLD_TCKSELECTION]	= { DISPC_CONFIG,  13, 13 }, +			[DISPC_MGR_FLD_CPR]		= { }, +			[DISPC_MGR_FLD_FIFOHANDCHECK]	= { DISPC_CONFIG,  16, 16 }, +		}, +	}, +	[OMAP_DSS_CHANNEL_LCD2] = { +		.name		= "LCD2", +		.vsync_irq	= DISPC_IRQ_VSYNC2, +		.framedone_irq	= DISPC_IRQ_FRAMEDONE2, +		.sync_lost_irq	= DISPC_IRQ_SYNC_LOST2, +		.reg_desc	= { +			[DISPC_MGR_FLD_ENABLE]		= { DISPC_CONTROL2,  0,  0 }, +			[DISPC_MGR_FLD_STNTFT]		= { DISPC_CONTROL2,  3,  3 }, +			[DISPC_MGR_FLD_GO]		= { DISPC_CONTROL2,  5,  5 }, +			[DISPC_MGR_FLD_TFTDATALINES]	= { DISPC_CONTROL2,  9,  8 }, +			[DISPC_MGR_FLD_STALLMODE]	= { DISPC_CONTROL2, 11, 11 }, +			[DISPC_MGR_FLD_TCKENABLE]	= { DISPC_CONFIG2,  10, 10 }, +			[DISPC_MGR_FLD_TCKSELECTION]	= { DISPC_CONFIG2,  11, 11 }, +			[DISPC_MGR_FLD_CPR]		= { DISPC_CONFIG2,  15, 15 }, +			[DISPC_MGR_FLD_FIFOHANDCHECK]	= { DISPC_CONFIG2,  16, 16 }, +		}, +	}, +	[OMAP_DSS_CHANNEL_LCD3] = { +		.name		= "LCD3", +		.vsync_irq	= DISPC_IRQ_VSYNC3, +		.framedone_irq	= DISPC_IRQ_FRAMEDONE3, +		.sync_lost_irq	= DISPC_IRQ_SYNC_LOST3, +		.reg_desc	= { +			[DISPC_MGR_FLD_ENABLE]		= { DISPC_CONTROL3,  0,  0 }, +			[DISPC_MGR_FLD_STNTFT]		= { DISPC_CONTROL3,  3,  3 }, +			[DISPC_MGR_FLD_GO]		= { DISPC_CONTROL3,  5,  5 }, +			[DISPC_MGR_FLD_TFTDATALINES]	= { DISPC_CONTROL3,  9,  8 }, +			[DISPC_MGR_FLD_STALLMODE]	= { DISPC_CONTROL3, 11, 11 }, +			[DISPC_MGR_FLD_TCKENABLE]	= { DISPC_CONFIG3,  10, 10 }, +			[DISPC_MGR_FLD_TCKSELECTION]	= { DISPC_CONFIG3,  11, 11 }, +			[DISPC_MGR_FLD_CPR]		= { DISPC_CONFIG3,  15, 15 }, +			[DISPC_MGR_FLD_FIFOHANDCHECK]	= { DISPC_CONFIG3,  16, 16 }, +		}, +	}, +}; + +struct color_conv_coef { +	int ry, rcr, rcb, gy, gcr, gcb, by, bcr, bcb; +	int full_range; +}; + +static unsigned long dispc_plane_pclk_rate(enum omap_plane plane); +static unsigned long dispc_plane_lclk_rate(enum omap_plane plane); + +static inline void dispc_write_reg(const u16 idx, u32 val) +{ +	__raw_writel(val, dispc.base + idx); +} + +static inline u32 dispc_read_reg(const u16 idx) +{ +	return __raw_readl(dispc.base + idx); +} + +static u32 mgr_fld_read(enum omap_channel channel, enum mgr_reg_fields regfld) +{ +	const struct dispc_reg_field rfld = mgr_desc[channel].reg_desc[regfld]; +	return REG_GET(rfld.reg, rfld.high, rfld.low); +} + +static void mgr_fld_write(enum omap_channel channel, +					enum mgr_reg_fields regfld, int val) { +	const struct dispc_reg_field rfld = mgr_desc[channel].reg_desc[regfld]; +	REG_FLD_MOD(rfld.reg, val, rfld.high, rfld.low); +} + +#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)]) + +static void dispc_save_context(void) +{ +	int i, j; + +	DSSDBG("dispc_save_context\n"); + +	SR(IRQENABLE); +	SR(CONTROL); +	SR(CONFIG); +	SR(LINE_NUMBER); +	if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || +			dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) +		SR(GLOBAL_ALPHA); +	if (dss_has_feature(FEAT_MGR_LCD2)) { +		SR(CONTROL2); +		SR(CONFIG2); +	} +	if (dss_has_feature(FEAT_MGR_LCD3)) { +		SR(CONTROL3); +		SR(CONFIG3); +	} + +	for (i = 0; i < dss_feat_get_num_mgrs(); i++) { +		SR(DEFAULT_COLOR(i)); +		SR(TRANS_COLOR(i)); +		SR(SIZE_MGR(i)); +		if (i == OMAP_DSS_CHANNEL_DIGIT) +			continue; +		SR(TIMING_H(i)); +		SR(TIMING_V(i)); +		SR(POL_FREQ(i)); +		SR(DIVISORo(i)); + +		SR(DATA_CYCLE1(i)); +		SR(DATA_CYCLE2(i)); +		SR(DATA_CYCLE3(i)); + +		if (dss_has_feature(FEAT_CPR)) { +			SR(CPR_COEF_R(i)); +			SR(CPR_COEF_G(i)); +			SR(CPR_COEF_B(i)); +		} +	} + +	for (i = 0; i < dss_feat_get_num_ovls(); i++) { +		SR(OVL_BA0(i)); +		SR(OVL_BA1(i)); +		SR(OVL_POSITION(i)); +		SR(OVL_SIZE(i)); +		SR(OVL_ATTRIBUTES(i)); +		SR(OVL_FIFO_THRESHOLD(i)); +		SR(OVL_ROW_INC(i)); +		SR(OVL_PIXEL_INC(i)); +		if (dss_has_feature(FEAT_PRELOAD)) +			SR(OVL_PRELOAD(i)); +		if (i == OMAP_DSS_GFX) { +			SR(OVL_WINDOW_SKIP(i)); +			SR(OVL_TABLE_BA(i)); +			continue; +		} +		SR(OVL_FIR(i)); +		SR(OVL_PICTURE_SIZE(i)); +		SR(OVL_ACCU0(i)); +		SR(OVL_ACCU1(i)); + +		for (j = 0; j < 8; j++) +			SR(OVL_FIR_COEF_H(i, j)); + +		for (j = 0; j < 8; j++) +			SR(OVL_FIR_COEF_HV(i, j)); + +		for (j = 0; j < 5; j++) +			SR(OVL_CONV_COEF(i, j)); + +		if (dss_has_feature(FEAT_FIR_COEF_V)) { +			for (j = 0; j < 8; j++) +				SR(OVL_FIR_COEF_V(i, j)); +		} + +		if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { +			SR(OVL_BA0_UV(i)); +			SR(OVL_BA1_UV(i)); +			SR(OVL_FIR2(i)); +			SR(OVL_ACCU2_0(i)); +			SR(OVL_ACCU2_1(i)); + +			for (j = 0; j < 8; j++) +				SR(OVL_FIR_COEF_H2(i, j)); + +			for (j = 0; j < 8; j++) +				SR(OVL_FIR_COEF_HV2(i, j)); + +			for (j = 0; j < 8; j++) +				SR(OVL_FIR_COEF_V2(i, j)); +		} +		if (dss_has_feature(FEAT_ATTR2)) +			SR(OVL_ATTRIBUTES2(i)); +	} + +	if (dss_has_feature(FEAT_CORE_CLK_DIV)) +		SR(DIVISOR); + +	dispc.ctx_valid = true; + +	DSSDBG("context saved\n"); +} + +static void dispc_restore_context(void) +{ +	int i, j; + +	DSSDBG("dispc_restore_context\n"); + +	if (!dispc.ctx_valid) +		return; + +	/*RR(IRQENABLE);*/ +	/*RR(CONTROL);*/ +	RR(CONFIG); +	RR(LINE_NUMBER); +	if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || +			dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) +		RR(GLOBAL_ALPHA); +	if (dss_has_feature(FEAT_MGR_LCD2)) +		RR(CONFIG2); +	if (dss_has_feature(FEAT_MGR_LCD3)) +		RR(CONFIG3); + +	for (i = 0; i < dss_feat_get_num_mgrs(); i++) { +		RR(DEFAULT_COLOR(i)); +		RR(TRANS_COLOR(i)); +		RR(SIZE_MGR(i)); +		if (i == OMAP_DSS_CHANNEL_DIGIT) +			continue; +		RR(TIMING_H(i)); +		RR(TIMING_V(i)); +		RR(POL_FREQ(i)); +		RR(DIVISORo(i)); + +		RR(DATA_CYCLE1(i)); +		RR(DATA_CYCLE2(i)); +		RR(DATA_CYCLE3(i)); + +		if (dss_has_feature(FEAT_CPR)) { +			RR(CPR_COEF_R(i)); +			RR(CPR_COEF_G(i)); +			RR(CPR_COEF_B(i)); +		} +	} + +	for (i = 0; i < dss_feat_get_num_ovls(); i++) { +		RR(OVL_BA0(i)); +		RR(OVL_BA1(i)); +		RR(OVL_POSITION(i)); +		RR(OVL_SIZE(i)); +		RR(OVL_ATTRIBUTES(i)); +		RR(OVL_FIFO_THRESHOLD(i)); +		RR(OVL_ROW_INC(i)); +		RR(OVL_PIXEL_INC(i)); +		if (dss_has_feature(FEAT_PRELOAD)) +			RR(OVL_PRELOAD(i)); +		if (i == OMAP_DSS_GFX) { +			RR(OVL_WINDOW_SKIP(i)); +			RR(OVL_TABLE_BA(i)); +			continue; +		} +		RR(OVL_FIR(i)); +		RR(OVL_PICTURE_SIZE(i)); +		RR(OVL_ACCU0(i)); +		RR(OVL_ACCU1(i)); + +		for (j = 0; j < 8; j++) +			RR(OVL_FIR_COEF_H(i, j)); + +		for (j = 0; j < 8; j++) +			RR(OVL_FIR_COEF_HV(i, j)); + +		for (j = 0; j < 5; j++) +			RR(OVL_CONV_COEF(i, j)); + +		if (dss_has_feature(FEAT_FIR_COEF_V)) { +			for (j = 0; j < 8; j++) +				RR(OVL_FIR_COEF_V(i, j)); +		} + +		if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { +			RR(OVL_BA0_UV(i)); +			RR(OVL_BA1_UV(i)); +			RR(OVL_FIR2(i)); +			RR(OVL_ACCU2_0(i)); +			RR(OVL_ACCU2_1(i)); + +			for (j = 0; j < 8; j++) +				RR(OVL_FIR_COEF_H2(i, j)); + +			for (j = 0; j < 8; j++) +				RR(OVL_FIR_COEF_HV2(i, j)); + +			for (j = 0; j < 8; j++) +				RR(OVL_FIR_COEF_V2(i, j)); +		} +		if (dss_has_feature(FEAT_ATTR2)) +			RR(OVL_ATTRIBUTES2(i)); +	} + +	if (dss_has_feature(FEAT_CORE_CLK_DIV)) +		RR(DIVISOR); + +	/* enable last, because LCD & DIGIT enable are here */ +	RR(CONTROL); +	if (dss_has_feature(FEAT_MGR_LCD2)) +		RR(CONTROL2); +	if (dss_has_feature(FEAT_MGR_LCD3)) +		RR(CONTROL3); +	/* clear spurious SYNC_LOST_DIGIT interrupts */ +	dispc_clear_irqstatus(DISPC_IRQ_SYNC_LOST_DIGIT); + +	/* +	 * enable last so IRQs won't trigger before +	 * the context is fully restored +	 */ +	RR(IRQENABLE); + +	DSSDBG("context restored\n"); +} + +#undef SR +#undef RR + +int dispc_runtime_get(void) +{ +	int r; + +	DSSDBG("dispc_runtime_get\n"); + +	r = pm_runtime_get_sync(&dispc.pdev->dev); +	WARN_ON(r < 0); +	return r < 0 ? r : 0; +} +EXPORT_SYMBOL(dispc_runtime_get); + +void dispc_runtime_put(void) +{ +	int r; + +	DSSDBG("dispc_runtime_put\n"); + +	r = pm_runtime_put_sync(&dispc.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} +EXPORT_SYMBOL(dispc_runtime_put); + +u32 dispc_mgr_get_vsync_irq(enum omap_channel channel) +{ +	return mgr_desc[channel].vsync_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_vsync_irq); + +u32 dispc_mgr_get_framedone_irq(enum omap_channel channel) +{ +	if (channel == OMAP_DSS_CHANNEL_DIGIT && dispc.feat->no_framedone_tv) +		return 0; + +	return mgr_desc[channel].framedone_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_framedone_irq); + +u32 dispc_mgr_get_sync_lost_irq(enum omap_channel channel) +{ +	return mgr_desc[channel].sync_lost_irq; +} +EXPORT_SYMBOL(dispc_mgr_get_sync_lost_irq); + +u32 dispc_wb_get_framedone_irq(void) +{ +	return DISPC_IRQ_FRAMEDONEWB; +} + +bool dispc_mgr_go_busy(enum omap_channel channel) +{ +	return mgr_fld_read(channel, DISPC_MGR_FLD_GO) == 1; +} +EXPORT_SYMBOL(dispc_mgr_go_busy); + +void dispc_mgr_go(enum omap_channel channel) +{ +	WARN_ON(dispc_mgr_is_enabled(channel) == false); +	WARN_ON(dispc_mgr_go_busy(channel)); + +	DSSDBG("GO %s\n", mgr_desc[channel].name); + +	mgr_fld_write(channel, DISPC_MGR_FLD_GO, 1); +} +EXPORT_SYMBOL(dispc_mgr_go); + +bool dispc_wb_go_busy(void) +{ +	return REG_GET(DISPC_CONTROL2, 6, 6) == 1; +} + +void dispc_wb_go(void) +{ +	enum omap_plane plane = OMAP_DSS_WB; +	bool enable, go; + +	enable = REG_GET(DISPC_OVL_ATTRIBUTES(plane), 0, 0) == 1; + +	if (!enable) +		return; + +	go = REG_GET(DISPC_CONTROL2, 6, 6) == 1; +	if (go) { +		DSSERR("GO bit not down for WB\n"); +		return; +	} + +	REG_FLD_MOD(DISPC_CONTROL2, 1, 6, 6); +} + +static void dispc_ovl_write_firh_reg(enum omap_plane plane, int reg, u32 value) +{ +	dispc_write_reg(DISPC_OVL_FIR_COEF_H(plane, reg), value); +} + +static void dispc_ovl_write_firhv_reg(enum omap_plane plane, int reg, u32 value) +{ +	dispc_write_reg(DISPC_OVL_FIR_COEF_HV(plane, reg), value); +} + +static void dispc_ovl_write_firv_reg(enum omap_plane plane, int reg, u32 value) +{ +	dispc_write_reg(DISPC_OVL_FIR_COEF_V(plane, reg), value); +} + +static void dispc_ovl_write_firh2_reg(enum omap_plane plane, int reg, u32 value) +{ +	BUG_ON(plane == OMAP_DSS_GFX); + +	dispc_write_reg(DISPC_OVL_FIR_COEF_H2(plane, reg), value); +} + +static void dispc_ovl_write_firhv2_reg(enum omap_plane plane, int reg, +		u32 value) +{ +	BUG_ON(plane == OMAP_DSS_GFX); + +	dispc_write_reg(DISPC_OVL_FIR_COEF_HV2(plane, reg), value); +} + +static void dispc_ovl_write_firv2_reg(enum omap_plane plane, int reg, u32 value) +{ +	BUG_ON(plane == OMAP_DSS_GFX); + +	dispc_write_reg(DISPC_OVL_FIR_COEF_V2(plane, reg), value); +} + +static void dispc_ovl_set_scale_coef(enum omap_plane plane, int fir_hinc, +				int fir_vinc, int five_taps, +				enum omap_color_component color_comp) +{ +	const struct dispc_coef *h_coef, *v_coef; +	int i; + +	h_coef = dispc_ovl_get_scale_coef(fir_hinc, true); +	v_coef = dispc_ovl_get_scale_coef(fir_vinc, five_taps); + +	for (i = 0; i < 8; i++) { +		u32 h, hv; + +		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_ovl_write_firh_reg(plane, i, h); +			dispc_ovl_write_firhv_reg(plane, i, hv); +		} else { +			dispc_ovl_write_firh2_reg(plane, i, h); +			dispc_ovl_write_firhv2_reg(plane, i, hv); +		} + +	} + +	if (five_taps) { +		for (i = 0; i < 8; i++) { +			u32 v; +			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_ovl_write_firv_reg(plane, i, v); +			else +				dispc_ovl_write_firv2_reg(plane, i, v); +		} +	} +} + + +static void dispc_ovl_write_color_conv_coef(enum omap_plane plane, +		const struct color_conv_coef *ct) +{ +#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) + +	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)); + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), ct->full_range, 11, 11); + +#undef CVAL +} + +static void dispc_setup_color_conv_coef(void) +{ +	int i; +	int num_ovl = dss_feat_get_num_ovls(); +	int num_wb = dss_feat_get_num_wbs(); +	const struct color_conv_coef ctbl_bt601_5_ovl = { +		298, 409, 0, 298, -208, -100, 298, 0, 517, 0, +	}; +	const struct color_conv_coef ctbl_bt601_5_wb = { +		66, 112, -38, 129, -94, -74, 25, -18, 112, 0, +	}; + +	for (i = 1; i < num_ovl; i++) +		dispc_ovl_write_color_conv_coef(i, &ctbl_bt601_5_ovl); + +	for (; i < num_wb; i++) +		dispc_ovl_write_color_conv_coef(i, &ctbl_bt601_5_wb); +} + +static void dispc_ovl_set_ba0(enum omap_plane plane, u32 paddr) +{ +	dispc_write_reg(DISPC_OVL_BA0(plane), paddr); +} + +static void dispc_ovl_set_ba1(enum omap_plane plane, u32 paddr) +{ +	dispc_write_reg(DISPC_OVL_BA1(plane), paddr); +} + +static void dispc_ovl_set_ba0_uv(enum omap_plane plane, u32 paddr) +{ +	dispc_write_reg(DISPC_OVL_BA0_UV(plane), paddr); +} + +static void dispc_ovl_set_ba1_uv(enum omap_plane plane, u32 paddr) +{ +	dispc_write_reg(DISPC_OVL_BA1_UV(plane), paddr); +} + +static void dispc_ovl_set_pos(enum omap_plane plane, +		enum omap_overlay_caps caps, int x, int y) +{ +	u32 val; + +	if ((caps & OMAP_DSS_OVL_CAP_POS) == 0) +		return; + +	val = FLD_VAL(y, 26, 16) | FLD_VAL(x, 10, 0); + +	dispc_write_reg(DISPC_OVL_POSITION(plane), val); +} + +static void dispc_ovl_set_input_size(enum omap_plane plane, int width, +		int height) +{ +	u32 val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + +	if (plane == OMAP_DSS_GFX || plane == OMAP_DSS_WB) +		dispc_write_reg(DISPC_OVL_SIZE(plane), val); +	else +		dispc_write_reg(DISPC_OVL_PICTURE_SIZE(plane), val); +} + +static void dispc_ovl_set_output_size(enum omap_plane plane, int width, +		int height) +{ +	u32 val; + +	BUG_ON(plane == OMAP_DSS_GFX); + +	val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + +	if (plane == OMAP_DSS_WB) +		dispc_write_reg(DISPC_OVL_PICTURE_SIZE(plane), val); +	else +		dispc_write_reg(DISPC_OVL_SIZE(plane), val); +} + +static void dispc_ovl_set_zorder(enum omap_plane plane, +		enum omap_overlay_caps caps, u8 zorder) +{ +	if ((caps & OMAP_DSS_OVL_CAP_ZORDER) == 0) +		return; + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), zorder, 27, 26); +} + +static void dispc_ovl_enable_zorder_planes(void) +{ +	int i; + +	if (!dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) +		return; + +	for (i = 0; i < dss_feat_get_num_ovls(); i++) +		REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(i), 1, 25, 25); +} + +static void dispc_ovl_set_pre_mult_alpha(enum omap_plane plane, +		enum omap_overlay_caps caps, bool enable) +{ +	if ((caps & OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA) == 0) +		return; + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 28, 28); +} + +static void dispc_ovl_setup_global_alpha(enum omap_plane plane, +		enum omap_overlay_caps caps, u8 global_alpha) +{ +	static const unsigned shifts[] = { 0, 8, 16, 24, }; +	int shift; + +	if ((caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0) +		return; + +	shift = shifts[plane]; +	REG_FLD_MOD(DISPC_GLOBAL_ALPHA, global_alpha, shift + 7, shift); +} + +static void dispc_ovl_set_pix_inc(enum omap_plane plane, s32 inc) +{ +	dispc_write_reg(DISPC_OVL_PIXEL_INC(plane), inc); +} + +static void dispc_ovl_set_row_inc(enum omap_plane plane, s32 inc) +{ +	dispc_write_reg(DISPC_OVL_ROW_INC(plane), inc); +} + +static void dispc_ovl_set_color_mode(enum omap_plane plane, +		enum omap_color_mode color_mode) +{ +	u32 m = 0; +	if (plane != OMAP_DSS_GFX) { +		switch (color_mode) { +		case OMAP_DSS_COLOR_NV12: +			m = 0x0; break; +		case OMAP_DSS_COLOR_RGBX16: +			m = 0x1; break; +		case OMAP_DSS_COLOR_RGBA16: +			m = 0x2; break; +		case OMAP_DSS_COLOR_RGB12U: +			m = 0x4; break; +		case OMAP_DSS_COLOR_ARGB16: +			m = 0x5; break; +		case OMAP_DSS_COLOR_RGB16: +			m = 0x6; break; +		case OMAP_DSS_COLOR_ARGB16_1555: +			m = 0x7; break; +		case OMAP_DSS_COLOR_RGB24U: +			m = 0x8; break; +		case OMAP_DSS_COLOR_RGB24P: +			m = 0x9; break; +		case OMAP_DSS_COLOR_YUV2: +			m = 0xa; break; +		case OMAP_DSS_COLOR_UYVY: +			m = 0xb; break; +		case OMAP_DSS_COLOR_ARGB32: +			m = 0xc; break; +		case OMAP_DSS_COLOR_RGBA32: +			m = 0xd; break; +		case OMAP_DSS_COLOR_RGBX32: +			m = 0xe; break; +		case OMAP_DSS_COLOR_XRGB16_1555: +			m = 0xf; break; +		default: +			BUG(); return; +		} +	} else { +		switch (color_mode) { +		case OMAP_DSS_COLOR_CLUT1: +			m = 0x0; break; +		case OMAP_DSS_COLOR_CLUT2: +			m = 0x1; break; +		case OMAP_DSS_COLOR_CLUT4: +			m = 0x2; break; +		case OMAP_DSS_COLOR_CLUT8: +			m = 0x3; break; +		case OMAP_DSS_COLOR_RGB12U: +			m = 0x4; break; +		case OMAP_DSS_COLOR_ARGB16: +			m = 0x5; break; +		case OMAP_DSS_COLOR_RGB16: +			m = 0x6; break; +		case OMAP_DSS_COLOR_ARGB16_1555: +			m = 0x7; break; +		case OMAP_DSS_COLOR_RGB24U: +			m = 0x8; break; +		case OMAP_DSS_COLOR_RGB24P: +			m = 0x9; break; +		case OMAP_DSS_COLOR_RGBX16: +			m = 0xa; break; +		case OMAP_DSS_COLOR_RGBA16: +			m = 0xb; break; +		case OMAP_DSS_COLOR_ARGB32: +			m = 0xc; break; +		case OMAP_DSS_COLOR_RGBA32: +			m = 0xd; break; +		case OMAP_DSS_COLOR_RGBX32: +			m = 0xe; break; +		case OMAP_DSS_COLOR_XRGB16_1555: +			m = 0xf; break; +		default: +			BUG(); return; +		} +	} + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), m, 4, 1); +} + +static void dispc_ovl_configure_burst_type(enum omap_plane plane, +		enum omap_dss_rotation_type rotation_type) +{ +	if (dss_has_feature(FEAT_BURST_2D) == 0) +		return; + +	if (rotation_type == OMAP_DSS_ROT_TILER) +		REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), 1, 29, 29); +	else +		REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), 0, 29, 29); +} + +void dispc_ovl_set_channel_out(enum omap_plane plane, enum omap_channel channel) +{ +	int shift; +	u32 val; +	int chan = 0, chan2 = 0; + +	switch (plane) { +	case OMAP_DSS_GFX: +		shift = 8; +		break; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +	case OMAP_DSS_VIDEO3: +		shift = 16; +		break; +	default: +		BUG(); +		return; +	} + +	val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); +	if (dss_has_feature(FEAT_MGR_LCD2)) { +		switch (channel) { +		case OMAP_DSS_CHANNEL_LCD: +			chan = 0; +			chan2 = 0; +			break; +		case OMAP_DSS_CHANNEL_DIGIT: +			chan = 1; +			chan2 = 0; +			break; +		case OMAP_DSS_CHANNEL_LCD2: +			chan = 0; +			chan2 = 1; +			break; +		case OMAP_DSS_CHANNEL_LCD3: +			if (dss_has_feature(FEAT_MGR_LCD3)) { +				chan = 0; +				chan2 = 2; +			} else { +				BUG(); +				return; +			} +			break; +		default: +			BUG(); +			return; +		} + +		val = FLD_MOD(val, chan, shift, shift); +		val = FLD_MOD(val, chan2, 31, 30); +	} else { +		val = FLD_MOD(val, channel, shift, shift); +	} +	dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} +EXPORT_SYMBOL(dispc_ovl_set_channel_out); + +static enum omap_channel dispc_ovl_get_channel_out(enum omap_plane plane) +{ +	int shift; +	u32 val; +	enum omap_channel channel; + +	switch (plane) { +	case OMAP_DSS_GFX: +		shift = 8; +		break; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +	case OMAP_DSS_VIDEO3: +		shift = 16; +		break; +	default: +		BUG(); +		return 0; +	} + +	val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + +	if (dss_has_feature(FEAT_MGR_LCD3)) { +		if (FLD_GET(val, 31, 30) == 0) +			channel = FLD_GET(val, shift, shift); +		else if (FLD_GET(val, 31, 30) == 1) +			channel = OMAP_DSS_CHANNEL_LCD2; +		else +			channel = OMAP_DSS_CHANNEL_LCD3; +	} else if (dss_has_feature(FEAT_MGR_LCD2)) { +		if (FLD_GET(val, 31, 30) == 0) +			channel = FLD_GET(val, shift, shift); +		else +			channel = OMAP_DSS_CHANNEL_LCD2; +	} else { +		channel = FLD_GET(val, shift, shift); +	} + +	return channel; +} + +void dispc_wb_set_channel_in(enum dss_writeback_channel channel) +{ +	enum omap_plane plane = OMAP_DSS_WB; + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), channel, 18, 16); +} + +static void dispc_ovl_set_burst_size(enum omap_plane plane, +		enum omap_burst_size burst_size) +{ +	static const unsigned shifts[] = { 6, 14, 14, 14, 14, }; +	int shift; + +	shift = shifts[plane]; +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), burst_size, shift + 1, shift); +} + +static void dispc_configure_burst_sizes(void) +{ +	int i; +	const int burst_size = BURST_SIZE_X8; + +	/* Configure burst size always to maximum size */ +	for (i = 0; i < dss_feat_get_num_ovls(); ++i) +		dispc_ovl_set_burst_size(i, burst_size); +} + +static u32 dispc_ovl_get_burst_size(enum omap_plane plane) +{ +	unsigned unit = dss_feat_get_burst_size_unit(); +	/* burst multiplier is always x8 (see dispc_configure_burst_sizes()) */ +	return unit * 8; +} + +void dispc_enable_gamma_table(bool enable) +{ +	/* +	 * This is partially implemented to support only disabling of +	 * the gamma table. +	 */ +	if (enable) { +		DSSWARN("Gamma table enabling for TV not yet supported"); +		return; +	} + +	REG_FLD_MOD(DISPC_CONFIG, enable, 9, 9); +} + +static void dispc_mgr_enable_cpr(enum omap_channel channel, bool enable) +{ +	if (channel == OMAP_DSS_CHANNEL_DIGIT) +		return; + +	mgr_fld_write(channel, DISPC_MGR_FLD_CPR, enable); +} + +static void dispc_mgr_set_cpr_coef(enum omap_channel channel, +		const struct omap_dss_cpr_coefs *coefs) +{ +	u32 coef_r, coef_g, coef_b; + +	if (!dss_mgr_is_lcd(channel)) +		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_ovl_set_vid_color_conv(enum omap_plane plane, bool enable) +{ +	u32 val; + +	BUG_ON(plane == OMAP_DSS_GFX); + +	val = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); +	val = FLD_MOD(val, enable, 9, 9); +	dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), val); +} + +static void dispc_ovl_enable_replication(enum omap_plane plane, +		enum omap_overlay_caps caps, bool enable) +{ +	static const unsigned shifts[] = { 5, 10, 10, 10 }; +	int shift; + +	if ((caps & OMAP_DSS_OVL_CAP_REPLICATION) == 0) +		return; + +	shift = shifts[plane]; +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), enable, shift, shift); +} + +static void dispc_mgr_set_size(enum omap_channel channel, u16 width, +		u16 height) +{ +	u32 val; + +	val = FLD_VAL(height - 1, dispc.feat->mgr_height_start, 16) | +		FLD_VAL(width - 1, dispc.feat->mgr_width_start, 0); + +	dispc_write_reg(DISPC_SIZE_MGR(channel), val); +} + +static void dispc_init_fifos(void) +{ +	u32 size; +	int fifo; +	u8 start, end; +	u32 unit; + +	unit = dss_feat_get_buffer_size_unit(); + +	dss_feat_get_reg_field(FEAT_REG_FIFOSIZE, &start, &end); + +	for (fifo = 0; fifo < dispc.feat->num_fifos; ++fifo) { +		size = REG_GET(DISPC_OVL_FIFO_SIZE_STATUS(fifo), start, end); +		size *= unit; +		dispc.fifo_size[fifo] = size; + +		/* +		 * By default fifos are mapped directly to overlays, fifo 0 to +		 * ovl 0, fifo 1 to ovl 1, etc. +		 */ +		dispc.fifo_assignment[fifo] = fifo; +	} + +	/* +	 * The GFX fifo on OMAP4 is smaller than the other fifos. The small fifo +	 * causes problems with certain use cases, like using the tiler in 2D +	 * mode. The below hack swaps the fifos of GFX and WB planes, thus +	 * giving GFX plane a larger fifo. WB but should work fine with a +	 * smaller fifo. +	 */ +	if (dispc.feat->gfx_fifo_workaround) { +		u32 v; + +		v = dispc_read_reg(DISPC_GLOBAL_BUFFER); + +		v = FLD_MOD(v, 4, 2, 0); /* GFX BUF top to WB */ +		v = FLD_MOD(v, 4, 5, 3); /* GFX BUF bottom to WB */ +		v = FLD_MOD(v, 0, 26, 24); /* WB BUF top to GFX */ +		v = FLD_MOD(v, 0, 29, 27); /* WB BUF bottom to GFX */ + +		dispc_write_reg(DISPC_GLOBAL_BUFFER, v); + +		dispc.fifo_assignment[OMAP_DSS_GFX] = OMAP_DSS_WB; +		dispc.fifo_assignment[OMAP_DSS_WB] = OMAP_DSS_GFX; +	} +} + +static u32 dispc_ovl_get_fifo_size(enum omap_plane plane) +{ +	int fifo; +	u32 size = 0; + +	for (fifo = 0; fifo < dispc.feat->num_fifos; ++fifo) { +		if (dispc.fifo_assignment[fifo] == plane) +			size += dispc.fifo_size[fifo]; +	} + +	return size; +} + +void dispc_ovl_set_fifo_threshold(enum omap_plane plane, u32 low, u32 high) +{ +	u8 hi_start, hi_end, lo_start, lo_end; +	u32 unit; + +	unit = dss_feat_get_buffer_size_unit(); + +	WARN_ON(low % unit != 0); +	WARN_ON(high % unit != 0); + +	low /= unit; +	high /= unit; + +	dss_feat_get_reg_field(FEAT_REG_FIFOHIGHTHRESHOLD, &hi_start, &hi_end); +	dss_feat_get_reg_field(FEAT_REG_FIFOLOWTHRESHOLD, &lo_start, &lo_end); + +	DSSDBG("fifo(%d) threshold (bytes), old %u/%u, new %u/%u\n", +			plane, +			REG_GET(DISPC_OVL_FIFO_THRESHOLD(plane), +				lo_start, lo_end) * unit, +			REG_GET(DISPC_OVL_FIFO_THRESHOLD(plane), +				hi_start, hi_end) * unit, +			low * unit, high * unit); + +	dispc_write_reg(DISPC_OVL_FIFO_THRESHOLD(plane), +			FLD_VAL(high, hi_start, hi_end) | +			FLD_VAL(low, lo_start, lo_end)); + +	/* +	 * configure the preload to the pipeline's high threhold, if HT it's too +	 * large for the preload field, set the threshold to the maximum value +	 * that can be held by the preload register +	 */ +	if (dss_has_feature(FEAT_PRELOAD) && dispc.feat->set_max_preload && +			plane != OMAP_DSS_WB) +		dispc_write_reg(DISPC_OVL_PRELOAD(plane), min(high, 0xfffu)); +} +EXPORT_SYMBOL(dispc_ovl_set_fifo_threshold); + +void dispc_enable_fifomerge(bool enable) +{ +	if (!dss_has_feature(FEAT_FIFO_MERGE)) { +		WARN_ON(enable); +		return; +	} + +	DSSDBG("FIFO merge %s\n", enable ? "enabled" : "disabled"); +	REG_FLD_MOD(DISPC_CONFIG, enable ? 1 : 0, 14, 14); +} + +void dispc_ovl_compute_fifo_thresholds(enum omap_plane plane, +		u32 *fifo_low, u32 *fifo_high, bool use_fifomerge, +		bool manual_update) +{ +	/* +	 * All sizes are in bytes. Both the buffer and burst are made of +	 * buffer_units, and the fifo thresholds must be buffer_unit aligned. +	 */ + +	unsigned buf_unit = dss_feat_get_buffer_size_unit(); +	unsigned ovl_fifo_size, total_fifo_size, burst_size; +	int i; + +	burst_size = dispc_ovl_get_burst_size(plane); +	ovl_fifo_size = dispc_ovl_get_fifo_size(plane); + +	if (use_fifomerge) { +		total_fifo_size = 0; +		for (i = 0; i < dss_feat_get_num_ovls(); ++i) +			total_fifo_size += dispc_ovl_get_fifo_size(i); +	} else { +		total_fifo_size = ovl_fifo_size; +	} + +	/* +	 * We use the same low threshold for both fifomerge and non-fifomerge +	 * cases, but for fifomerge we calculate the high threshold using the +	 * combined fifo size +	 */ + +	if (manual_update && dss_has_feature(FEAT_OMAP3_DSI_FIFO_BUG)) { +		*fifo_low = ovl_fifo_size - burst_size * 2; +		*fifo_high = total_fifo_size - burst_size; +	} else if (plane == OMAP_DSS_WB) { +		/* +		 * Most optimal configuration for writeback is to push out data +		 * to the interconnect the moment writeback pushes enough pixels +		 * in the FIFO to form a burst +		 */ +		*fifo_low = 0; +		*fifo_high = burst_size; +	} else { +		*fifo_low = ovl_fifo_size - burst_size; +		*fifo_high = total_fifo_size - buf_unit; +	} +} +EXPORT_SYMBOL(dispc_ovl_compute_fifo_thresholds); + +static void dispc_ovl_set_fir(enum omap_plane plane, +				int hinc, int vinc, +				enum omap_color_component color_comp) +{ +	u32 val; + +	if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { +		u8 hinc_start, hinc_end, vinc_start, vinc_end; + +		dss_feat_get_reg_field(FEAT_REG_FIRHINC, +					&hinc_start, &hinc_end); +		dss_feat_get_reg_field(FEAT_REG_FIRVINC, +					&vinc_start, &vinc_end); +		val = FLD_VAL(vinc, vinc_start, vinc_end) | +				FLD_VAL(hinc, hinc_start, hinc_end); + +		dispc_write_reg(DISPC_OVL_FIR(plane), val); +	} else { +		val = FLD_VAL(vinc, 28, 16) | FLD_VAL(hinc, 12, 0); +		dispc_write_reg(DISPC_OVL_FIR2(plane), val); +	} +} + +static void dispc_ovl_set_vid_accu0(enum omap_plane plane, int haccu, int vaccu) +{ +	u32 val; +	u8 hor_start, hor_end, vert_start, vert_end; + +	dss_feat_get_reg_field(FEAT_REG_HORIZONTALACCU, &hor_start, &hor_end); +	dss_feat_get_reg_field(FEAT_REG_VERTICALACCU, &vert_start, &vert_end); + +	val = FLD_VAL(vaccu, vert_start, vert_end) | +			FLD_VAL(haccu, hor_start, hor_end); + +	dispc_write_reg(DISPC_OVL_ACCU0(plane), val); +} + +static void dispc_ovl_set_vid_accu1(enum omap_plane plane, int haccu, int vaccu) +{ +	u32 val; +	u8 hor_start, hor_end, vert_start, vert_end; + +	dss_feat_get_reg_field(FEAT_REG_HORIZONTALACCU, &hor_start, &hor_end); +	dss_feat_get_reg_field(FEAT_REG_VERTICALACCU, &vert_start, &vert_end); + +	val = FLD_VAL(vaccu, vert_start, vert_end) | +			FLD_VAL(haccu, hor_start, hor_end); + +	dispc_write_reg(DISPC_OVL_ACCU1(plane), val); +} + +static void dispc_ovl_set_vid_accu2_0(enum omap_plane plane, int haccu, +		int vaccu) +{ +	u32 val; + +	val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); +	dispc_write_reg(DISPC_OVL_ACCU2_0(plane), val); +} + +static void dispc_ovl_set_vid_accu2_1(enum omap_plane plane, int haccu, +		int vaccu) +{ +	u32 val; + +	val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); +	dispc_write_reg(DISPC_OVL_ACCU2_1(plane), val); +} + +static void dispc_ovl_set_scale_param(enum omap_plane plane, +		u16 orig_width, u16 orig_height, +		u16 out_width, u16 out_height, +		bool five_taps, u8 rotation, +		enum omap_color_component color_comp) +{ +	int fir_hinc, fir_vinc; + +	fir_hinc = 1024 * orig_width / out_width; +	fir_vinc = 1024 * orig_height / out_height; + +	dispc_ovl_set_scale_coef(plane, fir_hinc, fir_vinc, five_taps, +				color_comp); +	dispc_ovl_set_fir(plane, fir_hinc, fir_vinc, color_comp); +} + +static void dispc_ovl_set_accu_uv(enum omap_plane plane, +		u16 orig_width,	u16 orig_height, u16 out_width, u16 out_height, +		bool ilace, enum omap_color_mode color_mode, u8 rotation) +{ +	int h_accu2_0, h_accu2_1; +	int v_accu2_0, v_accu2_1; +	int chroma_hinc, chroma_vinc; +	int idx; + +	struct accu { +		s8 h0_m, h0_n; +		s8 h1_m, h1_n; +		s8 v0_m, v0_n; +		s8 v1_m, v1_n; +	}; + +	const struct accu *accu_table; +	const struct accu *accu_val; + +	static const struct accu accu_nv12[4] = { +		{  0, 1,  0, 1 , -1, 2, 0, 1 }, +		{  1, 2, -3, 4 ,  0, 1, 0, 1 }, +		{ -1, 1,  0, 1 , -1, 2, 0, 1 }, +		{ -1, 2, -1, 2 , -1, 1, 0, 1 }, +	}; + +	static const struct accu accu_nv12_ilace[4] = { +		{  0, 1,  0, 1 , -3, 4, -1, 4 }, +		{ -1, 4, -3, 4 ,  0, 1,  0, 1 }, +		{ -1, 1,  0, 1 , -1, 4, -3, 4 }, +		{ -3, 4, -3, 4 , -1, 1,  0, 1 }, +	}; + +	static const struct accu accu_yuv[4] = { +		{  0, 1, 0, 1,  0, 1, 0, 1 }, +		{  0, 1, 0, 1,  0, 1, 0, 1 }, +		{ -1, 1, 0, 1,  0, 1, 0, 1 }, +		{  0, 1, 0, 1, -1, 1, 0, 1 }, +	}; + +	switch (rotation) { +	case OMAP_DSS_ROT_0: +		idx = 0; +		break; +	case OMAP_DSS_ROT_90: +		idx = 1; +		break; +	case OMAP_DSS_ROT_180: +		idx = 2; +		break; +	case OMAP_DSS_ROT_270: +		idx = 3; +		break; +	default: +		BUG(); +		return; +	} + +	switch (color_mode) { +	case OMAP_DSS_COLOR_NV12: +		if (ilace) +			accu_table = accu_nv12_ilace; +		else +			accu_table = accu_nv12; +		break; +	case OMAP_DSS_COLOR_YUV2: +	case OMAP_DSS_COLOR_UYVY: +		accu_table = accu_yuv; +		break; +	default: +		BUG(); +		return; +	} + +	accu_val = &accu_table[idx]; + +	chroma_hinc = 1024 * orig_width / out_width; +	chroma_vinc = 1024 * orig_height / out_height; + +	h_accu2_0 = (accu_val->h0_m * chroma_hinc / accu_val->h0_n) % 1024; +	h_accu2_1 = (accu_val->h1_m * chroma_hinc / accu_val->h1_n) % 1024; +	v_accu2_0 = (accu_val->v0_m * chroma_vinc / accu_val->v0_n) % 1024; +	v_accu2_1 = (accu_val->v1_m * chroma_vinc / accu_val->v1_n) % 1024; + +	dispc_ovl_set_vid_accu2_0(plane, h_accu2_0, v_accu2_0); +	dispc_ovl_set_vid_accu2_1(plane, h_accu2_1, v_accu2_1); +} + +static void dispc_ovl_set_scaling_common(enum omap_plane plane, +		u16 orig_width, u16 orig_height, +		u16 out_width, u16 out_height, +		bool ilace, bool five_taps, +		bool fieldmode, enum omap_color_mode color_mode, +		u8 rotation) +{ +	int accu0 = 0; +	int accu1 = 0; +	u32 l; + +	dispc_ovl_set_scale_param(plane, orig_width, orig_height, +				out_width, out_height, five_taps, +				rotation, DISPC_COLOR_COMPONENT_RGB_Y); +	l = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); + +	/* RESIZEENABLE and VERTICALTAPS */ +	l &= ~((0x3 << 5) | (0x1 << 21)); +	l |= (orig_width != out_width) ? (1 << 5) : 0; +	l |= (orig_height != out_height) ? (1 << 6) : 0; +	l |= five_taps ? (1 << 21) : 0; + +	/* VRESIZECONF and HRESIZECONF */ +	if (dss_has_feature(FEAT_RESIZECONF)) { +		l &= ~(0x3 << 7); +		l |= (orig_width <= out_width) ? 0 : (1 << 7); +		l |= (orig_height <= out_height) ? 0 : (1 << 8); +	} + +	/* LINEBUFFERSPLIT */ +	if (dss_has_feature(FEAT_LINEBUFFERSPLIT)) { +		l &= ~(0x1 << 22); +		l |= five_taps ? (1 << 22) : 0; +	} + +	dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), l); + +	/* +	 * field 0 = even field = bottom field +	 * field 1 = odd field = top field +	 */ +	if (ilace && !fieldmode) { +		accu1 = 0; +		accu0 = ((1024 * orig_height / out_height) / 2) & 0x3ff; +		if (accu0 >= 1024/2) { +			accu1 = 1024/2; +			accu0 -= accu1; +		} +	} + +	dispc_ovl_set_vid_accu0(plane, 0, accu0); +	dispc_ovl_set_vid_accu1(plane, 0, accu1); +} + +static void dispc_ovl_set_scaling_uv(enum omap_plane plane, +		u16 orig_width, u16 orig_height, +		u16 out_width, u16 out_height, +		bool ilace, bool five_taps, +		bool fieldmode, enum omap_color_mode color_mode, +		u8 rotation) +{ +	int scale_x = out_width != orig_width; +	int scale_y = out_height != orig_height; +	bool chroma_upscale = plane != OMAP_DSS_WB ? true : false; + +	if (!dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) +		return; +	if ((color_mode != OMAP_DSS_COLOR_YUV2 && +			color_mode != OMAP_DSS_COLOR_UYVY && +			color_mode != OMAP_DSS_COLOR_NV12)) { +		/* reset chroma resampling for RGB formats  */ +		if (plane != OMAP_DSS_WB) +			REG_FLD_MOD(DISPC_OVL_ATTRIBUTES2(plane), 0, 8, 8); +		return; +	} + +	dispc_ovl_set_accu_uv(plane, orig_width, orig_height, out_width, +			out_height, ilace, color_mode, rotation); + +	switch (color_mode) { +	case OMAP_DSS_COLOR_NV12: +		if (chroma_upscale) { +			/* UV is subsampled by 2 horizontally and vertically */ +			orig_height >>= 1; +			orig_width >>= 1; +		} else { +			/* UV is downsampled by 2 horizontally and vertically */ +			orig_height <<= 1; +			orig_width <<= 1; +		} + +		break; +	case OMAP_DSS_COLOR_YUV2: +	case OMAP_DSS_COLOR_UYVY: +		/* For YUV422 with 90/270 rotation, we don't upsample chroma */ +		if (rotation == OMAP_DSS_ROT_0 || +				rotation == OMAP_DSS_ROT_180) { +			if (chroma_upscale) +				/* UV is subsampled by 2 horizontally */ +				orig_width >>= 1; +			else +				/* UV is downsampled by 2 horizontally */ +				orig_width <<= 1; +		} + +		/* must use FIR for YUV422 if rotated */ +		if (rotation != OMAP_DSS_ROT_0) +			scale_x = scale_y = true; + +		break; +	default: +		BUG(); +		return; +	} + +	if (out_width != orig_width) +		scale_x = true; +	if (out_height != orig_height) +		scale_y = true; + +	dispc_ovl_set_scale_param(plane, orig_width, orig_height, +			out_width, out_height, five_taps, +				rotation, DISPC_COLOR_COMPONENT_UV); + +	if (plane != OMAP_DSS_WB) +		REG_FLD_MOD(DISPC_OVL_ATTRIBUTES2(plane), +			(scale_x || scale_y) ? 1 : 0, 8, 8); + +	/* set H scaling */ +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), scale_x ? 1 : 0, 5, 5); +	/* set V scaling */ +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), scale_y ? 1 : 0, 6, 6); +} + +static void dispc_ovl_set_scaling(enum omap_plane plane, +		u16 orig_width, u16 orig_height, +		u16 out_width, u16 out_height, +		bool ilace, bool five_taps, +		bool fieldmode, enum omap_color_mode color_mode, +		u8 rotation) +{ +	BUG_ON(plane == OMAP_DSS_GFX); + +	dispc_ovl_set_scaling_common(plane, +			orig_width, orig_height, +			out_width, out_height, +			ilace, five_taps, +			fieldmode, color_mode, +			rotation); + +	dispc_ovl_set_scaling_uv(plane, +		orig_width, orig_height, +		out_width, out_height, +		ilace, five_taps, +		fieldmode, color_mode, +		rotation); +} + +static void dispc_ovl_set_rotation_attrs(enum omap_plane plane, u8 rotation, +		enum omap_dss_rotation_type rotation_type, +		bool mirroring, enum omap_color_mode color_mode) +{ +	bool row_repeat = false; +	int vidrot = 0; + +	if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) { + +		if (mirroring) { +			switch (rotation) { +			case OMAP_DSS_ROT_0: +				vidrot = 2; +				break; +			case OMAP_DSS_ROT_90: +				vidrot = 1; +				break; +			case OMAP_DSS_ROT_180: +				vidrot = 0; +				break; +			case OMAP_DSS_ROT_270: +				vidrot = 3; +				break; +			} +		} else { +			switch (rotation) { +			case OMAP_DSS_ROT_0: +				vidrot = 0; +				break; +			case OMAP_DSS_ROT_90: +				vidrot = 1; +				break; +			case OMAP_DSS_ROT_180: +				vidrot = 2; +				break; +			case OMAP_DSS_ROT_270: +				vidrot = 3; +				break; +			} +		} + +		if (rotation == OMAP_DSS_ROT_90 || rotation == OMAP_DSS_ROT_270) +			row_repeat = true; +		else +			row_repeat = false; +	} + +	REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), vidrot, 13, 12); +	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) { +		bool doublestride = (rotation_type == OMAP_DSS_ROT_TILER) && +					(rotation == OMAP_DSS_ROT_0 || +					rotation == OMAP_DSS_ROT_180); +		/* DOUBLESTRIDE */ +		REG_FLD_MOD(DISPC_OVL_ATTRIBUTES(plane), doublestride, 22, 22); +	} + +} + +static int color_mode_to_bpp(enum omap_color_mode color_mode) +{ +	switch (color_mode) { +	case OMAP_DSS_COLOR_CLUT1: +		return 1; +	case OMAP_DSS_COLOR_CLUT2: +		return 2; +	case OMAP_DSS_COLOR_CLUT4: +		return 4; +	case OMAP_DSS_COLOR_CLUT8: +	case OMAP_DSS_COLOR_NV12: +		return 8; +	case OMAP_DSS_COLOR_RGB12U: +	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_RGBX16: +	case OMAP_DSS_COLOR_ARGB16_1555: +	case OMAP_DSS_COLOR_XRGB16_1555: +		return 16; +	case OMAP_DSS_COLOR_RGB24P: +		return 24; +	case OMAP_DSS_COLOR_RGB24U: +	case OMAP_DSS_COLOR_ARGB32: +	case OMAP_DSS_COLOR_RGBA32: +	case OMAP_DSS_COLOR_RGBX32: +		return 32; +	default: +		BUG(); +		return 0; +	} +} + +static s32 pixinc(int pixels, u8 ps) +{ +	if (pixels == 1) +		return 1; +	else if (pixels > 1) +		return 1 + (pixels - 1) * ps; +	else if (pixels < 0) +		return 1 - (-pixels + 1) * ps; +	else +		BUG(); +		return 0; +} + +static void calc_vrfb_rotation_offset(u8 rotation, bool mirror, +		u16 screen_width, +		u16 width, u16 height, +		enum omap_color_mode color_mode, bool fieldmode, +		unsigned int field_offset, +		unsigned *offset0, unsigned *offset1, +		s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ +	u8 ps; + +	/* FIXME CLUT formats */ +	switch (color_mode) { +	case OMAP_DSS_COLOR_CLUT1: +	case OMAP_DSS_COLOR_CLUT2: +	case OMAP_DSS_COLOR_CLUT4: +	case OMAP_DSS_COLOR_CLUT8: +		BUG(); +		return; +	case OMAP_DSS_COLOR_YUV2: +	case OMAP_DSS_COLOR_UYVY: +		ps = 4; +		break; +	default: +		ps = color_mode_to_bpp(color_mode) / 8; +		break; +	} + +	DSSDBG("calc_rot(%d): scrw %d, %dx%d\n", rotation, screen_width, +			width, height); + +	/* +	 * field 0 = even field = bottom field +	 * field 1 = odd field = top field +	 */ +	switch (rotation + mirror * 4) { +	case OMAP_DSS_ROT_0: +	case OMAP_DSS_ROT_180: +		/* +		 * If the pixel format is YUV or UYVY divide the width +		 * of the image by 2 for 0 and 180 degree rotation. +		 */ +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			width = width >> 1; +	case OMAP_DSS_ROT_90: +	case OMAP_DSS_ROT_270: +		*offset1 = 0; +		if (field_offset) +			*offset0 = field_offset * screen_width * ps; +		else +			*offset0 = 0; + +		*row_inc = pixinc(1 + +			(y_predecim * screen_width - x_predecim * width) + +			(fieldmode ? screen_width : 0), ps); +		*pix_inc = pixinc(x_predecim, ps); +		break; + +	case OMAP_DSS_ROT_0 + 4: +	case OMAP_DSS_ROT_180 + 4: +		/* If the pixel format is YUV or UYVY divide the width +		 * of the image by 2  for 0 degree and 180 degree +		 */ +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			width = width >> 1; +	case OMAP_DSS_ROT_90 + 4: +	case OMAP_DSS_ROT_270 + 4: +		*offset1 = 0; +		if (field_offset) +			*offset0 = field_offset * screen_width * ps; +		else +			*offset0 = 0; +		*row_inc = pixinc(1 - +			(y_predecim * screen_width + x_predecim * width) - +			(fieldmode ? screen_width : 0), ps); +		*pix_inc = pixinc(x_predecim, ps); +		break; + +	default: +		BUG(); +		return; +	} +} + +static void calc_dma_rotation_offset(u8 rotation, bool mirror, +		u16 screen_width, +		u16 width, u16 height, +		enum omap_color_mode color_mode, bool fieldmode, +		unsigned int field_offset, +		unsigned *offset0, unsigned *offset1, +		s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ +	u8 ps; +	u16 fbw, fbh; + +	/* FIXME CLUT formats */ +	switch (color_mode) { +	case OMAP_DSS_COLOR_CLUT1: +	case OMAP_DSS_COLOR_CLUT2: +	case OMAP_DSS_COLOR_CLUT4: +	case OMAP_DSS_COLOR_CLUT8: +		BUG(); +		return; +	default: +		ps = color_mode_to_bpp(color_mode) / 8; +		break; +	} + +	DSSDBG("calc_rot(%d): scrw %d, %dx%d\n", rotation, screen_width, +			width, height); + +	/* width & height are overlay sizes, convert to fb sizes */ + +	if (rotation == OMAP_DSS_ROT_0 || rotation == OMAP_DSS_ROT_180) { +		fbw = width; +		fbh = height; +	} else { +		fbw = height; +		fbh = width; +	} + +	/* +	 * field 0 = even field = bottom field +	 * field 1 = odd field = top field +	 */ +	switch (rotation + mirror * 4) { +	case OMAP_DSS_ROT_0: +		*offset1 = 0; +		if (field_offset) +			*offset0 = *offset1 + field_offset * screen_width * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(1 + +			(y_predecim * screen_width - fbw * x_predecim) + +			(fieldmode ? screen_width : 0),	ps); +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			*pix_inc = pixinc(x_predecim, 2 * ps); +		else +			*pix_inc = pixinc(x_predecim, ps); +		break; +	case OMAP_DSS_ROT_90: +		*offset1 = screen_width * (fbh - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 + field_offset * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(screen_width * (fbh * x_predecim - 1) + +				y_predecim + (fieldmode ? 1 : 0), ps); +		*pix_inc = pixinc(-x_predecim * screen_width, ps); +		break; +	case OMAP_DSS_ROT_180: +		*offset1 = (screen_width * (fbh - 1) + fbw - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 - field_offset * screen_width * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(-1 - +			(y_predecim * screen_width - fbw * x_predecim) - +			(fieldmode ? screen_width : 0),	ps); +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			*pix_inc = pixinc(-x_predecim, 2 * ps); +		else +			*pix_inc = pixinc(-x_predecim, ps); +		break; +	case OMAP_DSS_ROT_270: +		*offset1 = (fbw - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 - field_offset * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(-screen_width * (fbh * x_predecim - 1) - +				y_predecim - (fieldmode ? 1 : 0), ps); +		*pix_inc = pixinc(x_predecim * screen_width, ps); +		break; + +	/* mirroring */ +	case OMAP_DSS_ROT_0 + 4: +		*offset1 = (fbw - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 + field_offset * screen_width * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(y_predecim * screen_width * 2 - 1 + +				(fieldmode ? screen_width : 0), +				ps); +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			*pix_inc = pixinc(-x_predecim, 2 * ps); +		else +			*pix_inc = pixinc(-x_predecim, ps); +		break; + +	case OMAP_DSS_ROT_90 + 4: +		*offset1 = 0; +		if (field_offset) +			*offset0 = *offset1 + field_offset * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(-screen_width * (fbh * x_predecim - 1) + +				y_predecim + (fieldmode ? 1 : 0), +				ps); +		*pix_inc = pixinc(x_predecim * screen_width, ps); +		break; + +	case OMAP_DSS_ROT_180 + 4: +		*offset1 = screen_width * (fbh - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 - field_offset * screen_width * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(1 - y_predecim * screen_width * 2 - +				(fieldmode ? screen_width : 0), +				ps); +		if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY) +			*pix_inc = pixinc(x_predecim, 2 * ps); +		else +			*pix_inc = pixinc(x_predecim, ps); +		break; + +	case OMAP_DSS_ROT_270 + 4: +		*offset1 = (screen_width * (fbh - 1) + fbw - 1) * ps; +		if (field_offset) +			*offset0 = *offset1 - field_offset * ps; +		else +			*offset0 = *offset1; +		*row_inc = pixinc(screen_width * (fbh * x_predecim - 1) - +				y_predecim - (fieldmode ? 1 : 0), +				ps); +		*pix_inc = pixinc(-x_predecim * screen_width, ps); +		break; + +	default: +		BUG(); +		return; +	} +} + +static void calc_tiler_rotation_offset(u16 screen_width, u16 width, +		enum omap_color_mode color_mode, bool fieldmode, +		unsigned int field_offset, unsigned *offset0, unsigned *offset1, +		s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim) +{ +	u8 ps; + +	switch (color_mode) { +	case OMAP_DSS_COLOR_CLUT1: +	case OMAP_DSS_COLOR_CLUT2: +	case OMAP_DSS_COLOR_CLUT4: +	case OMAP_DSS_COLOR_CLUT8: +		BUG(); +		return; +	default: +		ps = color_mode_to_bpp(color_mode) / 8; +		break; +	} + +	DSSDBG("scrw %d, width %d\n", screen_width, width); + +	/* +	 * field 0 = even field = bottom field +	 * field 1 = odd field = top field +	 */ +	*offset1 = 0; +	if (field_offset) +		*offset0 = *offset1 + field_offset * screen_width * ps; +	else +		*offset0 = *offset1; +	*row_inc = pixinc(1 + (y_predecim * screen_width - width * x_predecim) + +			(fieldmode ? screen_width : 0), ps); +	if (color_mode == OMAP_DSS_COLOR_YUV2 || +		color_mode == OMAP_DSS_COLOR_UYVY) +		*pix_inc = pixinc(x_predecim, 2 * ps); +	else +		*pix_inc = pixinc(x_predecim, ps); +} + +/* + * This function is used to avoid synclosts in OMAP3, because of some + * undocumented horizontal position and timing related limitations. + */ +static int check_horiz_timing_omap3(unsigned long pclk, unsigned long lclk, +		const struct omap_video_timings *t, u16 pos_x, +		u16 width, u16 height, u16 out_width, u16 out_height, +		bool five_taps) +{ +	const int ds = DIV_ROUND_UP(height, out_height); +	unsigned long nonactive; +	static const u8 limits[3] = { 8, 10, 20 }; +	u64 val, blank; +	int i; + +	nonactive = t->x_res + t->hfp + t->hsw + t->hbp - out_width; + +	i = 0; +	if (out_height < height) +		i++; +	if (out_width < width) +		i++; +	blank = div_u64((u64)(t->hbp + t->hsw + t->hfp) * lclk, pclk); +	DSSDBG("blanking period + ppl = %llu (limit = %u)\n", blank, limits[i]); +	if (blank <= limits[i]) +		return -EINVAL; + +	/* FIXME add checks for 3-tap filter once the limitations are known */ +	if (!five_taps) +		return 0; + +	/* +	 * Pixel data should be prepared before visible display point starts. +	 * So, atleast DS-2 lines must have already been fetched by DISPC +	 * during nonactive - pos_x period. +	 */ +	val = div_u64((u64)(nonactive - pos_x) * lclk, pclk); +	DSSDBG("(nonactive - pos_x) * pcd = %llu max(0, DS - 2) * width = %d\n", +		val, max(0, ds - 2) * width); +	if (val < max(0, ds - 2) * width) +		return -EINVAL; + +	/* +	 * All lines need to be refilled during the nonactive period of which +	 * only one line can be loaded during the active period. So, atleast +	 * DS - 1 lines should be loaded during nonactive period. +	 */ +	val =  div_u64((u64)nonactive * lclk, pclk); +	DSSDBG("nonactive * pcd  = %llu, max(0, DS - 1) * width = %d\n", +		val, max(0, ds - 1) * width); +	if (val < max(0, ds - 1) * width) +		return -EINVAL; + +	return 0; +} + +static unsigned long calc_core_clk_five_taps(unsigned long pclk, +		const struct omap_video_timings *mgr_timings, u16 width, +		u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode) +{ +	u32 core_clk = 0; +	u64 tmp; + +	if (height <= out_height && width <= out_width) +		return (unsigned long) pclk; + +	if (height > out_height) { +		unsigned int ppl = mgr_timings->x_res; + +		tmp = pclk * height * out_width; +		do_div(tmp, 2 * out_height * ppl); +		core_clk = tmp; + +		if (height > 2 * out_height) { +			if (ppl == out_width) +				return 0; + +			tmp = pclk * (height - 2 * out_height) * out_width; +			do_div(tmp, 2 * out_height * (ppl - out_width)); +			core_clk = max_t(u32, core_clk, tmp); +		} +	} + +	if (width > out_width) { +		tmp = pclk * width; +		do_div(tmp, out_width); +		core_clk = max_t(u32, core_clk, tmp); + +		if (color_mode == OMAP_DSS_COLOR_RGB24U) +			core_clk <<= 1; +	} + +	return core_clk; +} + +static unsigned long calc_core_clk_24xx(unsigned long pclk, u16 width, +		u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ +	if (height > out_height && width > out_width) +		return pclk * 4; +	else +		return pclk * 2; +} + +static unsigned long calc_core_clk_34xx(unsigned long pclk, u16 width, +		u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ +	unsigned int hf, vf; + +	/* +	 * FIXME how to determine the 'A' factor +	 * for the no downscaling case ? +	 */ + +	if (width > 3 * out_width) +		hf = 4; +	else if (width > 2 * out_width) +		hf = 3; +	else if (width > out_width) +		hf = 2; +	else +		hf = 1; +	if (height > out_height) +		vf = 2; +	else +		vf = 1; + +	return pclk * vf * hf; +} + +static unsigned long calc_core_clk_44xx(unsigned long pclk, u16 width, +		u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ +	/* +	 * If the overlay/writeback is in mem to mem mode, there are no +	 * downscaling limitations with respect to pixel clock, return 1 as +	 * required core clock to represent that we have sufficient enough +	 * core clock to do maximum downscaling +	 */ +	if (mem_to_mem) +		return 1; + +	if (width > out_width) +		return DIV_ROUND_UP(pclk, out_width) * width; +	else +		return pclk; +} + +static int dispc_ovl_calc_scaling_24xx(unsigned long pclk, unsigned long lclk, +		const struct omap_video_timings *mgr_timings, +		u16 width, u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode, bool *five_taps, +		int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, +		u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ +	int error; +	u16 in_width, in_height; +	int min_factor = min(*decim_x, *decim_y); +	const int maxsinglelinewidth = +			dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); + +	*five_taps = false; + +	do { +		in_height = height / *decim_y; +		in_width = width / *decim_x; +		*core_clk = dispc.feat->calc_core_clk(pclk, in_width, +				in_height, out_width, out_height, mem_to_mem); +		error = (in_width > maxsinglelinewidth || !*core_clk || +			*core_clk > dispc_core_clk_rate()); +		if (error) { +			if (*decim_x == *decim_y) { +				*decim_x = min_factor; +				++*decim_y; +			} else { +				swap(*decim_x, *decim_y); +				if (*decim_x < *decim_y) +					++*decim_x; +			} +		} +	} while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + +	if (in_width > maxsinglelinewidth) { +		DSSERR("Cannot scale max input width exceeded"); +		return -EINVAL; +	} +	return 0; +} + +static int dispc_ovl_calc_scaling_34xx(unsigned long pclk, unsigned long lclk, +		const struct omap_video_timings *mgr_timings, +		u16 width, u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode, bool *five_taps, +		int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, +		u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ +	int error; +	u16 in_width, in_height; +	int min_factor = min(*decim_x, *decim_y); +	const int maxsinglelinewidth = +			dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); + +	do { +		in_height = height / *decim_y; +		in_width = width / *decim_x; +		*five_taps = in_height > out_height; + +		if (in_width > maxsinglelinewidth) +			if (in_height > out_height && +						in_height < out_height * 2) +				*five_taps = false; +again: +		if (*five_taps) +			*core_clk = calc_core_clk_five_taps(pclk, mgr_timings, +						in_width, in_height, out_width, +						out_height, color_mode); +		else +			*core_clk = dispc.feat->calc_core_clk(pclk, in_width, +					in_height, out_width, out_height, +					mem_to_mem); + +		error = check_horiz_timing_omap3(pclk, lclk, mgr_timings, +				pos_x, in_width, in_height, out_width, +				out_height, *five_taps); +		if (error && *five_taps) { +			*five_taps = false; +			goto again; +		} + +		error = (error || in_width > maxsinglelinewidth * 2 || +			(in_width > maxsinglelinewidth && *five_taps) || +			!*core_clk || *core_clk > dispc_core_clk_rate()); +		if (error) { +			if (*decim_x == *decim_y) { +				*decim_x = min_factor; +				++*decim_y; +			} else { +				swap(*decim_x, *decim_y); +				if (*decim_x < *decim_y) +					++*decim_x; +			} +		} +	} while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + +	if (check_horiz_timing_omap3(pclk, lclk, mgr_timings, pos_x, width, +				height, out_width, out_height, *five_taps)) { +			DSSERR("horizontal timing too tight\n"); +			return -EINVAL; +	} + +	if (in_width > (maxsinglelinewidth * 2)) { +		DSSERR("Cannot setup scaling"); +		DSSERR("width exceeds maximum width possible"); +		return -EINVAL; +	} + +	if (in_width > maxsinglelinewidth && *five_taps) { +		DSSERR("cannot setup scaling with five taps"); +		return -EINVAL; +	} +	return 0; +} + +static int dispc_ovl_calc_scaling_44xx(unsigned long pclk, unsigned long lclk, +		const struct omap_video_timings *mgr_timings, +		u16 width, u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode, bool *five_taps, +		int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, +		u16 pos_x, unsigned long *core_clk, bool mem_to_mem) +{ +	u16 in_width, in_width_max; +	int decim_x_min = *decim_x; +	u16 in_height = height / *decim_y; +	const int maxsinglelinewidth = +				dss_feat_get_param_max(FEAT_PARAM_LINEWIDTH); +	const int maxdownscale = dss_feat_get_param_max(FEAT_PARAM_DOWNSCALE); + +	if (mem_to_mem) { +		in_width_max = out_width * maxdownscale; +	} else { +		in_width_max = dispc_core_clk_rate() / +					DIV_ROUND_UP(pclk, out_width); +	} + +	*decim_x = DIV_ROUND_UP(width, in_width_max); + +	*decim_x = *decim_x > decim_x_min ? *decim_x : decim_x_min; +	if (*decim_x > *x_predecim) +		return -EINVAL; + +	do { +		in_width = width / *decim_x; +	} while (*decim_x <= *x_predecim && +			in_width > maxsinglelinewidth && ++*decim_x); + +	if (in_width > maxsinglelinewidth) { +		DSSERR("Cannot scale width exceeds max line width"); +		return -EINVAL; +	} + +	*core_clk = dispc.feat->calc_core_clk(pclk, in_width, in_height, +				out_width, out_height, mem_to_mem); +	return 0; +} + +static int dispc_ovl_calc_scaling(unsigned long pclk, unsigned long lclk, +		enum omap_overlay_caps caps, +		const struct omap_video_timings *mgr_timings, +		u16 width, u16 height, u16 out_width, u16 out_height, +		enum omap_color_mode color_mode, bool *five_taps, +		int *x_predecim, int *y_predecim, u16 pos_x, +		enum omap_dss_rotation_type rotation_type, bool mem_to_mem) +{ +	const int maxdownscale = dss_feat_get_param_max(FEAT_PARAM_DOWNSCALE); +	const int max_decim_limit = 16; +	unsigned long core_clk = 0; +	int decim_x, decim_y, ret; + +	if (width == out_width && height == out_height) +		return 0; + +	if ((caps & OMAP_DSS_OVL_CAP_SCALE) == 0) +		return -EINVAL; + +	if (mem_to_mem) { +		*x_predecim = *y_predecim = 1; +	} else { +		*x_predecim = max_decim_limit; +		*y_predecim = (rotation_type == OMAP_DSS_ROT_TILER && +				dss_has_feature(FEAT_BURST_2D)) ? +				2 : max_decim_limit; +	} + +	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_predecim = 1; +		*y_predecim = 1; +		*five_taps = false; +		return 0; +	} + +	decim_x = DIV_ROUND_UP(DIV_ROUND_UP(width, out_width), maxdownscale); +	decim_y = DIV_ROUND_UP(DIV_ROUND_UP(height, out_height), maxdownscale); + +	if (decim_x > *x_predecim || out_width > width * 8) +		return -EINVAL; + +	if (decim_y > *y_predecim || out_height > height * 8) +		return -EINVAL; + +	ret = dispc.feat->calc_scaling(pclk, lclk, mgr_timings, width, height, +		out_width, out_height, color_mode, five_taps, +		x_predecim, y_predecim, &decim_x, &decim_y, pos_x, &core_clk, +		mem_to_mem); +	if (ret) +		return ret; + +	DSSDBG("required core clk rate = %lu Hz\n", core_clk); +	DSSDBG("current core clk rate = %lu Hz\n", dispc_core_clk_rate()); + +	if (!core_clk || core_clk > dispc_core_clk_rate()) { +		DSSERR("failed to set up scaling, " +			"required core clk rate = %lu Hz, " +			"current core clk rate = %lu Hz\n", +			core_clk, dispc_core_clk_rate()); +		return -EINVAL; +	} + +	*x_predecim = decim_x; +	*y_predecim = decim_y; +	return 0; +} + +int dispc_ovl_check(enum omap_plane plane, enum omap_channel channel, +		const struct omap_overlay_info *oi, +		const struct omap_video_timings *timings, +		int *x_predecim, int *y_predecim) +{ +	enum omap_overlay_caps caps = dss_feat_get_overlay_caps(plane); +	bool five_taps = true; +	bool fieldmode = false; +	u16 in_height = oi->height; +	u16 in_width = oi->width; +	bool ilace = timings->interlace; +	u16 out_width, out_height; +	int pos_x = oi->pos_x; +	unsigned long pclk = dispc_mgr_pclk_rate(channel); +	unsigned long lclk = dispc_mgr_lclk_rate(channel); + +	out_width = oi->out_width == 0 ? oi->width : oi->out_width; +	out_height = oi->out_height == 0 ? oi->height : oi->out_height; + +	if (ilace && oi->height == out_height) +		fieldmode = true; + +	if (ilace) { +		if (fieldmode) +			in_height /= 2; +		out_height /= 2; + +		DSSDBG("adjusting for ilace: height %d, out_height %d\n", +				in_height, out_height); +	} + +	if (!dss_feat_color_mode_supported(plane, oi->color_mode)) +		return -EINVAL; + +	return dispc_ovl_calc_scaling(pclk, lclk, caps, timings, in_width, +			in_height, out_width, out_height, oi->color_mode, +			&five_taps, x_predecim, y_predecim, pos_x, +			oi->rotation_type, false); +} +EXPORT_SYMBOL(dispc_ovl_check); + +static int dispc_ovl_setup_common(enum omap_plane plane, +		enum omap_overlay_caps caps, u32 paddr, u32 p_uv_addr, +		u16 screen_width, int pos_x, int pos_y, u16 width, u16 height, +		u16 out_width, u16 out_height, enum omap_color_mode color_mode, +		u8 rotation, bool mirror, u8 zorder, u8 pre_mult_alpha, +		u8 global_alpha, enum omap_dss_rotation_type rotation_type, +		bool replication, const struct omap_video_timings *mgr_timings, +		bool mem_to_mem) +{ +	bool five_taps = true; +	bool fieldmode = false; +	int r, cconv = 0; +	unsigned offset0, offset1; +	s32 row_inc; +	s32 pix_inc; +	u16 frame_width, frame_height; +	unsigned int field_offset = 0; +	u16 in_height = height; +	u16 in_width = width; +	int x_predecim = 1, y_predecim = 1; +	bool ilace = mgr_timings->interlace; +	unsigned long pclk = dispc_plane_pclk_rate(plane); +	unsigned long lclk = dispc_plane_lclk_rate(plane); + +	if (paddr == 0) +		return -EINVAL; + +	out_width = out_width == 0 ? width : out_width; +	out_height = out_height == 0 ? height : out_height; + +	if (ilace && height == out_height) +		fieldmode = true; + +	if (ilace) { +		if (fieldmode) +			in_height /= 2; +		pos_y /= 2; +		out_height /= 2; + +		DSSDBG("adjusting for ilace: height %d, pos_y %d, " +			"out_height %d\n", in_height, pos_y, +			out_height); +	} + +	if (!dss_feat_color_mode_supported(plane, color_mode)) +		return -EINVAL; + +	r = dispc_ovl_calc_scaling(pclk, lclk, caps, mgr_timings, in_width, +			in_height, out_width, out_height, color_mode, +			&five_taps, &x_predecim, &y_predecim, pos_x, +			rotation_type, mem_to_mem); +	if (r) +		return r; + +	in_width = in_width / x_predecim; +	in_height = in_height / y_predecim; + +	if (color_mode == OMAP_DSS_COLOR_YUV2 || +			color_mode == OMAP_DSS_COLOR_UYVY || +			color_mode == OMAP_DSS_COLOR_NV12) +		cconv = 1; + +	if (ilace && !fieldmode) { +		/* +		 * when downscaling the bottom field may have to start several +		 * source lines below the top field. Unfortunately ACCUI +		 * registers will only hold the fractional part of the offset +		 * so the integer part must be added to the base address of the +		 * bottom field. +		 */ +		if (!in_height || in_height == out_height) +			field_offset = 0; +		else +			field_offset = in_height / out_height / 2; +	} + +	/* Fields are independent but interleaved in memory. */ +	if (fieldmode) +		field_offset = 1; + +	offset0 = 0; +	offset1 = 0; +	row_inc = 0; +	pix_inc = 0; + +	if (plane == OMAP_DSS_WB) { +		frame_width = out_width; +		frame_height = out_height; +	} else { +		frame_width = in_width; +		frame_height = height; +	} + +	if (rotation_type == OMAP_DSS_ROT_TILER) +		calc_tiler_rotation_offset(screen_width, frame_width, +				color_mode, fieldmode, field_offset, +				&offset0, &offset1, &row_inc, &pix_inc, +				x_predecim, y_predecim); +	else if (rotation_type == OMAP_DSS_ROT_DMA) +		calc_dma_rotation_offset(rotation, mirror, screen_width, +				frame_width, frame_height, +				color_mode, fieldmode, field_offset, +				&offset0, &offset1, &row_inc, &pix_inc, +				x_predecim, y_predecim); +	else +		calc_vrfb_rotation_offset(rotation, mirror, +				screen_width, frame_width, frame_height, +				color_mode, fieldmode, field_offset, +				&offset0, &offset1, &row_inc, &pix_inc, +				x_predecim, y_predecim); + +	DSSDBG("offset0 %u, offset1 %u, row_inc %d, pix_inc %d\n", +			offset0, offset1, row_inc, pix_inc); + +	dispc_ovl_set_color_mode(plane, color_mode); + +	dispc_ovl_configure_burst_type(plane, rotation_type); + +	dispc_ovl_set_ba0(plane, paddr + offset0); +	dispc_ovl_set_ba1(plane, paddr + offset1); + +	if (OMAP_DSS_COLOR_NV12 == color_mode) { +		dispc_ovl_set_ba0_uv(plane, p_uv_addr + offset0); +		dispc_ovl_set_ba1_uv(plane, p_uv_addr + offset1); +	} + +	dispc_ovl_set_row_inc(plane, row_inc); +	dispc_ovl_set_pix_inc(plane, pix_inc); + +	DSSDBG("%d,%d %dx%d -> %dx%d\n", pos_x, pos_y, in_width, +			in_height, out_width, out_height); + +	dispc_ovl_set_pos(plane, caps, pos_x, pos_y); + +	dispc_ovl_set_input_size(plane, in_width, in_height); + +	if (caps & OMAP_DSS_OVL_CAP_SCALE) { +		dispc_ovl_set_scaling(plane, in_width, in_height, out_width, +				   out_height, ilace, five_taps, fieldmode, +				   color_mode, rotation); +		dispc_ovl_set_output_size(plane, out_width, out_height); +		dispc_ovl_set_vid_color_conv(plane, cconv); +	} + +	dispc_ovl_set_rotation_attrs(plane, rotation, rotation_type, mirror, +			color_mode); + +	dispc_ovl_set_zorder(plane, caps, zorder); +	dispc_ovl_set_pre_mult_alpha(plane, caps, pre_mult_alpha); +	dispc_ovl_setup_global_alpha(plane, caps, global_alpha); + +	dispc_ovl_enable_replication(plane, caps, replication); + +	return 0; +} + +int dispc_ovl_setup(enum omap_plane plane, const struct omap_overlay_info *oi, +		bool replication, const struct omap_video_timings *mgr_timings, +		bool mem_to_mem) +{ +	int r; +	enum omap_overlay_caps caps = dss_feat_get_overlay_caps(plane); +	enum omap_channel channel; + +	channel = dispc_ovl_get_channel_out(plane); + +	DSSDBG("dispc_ovl_setup %d, pa %pad, pa_uv %pad, sw %d, %d,%d, %dx%d ->" +		" %dx%d, cmode %x, rot %d, mir %d, chan %d repl %d\n", +		plane, &oi->paddr, &oi->p_uv_addr, oi->screen_width, oi->pos_x, +		oi->pos_y, oi->width, oi->height, oi->out_width, oi->out_height, +		oi->color_mode, oi->rotation, oi->mirror, channel, replication); + +	r = dispc_ovl_setup_common(plane, caps, oi->paddr, oi->p_uv_addr, +		oi->screen_width, oi->pos_x, oi->pos_y, oi->width, oi->height, +		oi->out_width, oi->out_height, oi->color_mode, oi->rotation, +		oi->mirror, oi->zorder, oi->pre_mult_alpha, oi->global_alpha, +		oi->rotation_type, replication, mgr_timings, mem_to_mem); + +	return r; +} +EXPORT_SYMBOL(dispc_ovl_setup); + +int dispc_wb_setup(const struct omap_dss_writeback_info *wi, +		bool mem_to_mem, const struct omap_video_timings *mgr_timings) +{ +	int r; +	u32 l; +	enum omap_plane plane = OMAP_DSS_WB; +	const int pos_x = 0, pos_y = 0; +	const u8 zorder = 0, global_alpha = 0; +	const bool replication = false; +	bool truncation; +	int in_width = mgr_timings->x_res; +	int in_height = mgr_timings->y_res; +	enum omap_overlay_caps caps = +		OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA; + +	DSSDBG("dispc_wb_setup, pa %x, pa_uv %x, %d,%d -> %dx%d, cmode %x, " +		"rot %d, mir %d\n", wi->paddr, wi->p_uv_addr, in_width, +		in_height, wi->width, wi->height, wi->color_mode, wi->rotation, +		wi->mirror); + +	r = dispc_ovl_setup_common(plane, caps, wi->paddr, wi->p_uv_addr, +		wi->buf_width, pos_x, pos_y, in_width, in_height, wi->width, +		wi->height, wi->color_mode, wi->rotation, wi->mirror, zorder, +		wi->pre_mult_alpha, global_alpha, wi->rotation_type, +		replication, mgr_timings, mem_to_mem); + +	switch (wi->color_mode) { +	case OMAP_DSS_COLOR_RGB16: +	case OMAP_DSS_COLOR_RGB24P: +	case OMAP_DSS_COLOR_ARGB16: +	case OMAP_DSS_COLOR_RGBA16: +	case OMAP_DSS_COLOR_RGB12U: +	case OMAP_DSS_COLOR_ARGB16_1555: +	case OMAP_DSS_COLOR_XRGB16_1555: +	case OMAP_DSS_COLOR_RGBX16: +		truncation = true; +		break; +	default: +		truncation = false; +		break; +	} + +	/* setup extra DISPC_WB_ATTRIBUTES */ +	l = dispc_read_reg(DISPC_OVL_ATTRIBUTES(plane)); +	l = FLD_MOD(l, truncation, 10, 10);	/* TRUNCATIONENABLE */ +	l = FLD_MOD(l, mem_to_mem, 19, 19);	/* WRITEBACKMODE */ +	dispc_write_reg(DISPC_OVL_ATTRIBUTES(plane), l); + +	return r; +} + +int dispc_ovl_enable(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; +} +EXPORT_SYMBOL(dispc_ovl_enable); + +bool dispc_ovl_enabled(enum omap_plane plane) +{ +	return REG_GET(DISPC_OVL_ATTRIBUTES(plane), 0, 0); +} +EXPORT_SYMBOL(dispc_ovl_enabled); + +void dispc_mgr_enable(enum omap_channel channel, bool enable) +{ +	mgr_fld_write(channel, DISPC_MGR_FLD_ENABLE, enable); +	/* flush posted write */ +	mgr_fld_read(channel, DISPC_MGR_FLD_ENABLE); +} +EXPORT_SYMBOL(dispc_mgr_enable); + +bool dispc_mgr_is_enabled(enum omap_channel channel) +{ +	return !!mgr_fld_read(channel, DISPC_MGR_FLD_ENABLE); +} +EXPORT_SYMBOL(dispc_mgr_is_enabled); + +void dispc_wb_enable(bool enable) +{ +	dispc_ovl_enable(OMAP_DSS_WB, enable); +} + +bool dispc_wb_is_enabled(void) +{ +	return dispc_ovl_enabled(OMAP_DSS_WB); +} + +static void dispc_lcd_enable_signal_polarity(bool act_high) +{ +	if (!dss_has_feature(FEAT_LCDENABLEPOL)) +		return; + +	REG_FLD_MOD(DISPC_CONTROL, act_high ? 1 : 0, 29, 29); +} + +void dispc_lcd_enable_signal(bool enable) +{ +	if (!dss_has_feature(FEAT_LCDENABLESIGNAL)) +		return; + +	REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 28, 28); +} + +void dispc_pck_free_enable(bool enable) +{ +	if (!dss_has_feature(FEAT_PCKFREEENABLE)) +		return; + +	REG_FLD_MOD(DISPC_CONTROL, enable ? 1 : 0, 27, 27); +} + +static void dispc_mgr_enable_fifohandcheck(enum omap_channel channel, bool enable) +{ +	mgr_fld_write(channel, DISPC_MGR_FLD_FIFOHANDCHECK, enable); +} + + +static void dispc_mgr_set_lcd_type_tft(enum omap_channel channel) +{ +	mgr_fld_write(channel, DISPC_MGR_FLD_STNTFT, 1); +} + +void dispc_set_loadmode(enum omap_dss_load_mode mode) +{ +	REG_FLD_MOD(DISPC_CONFIG, mode, 2, 1); +} + + +static void dispc_mgr_set_default_color(enum omap_channel channel, u32 color) +{ +	dispc_write_reg(DISPC_DEFAULT_COLOR(channel), color); +} + +static void dispc_mgr_set_trans_key(enum omap_channel ch, +		enum omap_dss_trans_key_type type, +		u32 trans_key) +{ +	mgr_fld_write(ch, DISPC_MGR_FLD_TCKSELECTION, type); + +	dispc_write_reg(DISPC_TRANS_COLOR(ch), trans_key); +} + +static void dispc_mgr_enable_trans_key(enum omap_channel ch, bool enable) +{ +	mgr_fld_write(ch, DISPC_MGR_FLD_TCKENABLE, enable); +} + +static void dispc_mgr_enable_alpha_fixed_zorder(enum omap_channel ch, +		bool enable) +{ +	if (!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) +		return; + +	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); +} + +void dispc_mgr_setup(enum omap_channel channel, +		const struct omap_overlay_manager_info *info) +{ +	dispc_mgr_set_default_color(channel, info->default_color); +	dispc_mgr_set_trans_key(channel, info->trans_key_type, info->trans_key); +	dispc_mgr_enable_trans_key(channel, info->trans_enabled); +	dispc_mgr_enable_alpha_fixed_zorder(channel, +			info->partial_alpha_enabled); +	if (dss_has_feature(FEAT_CPR)) { +		dispc_mgr_enable_cpr(channel, info->cpr_enable); +		dispc_mgr_set_cpr_coef(channel, &info->cpr_coefs); +	} +} +EXPORT_SYMBOL(dispc_mgr_setup); + +static void dispc_mgr_set_tft_data_lines(enum omap_channel channel, u8 data_lines) +{ +	int code; + +	switch (data_lines) { +	case 12: +		code = 0; +		break; +	case 16: +		code = 1; +		break; +	case 18: +		code = 2; +		break; +	case 24: +		code = 3; +		break; +	default: +		BUG(); +		return; +	} + +	mgr_fld_write(channel, DISPC_MGR_FLD_TFTDATALINES, code); +} + +static void dispc_mgr_set_io_pad_mode(enum dss_io_pad_mode mode) +{ +	u32 l; +	int gpout0, gpout1; + +	switch (mode) { +	case DSS_IO_PAD_MODE_RESET: +		gpout0 = 0; +		gpout1 = 0; +		break; +	case DSS_IO_PAD_MODE_RFBI: +		gpout0 = 1; +		gpout1 = 0; +		break; +	case DSS_IO_PAD_MODE_BYPASS: +		gpout0 = 1; +		gpout1 = 1; +		break; +	default: +		BUG(); +		return; +	} + +	l = dispc_read_reg(DISPC_CONTROL); +	l = FLD_MOD(l, gpout0, 15, 15); +	l = FLD_MOD(l, gpout1, 16, 16); +	dispc_write_reg(DISPC_CONTROL, l); +} + +static void dispc_mgr_enable_stallmode(enum omap_channel channel, bool enable) +{ +	mgr_fld_write(channel, DISPC_MGR_FLD_STALLMODE, enable); +} + +void dispc_mgr_set_lcd_config(enum omap_channel channel, +		const struct dss_lcd_mgr_config *config) +{ +	dispc_mgr_set_io_pad_mode(config->io_pad_mode); + +	dispc_mgr_enable_stallmode(channel, config->stallmode); +	dispc_mgr_enable_fifohandcheck(channel, config->fifohandcheck); + +	dispc_mgr_set_clock_div(channel, &config->clock_info); + +	dispc_mgr_set_tft_data_lines(channel, config->video_port_width); + +	dispc_lcd_enable_signal_polarity(config->lcden_sig_polarity); + +	dispc_mgr_set_lcd_type_tft(channel); +} +EXPORT_SYMBOL(dispc_mgr_set_lcd_config); + +static bool _dispc_mgr_size_ok(u16 width, u16 height) +{ +	return width <= dispc.feat->mgr_width_max && +		height <= dispc.feat->mgr_height_max; +} + +static bool _dispc_lcd_timings_ok(int hsw, int hfp, int hbp, +		int vsw, int vfp, int vbp) +{ +	if (hsw < 1 || hsw > dispc.feat->sw_max || +			hfp < 1 || hfp > dispc.feat->hp_max || +			hbp < 1 || hbp > dispc.feat->hp_max || +			vsw < 1 || vsw > dispc.feat->sw_max || +			vfp < 0 || vfp > dispc.feat->vp_max || +			vbp < 0 || vbp > dispc.feat->vp_max) +		return false; +	return true; +} + +static bool _dispc_mgr_pclk_ok(enum omap_channel channel, +		unsigned long pclk) +{ +	if (dss_mgr_is_lcd(channel)) +		return pclk <= dispc.feat->max_lcd_pclk ? true : false; +	else +		return pclk <= dispc.feat->max_tv_pclk ? true : false; +} + +bool dispc_mgr_timings_ok(enum omap_channel channel, +		const struct omap_video_timings *timings) +{ +	bool timings_ok; + +	timings_ok = _dispc_mgr_size_ok(timings->x_res, timings->y_res); + +	timings_ok &= _dispc_mgr_pclk_ok(channel, timings->pixelclock); + +	if (dss_mgr_is_lcd(channel)) { +		timings_ok &= _dispc_lcd_timings_ok(timings->hsw, timings->hfp, +				timings->hbp, timings->vsw, timings->vfp, +				timings->vbp); +	} + +	return timings_ok; +} + +static void _dispc_mgr_set_lcd_timings(enum omap_channel channel, int hsw, +		int hfp, int hbp, int vsw, int vfp, int vbp, +		enum omap_dss_signal_level vsync_level, +		enum omap_dss_signal_level hsync_level, +		enum omap_dss_signal_edge data_pclk_edge, +		enum omap_dss_signal_level de_level, +		enum omap_dss_signal_edge sync_pclk_edge) + +{ +	u32 timing_h, timing_v, l; +	bool onoff, rf, ipc; + +	timing_h = FLD_VAL(hsw-1, dispc.feat->sw_start, 0) | +			FLD_VAL(hfp-1, dispc.feat->fp_start, 8) | +			FLD_VAL(hbp-1, dispc.feat->bp_start, 20); +	timing_v = FLD_VAL(vsw-1, dispc.feat->sw_start, 0) | +			FLD_VAL(vfp, dispc.feat->fp_start, 8) | +			FLD_VAL(vbp, dispc.feat->bp_start, 20); + +	dispc_write_reg(DISPC_TIMING_H(channel), timing_h); +	dispc_write_reg(DISPC_TIMING_V(channel), timing_v); + +	switch (data_pclk_edge) { +	case OMAPDSS_DRIVE_SIG_RISING_EDGE: +		ipc = false; +		break; +	case OMAPDSS_DRIVE_SIG_FALLING_EDGE: +		ipc = true; +		break; +	case OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES: +	default: +		BUG(); +	} + +	switch (sync_pclk_edge) { +	case OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES: +		onoff = false; +		rf = false; +		break; +	case OMAPDSS_DRIVE_SIG_FALLING_EDGE: +		onoff = true; +		rf = false; +		break; +	case OMAPDSS_DRIVE_SIG_RISING_EDGE: +		onoff = true; +		rf = true; +		break; +	default: +		BUG(); +	} + +	l = FLD_VAL(onoff, 17, 17) | +		FLD_VAL(rf, 16, 16) | +		FLD_VAL(de_level, 15, 15) | +		FLD_VAL(ipc, 14, 14) | +		FLD_VAL(hsync_level, 13, 13) | +		FLD_VAL(vsync_level, 12, 12); + +	dispc_write_reg(DISPC_POL_FREQ(channel), l); +} + +/* change name to mode? */ +void dispc_mgr_set_timings(enum omap_channel channel, +		const struct omap_video_timings *timings) +{ +	unsigned xtot, ytot; +	unsigned long ht, vt; +	struct omap_video_timings t = *timings; + +	DSSDBG("channel %d xres %u yres %u\n", channel, t.x_res, t.y_res); + +	if (!dispc_mgr_timings_ok(channel, &t)) { +		BUG(); +		return; +	} + +	if (dss_mgr_is_lcd(channel)) { +		_dispc_mgr_set_lcd_timings(channel, t.hsw, t.hfp, t.hbp, t.vsw, +				t.vfp, t.vbp, t.vsync_level, t.hsync_level, +				t.data_pclk_edge, t.de_level, t.sync_pclk_edge); + +		xtot = t.x_res + t.hfp + t.hsw + t.hbp; +		ytot = t.y_res + t.vfp + t.vsw + t.vbp; + +		ht = timings->pixelclock / xtot; +		vt = timings->pixelclock / xtot / ytot; + +		DSSDBG("pck %u\n", timings->pixelclock); +		DSSDBG("hsw %d hfp %d hbp %d vsw %d vfp %d vbp %d\n", +			t.hsw, t.hfp, t.hbp, t.vsw, t.vfp, t.vbp); +		DSSDBG("vsync_level %d hsync_level %d data_pclk_edge %d de_level %d sync_pclk_edge %d\n", +			t.vsync_level, t.hsync_level, t.data_pclk_edge, +			t.de_level, t.sync_pclk_edge); + +		DSSDBG("hsync %luHz, vsync %luHz\n", ht, vt); +	} else { +		if (t.interlace == true) +			t.y_res /= 2; +	} + +	dispc_mgr_set_size(channel, t.x_res, t.y_res); +} +EXPORT_SYMBOL(dispc_mgr_set_timings); + +static void dispc_mgr_set_lcd_divisor(enum omap_channel channel, u16 lck_div, +		u16 pck_div) +{ +	BUG_ON(lck_div < 1); +	BUG_ON(pck_div < 1); + +	dispc_write_reg(DISPC_DIVISORo(channel), +			FLD_VAL(lck_div, 23, 16) | FLD_VAL(pck_div, 7, 0)); + +	if (dss_has_feature(FEAT_CORE_CLK_DIV) == false && +			channel == OMAP_DSS_CHANNEL_LCD) +		dispc.core_clk_rate = dispc_fclk_rate() / lck_div; +} + +static void dispc_mgr_get_lcd_divisor(enum omap_channel channel, int *lck_div, +		int *pck_div) +{ +	u32 l; +	l = dispc_read_reg(DISPC_DIVISORo(channel)); +	*lck_div = FLD_GET(l, 23, 16); +	*pck_div = FLD_GET(l, 7, 0); +} + +unsigned long dispc_fclk_rate(void) +{ +	struct platform_device *dsidev; +	unsigned long r = 0; + +	switch (dss_get_dispc_clk_source()) { +	case OMAP_DSS_CLK_SRC_FCK: +		r = dss_get_dispc_clk_rate(); +		break; +	case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: +		dsidev = dsi_get_dsidev_from_id(0); +		r = dsi_get_pll_hsdiv_dispc_rate(dsidev); +		break; +	case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: +		dsidev = dsi_get_dsidev_from_id(1); +		r = dsi_get_pll_hsdiv_dispc_rate(dsidev); +		break; +	default: +		BUG(); +		return 0; +	} + +	return r; +} + +unsigned long dispc_mgr_lclk_rate(enum omap_channel channel) +{ +	struct platform_device *dsidev; +	int lcd; +	unsigned long r; +	u32 l; + +	if (dss_mgr_is_lcd(channel)) { +		l = dispc_read_reg(DISPC_DIVISORo(channel)); + +		lcd = FLD_GET(l, 23, 16); + +		switch (dss_get_lcd_clk_source(channel)) { +		case OMAP_DSS_CLK_SRC_FCK: +			r = dss_get_dispc_clk_rate(); +			break; +		case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: +			dsidev = dsi_get_dsidev_from_id(0); +			r = dsi_get_pll_hsdiv_dispc_rate(dsidev); +			break; +		case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: +			dsidev = dsi_get_dsidev_from_id(1); +			r = dsi_get_pll_hsdiv_dispc_rate(dsidev); +			break; +		default: +			BUG(); +			return 0; +		} + +		return r / lcd; +	} else { +		return dispc_fclk_rate(); +	} +} + +unsigned long dispc_mgr_pclk_rate(enum omap_channel channel) +{ +	unsigned long r; + +	if (dss_mgr_is_lcd(channel)) { +		int pcd; +		u32 l; + +		l = dispc_read_reg(DISPC_DIVISORo(channel)); + +		pcd = FLD_GET(l, 7, 0); + +		r = dispc_mgr_lclk_rate(channel); + +		return r / pcd; +	} else { +		return dispc.tv_pclk_rate; +	} +} + +void dispc_set_tv_pclk(unsigned long pclk) +{ +	dispc.tv_pclk_rate = pclk; +} + +unsigned long dispc_core_clk_rate(void) +{ +	return dispc.core_clk_rate; +} + +static unsigned long dispc_plane_pclk_rate(enum omap_plane plane) +{ +	enum omap_channel channel; + +	if (plane == OMAP_DSS_WB) +		return 0; + +	channel = dispc_ovl_get_channel_out(plane); + +	return dispc_mgr_pclk_rate(channel); +} + +static unsigned long dispc_plane_lclk_rate(enum omap_plane plane) +{ +	enum omap_channel channel; + +	if (plane == OMAP_DSS_WB) +		return 0; + +	channel	= dispc_ovl_get_channel_out(plane); + +	return dispc_mgr_lclk_rate(channel); +} + +static void dispc_dump_clocks_channel(struct seq_file *s, enum omap_channel channel) +{ +	int lcd, pcd; +	enum omap_dss_clk_source lcd_clk_src; + +	seq_printf(s, "- %s -\n", mgr_desc[channel].name); + +	lcd_clk_src = dss_get_lcd_clk_source(channel); + +	seq_printf(s, "%s clk source = %s (%s)\n", mgr_desc[channel].name, +		dss_get_generic_clk_source_name(lcd_clk_src), +		dss_feat_get_clk_source_name(lcd_clk_src)); + +	dispc_mgr_get_lcd_divisor(channel, &lcd, &pcd); + +	seq_printf(s, "lck\t\t%-16lulck div\t%u\n", +		dispc_mgr_lclk_rate(channel), lcd); +	seq_printf(s, "pck\t\t%-16lupck div\t%u\n", +		dispc_mgr_pclk_rate(channel), pcd); +} + +void dispc_dump_clocks(struct seq_file *s) +{ +	int lcd; +	u32 l; +	enum omap_dss_clk_source dispc_clk_src = dss_get_dispc_clk_source(); + +	if (dispc_runtime_get()) +		return; + +	seq_printf(s, "- DISPC -\n"); + +	seq_printf(s, "dispc fclk source = %s (%s)\n", +			dss_get_generic_clk_source_name(dispc_clk_src), +			dss_feat_get_clk_source_name(dispc_clk_src)); + +	seq_printf(s, "fck\t\t%-16lu\n", dispc_fclk_rate()); + +	if (dss_has_feature(FEAT_CORE_CLK_DIV)) { +		seq_printf(s, "- DISPC-CORE-CLK -\n"); +		l = dispc_read_reg(DISPC_DIVISOR); +		lcd = FLD_GET(l, 23, 16); + +		seq_printf(s, "lck\t\t%-16lulck div\t%u\n", +				(dispc_fclk_rate()/lcd), lcd); +	} + +	dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD); + +	if (dss_has_feature(FEAT_MGR_LCD2)) +		dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD2); +	if (dss_has_feature(FEAT_MGR_LCD3)) +		dispc_dump_clocks_channel(s, OMAP_DSS_CHANNEL_LCD3); + +	dispc_runtime_put(); +} + +static void dispc_dump_regs(struct seq_file *s) +{ +	int i, j; +	const char *mgr_names[] = { +		[OMAP_DSS_CHANNEL_LCD]		= "LCD", +		[OMAP_DSS_CHANNEL_DIGIT]	= "TV", +		[OMAP_DSS_CHANNEL_LCD2]		= "LCD2", +		[OMAP_DSS_CHANNEL_LCD3]		= "LCD3", +	}; +	const char *ovl_names[] = { +		[OMAP_DSS_GFX]		= "GFX", +		[OMAP_DSS_VIDEO1]	= "VID1", +		[OMAP_DSS_VIDEO2]	= "VID2", +		[OMAP_DSS_VIDEO3]	= "VID3", +	}; +	const char **p_names; + +#define DUMPREG(r) seq_printf(s, "%-50s %08x\n", #r, dispc_read_reg(r)) + +	if (dispc_runtime_get()) +		return; + +	/* DISPC common registers */ +	DUMPREG(DISPC_REVISION); +	DUMPREG(DISPC_SYSCONFIG); +	DUMPREG(DISPC_SYSSTATUS); +	DUMPREG(DISPC_IRQSTATUS); +	DUMPREG(DISPC_IRQENABLE); +	DUMPREG(DISPC_CONTROL); +	DUMPREG(DISPC_CONFIG); +	DUMPREG(DISPC_CAPABLE); +	DUMPREG(DISPC_LINE_STATUS); +	DUMPREG(DISPC_LINE_NUMBER); +	if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER) || +			dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) +		DUMPREG(DISPC_GLOBAL_ALPHA); +	if (dss_has_feature(FEAT_MGR_LCD2)) { +		DUMPREG(DISPC_CONTROL2); +		DUMPREG(DISPC_CONFIG2); +	} +	if (dss_has_feature(FEAT_MGR_LCD3)) { +		DUMPREG(DISPC_CONTROL3); +		DUMPREG(DISPC_CONFIG3); +	} +	if (dss_has_feature(FEAT_MFLAG)) +		DUMPREG(DISPC_GLOBAL_MFLAG_ATTRIBUTE); + +#undef DUMPREG + +#define DISPC_REG(i, name) name(i) +#define DUMPREG(i, r) seq_printf(s, "%s(%s)%*s %08x\n", #r, p_names[i], \ +	(int)(48 - strlen(#r) - strlen(p_names[i])), " ", \ +	dispc_read_reg(DISPC_REG(i, r))) + +	p_names = mgr_names; + +	/* DISPC channel specific registers */ +	for (i = 0; i < dss_feat_get_num_mgrs(); i++) { +		DUMPREG(i, DISPC_DEFAULT_COLOR); +		DUMPREG(i, DISPC_TRANS_COLOR); +		DUMPREG(i, DISPC_SIZE_MGR); + +		if (i == OMAP_DSS_CHANNEL_DIGIT) +			continue; + +		DUMPREG(i, DISPC_DEFAULT_COLOR); +		DUMPREG(i, DISPC_TRANS_COLOR); +		DUMPREG(i, DISPC_TIMING_H); +		DUMPREG(i, DISPC_TIMING_V); +		DUMPREG(i, DISPC_POL_FREQ); +		DUMPREG(i, DISPC_DIVISORo); +		DUMPREG(i, DISPC_SIZE_MGR); + +		DUMPREG(i, DISPC_DATA_CYCLE1); +		DUMPREG(i, DISPC_DATA_CYCLE2); +		DUMPREG(i, DISPC_DATA_CYCLE3); + +		if (dss_has_feature(FEAT_CPR)) { +			DUMPREG(i, DISPC_CPR_COEF_R); +			DUMPREG(i, DISPC_CPR_COEF_G); +			DUMPREG(i, DISPC_CPR_COEF_B); +		} +	} + +	p_names = ovl_names; + +	for (i = 0; i < dss_feat_get_num_ovls(); i++) { +		DUMPREG(i, DISPC_OVL_BA0); +		DUMPREG(i, DISPC_OVL_BA1); +		DUMPREG(i, DISPC_OVL_POSITION); +		DUMPREG(i, DISPC_OVL_SIZE); +		DUMPREG(i, DISPC_OVL_ATTRIBUTES); +		DUMPREG(i, DISPC_OVL_FIFO_THRESHOLD); +		DUMPREG(i, DISPC_OVL_FIFO_SIZE_STATUS); +		DUMPREG(i, DISPC_OVL_ROW_INC); +		DUMPREG(i, DISPC_OVL_PIXEL_INC); +		if (dss_has_feature(FEAT_PRELOAD)) +			DUMPREG(i, DISPC_OVL_PRELOAD); + +		if (i == OMAP_DSS_GFX) { +			DUMPREG(i, DISPC_OVL_WINDOW_SKIP); +			DUMPREG(i, DISPC_OVL_TABLE_BA); +			continue; +		} + +		DUMPREG(i, DISPC_OVL_FIR); +		DUMPREG(i, DISPC_OVL_PICTURE_SIZE); +		DUMPREG(i, DISPC_OVL_ACCU0); +		DUMPREG(i, DISPC_OVL_ACCU1); +		if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { +			DUMPREG(i, DISPC_OVL_BA0_UV); +			DUMPREG(i, DISPC_OVL_BA1_UV); +			DUMPREG(i, DISPC_OVL_FIR2); +			DUMPREG(i, DISPC_OVL_ACCU2_0); +			DUMPREG(i, DISPC_OVL_ACCU2_1); +		} +		if (dss_has_feature(FEAT_ATTR2)) +			DUMPREG(i, DISPC_OVL_ATTRIBUTES2); +		if (dss_has_feature(FEAT_PRELOAD)) +			DUMPREG(i, DISPC_OVL_PRELOAD); +		if (dss_has_feature(FEAT_MFLAG)) +			DUMPREG(i, DISPC_OVL_MFLAG_THRESHOLD); +	} + +#undef DISPC_REG +#undef DUMPREG + +#define DISPC_REG(plane, name, i) name(plane, i) +#define DUMPREG(plane, name, i) \ +	seq_printf(s, "%s_%d(%s)%*s %08x\n", #name, i, p_names[plane], \ +	(int)(46 - strlen(#name) - strlen(p_names[plane])), " ", \ +	dispc_read_reg(DISPC_REG(plane, name, i))) + +	/* Video pipeline coefficient registers */ + +	/* start from OMAP_DSS_VIDEO1 */ +	for (i = 1; i < dss_feat_get_num_ovls(); i++) { +		for (j = 0; j < 8; j++) +			DUMPREG(i, DISPC_OVL_FIR_COEF_H, j); + +		for (j = 0; j < 8; j++) +			DUMPREG(i, DISPC_OVL_FIR_COEF_HV, j); + +		for (j = 0; j < 5; j++) +			DUMPREG(i, DISPC_OVL_CONV_COEF, j); + +		if (dss_has_feature(FEAT_FIR_COEF_V)) { +			for (j = 0; j < 8; j++) +				DUMPREG(i, DISPC_OVL_FIR_COEF_V, j); +		} + +		if (dss_has_feature(FEAT_HANDLE_UV_SEPARATE)) { +			for (j = 0; j < 8; j++) +				DUMPREG(i, DISPC_OVL_FIR_COEF_H2, j); + +			for (j = 0; j < 8; j++) +				DUMPREG(i, DISPC_OVL_FIR_COEF_HV2, j); + +			for (j = 0; j < 8; j++) +				DUMPREG(i, DISPC_OVL_FIR_COEF_V2, j); +		} +	} + +	dispc_runtime_put(); + +#undef DISPC_REG +#undef DUMPREG +} + +/* calculate clock rates using dividers in cinfo */ +int dispc_calc_clock_rates(unsigned long dispc_fclk_rate, +		struct dispc_clock_info *cinfo) +{ +	if (cinfo->lck_div > 255 || cinfo->lck_div == 0) +		return -EINVAL; +	if (cinfo->pck_div < 1 || cinfo->pck_div > 255) +		return -EINVAL; + +	cinfo->lck = dispc_fclk_rate / cinfo->lck_div; +	cinfo->pck = cinfo->lck / cinfo->pck_div; + +	return 0; +} + +bool dispc_div_calc(unsigned long dispc, +		unsigned long pck_min, unsigned long pck_max, +		dispc_div_calc_func func, void *data) +{ +	int lckd, lckd_start, lckd_stop; +	int pckd, pckd_start, pckd_stop; +	unsigned long pck, lck; +	unsigned long lck_max; +	unsigned long pckd_hw_min, pckd_hw_max; +	unsigned min_fck_per_pck; +	unsigned long fck; + +#ifdef CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK +	min_fck_per_pck = CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK; +#else +	min_fck_per_pck = 0; +#endif + +	pckd_hw_min = dss_feat_get_param_min(FEAT_PARAM_DSS_PCD); +	pckd_hw_max = dss_feat_get_param_max(FEAT_PARAM_DSS_PCD); + +	lck_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + +	pck_min = pck_min ? pck_min : 1; +	pck_max = pck_max ? pck_max : ULONG_MAX; + +	lckd_start = max(DIV_ROUND_UP(dispc, lck_max), 1ul); +	lckd_stop = min(dispc / pck_min, 255ul); + +	for (lckd = lckd_start; lckd <= lckd_stop; ++lckd) { +		lck = dispc / lckd; + +		pckd_start = max(DIV_ROUND_UP(lck, pck_max), pckd_hw_min); +		pckd_stop = min(lck / pck_min, pckd_hw_max); + +		for (pckd = pckd_start; pckd <= pckd_stop; ++pckd) { +			pck = lck / pckd; + +			/* +			 * For OMAP2/3 the DISPC fclk is the same as LCD's logic +			 * clock, which means we're configuring DISPC fclk here +			 * also. Thus we need to use the calculated lck. For +			 * OMAP4+ the DISPC fclk is a separate clock. +			 */ +			if (dss_has_feature(FEAT_CORE_CLK_DIV)) +				fck = dispc_core_clk_rate(); +			else +				fck = lck; + +			if (fck < pck * min_fck_per_pck) +				continue; + +			if (func(lckd, pckd, lck, pck, data)) +				return true; +		} +	} + +	return false; +} + +void dispc_mgr_set_clock_div(enum omap_channel channel, +		const struct dispc_clock_info *cinfo) +{ +	DSSDBG("lck = %lu (%u)\n", cinfo->lck, cinfo->lck_div); +	DSSDBG("pck = %lu (%u)\n", cinfo->pck, cinfo->pck_div); + +	dispc_mgr_set_lcd_divisor(channel, cinfo->lck_div, cinfo->pck_div); +} + +int dispc_mgr_get_clock_div(enum omap_channel channel, +		struct dispc_clock_info *cinfo) +{ +	unsigned long fck; + +	fck = dispc_fclk_rate(); + +	cinfo->lck_div = REG_GET(DISPC_DIVISORo(channel), 23, 16); +	cinfo->pck_div = REG_GET(DISPC_DIVISORo(channel), 7, 0); + +	cinfo->lck = fck / cinfo->lck_div; +	cinfo->pck = cinfo->lck / cinfo->pck_div; + +	return 0; +} + +u32 dispc_read_irqstatus(void) +{ +	return dispc_read_reg(DISPC_IRQSTATUS); +} +EXPORT_SYMBOL(dispc_read_irqstatus); + +void dispc_clear_irqstatus(u32 mask) +{ +	dispc_write_reg(DISPC_IRQSTATUS, mask); +} +EXPORT_SYMBOL(dispc_clear_irqstatus); + +u32 dispc_read_irqenable(void) +{ +	return dispc_read_reg(DISPC_IRQENABLE); +} +EXPORT_SYMBOL(dispc_read_irqenable); + +void dispc_write_irqenable(u32 mask) +{ +	u32 old_mask = dispc_read_reg(DISPC_IRQENABLE); + +	/* clear the irqstatus for newly enabled irqs */ +	dispc_clear_irqstatus((mask ^ old_mask) & mask); + +	dispc_write_reg(DISPC_IRQENABLE, mask); +} +EXPORT_SYMBOL(dispc_write_irqenable); + +void dispc_enable_sidle(void) +{ +	REG_FLD_MOD(DISPC_SYSCONFIG, 2, 4, 3);	/* SIDLEMODE: smart idle */ +} + +void dispc_disable_sidle(void) +{ +	REG_FLD_MOD(DISPC_SYSCONFIG, 1, 4, 3);	/* SIDLEMODE: no idle */ +} + +static void _omap_dispc_initial_config(void) +{ +	u32 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); +		/* Use DISPC_DIVISOR.LCD, instead of DISPC_DIVISOR1.LCD */ +		l = FLD_MOD(l, 1, 0, 0); +		l = FLD_MOD(l, 1, 23, 16); +		dispc_write_reg(DISPC_DIVISOR, l); + +		dispc.core_clk_rate = dispc_fclk_rate(); +	} + +	/* FUNCGATED */ +	if (dss_has_feature(FEAT_FUNCGATED)) +		REG_FLD_MOD(DISPC_CONFIG, 1, 9, 9); + +	dispc_setup_color_conv_coef(); + +	dispc_set_loadmode(OMAP_DSS_LOAD_FRAME_ONLY); + +	dispc_init_fifos(); + +	dispc_configure_burst_sizes(); + +	dispc_ovl_enable_zorder_planes(); + +	if (dispc.feat->mstandby_workaround) +		REG_FLD_MOD(DISPC_MSTANDBY_CTRL, 1, 0, 0); +} + +static const struct dispc_features omap24xx_dispc_feats __initconst = { +	.sw_start		=	5, +	.fp_start		=	15, +	.bp_start		=	27, +	.sw_max			=	64, +	.vp_max			=	255, +	.hp_max			=	256, +	.mgr_width_start	=	10, +	.mgr_height_start	=	26, +	.mgr_width_max		=	2048, +	.mgr_height_max		=	2048, +	.max_lcd_pclk		=	66500000, +	.calc_scaling		=	dispc_ovl_calc_scaling_24xx, +	.calc_core_clk		=	calc_core_clk_24xx, +	.num_fifos		=	3, +	.no_framedone_tv	=	true, +	.set_max_preload	=	false, +}; + +static const struct dispc_features omap34xx_rev1_0_dispc_feats __initconst = { +	.sw_start		=	5, +	.fp_start		=	15, +	.bp_start		=	27, +	.sw_max			=	64, +	.vp_max			=	255, +	.hp_max			=	256, +	.mgr_width_start	=	10, +	.mgr_height_start	=	26, +	.mgr_width_max		=	2048, +	.mgr_height_max		=	2048, +	.max_lcd_pclk		=	173000000, +	.max_tv_pclk		=	59000000, +	.calc_scaling		=	dispc_ovl_calc_scaling_34xx, +	.calc_core_clk		=	calc_core_clk_34xx, +	.num_fifos		=	3, +	.no_framedone_tv	=	true, +	.set_max_preload	=	false, +}; + +static const struct dispc_features omap34xx_rev3_0_dispc_feats __initconst = { +	.sw_start		=	7, +	.fp_start		=	19, +	.bp_start		=	31, +	.sw_max			=	256, +	.vp_max			=	4095, +	.hp_max			=	4096, +	.mgr_width_start	=	10, +	.mgr_height_start	=	26, +	.mgr_width_max		=	2048, +	.mgr_height_max		=	2048, +	.max_lcd_pclk		=	173000000, +	.max_tv_pclk		=	59000000, +	.calc_scaling		=	dispc_ovl_calc_scaling_34xx, +	.calc_core_clk		=	calc_core_clk_34xx, +	.num_fifos		=	3, +	.no_framedone_tv	=	true, +	.set_max_preload	=	false, +}; + +static const struct dispc_features omap44xx_dispc_feats __initconst = { +	.sw_start		=	7, +	.fp_start		=	19, +	.bp_start		=	31, +	.sw_max			=	256, +	.vp_max			=	4095, +	.hp_max			=	4096, +	.mgr_width_start	=	10, +	.mgr_height_start	=	26, +	.mgr_width_max		=	2048, +	.mgr_height_max		=	2048, +	.max_lcd_pclk		=	170000000, +	.max_tv_pclk		=	185625000, +	.calc_scaling		=	dispc_ovl_calc_scaling_44xx, +	.calc_core_clk		=	calc_core_clk_44xx, +	.num_fifos		=	5, +	.gfx_fifo_workaround	=	true, +	.set_max_preload	=	true, +}; + +static const struct dispc_features omap54xx_dispc_feats __initconst = { +	.sw_start		=	7, +	.fp_start		=	19, +	.bp_start		=	31, +	.sw_max			=	256, +	.vp_max			=	4095, +	.hp_max			=	4096, +	.mgr_width_start	=	11, +	.mgr_height_start	=	27, +	.mgr_width_max		=	4096, +	.mgr_height_max		=	4096, +	.max_lcd_pclk		=	170000000, +	.max_tv_pclk		=	186000000, +	.calc_scaling		=	dispc_ovl_calc_scaling_44xx, +	.calc_core_clk		=	calc_core_clk_44xx, +	.num_fifos		=	5, +	.gfx_fifo_workaround	=	true, +	.mstandby_workaround	=	true, +	.set_max_preload	=	true, +}; + +static int __init dispc_init_features(struct platform_device *pdev) +{ +	const struct dispc_features *src; +	struct dispc_features *dst; + +	dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); +	if (!dst) { +		dev_err(&pdev->dev, "Failed to allocate DISPC Features\n"); +		return -ENOMEM; +	} + +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP24xx: +		src = &omap24xx_dispc_feats; +		break; + +	case OMAPDSS_VER_OMAP34xx_ES1: +		src = &omap34xx_rev1_0_dispc_feats; +		break; + +	case OMAPDSS_VER_OMAP34xx_ES3: +	case OMAPDSS_VER_OMAP3630: +	case OMAPDSS_VER_AM35xx: +	case OMAPDSS_VER_AM43xx: +		src = &omap34xx_rev3_0_dispc_feats; +		break; + +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		src = &omap44xx_dispc_feats; +		break; + +	case OMAPDSS_VER_OMAP5: +		src = &omap54xx_dispc_feats; +		break; + +	default: +		return -ENODEV; +	} + +	memcpy(dst, src, sizeof(*dst)); +	dispc.feat = dst; + +	return 0; +} + +static irqreturn_t dispc_irq_handler(int irq, void *arg) +{ +	if (!dispc.is_enabled) +		return IRQ_NONE; + +	return dispc.user_handler(irq, dispc.user_data); +} + +int dispc_request_irq(irq_handler_t handler, void *dev_id) +{ +	int r; + +	if (dispc.user_handler != NULL) +		return -EBUSY; + +	dispc.user_handler = handler; +	dispc.user_data = dev_id; + +	/* ensure the dispc_irq_handler sees the values above */ +	smp_wmb(); + +	r = devm_request_irq(&dispc.pdev->dev, dispc.irq, dispc_irq_handler, +			     IRQF_SHARED, "OMAP DISPC", &dispc); +	if (r) { +		dispc.user_handler = NULL; +		dispc.user_data = NULL; +	} + +	return r; +} +EXPORT_SYMBOL(dispc_request_irq); + +void dispc_free_irq(void *dev_id) +{ +	devm_free_irq(&dispc.pdev->dev, dispc.irq, &dispc); + +	dispc.user_handler = NULL; +	dispc.user_data = NULL; +} +EXPORT_SYMBOL(dispc_free_irq); + +/* DISPC HW IP initialisation */ +static int __init omap_dispchw_probe(struct platform_device *pdev) +{ +	u32 rev; +	int r = 0; +	struct resource *dispc_mem; + +	dispc.pdev = pdev; + +	r = dispc_init_features(dispc.pdev); +	if (r) +		return r; + +	dispc_mem = platform_get_resource(dispc.pdev, IORESOURCE_MEM, 0); +	if (!dispc_mem) { +		DSSERR("can't get IORESOURCE_MEM DISPC\n"); +		return -EINVAL; +	} + +	dispc.base = devm_ioremap(&pdev->dev, dispc_mem->start, +				  resource_size(dispc_mem)); +	if (!dispc.base) { +		DSSERR("can't ioremap DISPC\n"); +		return -ENOMEM; +	} + +	dispc.irq = platform_get_irq(dispc.pdev, 0); +	if (dispc.irq < 0) { +		DSSERR("platform_get_irq failed\n"); +		return -ENODEV; +	} + +	pm_runtime_enable(&pdev->dev); + +	r = dispc_runtime_get(); +	if (r) +		goto err_runtime_get; + +	_omap_dispc_initial_config(); + +	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)); + +	dispc_runtime_put(); + +	dss_init_overlay_managers(); + +	dss_debugfs_create_file("dispc", dispc_dump_regs); + +	return 0; + +err_runtime_get: +	pm_runtime_disable(&pdev->dev); +	return r; +} + +static int __exit omap_dispchw_remove(struct platform_device *pdev) +{ +	pm_runtime_disable(&pdev->dev); + +	dss_uninit_overlay_managers(); + +	return 0; +} + +static int dispc_runtime_suspend(struct device *dev) +{ +	dispc.is_enabled = false; +	/* ensure the dispc_irq_handler sees the is_enabled value */ +	smp_wmb(); +	/* wait for current handler to finish before turning the DISPC off */ +	synchronize_irq(dispc.irq); + +	dispc_save_context(); + +	return 0; +} + +static int dispc_runtime_resume(struct device *dev) +{ +	/* +	 * The reset value for load mode is 0 (OMAP_DSS_LOAD_CLUT_AND_FRAME) +	 * but we always initialize it to 2 (OMAP_DSS_LOAD_FRAME_ONLY) in +	 * _omap_dispc_initial_config(). We can thus use it to detect if +	 * we have lost register context. +	 */ +	if (REG_GET(DISPC_CONFIG, 2, 1) != OMAP_DSS_LOAD_FRAME_ONLY) { +		_omap_dispc_initial_config(); + +		dispc_restore_context(); +	} + +	dispc.is_enabled = true; +	/* ensure the dispc_irq_handler sees the is_enabled value */ +	smp_wmb(); + +	return 0; +} + +static const struct dev_pm_ops dispc_pm_ops = { +	.runtime_suspend = dispc_runtime_suspend, +	.runtime_resume = dispc_runtime_resume, +}; + +static const struct of_device_id dispc_of_match[] = { +	{ .compatible = "ti,omap2-dispc", }, +	{ .compatible = "ti,omap3-dispc", }, +	{ .compatible = "ti,omap4-dispc", }, +	{ .compatible = "ti,omap5-dispc", }, +	{}, +}; + +static struct platform_driver omap_dispchw_driver = { +	.remove         = __exit_p(omap_dispchw_remove), +	.driver         = { +		.name   = "omapdss_dispc", +		.owner  = THIS_MODULE, +		.pm	= &dispc_pm_ops, +		.of_match_table = dispc_of_match, +	}, +}; + +int __init dispc_init_platform_driver(void) +{ +	return platform_driver_probe(&omap_dispchw_driver, omap_dispchw_probe); +} + +void __exit dispc_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_dispchw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dispc.h b/drivers/video/fbdev/omap2/dss/dispc.h new file mode 100644 index 00000000000..78edb449c76 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc.h @@ -0,0 +1,917 @@ +/* + * linux/drivers/video/omap2/dss/dispc.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Archit Taneja <archit@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 __OMAP2_DISPC_REG_H +#define __OMAP2_DISPC_REG_H + +/* DISPC common registers */ +#define DISPC_REVISION			0x0000 +#define DISPC_SYSCONFIG			0x0010 +#define DISPC_SYSSTATUS			0x0014 +#define DISPC_IRQSTATUS			0x0018 +#define DISPC_IRQENABLE			0x001C +#define DISPC_CONTROL			0x0040 +#define DISPC_CONFIG			0x0044 +#define DISPC_CAPABLE			0x0048 +#define DISPC_LINE_STATUS		0x005C +#define DISPC_LINE_NUMBER		0x0060 +#define DISPC_GLOBAL_ALPHA		0x0074 +#define DISPC_CONTROL2			0x0238 +#define DISPC_CONFIG2			0x0620 +#define DISPC_DIVISOR			0x0804 +#define DISPC_GLOBAL_BUFFER		0x0800 +#define DISPC_CONTROL3                  0x0848 +#define DISPC_CONFIG3                   0x084C +#define DISPC_MSTANDBY_CTRL		0x0858 +#define DISPC_GLOBAL_MFLAG_ATTRIBUTE	0x085C + +/* DISPC overlay registers */ +#define DISPC_OVL_BA0(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_BA0_OFFSET(n)) +#define DISPC_OVL_BA1(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_BA1_OFFSET(n)) +#define DISPC_OVL_BA0_UV(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_BA0_UV_OFFSET(n)) +#define DISPC_OVL_BA1_UV(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_BA1_UV_OFFSET(n)) +#define DISPC_OVL_POSITION(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_POS_OFFSET(n)) +#define DISPC_OVL_SIZE(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_SIZE_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ATTR_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES2(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_ATTR2_OFFSET(n)) +#define DISPC_OVL_FIFO_THRESHOLD(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIFO_THRESH_OFFSET(n)) +#define DISPC_OVL_FIFO_SIZE_STATUS(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIFO_SIZE_STATUS_OFFSET(n)) +#define DISPC_OVL_ROW_INC(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ROW_INC_OFFSET(n)) +#define DISPC_OVL_PIXEL_INC(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_PIX_INC_OFFSET(n)) +#define DISPC_OVL_WINDOW_SKIP(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_WINDOW_SKIP_OFFSET(n)) +#define DISPC_OVL_TABLE_BA(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_TABLE_BA_OFFSET(n)) +#define DISPC_OVL_FIR(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_OFFSET(n)) +#define DISPC_OVL_FIR2(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_FIR2_OFFSET(n)) +#define DISPC_OVL_PICTURE_SIZE(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_PIC_SIZE_OFFSET(n)) +#define DISPC_OVL_ACCU0(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ACCU0_OFFSET(n)) +#define DISPC_OVL_ACCU1(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ACCU1_OFFSET(n)) +#define DISPC_OVL_ACCU2_0(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ACCU2_0_OFFSET(n)) +#define DISPC_OVL_ACCU2_1(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_ACCU2_1_OFFSET(n)) +#define DISPC_OVL_FIR_COEF_H(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_H_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_HV_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_H2(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_H2_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV2(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_HV2_OFFSET(n, i)) +#define DISPC_OVL_CONV_COEF(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_CONV_COEF_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_V_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V2(n, i)	(DISPC_OVL_BASE(n) + \ +					DISPC_FIR_COEF_V2_OFFSET(n, i)) +#define DISPC_OVL_PRELOAD(n)		(DISPC_OVL_BASE(n) + \ +					DISPC_PRELOAD_OFFSET(n)) +#define DISPC_OVL_MFLAG_THRESHOLD(n)	(DISPC_OVL_BASE(n) + \ +					DISPC_MFLAG_THRESHOLD_OFFSET(n)) + +/* DISPC up/downsampling FIR filter coefficient structure */ +struct dispc_coef { +	s8 hc4_vc22; +	s8 hc3_vc2; +	u8 hc2_vc1; +	s8 hc1_vc0; +	s8 hc0_vc00; +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps); + +/* DISPC manager/channel specific registers */ +static inline u16 DISPC_DEFAULT_COLOR(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x004C; +	case OMAP_DSS_CHANNEL_DIGIT: +		return 0x0050; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03AC; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0814; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_TRANS_COLOR(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0054; +	case OMAP_DSS_CHANNEL_DIGIT: +		return 0x0058; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03B0; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0818; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_TIMING_H(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0064; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x0400; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0840; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_TIMING_V(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0068; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x0404; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0844; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_POL_FREQ(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x006C; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x0408; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x083C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_DIVISORo(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0070; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x040C; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0838; +	default: +		BUG(); +		return 0; +	} +} + +/* Named as DISPC_SIZE_LCD, DISPC_SIZE_DIGIT and DISPC_SIZE_LCD2 in TRM */ +static inline u16 DISPC_SIZE_MGR(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x007C; +	case OMAP_DSS_CHANNEL_DIGIT: +		return 0x0078; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03CC; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0834; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_DATA_CYCLE1(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x01D4; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03C0; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0828; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_DATA_CYCLE2(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x01D8; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03C4; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x082C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_DATA_CYCLE3(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x01DC; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03C8; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0830; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_CPR_COEF_R(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0220; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03BC; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0824; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_CPR_COEF_G(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0224; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03B8; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x0820; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_CPR_COEF_B(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return 0x0228; +	case OMAP_DSS_CHANNEL_DIGIT: +		BUG(); +		return 0; +	case OMAP_DSS_CHANNEL_LCD2: +		return 0x03B4; +	case OMAP_DSS_CHANNEL_LCD3: +		return 0x081C; +	default: +		BUG(); +		return 0; +	} +} + +/* DISPC overlay register base addresses */ +static inline u16 DISPC_OVL_BASE(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0080; +	case OMAP_DSS_VIDEO1: +		return 0x00BC; +	case OMAP_DSS_VIDEO2: +		return 0x014C; +	case OMAP_DSS_VIDEO3: +		return 0x0300; +	case OMAP_DSS_WB: +		return 0x0500; +	default: +		BUG(); +		return 0; +	} +} + +/* DISPC overlay register offsets */ +static inline u16 DISPC_BA0_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0000; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0008; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_BA1_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0004; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x000C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_BA0_UV_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0544; +	case OMAP_DSS_VIDEO2: +		return 0x04BC; +	case OMAP_DSS_VIDEO3: +		return 0x0310; +	case OMAP_DSS_WB: +		return 0x0118; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_BA1_UV_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0548; +	case OMAP_DSS_VIDEO2: +		return 0x04C0; +	case OMAP_DSS_VIDEO3: +		return 0x0314; +	case OMAP_DSS_WB: +		return 0x011C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_POS_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0008; +	case OMAP_DSS_VIDEO3: +		return 0x009C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_SIZE_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x000C; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x00A8; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ATTR_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0020; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0010; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0070; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ATTR2_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0568; +	case OMAP_DSS_VIDEO2: +		return 0x04DC; +	case OMAP_DSS_VIDEO3: +		return 0x032C; +	case OMAP_DSS_WB: +		return 0x0310; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_FIFO_THRESH_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0024; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0014; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x008C; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_FIFO_SIZE_STATUS_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0028; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0018; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0088; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ROW_INC_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x002C; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x001C; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x00A4; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_PIX_INC_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0030; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0020; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0098; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_WINDOW_SKIP_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0034; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +	case OMAP_DSS_VIDEO3: +		BUG(); +		return 0; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_TABLE_BA_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0038; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +	case OMAP_DSS_VIDEO3: +		BUG(); +		return 0; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_FIR_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0024; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0090; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_FIR2_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0580; +	case OMAP_DSS_VIDEO2: +		return 0x055C; +	case OMAP_DSS_VIDEO3: +		return 0x0424; +	case OMAP_DSS_WB: +		return 0x290; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_PIC_SIZE_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0028; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0094; +	default: +		BUG(); +		return 0; +	} +} + + +static inline u16 DISPC_ACCU0_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x002C; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0000; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ACCU2_0_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0584; +	case OMAP_DSS_VIDEO2: +		return 0x0560; +	case OMAP_DSS_VIDEO3: +		return 0x0428; +	case OMAP_DSS_WB: +		return 0x0294; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ACCU1_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0030; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0004; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_ACCU2_1_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0588; +	case OMAP_DSS_VIDEO2: +		return 0x0564; +	case OMAP_DSS_VIDEO3: +		return 0x042C; +	case OMAP_DSS_WB: +		return 0x0298; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0034 + i * 0x8; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0010 + i * 0x8; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H2_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x058C + i * 0x8; +	case OMAP_DSS_VIDEO2: +		return 0x0568 + i * 0x8; +	case OMAP_DSS_VIDEO3: +		return 0x0430 + i * 0x8; +	case OMAP_DSS_WB: +		return 0x02A0 + i * 0x8; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +		return 0x0038 + i * 0x8; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0014 + i * 0x8; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV2_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0590 + i * 8; +	case OMAP_DSS_VIDEO2: +		return 0x056C + i * 0x8; +	case OMAP_DSS_VIDEO3: +		return 0x0434 + i * 0x8; +	case OMAP_DSS_WB: +		return 0x02A4 + i * 0x8; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4,} */ +static inline u16 DISPC_CONV_COEF_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +	case OMAP_DSS_VIDEO2: +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0074 + i * 0x4; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x0124 + i * 0x4; +	case OMAP_DSS_VIDEO2: +		return 0x00B4 + i * 0x4; +	case OMAP_DSS_VIDEO3: +	case OMAP_DSS_WB: +		return 0x0050 + i * 0x4; +	default: +		BUG(); +		return 0; +	} +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V2_OFFSET(enum omap_plane plane, u16 i) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		BUG(); +		return 0; +	case OMAP_DSS_VIDEO1: +		return 0x05CC + i * 0x4; +	case OMAP_DSS_VIDEO2: +		return 0x05A8 + i * 0x4; +	case OMAP_DSS_VIDEO3: +		return 0x0470 + i * 0x4; +	case OMAP_DSS_WB: +		return 0x02E0 + i * 0x4; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_PRELOAD_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x01AC; +	case OMAP_DSS_VIDEO1: +		return 0x0174; +	case OMAP_DSS_VIDEO2: +		return 0x00E8; +	case OMAP_DSS_VIDEO3: +		return 0x00A0; +	default: +		BUG(); +		return 0; +	} +} + +static inline u16 DISPC_MFLAG_THRESHOLD_OFFSET(enum omap_plane plane) +{ +	switch (plane) { +	case OMAP_DSS_GFX: +		return 0x0860; +	case OMAP_DSS_VIDEO1: +		return 0x0864; +	case OMAP_DSS_VIDEO2: +		return 0x0868; +	case OMAP_DSS_VIDEO3: +		return 0x086c; +	default: +		BUG(); +		return 0; +	} +} +#endif diff --git a/drivers/video/fbdev/omap2/dss/dispc_coefs.c b/drivers/video/fbdev/omap2/dss/dispc_coefs.c new file mode 100644 index 00000000000..038c15b0421 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dispc_coefs.c @@ -0,0 +1,325 @@ +/* + * linux/drivers/video/omap2/dss/dispc_coefs.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Chandrabhanu Mahapatra <cmahapatra@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 <video/omapdss.h> + +#include "dispc.h" + +static const struct dispc_coef coef3_M8[8] = { +	{ 0,  0, 128,  0, 0 }, +	{ 0, -4, 123,  9, 0 }, +	{ 0, -4, 108, 24, 0 }, +	{ 0, -2,  87, 43, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 43,  87, -2, 0 }, +	{ 0, 24, 108, -4, 0 }, +	{ 0,  9, 123, -4, 0 }, +}; + +static const struct dispc_coef coef3_M9[8] = { +	{ 0,  6, 116,  6, 0 }, +	{ 0,  0, 112, 16, 0 }, +	{ 0, -2, 100, 30, 0 }, +	{ 0, -2,  83, 47, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 47,  83, -2, 0 }, +	{ 0, 30, 100, -2, 0 }, +	{ 0, 16, 112,  0, 0 }, +}; + +static const struct dispc_coef coef3_M10[8] = { +	{ 0, 10, 108, 10, 0 }, +	{ 0,  3, 104, 21, 0 }, +	{ 0,  0,  94, 34, 0 }, +	{ 0, -1,  80, 49, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 49,  80, -1, 0 }, +	{ 0, 34,  94,  0, 0 }, +	{ 0, 21, 104,  3, 0 }, +}; + +static const struct dispc_coef coef3_M11[8] = { +	{ 0, 14, 100, 14, 0 }, +	{ 0,  6,  98, 24, 0 }, +	{ 0,  2,  90, 36, 0 }, +	{ 0,  0,  78, 50, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 50,  78,  0, 0 }, +	{ 0, 36,  90,  2, 0 }, +	{ 0, 24,  98,  6, 0 }, +}; + +static const struct dispc_coef coef3_M12[8] = { +	{ 0, 16,  96, 16, 0 }, +	{ 0,  9,  93, 26, 0 }, +	{ 0,  4,  86, 38, 0 }, +	{ 0,  1,  76, 51, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 51,  76,  1, 0 }, +	{ 0, 38,  86,  4, 0 }, +	{ 0, 26,  93,  9, 0 }, +}; + +static const struct dispc_coef coef3_M13[8] = { +	{ 0, 18,  92, 18, 0 }, +	{ 0, 10,  90, 28, 0 }, +	{ 0,  5,  83, 40, 0 }, +	{ 0,  1,  75, 52, 0 }, +	{ 0, 64,  64,  0, 0 }, +	{ 0, 52,  75,  1, 0 }, +	{ 0, 40,  83,  5, 0 }, +	{ 0, 28,  90, 10, 0 }, +}; + +static const struct dispc_coef coef3_M14[8] = { +	{ 0, 20, 88, 20, 0 }, +	{ 0, 12, 86, 30, 0 }, +	{ 0,  6, 81, 41, 0 }, +	{ 0,  2, 74, 52, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 52, 74,  2, 0 }, +	{ 0, 41, 81,  6, 0 }, +	{ 0, 30, 86, 12, 0 }, +}; + +static const struct dispc_coef coef3_M16[8] = { +	{ 0, 22, 84, 22, 0 }, +	{ 0, 14, 82, 32, 0 }, +	{ 0,  8, 78, 42, 0 }, +	{ 0,  3, 72, 53, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 53, 72,  3, 0 }, +	{ 0, 42, 78,  8, 0 }, +	{ 0, 32, 82, 14, 0 }, +}; + +static const struct dispc_coef coef3_M19[8] = { +	{ 0, 24, 80, 24, 0 }, +	{ 0, 16, 79, 33, 0 }, +	{ 0,  9, 76, 43, 0 }, +	{ 0,  4, 70, 54, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 54, 70,  4, 0 }, +	{ 0, 43, 76,  9, 0 }, +	{ 0, 33, 79, 16, 0 }, +}; + +static const struct dispc_coef coef3_M22[8] = { +	{ 0, 25, 78, 25, 0 }, +	{ 0, 17, 77, 34, 0 }, +	{ 0, 10, 74, 44, 0 }, +	{ 0,  5, 69, 54, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 54, 69,  5, 0 }, +	{ 0, 44, 74, 10, 0 }, +	{ 0, 34, 77, 17, 0 }, +}; + +static const struct dispc_coef coef3_M26[8] = { +	{ 0, 26, 76, 26, 0 }, +	{ 0, 19, 74, 35, 0 }, +	{ 0, 11, 72, 45, 0 }, +	{ 0,  5, 69, 54, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 54, 69,  5, 0 }, +	{ 0, 45, 72, 11, 0 }, +	{ 0, 35, 74, 19, 0 }, +}; + +static const struct dispc_coef coef3_M32[8] = { +	{ 0, 27, 74, 27, 0 }, +	{ 0, 19, 73, 36, 0 }, +	{ 0, 12, 71, 45, 0 }, +	{ 0,  6, 68, 54, 0 }, +	{ 0, 64, 64,  0, 0 }, +	{ 0, 54, 68,  6, 0 }, +	{ 0, 45, 71, 12, 0 }, +	{ 0, 36, 73, 19, 0 }, +}; + +static const struct dispc_coef coef5_M8[8] = { +	{   0,   0, 128,   0,   0 }, +	{  -2,  14, 125, -10,   1 }, +	{  -6,  33, 114, -15,   2 }, +	{ -10,  55,  98, -16,   1 }, +	{   0, -14,  78,  78, -14 }, +	{   1, -16,  98,  55, -10 }, +	{   2, -15, 114,  33,  -6 }, +	{   1, -10, 125,  14,  -2 }, +}; + +static const struct dispc_coef coef5_M9[8] = { +	{  -3,  10, 114,  10,  -3 }, +	{  -6,  24, 111,   0,  -1 }, +	{  -8,  40, 103,  -7,   0 }, +	{ -11,  58,  91, -11,   1 }, +	{   0, -12,  76,  76, -12 }, +	{   1, -11,  91,  58, -11 }, +	{   0,  -7, 103,  40,  -8 }, +	{  -1,   0, 111,  24,  -6 }, +}; + +static const struct dispc_coef coef5_M10[8] = { +	{  -4,  18, 100,  18,  -4 }, +	{  -6,  30,  99,   8,  -3 }, +	{  -8,  44,  93,   0,  -1 }, +	{  -9,  58,  84,  -5,   0 }, +	{   0,  -8,  72,  72,  -8 }, +	{   0,  -5,  84,  58,  -9 }, +	{  -1,   0,  93,  44,  -8 }, +	{  -3,   8,  99,  30,  -6 }, +}; + +static const struct dispc_coef coef5_M11[8] = { +	{  -5,  23,  92,  23,  -5 }, +	{  -6,  34,  90,  13,  -3 }, +	{  -6,  45,  85,   6,  -2 }, +	{  -6,  57,  78,   0,  -1 }, +	{   0,  -4,  68,  68,  -4 }, +	{  -1,   0,  78,  57,  -6 }, +	{  -2,   6,  85,  45,  -6 }, +	{  -3,  13,  90,  34,  -6 }, +}; + +static const struct dispc_coef coef5_M12[8] = { +	{  -4,  26,  84,  26,  -4 }, +	{  -5,  36,  82,  18,  -3 }, +	{  -4,  46,  78,  10,  -2 }, +	{  -3,  55,  72,   5,  -1 }, +	{   0,   0,  64,  64,   0 }, +	{  -1,   5,  72,  55,  -3 }, +	{  -2,  10,  78,  46,  -4 }, +	{  -3,  18,  82,  36,  -5 }, +}; + +static const struct dispc_coef coef5_M13[8] = { +	{  -3,  28,  78,  28,  -3 }, +	{  -3,  37,  76,  21,  -3 }, +	{  -2,  45,  73,  14,  -2 }, +	{   0,  53,  68,   8,  -1 }, +	{   0,   3,  61,  61,   3 }, +	{  -1,   8,  68,  53,   0 }, +	{  -2,  14,  73,  45,  -2 }, +	{  -3,  21,  76,  37,  -3 }, +}; + +static const struct dispc_coef coef5_M14[8] = { +	{  -2,  30,  72,  30,  -2 }, +	{  -1,  37,  71,  23,  -2 }, +	{   0,  45,  69,  16,  -2 }, +	{   3,  52,  64,  10,  -1 }, +	{   0,   6,  58,  58,   6 }, +	{  -1,  10,  64,  52,   3 }, +	{  -2,  16,  69,  45,   0 }, +	{  -2,  23,  71,  37,  -1 }, +}; + +static const struct dispc_coef coef5_M16[8] = { +	{   0,  31,  66,  31,   0 }, +	{   1,  38,  65,  25,  -1 }, +	{   3,  44,  62,  20,  -1 }, +	{   6,  49,  59,  14,   0 }, +	{   0,  10,  54,  54,  10 }, +	{   0,  14,  59,  49,   6 }, +	{  -1,  20,  62,  44,   3 }, +	{  -1,  25,  65,  38,   1 }, +}; + +static const struct dispc_coef coef5_M19[8] = { +	{   3,  32,  58,  32,   3 }, +	{   4,  38,  58,  27,   1 }, +	{   7,  42,  55,  23,   1 }, +	{  10,  46,  54,  18,   0 }, +	{   0,  14,  50,  50,  14 }, +	{   0,  18,  54,  46,  10 }, +	{   1,  23,  55,  42,   7 }, +	{   1,  27,  58,  38,   4 }, +}; + +static const struct dispc_coef coef5_M22[8] = { +	{   4,  33,  54,  33,   4 }, +	{   6,  37,  54,  28,   3 }, +	{   9,  41,  53,  24,   1 }, +	{  12,  45,  51,  20,   0 }, +	{   0,  16,  48,  48,  16 }, +	{   0,  20,  51,  45,  12 }, +	{   1,  24,  53,  41,   9 }, +	{   3,  28,  54,  37,   6 }, +}; + +static const struct dispc_coef coef5_M26[8] = { +	{   6,  33,  50,  33,   6 }, +	{   8,  36,  51,  29,   4 }, +	{  11,  40,  50,  25,   2 }, +	{  14,  43,  48,  22,   1 }, +	{   0,  18,  46,  46,  18 }, +	{   1,  22,  48,  43,  14 }, +	{   2,  25,  50,  40,  11 }, +	{   4,  29,  51,  36,   8 }, +}; + +static const struct dispc_coef coef5_M32[8] = { +	{   7,  33,  48,  33,   7 }, +	{  10,  36,  48,  29,   5 }, +	{  13,  39,  47,  26,   3 }, +	{  16,  42,  46,  23,   1 }, +	{   0,  19,  45,  45,  19 }, +	{   1,  23,  46,  42,  16 }, +	{   3,  26,  47,  39,  13 }, +	{   5,  29,  48,  36,  10 }, +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps) +{ +	int i; +	static const struct { +		int Mmin; +		int Mmax; +		const struct dispc_coef *coef_3; +		const struct dispc_coef *coef_5; +	} coefs[] = { +		{ 27, 32, coef3_M32, coef5_M32 }, +		{ 23, 26, coef3_M26, coef5_M26 }, +		{ 20, 22, coef3_M22, coef5_M22 }, +		{ 17, 19, coef3_M19, coef5_M19 }, +		{ 15, 16, coef3_M16, coef5_M16 }, +		{ 14, 14, coef3_M14, coef5_M14 }, +		{ 13, 13, coef3_M13, coef5_M13 }, +		{ 12, 12, coef3_M12, coef5_M12 }, +		{ 11, 11, coef3_M11, coef5_M11 }, +		{ 10, 10, coef3_M10, coef5_M10 }, +		{  9,  9,  coef3_M9,  coef5_M9 }, +		{  4,  8,  coef3_M8,  coef5_M8 }, +		/* +		 * When upscaling more than two times, blockiness and outlines +		 * around the image are observed when M8 tables are used. M11, +		 * M16 and M19 tables are used to prevent this. +		 */ +		{  3,  3, coef3_M11, coef5_M11 }, +		{  2,  2, coef3_M16, coef5_M16 }, +		{  0,  1, coef3_M19, coef5_M19 }, +	}; + +	inc /= 128; +	for (i = 0; i < ARRAY_SIZE(coefs); ++i) +		if (inc >= coefs[i].Mmin && inc <= coefs[i].Mmax) +			return five_taps ? coefs[i].coef_5 : coefs[i].coef_3; +	return NULL; +} diff --git a/drivers/video/fbdev/omap2/dss/display-sysfs.c b/drivers/video/fbdev/omap2/dss/display-sysfs.c new file mode 100644 index 00000000000..5a2095a98ed --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/display-sysfs.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "DISPLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#include <video/omapdss.h> +#include "dss.h" + +static struct omap_dss_device *to_dss_device_sysfs(struct device *dev) +{ +	struct omap_dss_device *dssdev = NULL; + +	for_each_dss_dev(dssdev) { +		if (dssdev->dev == dev) { +			omap_dss_put_device(dssdev); +			return dssdev; +		} +	} + +	return NULL; +} + +static ssize_t display_name_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + +	return snprintf(buf, PAGE_SIZE, "%s\n", +			dssdev->name ? +			dssdev->name : ""); +} + +static ssize_t display_enabled_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); + +	return snprintf(buf, PAGE_SIZE, "%d\n", +			omapdss_device_is_enabled(dssdev)); +} + +static ssize_t display_enabled_store(struct device *dev, +		struct device_attribute *attr, +		const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int r; +	bool enable; + +	r = strtobool(buf, &enable); +	if (r) +		return r; + +	if (enable == omapdss_device_is_enabled(dssdev)) +		return size; + +	if (omapdss_device_is_connected(dssdev) == false) +		return -ENODEV; + +	if (enable) { +		r = dssdev->driver->enable(dssdev); +		if (r) +			return r; +	} else { +		dssdev->driver->disable(dssdev); +	} + +	return size; +} + +static ssize_t display_tear_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	return snprintf(buf, PAGE_SIZE, "%d\n", +			dssdev->driver->get_te ? +			dssdev->driver->get_te(dssdev) : 0); +} + +static ssize_t display_tear_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int r; +	bool te; + +	if (!dssdev->driver->enable_te || !dssdev->driver->get_te) +		return -ENOENT; + +	r = strtobool(buf, &te); +	if (r) +		return r; + +	r = dssdev->driver->enable_te(dssdev, te); +	if (r) +		return r; + +	return size; +} + +static ssize_t display_timings_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	struct omap_video_timings t; + +	if (!dssdev->driver->get_timings) +		return -ENOENT; + +	dssdev->driver->get_timings(dssdev, &t); + +	return snprintf(buf, PAGE_SIZE, "%u,%u/%u/%u/%u,%u/%u/%u/%u\n", +			t.pixelclock, +			t.x_res, t.hfp, t.hbp, t.hsw, +			t.y_res, t.vfp, t.vbp, t.vsw); +} + +static ssize_t display_timings_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	struct omap_video_timings t = dssdev->panel.timings; +	int r, found; + +	if (!dssdev->driver->set_timings || !dssdev->driver->check_timings) +		return -ENOENT; + +	found = 0; +#ifdef CONFIG_OMAP2_DSS_VENC +	if (strncmp("pal", buf, 3) == 0) { +		t = omap_dss_pal_timings; +		found = 1; +	} else if (strncmp("ntsc", buf, 4) == 0) { +		t = omap_dss_ntsc_timings; +		found = 1; +	} +#endif +	if (!found && sscanf(buf, "%u,%hu/%hu/%hu/%hu,%hu/%hu/%hu/%hu", +				&t.pixelclock, +				&t.x_res, &t.hfp, &t.hbp, &t.hsw, +				&t.y_res, &t.vfp, &t.vbp, &t.vsw) != 9) +		return -EINVAL; + +	r = dssdev->driver->check_timings(dssdev, &t); +	if (r) +		return r; + +	dssdev->driver->disable(dssdev); +	dssdev->driver->set_timings(dssdev, &t); +	r = dssdev->driver->enable(dssdev); +	if (r) +		return r; + +	return size; +} + +static ssize_t display_rotate_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int rotate; +	if (!dssdev->driver->get_rotate) +		return -ENOENT; +	rotate = dssdev->driver->get_rotate(dssdev); +	return snprintf(buf, PAGE_SIZE, "%u\n", rotate); +} + +static ssize_t display_rotate_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int rot, r; + +	if (!dssdev->driver->set_rotate || !dssdev->driver->get_rotate) +		return -ENOENT; + +	r = kstrtoint(buf, 0, &rot); +	if (r) +		return r; + +	r = dssdev->driver->set_rotate(dssdev, rot); +	if (r) +		return r; + +	return size; +} + +static ssize_t display_mirror_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int mirror; +	if (!dssdev->driver->get_mirror) +		return -ENOENT; +	mirror = dssdev->driver->get_mirror(dssdev); +	return snprintf(buf, PAGE_SIZE, "%u\n", mirror); +} + +static ssize_t display_mirror_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	int r; +	bool mirror; + +	if (!dssdev->driver->set_mirror || !dssdev->driver->get_mirror) +		return -ENOENT; + +	r = strtobool(buf, &mirror); +	if (r) +		return r; + +	r = dssdev->driver->set_mirror(dssdev, mirror); +	if (r) +		return r; + +	return size; +} + +static ssize_t display_wss_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	unsigned int wss; + +	if (!dssdev->driver->get_wss) +		return -ENOENT; + +	wss = dssdev->driver->get_wss(dssdev); + +	return snprintf(buf, PAGE_SIZE, "0x%05x\n", wss); +} + +static ssize_t display_wss_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	struct omap_dss_device *dssdev = to_dss_device_sysfs(dev); +	u32 wss; +	int r; + +	if (!dssdev->driver->get_wss || !dssdev->driver->set_wss) +		return -ENOENT; + +	r = kstrtou32(buf, 0, &wss); +	if (r) +		return r; + +	if (wss > 0xfffff) +		return -EINVAL; + +	r = dssdev->driver->set_wss(dssdev, wss); +	if (r) +		return r; + +	return size; +} + +static DEVICE_ATTR(display_name, S_IRUGO, display_name_show, NULL); +static DEVICE_ATTR(enabled, S_IRUGO|S_IWUSR, +		display_enabled_show, display_enabled_store); +static DEVICE_ATTR(tear_elim, S_IRUGO|S_IWUSR, +		display_tear_show, display_tear_store); +static DEVICE_ATTR(timings, S_IRUGO|S_IWUSR, +		display_timings_show, display_timings_store); +static DEVICE_ATTR(rotate, S_IRUGO|S_IWUSR, +		display_rotate_show, display_rotate_store); +static DEVICE_ATTR(mirror, S_IRUGO|S_IWUSR, +		display_mirror_show, display_mirror_store); +static DEVICE_ATTR(wss, S_IRUGO|S_IWUSR, +		display_wss_show, display_wss_store); + +static const struct attribute *display_sysfs_attrs[] = { +	&dev_attr_display_name.attr, +	&dev_attr_enabled.attr, +	&dev_attr_tear_elim.attr, +	&dev_attr_timings.attr, +	&dev_attr_rotate.attr, +	&dev_attr_mirror.attr, +	&dev_attr_wss.attr, +	NULL +}; + +int display_init_sysfs(struct platform_device *pdev) +{ +	struct omap_dss_device *dssdev = NULL; +	int r; + +	for_each_dss_dev(dssdev) { +		struct kobject *kobj = &dssdev->dev->kobj; + +		r = sysfs_create_files(kobj, display_sysfs_attrs); +		if (r) { +			DSSERR("failed to create sysfs files\n"); +			goto err; +		} + +		r = sysfs_create_link(&pdev->dev.kobj, kobj, dssdev->alias); +		if (r) { +			sysfs_remove_files(kobj, display_sysfs_attrs); + +			DSSERR("failed to create sysfs display link\n"); +			goto err; +		} +	} + +	return 0; + +err: +	display_uninit_sysfs(pdev); + +	return r; +} + +void display_uninit_sysfs(struct platform_device *pdev) +{ +	struct omap_dss_device *dssdev = NULL; + +	for_each_dss_dev(dssdev) { +		sysfs_remove_link(&pdev->dev.kobj, dssdev->alias); +		sysfs_remove_files(&dssdev->dev->kobj, +				display_sysfs_attrs); +	} +} diff --git a/drivers/video/fbdev/omap2/dss/display.c b/drivers/video/fbdev/omap2/dss/display.c new file mode 100644 index 00000000000..2412a0dd0c1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/display.c @@ -0,0 +1,338 @@ +/* + * linux/drivers/video/omap2/dss/display.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "DISPLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <video/omapdss.h> +#include "dss.h" +#include "dss_features.h" + +void omapdss_default_get_resolution(struct omap_dss_device *dssdev, +			u16 *xres, u16 *yres) +{ +	*xres = dssdev->panel.timings.x_res; +	*yres = dssdev->panel.timings.y_res; +} +EXPORT_SYMBOL(omapdss_default_get_resolution); + +int omapdss_default_get_recommended_bpp(struct omap_dss_device *dssdev) +{ +	switch (dssdev->type) { +	case OMAP_DISPLAY_TYPE_DPI: +		if (dssdev->phy.dpi.data_lines == 24) +			return 24; +		else +			return 16; + +	case OMAP_DISPLAY_TYPE_DBI: +		if (dssdev->ctrl.pixel_size == 24) +			return 24; +		else +			return 16; +	case OMAP_DISPLAY_TYPE_DSI: +		if (dsi_get_pixel_size(dssdev->panel.dsi_pix_fmt) > 16) +			return 24; +		else +			return 16; +	case OMAP_DISPLAY_TYPE_VENC: +	case OMAP_DISPLAY_TYPE_SDI: +	case OMAP_DISPLAY_TYPE_HDMI: +	case OMAP_DISPLAY_TYPE_DVI: +		return 24; +	default: +		BUG(); +		return 0; +	} +} +EXPORT_SYMBOL(omapdss_default_get_recommended_bpp); + +void omapdss_default_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	*timings = dssdev->panel.timings; +} +EXPORT_SYMBOL(omapdss_default_get_timings); + +int dss_suspend_all_devices(void) +{ +	struct omap_dss_device *dssdev = NULL; + +	for_each_dss_dev(dssdev) { +		if (!dssdev->driver) +			continue; + +		if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { +			dssdev->driver->disable(dssdev); +			dssdev->activate_after_resume = true; +		} else { +			dssdev->activate_after_resume = false; +		} +	} + +	return 0; +} + +int dss_resume_all_devices(void) +{ +	struct omap_dss_device *dssdev = NULL; + +	for_each_dss_dev(dssdev) { +		if (!dssdev->driver) +			continue; + +		if (dssdev->activate_after_resume) { +			dssdev->driver->enable(dssdev); +			dssdev->activate_after_resume = false; +		} +	} + +	return 0; +} + +void dss_disable_all_devices(void) +{ +	struct omap_dss_device *dssdev = NULL; + +	for_each_dss_dev(dssdev) { +		if (!dssdev->driver) +			continue; + +		if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) +			dssdev->driver->disable(dssdev); +	} +} + +static LIST_HEAD(panel_list); +static DEFINE_MUTEX(panel_list_mutex); +static int disp_num_counter; + +int omapdss_register_display(struct omap_dss_device *dssdev) +{ +	struct omap_dss_driver *drv = dssdev->driver; +	int id; + +	/* +	 * Note: this presumes all the displays are either using DT or non-DT, +	 * which normally should be the case. This also presumes that all +	 * displays either have an DT alias, or none has. +	 */ + +	if (dssdev->dev->of_node) { +		id = of_alias_get_id(dssdev->dev->of_node, "display"); + +		if (id < 0) +			id = disp_num_counter++; +	} else { +		id = disp_num_counter++; +	} + +	snprintf(dssdev->alias, sizeof(dssdev->alias), "display%d", id); + +	/* Use 'label' property for name, if it exists */ +	if (dssdev->dev->of_node) +		of_property_read_string(dssdev->dev->of_node, "label", +			&dssdev->name); + +	if (dssdev->name == NULL) +		dssdev->name = dssdev->alias; + +	if (drv && drv->get_resolution == NULL) +		drv->get_resolution = omapdss_default_get_resolution; +	if (drv && drv->get_recommended_bpp == NULL) +		drv->get_recommended_bpp = omapdss_default_get_recommended_bpp; +	if (drv && drv->get_timings == NULL) +		drv->get_timings = omapdss_default_get_timings; + +	mutex_lock(&panel_list_mutex); +	list_add_tail(&dssdev->panel_list, &panel_list); +	mutex_unlock(&panel_list_mutex); +	return 0; +} +EXPORT_SYMBOL(omapdss_register_display); + +void omapdss_unregister_display(struct omap_dss_device *dssdev) +{ +	mutex_lock(&panel_list_mutex); +	list_del(&dssdev->panel_list); +	mutex_unlock(&panel_list_mutex); +} +EXPORT_SYMBOL(omapdss_unregister_display); + +struct omap_dss_device *omap_dss_get_device(struct omap_dss_device *dssdev) +{ +	if (!try_module_get(dssdev->owner)) +		return NULL; + +	if (get_device(dssdev->dev) == NULL) { +		module_put(dssdev->owner); +		return NULL; +	} + +	return dssdev; +} +EXPORT_SYMBOL(omap_dss_get_device); + +void omap_dss_put_device(struct omap_dss_device *dssdev) +{ +	put_device(dssdev->dev); +	module_put(dssdev->owner); +} +EXPORT_SYMBOL(omap_dss_put_device); + +/* + * ref count of the found device is incremented. + * ref count of from-device is decremented. + */ +struct omap_dss_device *omap_dss_get_next_device(struct omap_dss_device *from) +{ +	struct list_head *l; +	struct omap_dss_device *dssdev; + +	mutex_lock(&panel_list_mutex); + +	if (list_empty(&panel_list)) { +		dssdev = NULL; +		goto out; +	} + +	if (from == NULL) { +		dssdev = list_first_entry(&panel_list, struct omap_dss_device, +				panel_list); +		omap_dss_get_device(dssdev); +		goto out; +	} + +	omap_dss_put_device(from); + +	list_for_each(l, &panel_list) { +		dssdev = list_entry(l, struct omap_dss_device, panel_list); +		if (dssdev == from) { +			if (list_is_last(l, &panel_list)) { +				dssdev = NULL; +				goto out; +			} + +			dssdev = list_entry(l->next, struct omap_dss_device, +					panel_list); +			omap_dss_get_device(dssdev); +			goto out; +		} +	} + +	WARN(1, "'from' dssdev not found\n"); + +	dssdev = NULL; +out: +	mutex_unlock(&panel_list_mutex); +	return dssdev; +} +EXPORT_SYMBOL(omap_dss_get_next_device); + +struct omap_dss_device *omap_dss_find_device(void *data, +		int (*match)(struct omap_dss_device *dssdev, void *data)) +{ +	struct omap_dss_device *dssdev = NULL; + +	while ((dssdev = omap_dss_get_next_device(dssdev)) != NULL) { +		if (match(dssdev, data)) +			return dssdev; +	} + +	return NULL; +} +EXPORT_SYMBOL(omap_dss_find_device); + +void videomode_to_omap_video_timings(const struct videomode *vm, +		struct omap_video_timings *ovt) +{ +	memset(ovt, 0, sizeof(*ovt)); + +	ovt->pixelclock = vm->pixelclock; +	ovt->x_res = vm->hactive; +	ovt->hbp = vm->hback_porch; +	ovt->hfp = vm->hfront_porch; +	ovt->hsw = vm->hsync_len; +	ovt->y_res = vm->vactive; +	ovt->vbp = vm->vback_porch; +	ovt->vfp = vm->vfront_porch; +	ovt->vsw = vm->vsync_len; + +	ovt->vsync_level = vm->flags & DISPLAY_FLAGS_VSYNC_HIGH ? +		OMAPDSS_SIG_ACTIVE_HIGH : +		OMAPDSS_SIG_ACTIVE_LOW; +	ovt->hsync_level = vm->flags & DISPLAY_FLAGS_HSYNC_HIGH ? +		OMAPDSS_SIG_ACTIVE_HIGH : +		OMAPDSS_SIG_ACTIVE_LOW; +	ovt->de_level = vm->flags & DISPLAY_FLAGS_DE_HIGH ? +		OMAPDSS_SIG_ACTIVE_HIGH : +		OMAPDSS_SIG_ACTIVE_LOW; +	ovt->data_pclk_edge = vm->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ? +		OMAPDSS_DRIVE_SIG_RISING_EDGE : +		OMAPDSS_DRIVE_SIG_FALLING_EDGE; + +	ovt->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; +} +EXPORT_SYMBOL(videomode_to_omap_video_timings); + +void omap_video_timings_to_videomode(const struct omap_video_timings *ovt, +		struct videomode *vm) +{ +	memset(vm, 0, sizeof(*vm)); + +	vm->pixelclock = ovt->pixelclock; + +	vm->hactive = ovt->x_res; +	vm->hback_porch = ovt->hbp; +	vm->hfront_porch = ovt->hfp; +	vm->hsync_len = ovt->hsw; +	vm->vactive = ovt->y_res; +	vm->vback_porch = ovt->vbp; +	vm->vfront_porch = ovt->vfp; +	vm->vsync_len = ovt->vsw; + +	if (ovt->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH) +		vm->flags |= DISPLAY_FLAGS_HSYNC_HIGH; +	else +		vm->flags |= DISPLAY_FLAGS_HSYNC_LOW; + +	if (ovt->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH) +		vm->flags |= DISPLAY_FLAGS_VSYNC_HIGH; +	else +		vm->flags |= DISPLAY_FLAGS_VSYNC_LOW; + +	if (ovt->de_level == OMAPDSS_SIG_ACTIVE_HIGH) +		vm->flags |= DISPLAY_FLAGS_DE_HIGH; +	else +		vm->flags |= DISPLAY_FLAGS_DE_LOW; + +	if (ovt->data_pclk_edge == OMAPDSS_DRIVE_SIG_RISING_EDGE) +		vm->flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; +	else +		vm->flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; +} +EXPORT_SYMBOL(omap_video_timings_to_videomode); diff --git a/drivers/video/fbdev/omap2/dss/dpi.c b/drivers/video/fbdev/omap2/dss/dpi.c new file mode 100644 index 00000000000..9368972d696 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dpi.c @@ -0,0 +1,778 @@ +/* + * linux/drivers/video/omap2/dss/dpi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "DPI" + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static struct { +	struct platform_device *pdev; + +	struct regulator *vdds_dsi_reg; +	struct platform_device *dsidev; + +	struct mutex lock; + +	struct omap_video_timings timings; +	struct dss_lcd_mgr_config mgr_config; +	int data_lines; + +	struct omap_dss_device output; + +	bool port_initialized; +} dpi; + +static struct platform_device *dpi_get_dsidev(enum omap_channel channel) +{ +	/* +	 * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL +	 * would also be used for DISPC fclk. Meaning, when the DPI output is +	 * disabled, DISPC clock will be disabled, and TV out will stop. +	 */ +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP24xx: +	case OMAPDSS_VER_OMAP34xx_ES1: +	case OMAPDSS_VER_OMAP34xx_ES3: +	case OMAPDSS_VER_OMAP3630: +	case OMAPDSS_VER_AM35xx: +	case OMAPDSS_VER_AM43xx: +		return NULL; + +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		switch (channel) { +		case OMAP_DSS_CHANNEL_LCD: +			return dsi_get_dsidev_from_id(0); +		case OMAP_DSS_CHANNEL_LCD2: +			return dsi_get_dsidev_from_id(1); +		default: +			return NULL; +		} + +	case OMAPDSS_VER_OMAP5: +		switch (channel) { +		case OMAP_DSS_CHANNEL_LCD: +			return dsi_get_dsidev_from_id(0); +		case OMAP_DSS_CHANNEL_LCD3: +			return dsi_get_dsidev_from_id(1); +		default: +			return NULL; +		} + +	default: +		return NULL; +	} +} + +static enum omap_dss_clk_source dpi_get_alt_clk_src(enum omap_channel channel) +{ +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		return OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC; +	case OMAP_DSS_CHANNEL_LCD2: +		return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; +	case OMAP_DSS_CHANNEL_LCD3: +		return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; +	default: +		/* this shouldn't happen */ +		WARN_ON(1); +		return OMAP_DSS_CLK_SRC_FCK; +	} +} + +struct dpi_clk_calc_ctx { +	struct platform_device *dsidev; + +	/* inputs */ + +	unsigned long pck_min, pck_max; + +	/* outputs */ + +	struct dsi_clock_info dsi_cinfo; +	unsigned long fck; +	struct dispc_clock_info dispc_cinfo; +}; + +static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, +		unsigned long pck, void *data) +{ +	struct dpi_clk_calc_ctx *ctx = data; + +	/* +	 * Odd dividers give us uneven duty cycle, causing problem when level +	 * shifted. So skip all odd dividers when the pixel clock is on the +	 * higher side. +	 */ +	if (ctx->pck_min >= 100000000) { +		if (lckd > 1 && lckd % 2 != 0) +			return false; + +		if (pckd > 1 && pckd % 2 != 0) +			return false; +	} + +	ctx->dispc_cinfo.lck_div = lckd; +	ctx->dispc_cinfo.pck_div = pckd; +	ctx->dispc_cinfo.lck = lck; +	ctx->dispc_cinfo.pck = pck; + +	return true; +} + + +static bool dpi_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, +		void *data) +{ +	struct dpi_clk_calc_ctx *ctx = data; + +	/* +	 * Odd dividers give us uneven duty cycle, causing problem when level +	 * shifted. So skip all odd dividers when the pixel clock is on the +	 * higher side. +	 */ +	if (regm_dispc > 1 && regm_dispc % 2 != 0 && ctx->pck_min >= 100000000) +		return false; + +	ctx->dsi_cinfo.regm_dispc = regm_dispc; +	ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + +	return dispc_div_calc(dispc, ctx->pck_min, ctx->pck_max, +			dpi_calc_dispc_cb, ctx); +} + + +static bool dpi_calc_pll_cb(int regn, int regm, unsigned long fint, +		unsigned long pll, +		void *data) +{ +	struct dpi_clk_calc_ctx *ctx = data; + +	ctx->dsi_cinfo.regn = regn; +	ctx->dsi_cinfo.regm = regm; +	ctx->dsi_cinfo.fint = fint; +	ctx->dsi_cinfo.clkin4ddr = pll; + +	return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->pck_min, +			dpi_calc_hsdiv_cb, ctx); +} + +static bool dpi_calc_dss_cb(unsigned long fck, void *data) +{ +	struct dpi_clk_calc_ctx *ctx = data; + +	ctx->fck = fck; + +	return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, +			dpi_calc_dispc_cb, ctx); +} + +static bool dpi_dsi_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) +{ +	unsigned long clkin; +	unsigned long pll_min, pll_max; + +	clkin = dsi_get_pll_clkin(dpi.dsidev); + +	memset(ctx, 0, sizeof(*ctx)); +	ctx->dsidev = dpi.dsidev; +	ctx->pck_min = pck - 1000; +	ctx->pck_max = pck + 1000; +	ctx->dsi_cinfo.clkin = clkin; + +	pll_min = 0; +	pll_max = 0; + +	return dsi_pll_calc(dpi.dsidev, clkin, +			pll_min, pll_max, +			dpi_calc_pll_cb, ctx); +} + +static bool dpi_dss_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) +{ +	int i; + +	/* +	 * DSS fck gives us very few possibilities, so finding a good pixel +	 * clock may not be possible. We try multiple times to find the clock, +	 * each time widening the pixel clock range we look for, up to +	 * +/- ~15MHz. +	 */ + +	for (i = 0; i < 25; ++i) { +		bool ok; + +		memset(ctx, 0, sizeof(*ctx)); +		if (pck > 1000 * i * i * i) +			ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); +		else +			ctx->pck_min = 0; +		ctx->pck_max = pck + 1000 * i * i * i; + +		ok = dss_div_calc(pck, ctx->pck_min, dpi_calc_dss_cb, ctx); +		if (ok) +			return ok; +	} + +	return false; +} + + + +static int dpi_set_dsi_clk(enum omap_channel channel, +		unsigned long pck_req, unsigned long *fck, int *lck_div, +		int *pck_div) +{ +	struct dpi_clk_calc_ctx ctx; +	int r; +	bool ok; + +	ok = dpi_dsi_clk_calc(pck_req, &ctx); +	if (!ok) +		return -EINVAL; + +	r = dsi_pll_set_clock_div(dpi.dsidev, &ctx.dsi_cinfo); +	if (r) +		return r; + +	dss_select_lcd_clk_source(channel, +			dpi_get_alt_clk_src(channel)); + +	dpi.mgr_config.clock_info = ctx.dispc_cinfo; + +	*fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; +	*lck_div = ctx.dispc_cinfo.lck_div; +	*pck_div = ctx.dispc_cinfo.pck_div; + +	return 0; +} + +static int dpi_set_dispc_clk(unsigned long pck_req, unsigned long *fck, +		int *lck_div, int *pck_div) +{ +	struct dpi_clk_calc_ctx ctx; +	int r; +	bool ok; + +	ok = dpi_dss_clk_calc(pck_req, &ctx); +	if (!ok) +		return -EINVAL; + +	r = dss_set_fck_rate(ctx.fck); +	if (r) +		return r; + +	dpi.mgr_config.clock_info = ctx.dispc_cinfo; + +	*fck = ctx.fck; +	*lck_div = ctx.dispc_cinfo.lck_div; +	*pck_div = ctx.dispc_cinfo.pck_div; + +	return 0; +} + +static int dpi_set_mode(struct omap_overlay_manager *mgr) +{ +	struct omap_video_timings *t = &dpi.timings; +	int lck_div = 0, pck_div = 0; +	unsigned long fck = 0; +	unsigned long pck; +	int r = 0; + +	if (dpi.dsidev) +		r = dpi_set_dsi_clk(mgr->id, t->pixelclock, &fck, +				&lck_div, &pck_div); +	else +		r = dpi_set_dispc_clk(t->pixelclock, &fck, +				&lck_div, &pck_div); +	if (r) +		return r; + +	pck = fck / lck_div / pck_div; + +	if (pck != t->pixelclock) { +		DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", +			t->pixelclock, pck); + +		t->pixelclock = pck; +	} + +	dss_mgr_set_timings(mgr, t); + +	return 0; +} + +static void dpi_config_lcd_manager(struct omap_overlay_manager *mgr) +{ +	dpi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + +	dpi.mgr_config.stallmode = false; +	dpi.mgr_config.fifohandcheck = false; + +	dpi.mgr_config.video_port_width = dpi.data_lines; + +	dpi.mgr_config.lcden_sig_polarity = 0; + +	dss_mgr_set_lcd_config(mgr, &dpi.mgr_config); +} + +static int dpi_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &dpi.output; +	int r; + +	mutex_lock(&dpi.lock); + +	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && !dpi.vdds_dsi_reg) { +		DSSERR("no VDSS_DSI regulator\n"); +		r = -ENODEV; +		goto err_no_reg; +	} + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		r = -ENODEV; +		goto err_no_out_mgr; +	} + +	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) { +		r = regulator_enable(dpi.vdds_dsi_reg); +		if (r) +			goto err_reg_enable; +	} + +	r = dispc_runtime_get(); +	if (r) +		goto err_get_dispc; + +	r = dss_dpi_select_source(out->manager->id); +	if (r) +		goto err_src_sel; + +	if (dpi.dsidev) { +		r = dsi_runtime_get(dpi.dsidev); +		if (r) +			goto err_get_dsi; + +		r = dsi_pll_init(dpi.dsidev, 0, 1); +		if (r) +			goto err_dsi_pll_init; +	} + +	r = dpi_set_mode(out->manager); +	if (r) +		goto err_set_mode; + +	dpi_config_lcd_manager(out->manager); + +	mdelay(2); + +	r = dss_mgr_enable(out->manager); +	if (r) +		goto err_mgr_enable; + +	mutex_unlock(&dpi.lock); + +	return 0; + +err_mgr_enable: +err_set_mode: +	if (dpi.dsidev) +		dsi_pll_uninit(dpi.dsidev, true); +err_dsi_pll_init: +	if (dpi.dsidev) +		dsi_runtime_put(dpi.dsidev); +err_get_dsi: +err_src_sel: +	dispc_runtime_put(); +err_get_dispc: +	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) +		regulator_disable(dpi.vdds_dsi_reg); +err_reg_enable: +err_no_out_mgr: +err_no_reg: +	mutex_unlock(&dpi.lock); +	return r; +} + +static void dpi_display_disable(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = dpi.output.manager; + +	mutex_lock(&dpi.lock); + +	dss_mgr_disable(mgr); + +	if (dpi.dsidev) { +		dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); +		dsi_pll_uninit(dpi.dsidev, true); +		dsi_runtime_put(dpi.dsidev); +	} + +	dispc_runtime_put(); + +	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) +		regulator_disable(dpi.vdds_dsi_reg); + +	mutex_unlock(&dpi.lock); +} + +static void dpi_set_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	DSSDBG("dpi_set_timings\n"); + +	mutex_lock(&dpi.lock); + +	dpi.timings = *timings; + +	mutex_unlock(&dpi.lock); +} + +static void dpi_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	mutex_lock(&dpi.lock); + +	*timings = dpi.timings; + +	mutex_unlock(&dpi.lock); +} + +static int dpi_check_timings(struct omap_dss_device *dssdev, +			struct omap_video_timings *timings) +{ +	struct omap_overlay_manager *mgr = dpi.output.manager; +	int lck_div, pck_div; +	unsigned long fck; +	unsigned long pck; +	struct dpi_clk_calc_ctx ctx; +	bool ok; + +	if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) +		return -EINVAL; + +	if (timings->pixelclock == 0) +		return -EINVAL; + +	if (dpi.dsidev) { +		ok = dpi_dsi_clk_calc(timings->pixelclock, &ctx); +		if (!ok) +			return -EINVAL; + +		fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; +	} else { +		ok = dpi_dss_clk_calc(timings->pixelclock, &ctx); +		if (!ok) +			return -EINVAL; + +		fck = ctx.fck; +	} + +	lck_div = ctx.dispc_cinfo.lck_div; +	pck_div = ctx.dispc_cinfo.pck_div; + +	pck = fck / lck_div / pck_div; + +	timings->pixelclock = pck; + +	return 0; +} + +static void dpi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) +{ +	mutex_lock(&dpi.lock); + +	dpi.data_lines = data_lines; + +	mutex_unlock(&dpi.lock); +} + +static int dpi_verify_dsi_pll(struct platform_device *dsidev) +{ +	int r; + +	/* do initial setup with the PLL to see if it is operational */ + +	r = dsi_runtime_get(dsidev); +	if (r) +		return r; + +	r = dsi_pll_init(dsidev, 0, 1); +	if (r) { +		dsi_runtime_put(dsidev); +		return r; +	} + +	dsi_pll_uninit(dsidev, true); +	dsi_runtime_put(dsidev); + +	return 0; +} + +static int dpi_init_regulator(void) +{ +	struct regulator *vdds_dsi; + +	if (!dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) +		return 0; + +	if (dpi.vdds_dsi_reg) +		return 0; + +	vdds_dsi = devm_regulator_get(&dpi.pdev->dev, "vdds_dsi"); +	if (IS_ERR(vdds_dsi)) { +		if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) +			DSSERR("can't get VDDS_DSI regulator\n"); +		return PTR_ERR(vdds_dsi); +	} + +	dpi.vdds_dsi_reg = vdds_dsi; + +	return 0; +} + +static void dpi_init_pll(void) +{ +	struct platform_device *dsidev; + +	if (dpi.dsidev) +		return; + +	dsidev = dpi_get_dsidev(dpi.output.dispc_channel); +	if (!dsidev) +		return; + +	if (dpi_verify_dsi_pll(dsidev)) { +		DSSWARN("DSI PLL not operational\n"); +		return; +	} + +	dpi.dsidev = dsidev; +} + +/* + * Return a hardcoded channel for the DPI output. This should work for + * current use cases, but this can be later expanded to either resolve + * the channel in some more dynamic manner, or get the channel as a user + * parameter. + */ +static enum omap_channel dpi_get_channel(void) +{ +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP24xx: +	case OMAPDSS_VER_OMAP34xx_ES1: +	case OMAPDSS_VER_OMAP34xx_ES3: +	case OMAPDSS_VER_OMAP3630: +	case OMAPDSS_VER_AM35xx: +	case OMAPDSS_VER_AM43xx: +		return OMAP_DSS_CHANNEL_LCD; + +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		return OMAP_DSS_CHANNEL_LCD2; + +	case OMAPDSS_VER_OMAP5: +		return OMAP_DSS_CHANNEL_LCD3; + +	default: +		DSSWARN("unsupported DSS version\n"); +		return OMAP_DSS_CHANNEL_LCD; +	} +} + +static int dpi_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct omap_overlay_manager *mgr; +	int r; + +	r = dpi_init_regulator(); +	if (r) +		return r; + +	dpi_init_pll(); + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dst->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void dpi_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_dpi_ops dpi_ops = { +	.connect = dpi_connect, +	.disconnect = dpi_disconnect, + +	.enable = dpi_display_enable, +	.disable = dpi_display_disable, + +	.check_timings = dpi_check_timings, +	.set_timings = dpi_set_timings, +	.get_timings = dpi_get_timings, + +	.set_data_lines = dpi_set_data_lines, +}; + +static void dpi_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &dpi.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_DPI; +	out->output_type = OMAP_DISPLAY_TYPE_DPI; +	out->name = "dpi.0"; +	out->dispc_channel = dpi_get_channel(); +	out->ops.dpi = &dpi_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit dpi_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &dpi.output; + +	omapdss_unregister_output(out); +} + +static int omap_dpi_probe(struct platform_device *pdev) +{ +	dpi.pdev = pdev; + +	mutex_init(&dpi.lock); + +	dpi_init_output(pdev); + +	return 0; +} + +static int __exit omap_dpi_remove(struct platform_device *pdev) +{ +	dpi_uninit_output(pdev); + +	return 0; +} + +static struct platform_driver omap_dpi_driver = { +	.probe		= omap_dpi_probe, +	.remove         = __exit_p(omap_dpi_remove), +	.driver         = { +		.name   = "omapdss_dpi", +		.owner  = THIS_MODULE, +	}, +}; + +int __init dpi_init_platform_driver(void) +{ +	return platform_driver_register(&omap_dpi_driver); +} + +void __exit dpi_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_dpi_driver); +} + +int __init dpi_init_port(struct platform_device *pdev, struct device_node *port) +{ +	struct device_node *ep; +	u32 datalines; +	int r; + +	ep = omapdss_of_get_next_endpoint(port, NULL); +	if (!ep) +		return 0; + +	r = of_property_read_u32(ep, "data-lines", &datalines); +	if (r) { +		DSSERR("failed to parse datalines\n"); +		goto err_datalines; +	} + +	dpi.data_lines = datalines; + +	of_node_put(ep); + +	dpi.pdev = pdev; + +	mutex_init(&dpi.lock); + +	dpi_init_output(pdev); + +	dpi.port_initialized = true; + +	return 0; + +err_datalines: +	of_node_put(ep); + +	return r; +} + +void __exit dpi_uninit_port(void) +{ +	if (!dpi.port_initialized) +		return; + +	dpi_uninit_output(dpi.pdev); +} diff --git a/drivers/video/fbdev/omap2/dss/dsi.c b/drivers/video/fbdev/omap2/dss/dsi.c new file mode 100644 index 00000000000..4755a34a542 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dsi.c @@ -0,0 +1,5769 @@ +/* + * linux/drivers/video/omap2/dss/dsi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.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 DSS_SUBSYS_NAME "DSI" + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/semaphore.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <video/omapdss.h> +#include <video/mipi_display.h> + +#include "dss.h" +#include "dss_features.h" + +#define DSI_CATCH_MISSING_TE + +struct dsi_reg { u16 module; u16 idx; }; + +#define DSI_REG(mod, idx)		((const struct dsi_reg) { mod, idx }) + +/* DSI Protocol Engine */ + +#define DSI_PROTO			0 +#define DSI_PROTO_SZ			0x200 + +#define DSI_REVISION			DSI_REG(DSI_PROTO, 0x0000) +#define DSI_SYSCONFIG			DSI_REG(DSI_PROTO, 0x0010) +#define DSI_SYSSTATUS			DSI_REG(DSI_PROTO, 0x0014) +#define DSI_IRQSTATUS			DSI_REG(DSI_PROTO, 0x0018) +#define DSI_IRQENABLE			DSI_REG(DSI_PROTO, 0x001C) +#define DSI_CTRL			DSI_REG(DSI_PROTO, 0x0040) +#define DSI_GNQ				DSI_REG(DSI_PROTO, 0x0044) +#define DSI_COMPLEXIO_CFG1		DSI_REG(DSI_PROTO, 0x0048) +#define DSI_COMPLEXIO_IRQ_STATUS	DSI_REG(DSI_PROTO, 0x004C) +#define DSI_COMPLEXIO_IRQ_ENABLE	DSI_REG(DSI_PROTO, 0x0050) +#define DSI_CLK_CTRL			DSI_REG(DSI_PROTO, 0x0054) +#define DSI_TIMING1			DSI_REG(DSI_PROTO, 0x0058) +#define DSI_TIMING2			DSI_REG(DSI_PROTO, 0x005C) +#define DSI_VM_TIMING1			DSI_REG(DSI_PROTO, 0x0060) +#define DSI_VM_TIMING2			DSI_REG(DSI_PROTO, 0x0064) +#define DSI_VM_TIMING3			DSI_REG(DSI_PROTO, 0x0068) +#define DSI_CLK_TIMING			DSI_REG(DSI_PROTO, 0x006C) +#define DSI_TX_FIFO_VC_SIZE		DSI_REG(DSI_PROTO, 0x0070) +#define DSI_RX_FIFO_VC_SIZE		DSI_REG(DSI_PROTO, 0x0074) +#define DSI_COMPLEXIO_CFG2		DSI_REG(DSI_PROTO, 0x0078) +#define DSI_RX_FIFO_VC_FULLNESS		DSI_REG(DSI_PROTO, 0x007C) +#define DSI_VM_TIMING4			DSI_REG(DSI_PROTO, 0x0080) +#define DSI_TX_FIFO_VC_EMPTINESS	DSI_REG(DSI_PROTO, 0x0084) +#define DSI_VM_TIMING5			DSI_REG(DSI_PROTO, 0x0088) +#define DSI_VM_TIMING6			DSI_REG(DSI_PROTO, 0x008C) +#define DSI_VM_TIMING7			DSI_REG(DSI_PROTO, 0x0090) +#define DSI_STOPCLK_TIMING		DSI_REG(DSI_PROTO, 0x0094) +#define DSI_VC_CTRL(n)			DSI_REG(DSI_PROTO, 0x0100 + (n * 0x20)) +#define DSI_VC_TE(n)			DSI_REG(DSI_PROTO, 0x0104 + (n * 0x20)) +#define DSI_VC_LONG_PACKET_HEADER(n)	DSI_REG(DSI_PROTO, 0x0108 + (n * 0x20)) +#define DSI_VC_LONG_PACKET_PAYLOAD(n)	DSI_REG(DSI_PROTO, 0x010C + (n * 0x20)) +#define DSI_VC_SHORT_PACKET_HEADER(n)	DSI_REG(DSI_PROTO, 0x0110 + (n * 0x20)) +#define DSI_VC_IRQSTATUS(n)		DSI_REG(DSI_PROTO, 0x0118 + (n * 0x20)) +#define DSI_VC_IRQENABLE(n)		DSI_REG(DSI_PROTO, 0x011C + (n * 0x20)) + +/* DSIPHY_SCP */ + +#define DSI_PHY				1 +#define DSI_PHY_OFFSET			0x200 +#define DSI_PHY_SZ			0x40 + +#define DSI_DSIPHY_CFG0			DSI_REG(DSI_PHY, 0x0000) +#define DSI_DSIPHY_CFG1			DSI_REG(DSI_PHY, 0x0004) +#define DSI_DSIPHY_CFG2			DSI_REG(DSI_PHY, 0x0008) +#define DSI_DSIPHY_CFG5			DSI_REG(DSI_PHY, 0x0014) +#define DSI_DSIPHY_CFG10		DSI_REG(DSI_PHY, 0x0028) + +/* DSI_PLL_CTRL_SCP */ + +#define DSI_PLL				2 +#define DSI_PLL_OFFSET			0x300 +#define DSI_PLL_SZ			0x20 + +#define DSI_PLL_CONTROL			DSI_REG(DSI_PLL, 0x0000) +#define DSI_PLL_STATUS			DSI_REG(DSI_PLL, 0x0004) +#define DSI_PLL_GO			DSI_REG(DSI_PLL, 0x0008) +#define DSI_PLL_CONFIGURATION1		DSI_REG(DSI_PLL, 0x000C) +#define DSI_PLL_CONFIGURATION2		DSI_REG(DSI_PLL, 0x0010) + +#define REG_GET(dsidev, idx, start, end) \ +	FLD_GET(dsi_read_reg(dsidev, idx), start, end) + +#define REG_FLD_MOD(dsidev, idx, val, start, end) \ +	dsi_write_reg(dsidev, idx, FLD_MOD(dsi_read_reg(dsidev, idx), val, start, end)) + +/* Global interrupts */ +#define DSI_IRQ_VC0		(1 << 0) +#define DSI_IRQ_VC1		(1 << 1) +#define DSI_IRQ_VC2		(1 << 2) +#define DSI_IRQ_VC3		(1 << 3) +#define DSI_IRQ_WAKEUP		(1 << 4) +#define DSI_IRQ_RESYNC		(1 << 5) +#define DSI_IRQ_PLL_LOCK	(1 << 7) +#define DSI_IRQ_PLL_UNLOCK	(1 << 8) +#define DSI_IRQ_PLL_RECALL	(1 << 9) +#define DSI_IRQ_COMPLEXIO_ERR	(1 << 10) +#define DSI_IRQ_HS_TX_TIMEOUT	(1 << 14) +#define DSI_IRQ_LP_RX_TIMEOUT	(1 << 15) +#define DSI_IRQ_TE_TRIGGER	(1 << 16) +#define DSI_IRQ_ACK_TRIGGER	(1 << 17) +#define DSI_IRQ_SYNC_LOST	(1 << 18) +#define DSI_IRQ_LDO_POWER_GOOD	(1 << 19) +#define DSI_IRQ_TA_TIMEOUT	(1 << 20) +#define DSI_IRQ_ERROR_MASK \ +	(DSI_IRQ_HS_TX_TIMEOUT | DSI_IRQ_LP_RX_TIMEOUT | DSI_IRQ_SYNC_LOST | \ +	DSI_IRQ_TA_TIMEOUT | DSI_IRQ_SYNC_LOST) +#define DSI_IRQ_CHANNEL_MASK	0xf + +/* Virtual channel interrupts */ +#define DSI_VC_IRQ_CS		(1 << 0) +#define DSI_VC_IRQ_ECC_CORR	(1 << 1) +#define DSI_VC_IRQ_PACKET_SENT	(1 << 2) +#define DSI_VC_IRQ_FIFO_TX_OVF	(1 << 3) +#define DSI_VC_IRQ_FIFO_RX_OVF	(1 << 4) +#define DSI_VC_IRQ_BTA		(1 << 5) +#define DSI_VC_IRQ_ECC_NO_CORR	(1 << 6) +#define DSI_VC_IRQ_FIFO_TX_UDF	(1 << 7) +#define DSI_VC_IRQ_PP_BUSY_CHANGE (1 << 8) +#define DSI_VC_IRQ_ERROR_MASK \ +	(DSI_VC_IRQ_CS | DSI_VC_IRQ_ECC_CORR | DSI_VC_IRQ_FIFO_TX_OVF | \ +	DSI_VC_IRQ_FIFO_RX_OVF | DSI_VC_IRQ_ECC_NO_CORR | \ +	DSI_VC_IRQ_FIFO_TX_UDF) + +/* ComplexIO interrupts */ +#define DSI_CIO_IRQ_ERRSYNCESC1		(1 << 0) +#define DSI_CIO_IRQ_ERRSYNCESC2		(1 << 1) +#define DSI_CIO_IRQ_ERRSYNCESC3		(1 << 2) +#define DSI_CIO_IRQ_ERRSYNCESC4		(1 << 3) +#define DSI_CIO_IRQ_ERRSYNCESC5		(1 << 4) +#define DSI_CIO_IRQ_ERRESC1		(1 << 5) +#define DSI_CIO_IRQ_ERRESC2		(1 << 6) +#define DSI_CIO_IRQ_ERRESC3		(1 << 7) +#define DSI_CIO_IRQ_ERRESC4		(1 << 8) +#define DSI_CIO_IRQ_ERRESC5		(1 << 9) +#define DSI_CIO_IRQ_ERRCONTROL1		(1 << 10) +#define DSI_CIO_IRQ_ERRCONTROL2		(1 << 11) +#define DSI_CIO_IRQ_ERRCONTROL3		(1 << 12) +#define DSI_CIO_IRQ_ERRCONTROL4		(1 << 13) +#define DSI_CIO_IRQ_ERRCONTROL5		(1 << 14) +#define DSI_CIO_IRQ_STATEULPS1		(1 << 15) +#define DSI_CIO_IRQ_STATEULPS2		(1 << 16) +#define DSI_CIO_IRQ_STATEULPS3		(1 << 17) +#define DSI_CIO_IRQ_STATEULPS4		(1 << 18) +#define DSI_CIO_IRQ_STATEULPS5		(1 << 19) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_1	(1 << 20) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_1	(1 << 21) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_2	(1 << 22) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_2	(1 << 23) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_3	(1 << 24) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_3	(1 << 25) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_4	(1 << 26) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_4	(1 << 27) +#define DSI_CIO_IRQ_ERRCONTENTIONLP0_5	(1 << 28) +#define DSI_CIO_IRQ_ERRCONTENTIONLP1_5	(1 << 29) +#define DSI_CIO_IRQ_ULPSACTIVENOT_ALL0	(1 << 30) +#define DSI_CIO_IRQ_ULPSACTIVENOT_ALL1	(1 << 31) +#define DSI_CIO_IRQ_ERROR_MASK \ +	(DSI_CIO_IRQ_ERRSYNCESC1 | DSI_CIO_IRQ_ERRSYNCESC2 | \ +	 DSI_CIO_IRQ_ERRSYNCESC3 | DSI_CIO_IRQ_ERRSYNCESC4 | \ +	 DSI_CIO_IRQ_ERRSYNCESC5 | \ +	 DSI_CIO_IRQ_ERRESC1 | DSI_CIO_IRQ_ERRESC2 | \ +	 DSI_CIO_IRQ_ERRESC3 | DSI_CIO_IRQ_ERRESC4 | \ +	 DSI_CIO_IRQ_ERRESC5 | \ +	 DSI_CIO_IRQ_ERRCONTROL1 | DSI_CIO_IRQ_ERRCONTROL2 | \ +	 DSI_CIO_IRQ_ERRCONTROL3 | DSI_CIO_IRQ_ERRCONTROL4 | \ +	 DSI_CIO_IRQ_ERRCONTROL5 | \ +	 DSI_CIO_IRQ_ERRCONTENTIONLP0_1 | DSI_CIO_IRQ_ERRCONTENTIONLP1_1 | \ +	 DSI_CIO_IRQ_ERRCONTENTIONLP0_2 | DSI_CIO_IRQ_ERRCONTENTIONLP1_2 | \ +	 DSI_CIO_IRQ_ERRCONTENTIONLP0_3 | DSI_CIO_IRQ_ERRCONTENTIONLP1_3 | \ +	 DSI_CIO_IRQ_ERRCONTENTIONLP0_4 | DSI_CIO_IRQ_ERRCONTENTIONLP1_4 | \ +	 DSI_CIO_IRQ_ERRCONTENTIONLP0_5 | DSI_CIO_IRQ_ERRCONTENTIONLP1_5) + +typedef void (*omap_dsi_isr_t) (void *arg, u32 mask); + +static int dsi_display_init_dispc(struct platform_device *dsidev, +	struct omap_overlay_manager *mgr); +static void dsi_display_uninit_dispc(struct platform_device *dsidev, +	struct omap_overlay_manager *mgr); + +static int dsi_vc_send_null(struct omap_dss_device *dssdev, int channel); + +#define DSI_MAX_NR_ISRS                2 +#define DSI_MAX_NR_LANES	5 + +enum dsi_lane_function { +	DSI_LANE_UNUSED	= 0, +	DSI_LANE_CLK, +	DSI_LANE_DATA1, +	DSI_LANE_DATA2, +	DSI_LANE_DATA3, +	DSI_LANE_DATA4, +}; + +struct dsi_lane_config { +	enum dsi_lane_function function; +	u8 polarity; +}; + +struct dsi_isr_data { +	omap_dsi_isr_t	isr; +	void		*arg; +	u32		mask; +}; + +enum fifo_size { +	DSI_FIFO_SIZE_0		= 0, +	DSI_FIFO_SIZE_32	= 1, +	DSI_FIFO_SIZE_64	= 2, +	DSI_FIFO_SIZE_96	= 3, +	DSI_FIFO_SIZE_128	= 4, +}; + +enum dsi_vc_source { +	DSI_VC_SOURCE_L4 = 0, +	DSI_VC_SOURCE_VP, +}; + +struct dsi_irq_stats { +	unsigned long last_reset; +	unsigned irq_count; +	unsigned dsi_irqs[32]; +	unsigned vc_irqs[4][32]; +	unsigned cio_irqs[32]; +}; + +struct dsi_isr_tables { +	struct dsi_isr_data isr_table[DSI_MAX_NR_ISRS]; +	struct dsi_isr_data isr_table_vc[4][DSI_MAX_NR_ISRS]; +	struct dsi_isr_data isr_table_cio[DSI_MAX_NR_ISRS]; +}; + +struct dsi_clk_calc_ctx { +	struct platform_device *dsidev; + +	/* inputs */ + +	const struct omap_dss_dsi_config *config; + +	unsigned long req_pck_min, req_pck_nom, req_pck_max; + +	/* outputs */ + +	struct dsi_clock_info dsi_cinfo; +	struct dispc_clock_info dispc_cinfo; + +	struct omap_video_timings dispc_vm; +	struct omap_dss_dsi_videomode_timings dsi_vm; +}; + +struct dsi_data { +	struct platform_device *pdev; +	void __iomem *proto_base; +	void __iomem *phy_base; +	void __iomem *pll_base; + +	int module_id; + +	int irq; + +	bool is_enabled; + +	struct clk *dss_clk; +	struct clk *sys_clk; + +	struct dispc_clock_info user_dispc_cinfo; +	struct dsi_clock_info user_dsi_cinfo; + +	struct dsi_clock_info current_cinfo; + +	bool vdds_dsi_enabled; +	struct regulator *vdds_dsi_reg; + +	struct { +		enum dsi_vc_source source; +		struct omap_dss_device *dssdev; +		enum fifo_size tx_fifo_size; +		enum fifo_size rx_fifo_size; +		int vc_id; +	} vc[4]; + +	struct mutex lock; +	struct semaphore bus_lock; + +	unsigned pll_locked; + +	spinlock_t irq_lock; +	struct dsi_isr_tables isr_tables; +	/* space for a copy used by the interrupt handler */ +	struct dsi_isr_tables isr_tables_copy; + +	int update_channel; +#ifdef DSI_PERF_MEASURE +	unsigned update_bytes; +#endif + +	bool te_enabled; +	bool ulps_enabled; + +	void (*framedone_callback)(int, void *); +	void *framedone_data; + +	struct delayed_work framedone_timeout_work; + +#ifdef DSI_CATCH_MISSING_TE +	struct timer_list te_timer; +#endif + +	unsigned long cache_req_pck; +	unsigned long cache_clk_freq; +	struct dsi_clock_info cache_cinfo; + +	u32		errors; +	spinlock_t	errors_lock; +#ifdef DSI_PERF_MEASURE +	ktime_t perf_setup_time; +	ktime_t perf_start_time; +#endif +	int debug_read; +	int debug_write; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	spinlock_t irq_stats_lock; +	struct dsi_irq_stats irq_stats; +#endif +	/* DSI PLL Parameter Ranges */ +	unsigned long regm_max, regn_max; +	unsigned long  regm_dispc_max, regm_dsi_max; +	unsigned long  fint_min, fint_max; +	unsigned long lpdiv_max; + +	unsigned num_lanes_supported; +	unsigned line_buffer_size; + +	struct dsi_lane_config lanes[DSI_MAX_NR_LANES]; +	unsigned num_lanes_used; + +	unsigned scp_clk_refcount; + +	struct dss_lcd_mgr_config mgr_config; +	struct omap_video_timings timings; +	enum omap_dss_dsi_pixel_format pix_fmt; +	enum omap_dss_dsi_mode mode; +	struct omap_dss_dsi_videomode_timings vm_timings; + +	struct omap_dss_device output; +}; + +struct dsi_packet_sent_handler_data { +	struct platform_device *dsidev; +	struct completion *completion; +}; + +struct dsi_module_id_data { +	u32 address; +	int id; +}; + +static const struct of_device_id dsi_of_match[]; + +#ifdef DSI_PERF_MEASURE +static bool dsi_perf; +module_param(dsi_perf, bool, 0644); +#endif + +static inline struct dsi_data *dsi_get_dsidrv_data(struct platform_device *dsidev) +{ +	return dev_get_drvdata(&dsidev->dev); +} + +static inline struct platform_device *dsi_get_dsidev_from_dssdev(struct omap_dss_device *dssdev) +{ +	return to_platform_device(dssdev->dev); +} + +struct platform_device *dsi_get_dsidev_from_id(int module) +{ +	struct omap_dss_device *out; +	enum omap_dss_output_id	id; + +	switch (module) { +	case 0: +		id = OMAP_DSS_OUTPUT_DSI1; +		break; +	case 1: +		id = OMAP_DSS_OUTPUT_DSI2; +		break; +	default: +		return NULL; +	} + +	out = omap_dss_get_output(id); + +	return out ? to_platform_device(out->dev) : NULL; +} + +static inline void dsi_write_reg(struct platform_device *dsidev, +		const struct dsi_reg idx, u32 val) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	void __iomem *base; + +	switch(idx.module) { +		case DSI_PROTO: base = dsi->proto_base; break; +		case DSI_PHY: base = dsi->phy_base; break; +		case DSI_PLL: base = dsi->pll_base; break; +		default: return; +	} + +	__raw_writel(val, base + idx.idx); +} + +static inline u32 dsi_read_reg(struct platform_device *dsidev, +		const struct dsi_reg idx) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	void __iomem *base; + +	switch(idx.module) { +		case DSI_PROTO: base = dsi->proto_base; break; +		case DSI_PHY: base = dsi->phy_base; break; +		case DSI_PLL: base = dsi->pll_base; break; +		default: return 0; +	} + +	return __raw_readl(base + idx.idx); +} + +static void dsi_bus_lock(struct omap_dss_device *dssdev) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	down(&dsi->bus_lock); +} + +static void dsi_bus_unlock(struct omap_dss_device *dssdev) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	up(&dsi->bus_lock); +} + +static bool dsi_bus_is_locked(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	return dsi->bus_lock.count == 0; +} + +static void dsi_completion_handler(void *data, u32 mask) +{ +	complete((struct completion *)data); +} + +static inline int wait_for_bit_change(struct platform_device *dsidev, +		const struct dsi_reg idx, int bitnum, int value) +{ +	unsigned long timeout; +	ktime_t wait; +	int t; + +	/* first busyloop to see if the bit changes right away */ +	t = 100; +	while (t-- > 0) { +		if (REG_GET(dsidev, idx, bitnum, bitnum) == value) +			return value; +	} + +	/* then loop for 500ms, sleeping for 1ms in between */ +	timeout = jiffies + msecs_to_jiffies(500); +	while (time_before(jiffies, timeout)) { +		if (REG_GET(dsidev, idx, bitnum, bitnum) == value) +			return value; + +		wait = ns_to_ktime(1000 * 1000); +		set_current_state(TASK_UNINTERRUPTIBLE); +		schedule_hrtimeout(&wait, HRTIMER_MODE_REL); +	} + +	return !value; +} + +u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt) +{ +	switch (fmt) { +	case OMAP_DSS_DSI_FMT_RGB888: +	case OMAP_DSS_DSI_FMT_RGB666: +		return 24; +	case OMAP_DSS_DSI_FMT_RGB666_PACKED: +		return 18; +	case OMAP_DSS_DSI_FMT_RGB565: +		return 16; +	default: +		BUG(); +		return 0; +	} +} + +#ifdef DSI_PERF_MEASURE +static void dsi_perf_mark_setup(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	dsi->perf_setup_time = ktime_get(); +} + +static void dsi_perf_mark_start(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	dsi->perf_start_time = ktime_get(); +} + +static void dsi_perf_show(struct platform_device *dsidev, const char *name) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	ktime_t t, setup_time, trans_time; +	u32 total_bytes; +	u32 setup_us, trans_us, total_us; + +	if (!dsi_perf) +		return; + +	t = ktime_get(); + +	setup_time = ktime_sub(dsi->perf_start_time, dsi->perf_setup_time); +	setup_us = (u32)ktime_to_us(setup_time); +	if (setup_us == 0) +		setup_us = 1; + +	trans_time = ktime_sub(t, dsi->perf_start_time); +	trans_us = (u32)ktime_to_us(trans_time); +	if (trans_us == 0) +		trans_us = 1; + +	total_us = setup_us + trans_us; + +	total_bytes = dsi->update_bytes; + +	printk(KERN_INFO "DSI(%s): %u us + %u us = %u us (%uHz), " +			"%u bytes, %u kbytes/sec\n", +			name, +			setup_us, +			trans_us, +			total_us, +			1000*1000 / total_us, +			total_bytes, +			total_bytes * 1000 / total_us); +} +#else +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 int verbose_irq; + +static void print_irq_status(u32 status) +{ +	if (status == 0) +		return; + +	if (!verbose_irq && (status & ~DSI_IRQ_CHANNEL_MASK) == 0) +		return; + +#define PIS(x) (status & DSI_IRQ_##x) ? (#x " ") : "" + +	pr_debug("DSI IRQ: 0x%x: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", +		status, +		verbose_irq ? PIS(VC0) : "", +		verbose_irq ? PIS(VC1) : "", +		verbose_irq ? PIS(VC2) : "", +		verbose_irq ? PIS(VC3) : "", +		PIS(WAKEUP), +		PIS(RESYNC), +		PIS(PLL_LOCK), +		PIS(PLL_UNLOCK), +		PIS(PLL_RECALL), +		PIS(COMPLEXIO_ERR), +		PIS(HS_TX_TIMEOUT), +		PIS(LP_RX_TIMEOUT), +		PIS(TE_TRIGGER), +		PIS(ACK_TRIGGER), +		PIS(SYNC_LOST), +		PIS(LDO_POWER_GOOD), +		PIS(TA_TIMEOUT)); +#undef PIS +} + +static void print_irq_status_vc(int channel, u32 status) +{ +	if (status == 0) +		return; + +	if (!verbose_irq && (status & ~DSI_VC_IRQ_PACKET_SENT) == 0) +		return; + +#define PIS(x) (status & DSI_VC_IRQ_##x) ? (#x " ") : "" + +	pr_debug("DSI VC(%d) IRQ 0x%x: %s%s%s%s%s%s%s%s%s\n", +		channel, +		status, +		PIS(CS), +		PIS(ECC_CORR), +		PIS(ECC_NO_CORR), +		verbose_irq ? PIS(PACKET_SENT) : "", +		PIS(BTA), +		PIS(FIFO_TX_OVF), +		PIS(FIFO_RX_OVF), +		PIS(FIFO_TX_UDF), +		PIS(PP_BUSY_CHANGE)); +#undef PIS +} + +static void print_irq_status_cio(u32 status) +{ +	if (status == 0) +		return; + +#define PIS(x) (status & DSI_CIO_IRQ_##x) ? (#x " ") : "" + +	pr_debug("DSI CIO IRQ 0x%x: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", +		status, +		PIS(ERRSYNCESC1), +		PIS(ERRSYNCESC2), +		PIS(ERRSYNCESC3), +		PIS(ERRESC1), +		PIS(ERRESC2), +		PIS(ERRESC3), +		PIS(ERRCONTROL1), +		PIS(ERRCONTROL2), +		PIS(ERRCONTROL3), +		PIS(STATEULPS1), +		PIS(STATEULPS2), +		PIS(STATEULPS3), +		PIS(ERRCONTENTIONLP0_1), +		PIS(ERRCONTENTIONLP1_1), +		PIS(ERRCONTENTIONLP0_2), +		PIS(ERRCONTENTIONLP1_2), +		PIS(ERRCONTENTIONLP0_3), +		PIS(ERRCONTENTIONLP1_3), +		PIS(ULPSACTIVENOT_ALL0), +		PIS(ULPSACTIVENOT_ALL1)); +#undef PIS +} + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dsi_collect_irq_stats(struct platform_device *dsidev, u32 irqstatus, +		u32 *vcstatus, u32 ciostatus) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int i; + +	spin_lock(&dsi->irq_stats_lock); + +	dsi->irq_stats.irq_count++; +	dss_collect_irq_stats(irqstatus, dsi->irq_stats.dsi_irqs); + +	for (i = 0; i < 4; ++i) +		dss_collect_irq_stats(vcstatus[i], dsi->irq_stats.vc_irqs[i]); + +	dss_collect_irq_stats(ciostatus, dsi->irq_stats.cio_irqs); + +	spin_unlock(&dsi->irq_stats_lock); +} +#else +#define dsi_collect_irq_stats(dsidev, irqstatus, vcstatus, ciostatus) +#endif + +static int debug_irq; + +static void dsi_handle_irq_errors(struct platform_device *dsidev, u32 irqstatus, +		u32 *vcstatus, u32 ciostatus) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int i; + +	if (irqstatus & DSI_IRQ_ERROR_MASK) { +		DSSERR("DSI error, irqstatus %x\n", irqstatus); +		print_irq_status(irqstatus); +		spin_lock(&dsi->errors_lock); +		dsi->errors |= irqstatus & DSI_IRQ_ERROR_MASK; +		spin_unlock(&dsi->errors_lock); +	} else if (debug_irq) { +		print_irq_status(irqstatus); +	} + +	for (i = 0; i < 4; ++i) { +		if (vcstatus[i] & DSI_VC_IRQ_ERROR_MASK) { +			DSSERR("DSI VC(%d) error, vc irqstatus %x\n", +				       i, vcstatus[i]); +			print_irq_status_vc(i, vcstatus[i]); +		} else if (debug_irq) { +			print_irq_status_vc(i, vcstatus[i]); +		} +	} + +	if (ciostatus & DSI_CIO_IRQ_ERROR_MASK) { +		DSSERR("DSI CIO error, cio irqstatus %x\n", ciostatus); +		print_irq_status_cio(ciostatus); +	} else if (debug_irq) { +		print_irq_status_cio(ciostatus); +	} +} + +static void dsi_call_isrs(struct dsi_isr_data *isr_array, +		unsigned isr_array_size, u32 irqstatus) +{ +	struct dsi_isr_data *isr_data; +	int i; + +	for (i = 0; i < isr_array_size; i++) { +		isr_data = &isr_array[i]; +		if (isr_data->isr && isr_data->mask & irqstatus) +			isr_data->isr(isr_data->arg, irqstatus); +	} +} + +static void dsi_handle_isrs(struct dsi_isr_tables *isr_tables, +		u32 irqstatus, u32 *vcstatus, u32 ciostatus) +{ +	int i; + +	dsi_call_isrs(isr_tables->isr_table, +			ARRAY_SIZE(isr_tables->isr_table), +			irqstatus); + +	for (i = 0; i < 4; ++i) { +		if (vcstatus[i] == 0) +			continue; +		dsi_call_isrs(isr_tables->isr_table_vc[i], +				ARRAY_SIZE(isr_tables->isr_table_vc[i]), +				vcstatus[i]); +	} + +	if (ciostatus != 0) +		dsi_call_isrs(isr_tables->isr_table_cio, +				ARRAY_SIZE(isr_tables->isr_table_cio), +				ciostatus); +} + +static irqreturn_t omap_dsi_irq_handler(int irq, void *arg) +{ +	struct platform_device *dsidev; +	struct dsi_data *dsi; +	u32 irqstatus, vcstatus[4], ciostatus; +	int i; + +	dsidev = (struct platform_device *) arg; +	dsi = dsi_get_dsidrv_data(dsidev); + +	if (!dsi->is_enabled) +		return IRQ_NONE; + +	spin_lock(&dsi->irq_lock); + +	irqstatus = dsi_read_reg(dsidev, DSI_IRQSTATUS); + +	/* IRQ is not for us */ +	if (!irqstatus) { +		spin_unlock(&dsi->irq_lock); +		return IRQ_NONE; +	} + +	dsi_write_reg(dsidev, DSI_IRQSTATUS, irqstatus & ~DSI_IRQ_CHANNEL_MASK); +	/* flush posted write */ +	dsi_read_reg(dsidev, DSI_IRQSTATUS); + +	for (i = 0; i < 4; ++i) { +		if ((irqstatus & (1 << i)) == 0) { +			vcstatus[i] = 0; +			continue; +		} + +		vcstatus[i] = dsi_read_reg(dsidev, DSI_VC_IRQSTATUS(i)); + +		dsi_write_reg(dsidev, DSI_VC_IRQSTATUS(i), vcstatus[i]); +		/* flush posted write */ +		dsi_read_reg(dsidev, DSI_VC_IRQSTATUS(i)); +	} + +	if (irqstatus & DSI_IRQ_COMPLEXIO_ERR) { +		ciostatus = dsi_read_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS); + +		dsi_write_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS, ciostatus); +		/* flush posted write */ +		dsi_read_reg(dsidev, DSI_COMPLEXIO_IRQ_STATUS); +	} else { +		ciostatus = 0; +	} + +#ifdef DSI_CATCH_MISSING_TE +	if (irqstatus & DSI_IRQ_TE_TRIGGER) +		del_timer(&dsi->te_timer); +#endif + +	/* make a copy and unlock, so that isrs can unregister +	 * themselves */ +	memcpy(&dsi->isr_tables_copy, &dsi->isr_tables, +		sizeof(dsi->isr_tables)); + +	spin_unlock(&dsi->irq_lock); + +	dsi_handle_isrs(&dsi->isr_tables_copy, irqstatus, vcstatus, ciostatus); + +	dsi_handle_irq_errors(dsidev, irqstatus, vcstatus, ciostatus); + +	dsi_collect_irq_stats(dsidev, irqstatus, vcstatus, ciostatus); + +	return IRQ_HANDLED; +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_configure_irqs(struct platform_device *dsidev, +		struct dsi_isr_data *isr_array, +		unsigned isr_array_size, u32 default_mask, +		const struct dsi_reg enable_reg, +		const struct dsi_reg status_reg) +{ +	struct dsi_isr_data *isr_data; +	u32 mask; +	u32 old_mask; +	int i; + +	mask = default_mask; + +	for (i = 0; i < isr_array_size; i++) { +		isr_data = &isr_array[i]; + +		if (isr_data->isr == NULL) +			continue; + +		mask |= isr_data->mask; +	} + +	old_mask = dsi_read_reg(dsidev, enable_reg); +	/* clear the irqstatus for newly enabled irqs */ +	dsi_write_reg(dsidev, status_reg, (mask ^ old_mask) & mask); +	dsi_write_reg(dsidev, enable_reg, mask); + +	/* flush posted writes */ +	dsi_read_reg(dsidev, enable_reg); +	dsi_read_reg(dsidev, status_reg); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 mask = DSI_IRQ_ERROR_MASK; +#ifdef DSI_CATCH_MISSING_TE +	mask |= DSI_IRQ_TE_TRIGGER; +#endif +	_omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table, +			ARRAY_SIZE(dsi->isr_tables.isr_table), mask, +			DSI_IRQENABLE, DSI_IRQSTATUS); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs_vc(struct platform_device *dsidev, int vc) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	_omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table_vc[vc], +			ARRAY_SIZE(dsi->isr_tables.isr_table_vc[vc]), +			DSI_VC_IRQ_ERROR_MASK, +			DSI_VC_IRQENABLE(vc), DSI_VC_IRQSTATUS(vc)); +} + +/* dsi->irq_lock has to be locked by the caller */ +static void _omap_dsi_set_irqs_cio(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	_omap_dsi_configure_irqs(dsidev, dsi->isr_tables.isr_table_cio, +			ARRAY_SIZE(dsi->isr_tables.isr_table_cio), +			DSI_CIO_IRQ_ERROR_MASK, +			DSI_COMPLEXIO_IRQ_ENABLE, DSI_COMPLEXIO_IRQ_STATUS); +} + +static void _dsi_initialize_irq(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int vc; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	memset(&dsi->isr_tables, 0, sizeof(dsi->isr_tables)); + +	_omap_dsi_set_irqs(dsidev); +	for (vc = 0; vc < 4; ++vc) +		_omap_dsi_set_irqs_vc(dsidev, vc); +	_omap_dsi_set_irqs_cio(dsidev); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); +} + +static int _dsi_register_isr(omap_dsi_isr_t isr, void *arg, u32 mask, +		struct dsi_isr_data *isr_array, unsigned isr_array_size) +{ +	struct dsi_isr_data *isr_data; +	int free_idx; +	int i; + +	BUG_ON(isr == NULL); + +	/* check for duplicate entry and find a free slot */ +	free_idx = -1; +	for (i = 0; i < isr_array_size; i++) { +		isr_data = &isr_array[i]; + +		if (isr_data->isr == isr && isr_data->arg == arg && +				isr_data->mask == mask) { +			return -EINVAL; +		} + +		if (isr_data->isr == NULL && free_idx == -1) +			free_idx = i; +	} + +	if (free_idx == -1) +		return -EBUSY; + +	isr_data = &isr_array[free_idx]; +	isr_data->isr = isr; +	isr_data->arg = arg; +	isr_data->mask = mask; + +	return 0; +} + +static int _dsi_unregister_isr(omap_dsi_isr_t isr, void *arg, u32 mask, +		struct dsi_isr_data *isr_array, unsigned isr_array_size) +{ +	struct dsi_isr_data *isr_data; +	int i; + +	for (i = 0; i < isr_array_size; i++) { +		isr_data = &isr_array[i]; +		if (isr_data->isr != isr || isr_data->arg != arg || +				isr_data->mask != mask) +			continue; + +		isr_data->isr = NULL; +		isr_data->arg = NULL; +		isr_data->mask = 0; + +		return 0; +	} + +	return -EINVAL; +} + +static int dsi_register_isr(struct platform_device *dsidev, omap_dsi_isr_t isr, +		void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_register_isr(isr, arg, mask, dsi->isr_tables.isr_table, +			ARRAY_SIZE(dsi->isr_tables.isr_table)); + +	if (r == 0) +		_omap_dsi_set_irqs(dsidev); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static int dsi_unregister_isr(struct platform_device *dsidev, +		omap_dsi_isr_t isr, void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_unregister_isr(isr, arg, mask, dsi->isr_tables.isr_table, +			ARRAY_SIZE(dsi->isr_tables.isr_table)); + +	if (r == 0) +		_omap_dsi_set_irqs(dsidev); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static int dsi_register_isr_vc(struct platform_device *dsidev, int channel, +		omap_dsi_isr_t isr, void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_register_isr(isr, arg, mask, +			dsi->isr_tables.isr_table_vc[channel], +			ARRAY_SIZE(dsi->isr_tables.isr_table_vc[channel])); + +	if (r == 0) +		_omap_dsi_set_irqs_vc(dsidev, channel); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static int dsi_unregister_isr_vc(struct platform_device *dsidev, int channel, +		omap_dsi_isr_t isr, void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_unregister_isr(isr, arg, mask, +			dsi->isr_tables.isr_table_vc[channel], +			ARRAY_SIZE(dsi->isr_tables.isr_table_vc[channel])); + +	if (r == 0) +		_omap_dsi_set_irqs_vc(dsidev, channel); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static int dsi_register_isr_cio(struct platform_device *dsidev, +		omap_dsi_isr_t isr, void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_register_isr(isr, arg, mask, dsi->isr_tables.isr_table_cio, +			ARRAY_SIZE(dsi->isr_tables.isr_table_cio)); + +	if (r == 0) +		_omap_dsi_set_irqs_cio(dsidev); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static int dsi_unregister_isr_cio(struct platform_device *dsidev, +		omap_dsi_isr_t isr, void *arg, u32 mask) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	int r; + +	spin_lock_irqsave(&dsi->irq_lock, flags); + +	r = _dsi_unregister_isr(isr, arg, mask, dsi->isr_tables.isr_table_cio, +			ARRAY_SIZE(dsi->isr_tables.isr_table_cio)); + +	if (r == 0) +		_omap_dsi_set_irqs_cio(dsidev); + +	spin_unlock_irqrestore(&dsi->irq_lock, flags); + +	return r; +} + +static u32 dsi_get_errors(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	u32 e; +	spin_lock_irqsave(&dsi->errors_lock, flags); +	e = dsi->errors; +	dsi->errors = 0; +	spin_unlock_irqrestore(&dsi->errors_lock, flags); +	return e; +} + +int dsi_runtime_get(struct platform_device *dsidev) +{ +	int r; +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	DSSDBG("dsi_runtime_get\n"); + +	r = pm_runtime_get_sync(&dsi->pdev->dev); +	WARN_ON(r < 0); +	return r < 0 ? r : 0; +} + +void dsi_runtime_put(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r; + +	DSSDBG("dsi_runtime_put\n"); + +	r = pm_runtime_put_sync(&dsi->pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} + +static int dsi_regulator_init(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct regulator *vdds_dsi; +	int r; + +	if (dsi->vdds_dsi_reg != NULL) +		return 0; + +	vdds_dsi = devm_regulator_get(&dsi->pdev->dev, "vdd"); + +	if (IS_ERR(vdds_dsi)) { +		if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) +			DSSERR("can't get DSI VDD regulator\n"); +		return PTR_ERR(vdds_dsi); +	} + +	if (regulator_can_change_voltage(vdds_dsi)) { +		r = regulator_set_voltage(vdds_dsi, 1800000, 1800000); +		if (r) { +			devm_regulator_put(vdds_dsi); +			DSSERR("can't set the DSI regulator voltage\n"); +			return r; +		} +	} + +	dsi->vdds_dsi_reg = vdds_dsi; + +	return 0; +} + +/* source clock for DSI PLL. this could also be PCLKFREE */ +static inline void dsi_enable_pll_clock(struct platform_device *dsidev, +		bool enable) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (enable) +		clk_prepare_enable(dsi->sys_clk); +	else +		clk_disable_unprepare(dsi->sys_clk); + +	if (enable && dsi->pll_locked) { +		if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 1, 1) != 1) +			DSSERR("cannot lock PLL when enabling clocks\n"); +	} +} + +static void _dsi_print_reset_status(struct platform_device *dsidev) +{ +	u32 l; +	int b0, b1, b2; + +	/* A dummy read using the SCP interface to any DSIPHY register is +	 * required after DSIPHY reset to complete the reset of the DSI complex +	 * I/O. */ +	l = dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + +	if (dss_has_feature(FEAT_DSI_REVERSE_TXCLKESC)) { +		b0 = 28; +		b1 = 27; +		b2 = 26; +	} else { +		b0 = 24; +		b1 = 25; +		b2 = 26; +	} + +#define DSI_FLD_GET(fld, start, end)\ +	FLD_GET(dsi_read_reg(dsidev, DSI_##fld), start, end) + +	pr_debug("DSI resets: PLL (%d) CIO (%d) PHY (%x%x%x, %d, %d, %d)\n", +		DSI_FLD_GET(PLL_STATUS, 0, 0), +		DSI_FLD_GET(COMPLEXIO_CFG1, 29, 29), +		DSI_FLD_GET(DSIPHY_CFG5, b0, b0), +		DSI_FLD_GET(DSIPHY_CFG5, b1, b1), +		DSI_FLD_GET(DSIPHY_CFG5, b2, b2), +		DSI_FLD_GET(DSIPHY_CFG5, 29, 29), +		DSI_FLD_GET(DSIPHY_CFG5, 30, 30), +		DSI_FLD_GET(DSIPHY_CFG5, 31, 31)); + +#undef DSI_FLD_GET +} + +static inline int dsi_if_enable(struct platform_device *dsidev, bool enable) +{ +	DSSDBG("dsi_if_enable(%d)\n", enable); + +	enable = enable ? 1 : 0; +	REG_FLD_MOD(dsidev, DSI_CTRL, enable, 0, 0); /* IF_EN */ + +	if (wait_for_bit_change(dsidev, DSI_CTRL, 0, enable) != enable) { +			DSSERR("Failed to set dsi_if_enable to %d\n", enable); +			return -EIO; +	} + +	return 0; +} + +unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	return dsi->current_cinfo.dsi_pll_hsdiv_dispc_clk; +} + +static unsigned long dsi_get_pll_hsdiv_dsi_rate(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	return dsi->current_cinfo.dsi_pll_hsdiv_dsi_clk; +} + +static unsigned long dsi_get_txbyteclkhs(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	return dsi->current_cinfo.clkin4ddr / 16; +} + +static unsigned long dsi_fclk_rate(struct platform_device *dsidev) +{ +	unsigned long r; +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (dss_get_dsi_clk_source(dsi->module_id) == OMAP_DSS_CLK_SRC_FCK) { +		/* DSI FCLK source is 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); +	} + +	return r; +} + +static int dsi_lp_clock_calc(struct dsi_clock_info *cinfo, +		unsigned long lp_clk_min, unsigned long lp_clk_max) +{ +	unsigned long dsi_fclk = cinfo->dsi_pll_hsdiv_dsi_clk; +	unsigned lp_clk_div; +	unsigned long lp_clk; + +	lp_clk_div = DIV_ROUND_UP(dsi_fclk, lp_clk_max * 2); +	lp_clk = dsi_fclk / 2 / lp_clk_div; + +	if (lp_clk < lp_clk_min || lp_clk > lp_clk_max) +		return -EINVAL; + +	cinfo->lp_clk_div = lp_clk_div; +	cinfo->lp_clk = lp_clk; + +	return 0; +} + +static int dsi_set_lp_clk_divisor(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long dsi_fclk; +	unsigned lp_clk_div; +	unsigned long lp_clk; + +	lp_clk_div = dsi->user_dsi_cinfo.lp_clk_div; + +	if (lp_clk_div == 0 || lp_clk_div > dsi->lpdiv_max) +		return -EINVAL; + +	dsi_fclk = dsi_fclk_rate(dsidev); + +	lp_clk = dsi_fclk / 2 / lp_clk_div; + +	DSSDBG("LP_CLK_DIV %u, LP_CLK %lu\n", lp_clk_div, lp_clk); +	dsi->current_cinfo.lp_clk = lp_clk; +	dsi->current_cinfo.lp_clk_div = lp_clk_div; + +	/* LP_CLK_DIVISOR */ +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, lp_clk_div, 12, 0); + +	/* LP_RX_SYNCHRO_ENABLE */ +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, dsi_fclk > 30000000 ? 1 : 0, 21, 21); + +	return 0; +} + +static void dsi_enable_scp_clk(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (dsi->scp_clk_refcount++ == 0) +		REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 14, 14); /* CIO_CLK_ICG */ +} + +static void dsi_disable_scp_clk(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	WARN_ON(dsi->scp_clk_refcount == 0); +	if (--dsi->scp_clk_refcount == 0) +		REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 14, 14); /* CIO_CLK_ICG */ +} + +enum dsi_pll_power_state { +	DSI_PLL_POWER_OFF	= 0x0, +	DSI_PLL_POWER_ON_HSCLK	= 0x1, +	DSI_PLL_POWER_ON_ALL	= 0x2, +	DSI_PLL_POWER_ON_DIV	= 0x3, +}; + +static int dsi_pll_power(struct platform_device *dsidev, +		enum dsi_pll_power_state state) +{ +	int t = 0; + +	/* DSI-PLL power command 0x3 is not working */ +	if (dss_has_feature(FEAT_DSI_PLL_PWR_BUG) && +			state == DSI_PLL_POWER_ON_DIV) +		state = DSI_PLL_POWER_ON_ALL; + +	/* PLL_PWR_CMD */ +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, state, 31, 30); + +	/* PLL_PWR_STATUS */ +	while (FLD_GET(dsi_read_reg(dsidev, DSI_CLK_CTRL), 29, 28) != state) { +		if (++t > 1000) { +			DSSERR("Failed to set DSI PLL power mode to %d\n", +					state); +			return -ENODEV; +		} +		udelay(1); +	} + +	return 0; +} + +unsigned long dsi_get_pll_clkin(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	return clk_get_rate(dsi->sys_clk); +} + +bool dsi_hsdiv_calc(struct platform_device *dsidev, unsigned long pll, +		unsigned long out_min, dsi_hsdiv_calc_func func, void *data) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int regm, regm_start, regm_stop; +	unsigned long out_max; +	unsigned long out; + +	out_min = out_min ? out_min : 1; +	out_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + +	regm_start = max(DIV_ROUND_UP(pll, out_max), 1ul); +	regm_stop = min(pll / out_min, dsi->regm_dispc_max); + +	for (regm = regm_start; regm <= regm_stop; ++regm) { +		out = pll / regm; + +		if (func(regm, out, data)) +			return true; +	} + +	return false; +} + +bool dsi_pll_calc(struct platform_device *dsidev, unsigned long clkin, +		unsigned long pll_min, unsigned long pll_max, +		dsi_pll_calc_func func, void *data) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int regn, regn_start, regn_stop; +	int regm, regm_start, regm_stop; +	unsigned long fint, pll; +	const unsigned long pll_hw_max = 1800000000; +	unsigned long fint_hw_min, fint_hw_max; + +	fint_hw_min = dsi->fint_min; +	fint_hw_max = dsi->fint_max; + +	regn_start = max(DIV_ROUND_UP(clkin, fint_hw_max), 1ul); +	regn_stop = min(clkin / fint_hw_min, dsi->regn_max); + +	pll_max = pll_max ? pll_max : ULONG_MAX; + +	for (regn = regn_start; regn <= regn_stop; ++regn) { +		fint = clkin / regn; + +		regm_start = max(DIV_ROUND_UP(DIV_ROUND_UP(pll_min, fint), 2), +				1ul); +		regm_stop = min3(pll_max / fint / 2, +				pll_hw_max / fint / 2, +				dsi->regm_max); + +		for (regm = regm_start; regm <= regm_stop; ++regm) { +			pll = 2 * regm * fint; + +			if (func(regn, regm, fint, pll, data)) +				return true; +		} +	} + +	return false; +} + +/* calculate clock rates using dividers in cinfo */ +static int dsi_calc_clock_rates(struct platform_device *dsidev, +		struct dsi_clock_info *cinfo) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (cinfo->regn == 0 || cinfo->regn > dsi->regn_max) +		return -EINVAL; + +	if (cinfo->regm == 0 || cinfo->regm > dsi->regm_max) +		return -EINVAL; + +	if (cinfo->regm_dispc > dsi->regm_dispc_max) +		return -EINVAL; + +	if (cinfo->regm_dsi > dsi->regm_dsi_max) +		return -EINVAL; + +	cinfo->clkin = clk_get_rate(dsi->sys_clk); +	cinfo->fint = cinfo->clkin / cinfo->regn; + +	if (cinfo->fint > dsi->fint_max || cinfo->fint < dsi->fint_min) +		return -EINVAL; + +	cinfo->clkin4ddr = 2 * cinfo->regm * cinfo->fint; + +	if (cinfo->clkin4ddr > 1800 * 1000 * 1000) +		return -EINVAL; + +	if (cinfo->regm_dispc > 0) +		cinfo->dsi_pll_hsdiv_dispc_clk = +			cinfo->clkin4ddr / cinfo->regm_dispc; +	else +		cinfo->dsi_pll_hsdiv_dispc_clk = 0; + +	if (cinfo->regm_dsi > 0) +		cinfo->dsi_pll_hsdiv_dsi_clk = +			cinfo->clkin4ddr / cinfo->regm_dsi; +	else +		cinfo->dsi_pll_hsdiv_dsi_clk = 0; + +	return 0; +} + +static void dsi_pll_calc_dsi_fck(struct dsi_clock_info *cinfo) +{ +	unsigned long max_dsi_fck; + +	max_dsi_fck = dss_feat_get_param_max(FEAT_PARAM_DSI_FCK); + +	cinfo->regm_dsi = DIV_ROUND_UP(cinfo->clkin4ddr, max_dsi_fck); +	cinfo->dsi_pll_hsdiv_dsi_clk = cinfo->clkin4ddr / cinfo->regm_dsi; +} + +int dsi_pll_set_clock_div(struct platform_device *dsidev, +		struct dsi_clock_info *cinfo) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r = 0; +	u32 l; +	int f = 0; +	u8 regn_start, regn_end, regm_start, regm_end; +	u8 regm_dispc_start, regm_dispc_end, regm_dsi_start, regm_dsi_end; + +	DSSDBG("DSI PLL clock config starts"); + +	dsi->current_cinfo.clkin = cinfo->clkin; +	dsi->current_cinfo.fint = cinfo->fint; +	dsi->current_cinfo.clkin4ddr = cinfo->clkin4ddr; +	dsi->current_cinfo.dsi_pll_hsdiv_dispc_clk = +			cinfo->dsi_pll_hsdiv_dispc_clk; +	dsi->current_cinfo.dsi_pll_hsdiv_dsi_clk = +			cinfo->dsi_pll_hsdiv_dsi_clk; + +	dsi->current_cinfo.regn = cinfo->regn; +	dsi->current_cinfo.regm = cinfo->regm; +	dsi->current_cinfo.regm_dispc = cinfo->regm_dispc; +	dsi->current_cinfo.regm_dsi = cinfo->regm_dsi; + +	DSSDBG("DSI Fint %ld\n", cinfo->fint); + +	DSSDBG("clkin rate %ld\n", cinfo->clkin); + +	/* DSIPHY == CLKIN4DDR */ +	DSSDBG("CLKIN4DDR = 2 * %d / %d * %lu = %lu\n", +			cinfo->regm, +			cinfo->regn, +			cinfo->clkin, +			cinfo->clkin4ddr); + +	DSSDBG("Data rate on 1 DSI lane %ld Mbps\n", +			cinfo->clkin4ddr / 1000 / 1000 / 2); + +	DSSDBG("Clock lane freq %ld Hz\n", cinfo->clkin4ddr / 4); + +	DSSDBG("regm_dispc = %d, %s (%s) = %lu\n", cinfo->regm_dispc, +		dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), +		dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), +		cinfo->dsi_pll_hsdiv_dispc_clk); +	DSSDBG("regm_dsi = %d, %s (%s) = %lu\n", cinfo->regm_dsi, +		dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), +		dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), +		cinfo->dsi_pll_hsdiv_dsi_clk); + +	dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGN, ®n_start, ®n_end); +	dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM, ®m_start, ®m_end); +	dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM_DISPC, ®m_dispc_start, +			®m_dispc_end); +	dss_feat_get_reg_field(FEAT_REG_DSIPLL_REGM_DSI, ®m_dsi_start, +			®m_dsi_end); + +	/* DSI_PLL_AUTOMODE = manual */ +	REG_FLD_MOD(dsidev, DSI_PLL_CONTROL, 0, 0, 0); + +	l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION1); +	l = FLD_MOD(l, 1, 0, 0);		/* DSI_PLL_STOPMODE */ +	/* DSI_PLL_REGN */ +	l = FLD_MOD(l, cinfo->regn - 1, regn_start, regn_end); +	/* DSI_PLL_REGM */ +	l = FLD_MOD(l, cinfo->regm, regm_start, regm_end); +	/* DSI_CLOCK_DIV */ +	l = FLD_MOD(l, cinfo->regm_dispc > 0 ? cinfo->regm_dispc - 1 : 0, +			regm_dispc_start, regm_dispc_end); +	/* DSIPROTO_CLOCK_DIV */ +	l = FLD_MOD(l, cinfo->regm_dsi > 0 ? cinfo->regm_dsi - 1 : 0, +			regm_dsi_start, regm_dsi_end); +	dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION1, l); + +	BUG_ON(cinfo->fint < dsi->fint_min || cinfo->fint > dsi->fint_max); + +	l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION2); + +	if (dss_has_feature(FEAT_DSI_PLL_FREQSEL)) { +		f = cinfo->fint < 1000000 ? 0x3 : +			cinfo->fint < 1250000 ? 0x4 : +			cinfo->fint < 1500000 ? 0x5 : +			cinfo->fint < 1750000 ? 0x6 : +			0x7; + +		l = FLD_MOD(l, f, 4, 1);	/* DSI_PLL_FREQSEL */ +	} else if (dss_has_feature(FEAT_DSI_PLL_SELFREQDCO)) { +		f = cinfo->clkin4ddr < 1000000000 ? 0x2 : 0x4; + +		l = FLD_MOD(l, f, 4, 1);	/* PLL_SELFREQDCO */ +	} + +	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 (dss_has_feature(FEAT_DSI_PLL_REFSEL)) +		l = FLD_MOD(l, 3, 22, 21);	/* REF_SYSCLK = sysclk */ +	dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION2, l); + +	REG_FLD_MOD(dsidev, DSI_PLL_GO, 1, 0, 0);	/* DSI_PLL_GO */ + +	if (wait_for_bit_change(dsidev, DSI_PLL_GO, 0, 0) != 0) { +		DSSERR("dsi pll go bit not going down.\n"); +		r = -EIO; +		goto err; +	} + +	if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 1, 1) != 1) { +		DSSERR("cannot lock PLL\n"); +		r = -EIO; +		goto err; +	} + +	dsi->pll_locked = 1; + +	l = dsi_read_reg(dsidev, DSI_PLL_CONFIGURATION2); +	l = FLD_MOD(l, 0, 0, 0);	/* DSI_PLL_IDLE */ +	l = FLD_MOD(l, 0, 5, 5);	/* DSI_PLL_PLLLPMODE */ +	l = FLD_MOD(l, 0, 6, 6);	/* DSI_PLL_LOWCURRSTBY */ +	l = FLD_MOD(l, 0, 7, 7);	/* DSI_PLL_TIGHTPHASELOCK */ +	l = FLD_MOD(l, 0, 8, 8);	/* DSI_PLL_DRIFTGUARDEN */ +	l = FLD_MOD(l, 0, 10, 9);	/* DSI_PLL_LOCKSEL */ +	l = FLD_MOD(l, 1, 13, 13);	/* DSI_PLL_REFEN */ +	l = FLD_MOD(l, 1, 14, 14);	/* DSIPHY_CLKINEN */ +	l = FLD_MOD(l, 0, 15, 15);	/* DSI_BYPASSEN */ +	l = FLD_MOD(l, 1, 16, 16);	/* DSS_CLOCK_EN */ +	l = FLD_MOD(l, 0, 17, 17);	/* DSS_CLOCK_PWDN */ +	l = FLD_MOD(l, 1, 18, 18);	/* DSI_PROTO_CLOCK_EN */ +	l = FLD_MOD(l, 0, 19, 19);	/* DSI_PROTO_CLOCK_PWDN */ +	l = FLD_MOD(l, 0, 20, 20);	/* DSI_HSDIVBYPASS */ +	dsi_write_reg(dsidev, DSI_PLL_CONFIGURATION2, l); + +	DSSDBG("PLL config done\n"); +err: +	return r; +} + +int dsi_pll_init(struct platform_device *dsidev, bool enable_hsclk, +		bool enable_hsdiv) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r = 0; +	enum dsi_pll_power_state pwstate; + +	DSSDBG("PLL init\n"); + +	/* +	 * It seems that on many OMAPs we need to enable both to have a +	 * functional HSDivider. +	 */ +	enable_hsclk = enable_hsdiv = true; + +	r = dsi_regulator_init(dsidev); +	if (r) +		return r; + +	dsi_enable_pll_clock(dsidev, 1); +	/* +	 * Note: SCP CLK is not required on OMAP3, but it is required on OMAP4. +	 */ +	dsi_enable_scp_clk(dsidev); + +	if (!dsi->vdds_dsi_enabled) { +		r = regulator_enable(dsi->vdds_dsi_reg); +		if (r) +			goto err0; +		dsi->vdds_dsi_enabled = true; +	} + +	/* XXX PLL does not come out of reset without this... */ +	dispc_pck_free_enable(1); + +	if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 0, 1) != 1) { +		DSSERR("PLL not coming out of reset.\n"); +		r = -ENODEV; +		dispc_pck_free_enable(0); +		goto err1; +	} + +	/* XXX ... but if left on, we get problems when planes do not +	 * fill the whole display. No idea about this */ +	dispc_pck_free_enable(0); + +	if (enable_hsclk && enable_hsdiv) +		pwstate = DSI_PLL_POWER_ON_ALL; +	else if (enable_hsclk) +		pwstate = DSI_PLL_POWER_ON_HSCLK; +	else if (enable_hsdiv) +		pwstate = DSI_PLL_POWER_ON_DIV; +	else +		pwstate = DSI_PLL_POWER_OFF; + +	r = dsi_pll_power(dsidev, pwstate); + +	if (r) +		goto err1; + +	DSSDBG("PLL init done\n"); + +	return 0; +err1: +	if (dsi->vdds_dsi_enabled) { +		regulator_disable(dsi->vdds_dsi_reg); +		dsi->vdds_dsi_enabled = false; +	} +err0: +	dsi_disable_scp_clk(dsidev); +	dsi_enable_pll_clock(dsidev, 0); +	return r; +} + +void dsi_pll_uninit(struct platform_device *dsidev, bool disconnect_lanes) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	dsi->pll_locked = 0; +	dsi_pll_power(dsidev, DSI_PLL_POWER_OFF); +	if (disconnect_lanes) { +		WARN_ON(!dsi->vdds_dsi_enabled); +		regulator_disable(dsi->vdds_dsi_reg); +		dsi->vdds_dsi_enabled = false; +	} + +	dsi_disable_scp_clk(dsidev); +	dsi_enable_pll_clock(dsidev, 0); + +	DSSDBG("PLL uninit done\n"); +} + +static void dsi_dump_dsidev_clocks(struct platform_device *dsidev, +		struct seq_file *s) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct dsi_clock_info *cinfo = &dsi->current_cinfo; +	enum omap_dss_clk_source dispc_clk_src, dsi_clk_src; +	int dsi_module = dsi->module_id; + +	dispc_clk_src = dss_get_dispc_clk_source(); +	dsi_clk_src = dss_get_dsi_clk_source(dsi_module); + +	if (dsi_runtime_get(dsidev)) +		return; + +	seq_printf(s,	"- DSI%d PLL -\n", dsi_module + 1); + +	seq_printf(s,	"dsi pll clkin\t%lu\n", cinfo->clkin); + +	seq_printf(s,	"Fint\t\t%-16luregn %u\n", cinfo->fint, cinfo->regn); + +	seq_printf(s,	"CLKIN4DDR\t%-16luregm %u\n", +			cinfo->clkin4ddr, cinfo->regm); + +	seq_printf(s,	"DSI_PLL_HSDIV_DISPC (%s)\t%-16luregm_dispc %u\t(%s)\n", +			dss_feat_get_clk_source_name(dsi_module == 0 ? +				OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC : +				OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC), +			cinfo->dsi_pll_hsdiv_dispc_clk, +			cinfo->regm_dispc, +			dispc_clk_src == OMAP_DSS_CLK_SRC_FCK ? +			"off" : "on"); + +	seq_printf(s,	"DSI_PLL_HSDIV_DSI (%s)\t%-16luregm_dsi %u\t(%s)\n", +			dss_feat_get_clk_source_name(dsi_module == 0 ? +				OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI : +				OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI), +			cinfo->dsi_pll_hsdiv_dsi_clk, +			cinfo->regm_dsi, +			dsi_clk_src == OMAP_DSS_CLK_SRC_FCK ? +			"off" : "on"); + +	seq_printf(s,	"- DSI%d -\n", dsi_module + 1); + +	seq_printf(s,	"dsi fclk source = %s (%s)\n", +			dss_get_generic_clk_source_name(dsi_clk_src), +			dss_feat_get_clk_source_name(dsi_clk_src)); + +	seq_printf(s,	"DSI_FCLK\t%lu\n", dsi_fclk_rate(dsidev)); + +	seq_printf(s,	"DDR_CLK\t\t%lu\n", +			cinfo->clkin4ddr / 4); + +	seq_printf(s,	"TxByteClkHS\t%lu\n", dsi_get_txbyteclkhs(dsidev)); + +	seq_printf(s,	"LP_CLK\t\t%lu\n", cinfo->lp_clk); + +	dsi_runtime_put(dsidev); +} + +void dsi_dump_clocks(struct seq_file *s) +{ +	struct platform_device *dsidev; +	int i; + +	for  (i = 0; i < MAX_NUM_DSI; i++) { +		dsidev = dsi_get_dsidev_from_id(i); +		if (dsidev) +			dsi_dump_dsidev_clocks(dsidev, s); +	} +} + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static void dsi_dump_dsidev_irqs(struct platform_device *dsidev, +		struct seq_file *s) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned long flags; +	struct dsi_irq_stats stats; + +	spin_lock_irqsave(&dsi->irq_stats_lock, flags); + +	stats = dsi->irq_stats; +	memset(&dsi->irq_stats, 0, sizeof(dsi->irq_stats)); +	dsi->irq_stats.last_reset = jiffies; + +	spin_unlock_irqrestore(&dsi->irq_stats_lock, flags); + +	seq_printf(s, "period %u ms\n", +			jiffies_to_msecs(jiffies - stats.last_reset)); + +	seq_printf(s, "irqs %d\n", stats.irq_count); +#define PIS(x) \ +	seq_printf(s, "%-20s %10d\n", #x, stats.dsi_irqs[ffs(DSI_IRQ_##x)-1]); + +	seq_printf(s, "-- DSI%d interrupts --\n", dsi->module_id + 1); +	PIS(VC0); +	PIS(VC1); +	PIS(VC2); +	PIS(VC3); +	PIS(WAKEUP); +	PIS(RESYNC); +	PIS(PLL_LOCK); +	PIS(PLL_UNLOCK); +	PIS(PLL_RECALL); +	PIS(COMPLEXIO_ERR); +	PIS(HS_TX_TIMEOUT); +	PIS(LP_RX_TIMEOUT); +	PIS(TE_TRIGGER); +	PIS(ACK_TRIGGER); +	PIS(SYNC_LOST); +	PIS(LDO_POWER_GOOD); +	PIS(TA_TIMEOUT); +#undef PIS + +#define PIS(x) \ +	seq_printf(s, "%-20s %10d %10d %10d %10d\n", #x, \ +			stats.vc_irqs[0][ffs(DSI_VC_IRQ_##x)-1], \ +			stats.vc_irqs[1][ffs(DSI_VC_IRQ_##x)-1], \ +			stats.vc_irqs[2][ffs(DSI_VC_IRQ_##x)-1], \ +			stats.vc_irqs[3][ffs(DSI_VC_IRQ_##x)-1]); + +	seq_printf(s, "-- VC interrupts --\n"); +	PIS(CS); +	PIS(ECC_CORR); +	PIS(PACKET_SENT); +	PIS(FIFO_TX_OVF); +	PIS(FIFO_RX_OVF); +	PIS(BTA); +	PIS(ECC_NO_CORR); +	PIS(FIFO_TX_UDF); +	PIS(PP_BUSY_CHANGE); +#undef PIS + +#define PIS(x) \ +	seq_printf(s, "%-20s %10d\n", #x, \ +			stats.cio_irqs[ffs(DSI_CIO_IRQ_##x)-1]); + +	seq_printf(s, "-- CIO interrupts --\n"); +	PIS(ERRSYNCESC1); +	PIS(ERRSYNCESC2); +	PIS(ERRSYNCESC3); +	PIS(ERRESC1); +	PIS(ERRESC2); +	PIS(ERRESC3); +	PIS(ERRCONTROL1); +	PIS(ERRCONTROL2); +	PIS(ERRCONTROL3); +	PIS(STATEULPS1); +	PIS(STATEULPS2); +	PIS(STATEULPS3); +	PIS(ERRCONTENTIONLP0_1); +	PIS(ERRCONTENTIONLP1_1); +	PIS(ERRCONTENTIONLP0_2); +	PIS(ERRCONTENTIONLP1_2); +	PIS(ERRCONTENTIONLP0_3); +	PIS(ERRCONTENTIONLP1_3); +	PIS(ULPSACTIVENOT_ALL0); +	PIS(ULPSACTIVENOT_ALL1); +#undef PIS +} + +static void dsi1_dump_irqs(struct seq_file *s) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_id(0); + +	dsi_dump_dsidev_irqs(dsidev, s); +} + +static void dsi2_dump_irqs(struct seq_file *s) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_id(1); + +	dsi_dump_dsidev_irqs(dsidev, s); +} +#endif + +static void dsi_dump_dsidev_regs(struct platform_device *dsidev, +		struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dsi_read_reg(dsidev, r)) + +	if (dsi_runtime_get(dsidev)) +		return; +	dsi_enable_scp_clk(dsidev); + +	DUMPREG(DSI_REVISION); +	DUMPREG(DSI_SYSCONFIG); +	DUMPREG(DSI_SYSSTATUS); +	DUMPREG(DSI_IRQSTATUS); +	DUMPREG(DSI_IRQENABLE); +	DUMPREG(DSI_CTRL); +	DUMPREG(DSI_COMPLEXIO_CFG1); +	DUMPREG(DSI_COMPLEXIO_IRQ_STATUS); +	DUMPREG(DSI_COMPLEXIO_IRQ_ENABLE); +	DUMPREG(DSI_CLK_CTRL); +	DUMPREG(DSI_TIMING1); +	DUMPREG(DSI_TIMING2); +	DUMPREG(DSI_VM_TIMING1); +	DUMPREG(DSI_VM_TIMING2); +	DUMPREG(DSI_VM_TIMING3); +	DUMPREG(DSI_CLK_TIMING); +	DUMPREG(DSI_TX_FIFO_VC_SIZE); +	DUMPREG(DSI_RX_FIFO_VC_SIZE); +	DUMPREG(DSI_COMPLEXIO_CFG2); +	DUMPREG(DSI_RX_FIFO_VC_FULLNESS); +	DUMPREG(DSI_VM_TIMING4); +	DUMPREG(DSI_TX_FIFO_VC_EMPTINESS); +	DUMPREG(DSI_VM_TIMING5); +	DUMPREG(DSI_VM_TIMING6); +	DUMPREG(DSI_VM_TIMING7); +	DUMPREG(DSI_STOPCLK_TIMING); + +	DUMPREG(DSI_VC_CTRL(0)); +	DUMPREG(DSI_VC_TE(0)); +	DUMPREG(DSI_VC_LONG_PACKET_HEADER(0)); +	DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(0)); +	DUMPREG(DSI_VC_SHORT_PACKET_HEADER(0)); +	DUMPREG(DSI_VC_IRQSTATUS(0)); +	DUMPREG(DSI_VC_IRQENABLE(0)); + +	DUMPREG(DSI_VC_CTRL(1)); +	DUMPREG(DSI_VC_TE(1)); +	DUMPREG(DSI_VC_LONG_PACKET_HEADER(1)); +	DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(1)); +	DUMPREG(DSI_VC_SHORT_PACKET_HEADER(1)); +	DUMPREG(DSI_VC_IRQSTATUS(1)); +	DUMPREG(DSI_VC_IRQENABLE(1)); + +	DUMPREG(DSI_VC_CTRL(2)); +	DUMPREG(DSI_VC_TE(2)); +	DUMPREG(DSI_VC_LONG_PACKET_HEADER(2)); +	DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(2)); +	DUMPREG(DSI_VC_SHORT_PACKET_HEADER(2)); +	DUMPREG(DSI_VC_IRQSTATUS(2)); +	DUMPREG(DSI_VC_IRQENABLE(2)); + +	DUMPREG(DSI_VC_CTRL(3)); +	DUMPREG(DSI_VC_TE(3)); +	DUMPREG(DSI_VC_LONG_PACKET_HEADER(3)); +	DUMPREG(DSI_VC_LONG_PACKET_PAYLOAD(3)); +	DUMPREG(DSI_VC_SHORT_PACKET_HEADER(3)); +	DUMPREG(DSI_VC_IRQSTATUS(3)); +	DUMPREG(DSI_VC_IRQENABLE(3)); + +	DUMPREG(DSI_DSIPHY_CFG0); +	DUMPREG(DSI_DSIPHY_CFG1); +	DUMPREG(DSI_DSIPHY_CFG2); +	DUMPREG(DSI_DSIPHY_CFG5); + +	DUMPREG(DSI_PLL_CONTROL); +	DUMPREG(DSI_PLL_STATUS); +	DUMPREG(DSI_PLL_GO); +	DUMPREG(DSI_PLL_CONFIGURATION1); +	DUMPREG(DSI_PLL_CONFIGURATION2); + +	dsi_disable_scp_clk(dsidev); +	dsi_runtime_put(dsidev); +#undef DUMPREG +} + +static void dsi1_dump_regs(struct seq_file *s) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_id(0); + +	dsi_dump_dsidev_regs(dsidev, s); +} + +static void dsi2_dump_regs(struct seq_file *s) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_id(1); + +	dsi_dump_dsidev_regs(dsidev, s); +} + +enum dsi_cio_power_state { +	DSI_COMPLEXIO_POWER_OFF		= 0x0, +	DSI_COMPLEXIO_POWER_ON		= 0x1, +	DSI_COMPLEXIO_POWER_ULPS	= 0x2, +}; + +static int dsi_cio_power(struct platform_device *dsidev, +		enum dsi_cio_power_state state) +{ +	int t = 0; + +	/* PWR_CMD */ +	REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG1, state, 28, 27); + +	/* PWR_STATUS */ +	while (FLD_GET(dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG1), +			26, 25) != state) { +		if (++t > 1000) { +			DSSERR("failed to set complexio power state to " +					"%d\n", state); +			return -ENODEV; +		} +		udelay(1); +	} + +	return 0; +} + +static unsigned dsi_get_line_buf_size(struct platform_device *dsidev) +{ +	int val; + +	/* line buffer on OMAP3 is 1024 x 24bits */ +	/* XXX: for some reason using full buffer size causes +	 * considerable TX slowdown with update sizes that fill the +	 * whole buffer */ +	if (!dss_has_feature(FEAT_DSI_GNQ)) +		return 1023 * 3; + +	val = REG_GET(dsidev, DSI_GNQ, 14, 12); /* VP1_LINE_BUFFER_SIZE */ + +	switch (val) { +	case 1: +		return 512 * 3;		/* 512x24 bits */ +	case 2: +		return 682 * 3;		/* 682x24 bits */ +	case 3: +		return 853 * 3;		/* 853x24 bits */ +	case 4: +		return 1024 * 3;	/* 1024x24 bits */ +	case 5: +		return 1194 * 3;	/* 1194x24 bits */ +	case 6: +		return 1365 * 3;	/* 1365x24 bits */ +	case 7: +		return 1920 * 3;	/* 1920x24 bits */ +	default: +		BUG(); +		return 0; +	} +} + +static int dsi_set_lane_config(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	static const u8 offsets[] = { 0, 4, 8, 12, 16 }; +	static const enum dsi_lane_function functions[] = { +		DSI_LANE_CLK, +		DSI_LANE_DATA1, +		DSI_LANE_DATA2, +		DSI_LANE_DATA3, +		DSI_LANE_DATA4, +	}; +	u32 r; +	int i; + +	r = dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG1); + +	for (i = 0; i < dsi->num_lanes_used; ++i) { +		unsigned offset = offsets[i]; +		unsigned polarity, lane_number; +		unsigned t; + +		for (t = 0; t < dsi->num_lanes_supported; ++t) +			if (dsi->lanes[t].function == functions[i]) +				break; + +		if (t == dsi->num_lanes_supported) +			return -EINVAL; + +		lane_number = t; +		polarity = dsi->lanes[t].polarity; + +		r = FLD_MOD(r, lane_number + 1, offset + 2, offset); +		r = FLD_MOD(r, polarity, offset + 3, offset + 3); +	} + +	/* clear the unused lanes */ +	for (; i < dsi->num_lanes_supported; ++i) { +		unsigned offset = offsets[i]; + +		r = FLD_MOD(r, 0, offset + 2, offset); +		r = FLD_MOD(r, 0, offset + 3, offset + 3); +	} + +	dsi_write_reg(dsidev, DSI_COMPLEXIO_CFG1, r); + +	return 0; +} + +static inline unsigned ns2ddr(struct platform_device *dsidev, unsigned ns) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	/* convert time in ns to ddr ticks, rounding up */ +	unsigned long ddr_clk = dsi->current_cinfo.clkin4ddr / 4; +	return (ns * (ddr_clk / 1000 / 1000) + 999) / 1000; +} + +static inline unsigned ddr2ns(struct platform_device *dsidev, unsigned ddr) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	unsigned long ddr_clk = dsi->current_cinfo.clkin4ddr / 4; +	return ddr * 1000 * 1000 / (ddr_clk / 1000); +} + +static void dsi_cio_timings(struct platform_device *dsidev) +{ +	u32 r; +	u32 ths_prepare, ths_prepare_ths_zero, ths_trail, ths_exit; +	u32 tlpx_half, tclk_trail, tclk_zero; +	u32 tclk_prepare; + +	/* calculate timings */ + +	/* 1 * DDR_CLK = 2 * UI */ + +	/* min 40ns + 4*UI	max 85ns + 6*UI */ +	ths_prepare = ns2ddr(dsidev, 70) + 2; + +	/* min 145ns + 10*UI */ +	ths_prepare_ths_zero = ns2ddr(dsidev, 175) + 2; + +	/* min max(8*UI, 60ns+4*UI) */ +	ths_trail = ns2ddr(dsidev, 60) + 5; + +	/* min 100ns */ +	ths_exit = ns2ddr(dsidev, 145); + +	/* tlpx min 50n */ +	tlpx_half = ns2ddr(dsidev, 25); + +	/* min 60ns */ +	tclk_trail = ns2ddr(dsidev, 60) + 2; + +	/* min 38ns, max 95ns */ +	tclk_prepare = ns2ddr(dsidev, 65); + +	/* min tclk-prepare + tclk-zero = 300ns */ +	tclk_zero = ns2ddr(dsidev, 260); + +	DSSDBG("ths_prepare %u (%uns), ths_prepare_ths_zero %u (%uns)\n", +		ths_prepare, ddr2ns(dsidev, ths_prepare), +		ths_prepare_ths_zero, ddr2ns(dsidev, ths_prepare_ths_zero)); +	DSSDBG("ths_trail %u (%uns), ths_exit %u (%uns)\n", +			ths_trail, ddr2ns(dsidev, ths_trail), +			ths_exit, ddr2ns(dsidev, ths_exit)); + +	DSSDBG("tlpx_half %u (%uns), tclk_trail %u (%uns), " +			"tclk_zero %u (%uns)\n", +			tlpx_half, ddr2ns(dsidev, tlpx_half), +			tclk_trail, ddr2ns(dsidev, tclk_trail), +			tclk_zero, ddr2ns(dsidev, tclk_zero)); +	DSSDBG("tclk_prepare %u (%uns)\n", +			tclk_prepare, ddr2ns(dsidev, tclk_prepare)); + +	/* program timings */ + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); +	r = FLD_MOD(r, ths_prepare, 31, 24); +	r = FLD_MOD(r, ths_prepare_ths_zero, 23, 16); +	r = FLD_MOD(r, ths_trail, 15, 8); +	r = FLD_MOD(r, ths_exit, 7, 0); +	dsi_write_reg(dsidev, DSI_DSIPHY_CFG0, r); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); +	r = FLD_MOD(r, tlpx_half, 20, 16); +	r = FLD_MOD(r, tclk_trail, 15, 8); +	r = FLD_MOD(r, tclk_zero, 7, 0); + +	if (dss_has_feature(FEAT_DSI_PHY_DCC)) { +		r = FLD_MOD(r, 0, 21, 21);	/* DCCEN = disable */ +		r = FLD_MOD(r, 1, 22, 22);	/* CLKINP_DIVBY2EN = enable */ +		r = FLD_MOD(r, 1, 23, 23);	/* CLKINP_SEL = enable */ +	} + +	dsi_write_reg(dsidev, DSI_DSIPHY_CFG1, r); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG2); +	r = FLD_MOD(r, tclk_prepare, 7, 0); +	dsi_write_reg(dsidev, DSI_DSIPHY_CFG2, r); +} + +/* lane masks have lane 0 at lsb. mask_p for positive lines, n for negative */ +static void dsi_cio_enable_lane_override(struct platform_device *dsidev, +		unsigned mask_p, unsigned mask_n) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int i; +	u32 l; +	u8 lptxscp_start = dsi->num_lanes_supported == 3 ? 22 : 26; + +	l = 0; + +	for (i = 0; i < dsi->num_lanes_supported; ++i) { +		unsigned p = dsi->lanes[i].polarity; + +		if (mask_p & (1 << i)) +			l |= 1 << (i * 2 + (p ? 0 : 1)); + +		if (mask_n & (1 << i)) +			l |= 1 << (i * 2 + (p ? 1 : 0)); +	} + +	/* +	 * Bits in REGLPTXSCPDAT4TO0DXDY: +	 * 17: DY0 18: DX0 +	 * 19: DY1 20: DX1 +	 * 21: DY2 22: DX2 +	 * 23: DY3 24: DX3 +	 * 25: DY4 26: DX4 +	 */ + +	/* Set the lane override configuration */ + +	/* REGLPTXSCPDAT4TO0DXDY */ +	REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, l, lptxscp_start, 17); + +	/* Enable lane override */ + +	/* ENLPTXSCPDAT */ +	REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 1, 27, 27); +} + +static void dsi_cio_disable_lane_override(struct platform_device *dsidev) +{ +	/* Disable lane override */ +	REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 0, 27, 27); /* ENLPTXSCPDAT */ +	/* Reset the lane override configuration */ +	/* REGLPTXSCPDAT4TO0DXDY */ +	REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 0, 22, 17); +} + +static int dsi_cio_wait_tx_clk_esc_reset(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int t, i; +	bool in_use[DSI_MAX_NR_LANES]; +	static const u8 offsets_old[] = { 28, 27, 26 }; +	static const u8 offsets_new[] = { 24, 25, 26, 27, 28 }; +	const u8 *offsets; + +	if (dss_has_feature(FEAT_DSI_REVERSE_TXCLKESC)) +		offsets = offsets_old; +	else +		offsets = offsets_new; + +	for (i = 0; i < dsi->num_lanes_supported; ++i) +		in_use[i] = dsi->lanes[i].function != DSI_LANE_UNUSED; + +	t = 100000; +	while (true) { +		u32 l; +		int ok; + +		l = dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + +		ok = 0; +		for (i = 0; i < dsi->num_lanes_supported; ++i) { +			if (!in_use[i] || (l & (1 << offsets[i]))) +				ok++; +		} + +		if (ok == dsi->num_lanes_supported) +			break; + +		if (--t == 0) { +			for (i = 0; i < dsi->num_lanes_supported; ++i) { +				if (!in_use[i] || (l & (1 << offsets[i]))) +					continue; + +				DSSERR("CIO TXCLKESC%d domain not coming " \ +						"out of reset\n", i); +			} +			return -EIO; +		} +	} + +	return 0; +} + +/* return bitmask of enabled lanes, lane0 being the lsb */ +static unsigned dsi_get_lane_mask(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned mask = 0; +	int i; + +	for (i = 0; i < dsi->num_lanes_supported; ++i) { +		if (dsi->lanes[i].function != DSI_LANE_UNUSED) +			mask |= 1 << i; +	} + +	return mask; +} + +static int dsi_cio_init(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r; +	u32 l; + +	DSSDBG("DSI CIO init starts"); + +	r = dss_dsi_enable_pads(dsi->module_id, dsi_get_lane_mask(dsidev)); +	if (r) +		return r; + +	dsi_enable_scp_clk(dsidev); + +	/* A dummy read using the SCP interface to any DSIPHY register is +	 * required after DSIPHY reset to complete the reset of the DSI complex +	 * I/O. */ +	dsi_read_reg(dsidev, DSI_DSIPHY_CFG5); + +	if (wait_for_bit_change(dsidev, DSI_DSIPHY_CFG5, 30, 1) != 1) { +		DSSERR("CIO SCP Clock domain not coming out of reset.\n"); +		r = -EIO; +		goto err_scp_clk_dom; +	} + +	r = dsi_set_lane_config(dsidev); +	if (r) +		goto err_scp_clk_dom; + +	/* set TX STOP MODE timer to maximum for this operation */ +	l = dsi_read_reg(dsidev, DSI_TIMING1); +	l = FLD_MOD(l, 1, 15, 15);	/* FORCE_TX_STOP_MODE_IO */ +	l = FLD_MOD(l, 1, 14, 14);	/* STOP_STATE_X16_IO */ +	l = FLD_MOD(l, 1, 13, 13);	/* STOP_STATE_X4_IO */ +	l = FLD_MOD(l, 0x1fff, 12, 0);	/* STOP_STATE_COUNTER_IO */ +	dsi_write_reg(dsidev, DSI_TIMING1, l); + +	if (dsi->ulps_enabled) { +		unsigned mask_p; +		int i; + +		DSSDBG("manual ulps exit\n"); + +		/* ULPS is exited by Mark-1 state for 1ms, followed by +		 * stop state. DSS HW cannot do this via the normal +		 * ULPS exit sequence, as after reset the DSS HW thinks +		 * that we are not in ULPS mode, and refuses to send the +		 * sequence. So we need to send the ULPS exit sequence +		 * manually by setting positive lines high and negative lines +		 * low for 1ms. +		 */ + +		mask_p = 0; + +		for (i = 0; i < dsi->num_lanes_supported; ++i) { +			if (dsi->lanes[i].function == DSI_LANE_UNUSED) +				continue; +			mask_p |= 1 << i; +		} + +		dsi_cio_enable_lane_override(dsidev, mask_p, 0); +	} + +	r = dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ON); +	if (r) +		goto err_cio_pwr; + +	if (wait_for_bit_change(dsidev, DSI_COMPLEXIO_CFG1, 29, 1) != 1) { +		DSSERR("CIO PWR clock domain not coming out of reset.\n"); +		r = -ENODEV; +		goto err_cio_pwr_dom; +	} + +	dsi_if_enable(dsidev, true); +	dsi_if_enable(dsidev, false); +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 20, 20); /* LP_CLK_ENABLE */ + +	r = dsi_cio_wait_tx_clk_esc_reset(dsidev); +	if (r) +		goto err_tx_clk_esc_rst; + +	if (dsi->ulps_enabled) { +		/* Keep Mark-1 state for 1ms (as per DSI spec) */ +		ktime_t wait = ns_to_ktime(1000 * 1000); +		set_current_state(TASK_UNINTERRUPTIBLE); +		schedule_hrtimeout(&wait, HRTIMER_MODE_REL); + +		/* Disable the override. The lanes should be set to Mark-11 +		 * state by the HW */ +		dsi_cio_disable_lane_override(dsidev); +	} + +	/* FORCE_TX_STOP_MODE_IO */ +	REG_FLD_MOD(dsidev, DSI_TIMING1, 0, 15, 15); + +	dsi_cio_timings(dsidev); + +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		/* DDR_CLK_ALWAYS_ON */ +		REG_FLD_MOD(dsidev, DSI_CLK_CTRL, +			dsi->vm_timings.ddr_clk_always_on, 13, 13); +	} + +	dsi->ulps_enabled = false; + +	DSSDBG("CIO init done\n"); + +	return 0; + +err_tx_clk_esc_rst: +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 20, 20); /* LP_CLK_ENABLE */ +err_cio_pwr_dom: +	dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_OFF); +err_cio_pwr: +	if (dsi->ulps_enabled) +		dsi_cio_disable_lane_override(dsidev); +err_scp_clk_dom: +	dsi_disable_scp_clk(dsidev); +	dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dsidev)); +	return r; +} + +static void dsi_cio_uninit(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	/* DDR_CLK_ALWAYS_ON */ +	REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 13, 13); + +	dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_OFF); +	dsi_disable_scp_clk(dsidev); +	dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(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) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 r = 0; +	int add = 0; +	int i; + +	dsi->vc[0].tx_fifo_size = size1; +	dsi->vc[1].tx_fifo_size = size2; +	dsi->vc[2].tx_fifo_size = size3; +	dsi->vc[3].tx_fifo_size = size4; + +	for (i = 0; i < 4; i++) { +		u8 v; +		int size = dsi->vc[i].tx_fifo_size; + +		if (add + size > 4) { +			DSSERR("Illegal FIFO configuration\n"); +			BUG(); +			return; +		} + +		v = FLD_VAL(add, 2, 0) | FLD_VAL(size, 7, 4); +		r |= v << (8 * i); +		/*DSSDBG("TX FIFO vc %d: size %d, add %d\n", i, size, add); */ +		add += size; +	} + +	dsi_write_reg(dsidev, DSI_TX_FIFO_VC_SIZE, r); +} + +static void dsi_config_rx_fifo(struct platform_device *dsidev, +		enum fifo_size size1, enum fifo_size size2, +		enum fifo_size size3, enum fifo_size size4) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 r = 0; +	int add = 0; +	int i; + +	dsi->vc[0].rx_fifo_size = size1; +	dsi->vc[1].rx_fifo_size = size2; +	dsi->vc[2].rx_fifo_size = size3; +	dsi->vc[3].rx_fifo_size = size4; + +	for (i = 0; i < 4; i++) { +		u8 v; +		int size = dsi->vc[i].rx_fifo_size; + +		if (add + size > 4) { +			DSSERR("Illegal FIFO configuration\n"); +			BUG(); +			return; +		} + +		v = FLD_VAL(add, 2, 0) | FLD_VAL(size, 7, 4); +		r |= v << (8 * i); +		/*DSSDBG("RX FIFO vc %d: size %d, add %d\n", i, size, add); */ +		add += size; +	} + +	dsi_write_reg(dsidev, DSI_RX_FIFO_VC_SIZE, r); +} + +static int dsi_force_tx_stop_mode_io(struct platform_device *dsidev) +{ +	u32 r; + +	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_TIMING1, 15, 0) != 0) { +		DSSERR("TX_STOP bit not going down\n"); +		return -EIO; +	} + +	return 0; +} + +static bool dsi_vc_is_enabled(struct platform_device *dsidev, int channel) +{ +	return REG_GET(dsidev, DSI_VC_CTRL(channel), 0, 0); +} + +static void dsi_packet_sent_handler_vp(void *data, u32 mask) +{ +	struct dsi_packet_sent_handler_data *vp_data = +		(struct dsi_packet_sent_handler_data *) data; +	struct dsi_data *dsi = dsi_get_dsidrv_data(vp_data->dsidev); +	const int channel = dsi->update_channel; +	u8 bit = dsi->te_enabled ? 30 : 31; + +	if (REG_GET(vp_data->dsidev, DSI_VC_TE(channel), bit, bit) == 0) +		complete(vp_data->completion); +} + +static int dsi_sync_vc_vp(struct platform_device *dsidev, int channel) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	DECLARE_COMPLETION_ONSTACK(completion); +	struct dsi_packet_sent_handler_data vp_data = { dsidev, &completion }; +	int r = 0; +	u8 bit; + +	bit = dsi->te_enabled ? 30 : 31; + +	r = dsi_register_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, +		&vp_data, DSI_VC_IRQ_PACKET_SENT); +	if (r) +		goto err0; + +	/* Wait for completion only if TE_EN/TE_START is still set */ +	if (REG_GET(dsidev, DSI_VC_TE(channel), bit, bit)) { +		if (wait_for_completion_timeout(&completion, +				msecs_to_jiffies(10)) == 0) { +			DSSERR("Failed to complete previous frame transfer\n"); +			r = -EIO; +			goto err1; +		} +	} + +	dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, +		&vp_data, DSI_VC_IRQ_PACKET_SENT); + +	return 0; +err1: +	dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_vp, +		&vp_data, DSI_VC_IRQ_PACKET_SENT); +err0: +	return r; +} + +static void dsi_packet_sent_handler_l4(void *data, u32 mask) +{ +	struct dsi_packet_sent_handler_data *l4_data = +		(struct dsi_packet_sent_handler_data *) data; +	struct dsi_data *dsi = dsi_get_dsidrv_data(l4_data->dsidev); +	const int channel = dsi->update_channel; + +	if (REG_GET(l4_data->dsidev, DSI_VC_CTRL(channel), 5, 5) == 0) +		complete(l4_data->completion); +} + +static int dsi_sync_vc_l4(struct platform_device *dsidev, int channel) +{ +	DECLARE_COMPLETION_ONSTACK(completion); +	struct dsi_packet_sent_handler_data l4_data = { dsidev, &completion }; +	int r = 0; + +	r = dsi_register_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, +		&l4_data, DSI_VC_IRQ_PACKET_SENT); +	if (r) +		goto err0; + +	/* Wait for completion only if TX_FIFO_NOT_EMPTY is still set */ +	if (REG_GET(dsidev, DSI_VC_CTRL(channel), 5, 5)) { +		if (wait_for_completion_timeout(&completion, +				msecs_to_jiffies(10)) == 0) { +			DSSERR("Failed to complete previous l4 transfer\n"); +			r = -EIO; +			goto err1; +		} +	} + +	dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, +		&l4_data, DSI_VC_IRQ_PACKET_SENT); + +	return 0; +err1: +	dsi_unregister_isr_vc(dsidev, channel, dsi_packet_sent_handler_l4, +		&l4_data, DSI_VC_IRQ_PACKET_SENT); +err0: +	return r; +} + +static int dsi_sync_vc(struct platform_device *dsidev, int channel) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	WARN_ON(in_interrupt()); + +	if (!dsi_vc_is_enabled(dsidev, channel)) +		return 0; + +	switch (dsi->vc[channel].source) { +	case DSI_VC_SOURCE_VP: +		return dsi_sync_vc_vp(dsidev, channel); +	case DSI_VC_SOURCE_L4: +		return dsi_sync_vc_l4(dsidev, channel); +	default: +		BUG(); +		return -EINVAL; +	} +} + +static int dsi_vc_enable(struct platform_device *dsidev, int channel, +		bool enable) +{ +	DSSDBG("dsi_vc_enable channel %d, enable %d\n", +			channel, enable); + +	enable = enable ? 1 : 0; + +	REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 0, 0); + +	if (wait_for_bit_change(dsidev, DSI_VC_CTRL(channel), +		0, enable) != enable) { +			DSSERR("Failed to set dsi_vc_enable to %d\n", enable); +			return -EIO; +	} + +	return 0; +} + +static void dsi_vc_initial_config(struct platform_device *dsidev, int channel) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 r; + +	DSSDBG("Initial config of virtual channel %d", channel); + +	r = dsi_read_reg(dsidev, DSI_VC_CTRL(channel)); + +	if (FLD_GET(r, 15, 15)) /* VC_BUSY */ +		DSSERR("VC(%d) busy when trying to configure it!\n", +				channel); + +	r = FLD_MOD(r, 0, 1, 1); /* SOURCE, 0 = L4 */ +	r = FLD_MOD(r, 0, 2, 2); /* BTA_SHORT_EN  */ +	r = FLD_MOD(r, 0, 3, 3); /* BTA_LONG_EN */ +	r = FLD_MOD(r, 0, 4, 4); /* MODE, 0 = command */ +	r = FLD_MOD(r, 1, 7, 7); /* CS_TX_EN */ +	r = FLD_MOD(r, 1, 8, 8); /* ECC_TX_EN */ +	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 */ + +	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 */ + +	dsi_write_reg(dsidev, DSI_VC_CTRL(channel), r); + +	dsi->vc[channel].source = DSI_VC_SOURCE_L4; +} + +static int dsi_vc_config_source(struct platform_device *dsidev, int channel, +		enum dsi_vc_source source) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (dsi->vc[channel].source == source) +		return 0; + +	DSSDBG("Source config of virtual channel %d", channel); + +	dsi_sync_vc(dsidev, channel); + +	dsi_vc_enable(dsidev, channel, 0); + +	/* VC_BUSY */ +	if (wait_for_bit_change(dsidev, DSI_VC_CTRL(channel), 15, 0) != 0) { +		DSSERR("vc(%d) busy when trying to config for VP\n", channel); +		return -EIO; +	} + +	/* SOURCE, 0 = L4, 1 = video port */ +	REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), source, 1, 1); + +	/* DCS_CMD_ENABLE */ +	if (dss_has_feature(FEAT_DSI_DCS_CMD_CONFIG_VC)) { +		bool enable = source == DSI_VC_SOURCE_VP; +		REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 30, 30); +	} + +	dsi_vc_enable(dsidev, channel, 1); + +	dsi->vc[channel].source = source; + +	return 0; +} + +static void dsi_vc_enable_hs(struct omap_dss_device *dssdev, int channel, +		bool enable) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	DSSDBG("dsi_vc_enable_hs(%d, %d)\n", channel, enable); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	dsi_vc_enable(dsidev, channel, 0); +	dsi_if_enable(dsidev, 0); + +	REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), enable, 9, 9); + +	dsi_vc_enable(dsidev, channel, 1); +	dsi_if_enable(dsidev, 1); + +	dsi_force_tx_stop_mode_io(dsidev); + +	/* start the DDR clock by sending a NULL packet */ +	if (dsi->vm_timings.ddr_clk_always_on && enable) +		dsi_vc_send_null(dssdev, channel); +} + +static void dsi_vc_flush_long_data(struct platform_device *dsidev, int channel) +{ +	while (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { +		u32 val; +		val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); +		DSSDBG("\t\tb1 %#02x b2 %#02x b3 %#02x b4 %#02x\n", +				(val >> 0) & 0xff, +				(val >> 8) & 0xff, +				(val >> 16) & 0xff, +				(val >> 24) & 0xff); +	} +} + +static void dsi_show_rx_ack_with_err(u16 err) +{ +	DSSERR("\tACK with ERROR (%#x):\n", err); +	if (err & (1 << 0)) +		DSSERR("\t\tSoT Error\n"); +	if (err & (1 << 1)) +		DSSERR("\t\tSoT Sync Error\n"); +	if (err & (1 << 2)) +		DSSERR("\t\tEoT Sync Error\n"); +	if (err & (1 << 3)) +		DSSERR("\t\tEscape Mode Entry Command Error\n"); +	if (err & (1 << 4)) +		DSSERR("\t\tLP Transmit Sync Error\n"); +	if (err & (1 << 5)) +		DSSERR("\t\tHS Receive Timeout Error\n"); +	if (err & (1 << 6)) +		DSSERR("\t\tFalse Control Error\n"); +	if (err & (1 << 7)) +		DSSERR("\t\t(reserved7)\n"); +	if (err & (1 << 8)) +		DSSERR("\t\tECC Error, single-bit (corrected)\n"); +	if (err & (1 << 9)) +		DSSERR("\t\tECC Error, multi-bit (not corrected)\n"); +	if (err & (1 << 10)) +		DSSERR("\t\tChecksum Error\n"); +	if (err & (1 << 11)) +		DSSERR("\t\tData type not recognized\n"); +	if (err & (1 << 12)) +		DSSERR("\t\tInvalid VC ID\n"); +	if (err & (1 << 13)) +		DSSERR("\t\tInvalid Transmission Length\n"); +	if (err & (1 << 14)) +		DSSERR("\t\t(reserved14)\n"); +	if (err & (1 << 15)) +		DSSERR("\t\tDSI Protocol Violation\n"); +} + +static u16 dsi_vc_flush_receive_data(struct platform_device *dsidev, +		int channel) +{ +	/* RX_FIFO_NOT_EMPTY */ +	while (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { +		u32 val; +		u8 dt; +		val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); +		DSSERR("\trawval %#08x\n", val); +		dt = FLD_GET(val, 5, 0); +		if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { +			u16 err = FLD_GET(val, 23, 8); +			dsi_show_rx_ack_with_err(err); +		} else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE) { +			DSSERR("\tDCS short response, 1 byte: %#x\n", +					FLD_GET(val, 23, 8)); +		} else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE) { +			DSSERR("\tDCS short response, 2 byte: %#x\n", +					FLD_GET(val, 23, 8)); +		} else if (dt == MIPI_DSI_RX_DCS_LONG_READ_RESPONSE) { +			DSSERR("\tDCS long response, len %d\n", +					FLD_GET(val, 23, 8)); +			dsi_vc_flush_long_data(dsidev, channel); +		} else { +			DSSERR("\tunknown datatype 0x%02x\n", dt); +		} +	} +	return 0; +} + +static int dsi_vc_send_bta(struct platform_device *dsidev, int channel) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (dsi->debug_write || dsi->debug_read) +		DSSDBG("dsi_vc_send_bta %d\n", channel); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	/* RX_FIFO_NOT_EMPTY */ +	if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { +		DSSERR("rx fifo not empty when sending BTA, dumping data:\n"); +		dsi_vc_flush_receive_data(dsidev, channel); +	} + +	REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 1, 6, 6); /* BTA_EN */ + +	/* flush posted write */ +	dsi_read_reg(dsidev, DSI_VC_CTRL(channel)); + +	return 0; +} + +static int dsi_vc_send_bta_sync(struct omap_dss_device *dssdev, int channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	DECLARE_COMPLETION_ONSTACK(completion); +	int r = 0; +	u32 err; + +	r = dsi_register_isr_vc(dsidev, channel, dsi_completion_handler, +			&completion, DSI_VC_IRQ_BTA); +	if (r) +		goto err0; + +	r = dsi_register_isr(dsidev, dsi_completion_handler, &completion, +			DSI_IRQ_ERROR_MASK); +	if (r) +		goto err1; + +	r = dsi_vc_send_bta(dsidev, channel); +	if (r) +		goto err2; + +	if (wait_for_completion_timeout(&completion, +				msecs_to_jiffies(500)) == 0) { +		DSSERR("Failed to receive BTA\n"); +		r = -EIO; +		goto err2; +	} + +	err = dsi_get_errors(dsidev); +	if (err) { +		DSSERR("Error while sending BTA: %x\n", err); +		r = -EIO; +		goto err2; +	} +err2: +	dsi_unregister_isr(dsidev, dsi_completion_handler, &completion, +			DSI_IRQ_ERROR_MASK); +err1: +	dsi_unregister_isr_vc(dsidev, channel, dsi_completion_handler, +			&completion, DSI_VC_IRQ_BTA); +err0: +	return r; +} + +static inline void dsi_vc_write_long_header(struct platform_device *dsidev, +		int channel, u8 data_type, u16 len, u8 ecc) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 val; +	u8 data_id; + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	data_id = data_type | dsi->vc[channel].vc_id << 6; + +	val = FLD_VAL(data_id, 7, 0) | FLD_VAL(len, 23, 8) | +		FLD_VAL(ecc, 31, 24); + +	dsi_write_reg(dsidev, DSI_VC_LONG_PACKET_HEADER(channel), val); +} + +static inline void dsi_vc_write_long_payload(struct platform_device *dsidev, +		int channel, u8 b1, u8 b2, u8 b3, u8 b4) +{ +	u32 val; + +	val = b4 << 24 | b3 << 16 | b2 << 8  | b1 << 0; + +/*	DSSDBG("\twriting %02x, %02x, %02x, %02x (%#010x)\n", +			b1, b2, b3, b4, val); */ + +	dsi_write_reg(dsidev, DSI_VC_LONG_PACKET_PAYLOAD(channel), val); +} + +static int dsi_vc_send_long(struct platform_device *dsidev, int channel, +		u8 data_type, u8 *data, u16 len, u8 ecc) +{ +	/*u32 val; */ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int i; +	u8 *p; +	int r = 0; +	u8 b1, b2, b3, b4; + +	if (dsi->debug_write) +		DSSDBG("dsi_vc_send_long, %d bytes\n", len); + +	/* len + header */ +	if (dsi->vc[channel].tx_fifo_size * 32 * 4 < len + 4) { +		DSSERR("unable to send long packet: packet too long.\n"); +		return -EINVAL; +	} + +	dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_L4); + +	dsi_vc_write_long_header(dsidev, channel, data_type, len, ecc); + +	p = data; +	for (i = 0; i < len >> 2; i++) { +		if (dsi->debug_write) +			DSSDBG("\tsending full packet %d\n", i); + +		b1 = *p++; +		b2 = *p++; +		b3 = *p++; +		b4 = *p++; + +		dsi_vc_write_long_payload(dsidev, channel, b1, b2, b3, b4); +	} + +	i = len % 4; +	if (i) { +		b1 = 0; b2 = 0; b3 = 0; + +		if (dsi->debug_write) +			DSSDBG("\tsending remainder bytes %d\n", i); + +		switch (i) { +		case 3: +			b1 = *p++; +			b2 = *p++; +			b3 = *p++; +			break; +		case 2: +			b1 = *p++; +			b2 = *p++; +			break; +		case 1: +			b1 = *p++; +			break; +		} + +		dsi_vc_write_long_payload(dsidev, channel, b1, b2, b3, 0); +	} + +	return r; +} + +static int dsi_vc_send_short(struct platform_device *dsidev, int channel, +		u8 data_type, u16 data, u8 ecc) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 r; +	u8 data_id; + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	if (dsi->debug_write) +		DSSDBG("dsi_vc_send_short(ch%d, dt %#x, b1 %#x, b2 %#x)\n", +				channel, +				data_type, data & 0xff, (data >> 8) & 0xff); + +	dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_L4); + +	if (FLD_GET(dsi_read_reg(dsidev, DSI_VC_CTRL(channel)), 16, 16)) { +		DSSERR("ERROR FIFO FULL, aborting transfer\n"); +		return -EINVAL; +	} + +	data_id = data_type | dsi->vc[channel].vc_id << 6; + +	r = (data_id << 0) | (data << 8) | (ecc << 24); + +	dsi_write_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel), r); + +	return 0; +} + +static int dsi_vc_send_null(struct omap_dss_device *dssdev, int channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + +	return dsi_vc_send_long(dsidev, channel, MIPI_DSI_NULL_PACKET, NULL, +		0, 0); +} + +static int dsi_vc_write_nosync_common(struct platform_device *dsidev, +		int channel, u8 *data, int len, enum dss_dsi_content_type type) +{ +	int r; + +	if (len == 0) { +		BUG_ON(type == DSS_DSI_CONTENT_DCS); +		r = dsi_vc_send_short(dsidev, channel, +				MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM, 0, 0); +	} else if (len == 1) { +		r = dsi_vc_send_short(dsidev, channel, +				type == DSS_DSI_CONTENT_GENERIC ? +				MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM : +				MIPI_DSI_DCS_SHORT_WRITE, data[0], 0); +	} else if (len == 2) { +		r = dsi_vc_send_short(dsidev, channel, +				type == DSS_DSI_CONTENT_GENERIC ? +				MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM : +				MIPI_DSI_DCS_SHORT_WRITE_PARAM, +				data[0] | (data[1] << 8), 0); +	} else { +		r = dsi_vc_send_long(dsidev, channel, +				type == DSS_DSI_CONTENT_GENERIC ? +				MIPI_DSI_GENERIC_LONG_WRITE : +				MIPI_DSI_DCS_LONG_WRITE, data, len, 0); +	} + +	return r; +} + +static int dsi_vc_dcs_write_nosync(struct omap_dss_device *dssdev, int channel, +		u8 *data, int len) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + +	return dsi_vc_write_nosync_common(dsidev, channel, data, len, +			DSS_DSI_CONTENT_DCS); +} + +static int dsi_vc_generic_write_nosync(struct omap_dss_device *dssdev, int channel, +		u8 *data, int len) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + +	return dsi_vc_write_nosync_common(dsidev, channel, data, len, +			DSS_DSI_CONTENT_GENERIC); +} + +static int dsi_vc_write_common(struct omap_dss_device *dssdev, int channel, +		u8 *data, int len, enum dss_dsi_content_type type) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	int r; + +	r = dsi_vc_write_nosync_common(dsidev, channel, data, len, type); +	if (r) +		goto err; + +	r = dsi_vc_send_bta_sync(dssdev, channel); +	if (r) +		goto err; + +	/* RX_FIFO_NOT_EMPTY */ +	if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20)) { +		DSSERR("rx fifo not empty after write, dumping data:\n"); +		dsi_vc_flush_receive_data(dsidev, channel); +		r = -EIO; +		goto err; +	} + +	return 0; +err: +	DSSERR("dsi_vc_write_common(ch %d, cmd 0x%02x, len %d) failed\n", +			channel, data[0], len); +	return r; +} + +static int dsi_vc_dcs_write(struct omap_dss_device *dssdev, int channel, u8 *data, +		int len) +{ +	return dsi_vc_write_common(dssdev, channel, data, len, +			DSS_DSI_CONTENT_DCS); +} + +static int dsi_vc_generic_write(struct omap_dss_device *dssdev, int channel, u8 *data, +		int len) +{ +	return dsi_vc_write_common(dssdev, channel, data, len, +			DSS_DSI_CONTENT_GENERIC); +} + +static int dsi_vc_dcs_send_read_request(struct platform_device *dsidev, +		int channel, u8 dcs_cmd) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r; + +	if (dsi->debug_read) +		DSSDBG("dsi_vc_dcs_send_read_request(ch%d, dcs_cmd %x)\n", +			channel, dcs_cmd); + +	r = dsi_vc_send_short(dsidev, channel, MIPI_DSI_DCS_READ, dcs_cmd, 0); +	if (r) { +		DSSERR("dsi_vc_dcs_send_read_request(ch %d, cmd 0x%02x)" +			" failed\n", channel, dcs_cmd); +		return r; +	} + +	return 0; +} + +static int dsi_vc_generic_send_read_request(struct platform_device *dsidev, +		int channel, u8 *reqdata, int reqlen) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u16 data; +	u8 data_type; +	int r; + +	if (dsi->debug_read) +		DSSDBG("dsi_vc_generic_send_read_request(ch %d, reqlen %d)\n", +			channel, reqlen); + +	if (reqlen == 0) { +		data_type = MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM; +		data = 0; +	} else if (reqlen == 1) { +		data_type = MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM; +		data = reqdata[0]; +	} else if (reqlen == 2) { +		data_type = MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM; +		data = reqdata[0] | (reqdata[1] << 8); +	} else { +		BUG(); +		return -EINVAL; +	} + +	r = dsi_vc_send_short(dsidev, channel, data_type, data, 0); +	if (r) { +		DSSERR("dsi_vc_generic_send_read_request(ch %d, reqlen %d)" +			" failed\n", channel, reqlen); +		return r; +	} + +	return 0; +} + +static int dsi_vc_read_rx_fifo(struct platform_device *dsidev, int channel, +		u8 *buf, int buflen, enum dss_dsi_content_type type) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 val; +	u8 dt; +	int r; + +	/* RX_FIFO_NOT_EMPTY */ +	if (REG_GET(dsidev, DSI_VC_CTRL(channel), 20, 20) == 0) { +		DSSERR("RX fifo empty when trying to read.\n"); +		r = -EIO; +		goto err; +	} + +	val = dsi_read_reg(dsidev, DSI_VC_SHORT_PACKET_HEADER(channel)); +	if (dsi->debug_read) +		DSSDBG("\theader: %08x\n", val); +	dt = FLD_GET(val, 5, 0); +	if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { +		u16 err = FLD_GET(val, 23, 8); +		dsi_show_rx_ack_with_err(err); +		r = -EIO; +		goto err; + +	} else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? +			MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE : +			MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE)) { +		u8 data = FLD_GET(val, 15, 8); +		if (dsi->debug_read) +			DSSDBG("\t%s short response, 1 byte: %02x\n", +				type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : +				"DCS", data); + +		if (buflen < 1) { +			r = -EIO; +			goto err; +		} + +		buf[0] = data; + +		return 1; +	} else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? +			MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE : +			MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE)) { +		u16 data = FLD_GET(val, 23, 8); +		if (dsi->debug_read) +			DSSDBG("\t%s short response, 2 byte: %04x\n", +				type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : +				"DCS", data); + +		if (buflen < 2) { +			r = -EIO; +			goto err; +		} + +		buf[0] = data & 0xff; +		buf[1] = (data >> 8) & 0xff; + +		return 2; +	} else if (dt == (type == DSS_DSI_CONTENT_GENERIC ? +			MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE : +			MIPI_DSI_RX_DCS_LONG_READ_RESPONSE)) { +		int w; +		int len = FLD_GET(val, 23, 8); +		if (dsi->debug_read) +			DSSDBG("\t%s long response, len %d\n", +				type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : +				"DCS", len); + +		if (len > buflen) { +			r = -EIO; +			goto err; +		} + +		/* two byte checksum ends the packet, not included in len */ +		for (w = 0; w < len + 2;) { +			int b; +			val = dsi_read_reg(dsidev, +				DSI_VC_SHORT_PACKET_HEADER(channel)); +			if (dsi->debug_read) +				DSSDBG("\t\t%02x %02x %02x %02x\n", +						(val >> 0) & 0xff, +						(val >> 8) & 0xff, +						(val >> 16) & 0xff, +						(val >> 24) & 0xff); + +			for (b = 0; b < 4; ++b) { +				if (w < len) +					buf[w] = (val >> (b * 8)) & 0xff; +				/* we discard the 2 byte checksum */ +				++w; +			} +		} + +		return len; +	} else { +		DSSERR("\tunknown datatype 0x%02x\n", dt); +		r = -EIO; +		goto err; +	} + +err: +	DSSERR("dsi_vc_read_rx_fifo(ch %d type %s) failed\n", channel, +		type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS"); + +	return r; +} + +static int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int channel, u8 dcs_cmd, +		u8 *buf, int buflen) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	int r; + +	r = dsi_vc_dcs_send_read_request(dsidev, channel, dcs_cmd); +	if (r) +		goto err; + +	r = dsi_vc_send_bta_sync(dssdev, channel); +	if (r) +		goto err; + +	r = dsi_vc_read_rx_fifo(dsidev, channel, buf, buflen, +		DSS_DSI_CONTENT_DCS); +	if (r < 0) +		goto err; + +	if (r != buflen) { +		r = -EIO; +		goto err; +	} + +	return 0; +err: +	DSSERR("dsi_vc_dcs_read(ch %d, cmd 0x%02x) failed\n", channel, dcs_cmd); +	return r; +} + +static int dsi_vc_generic_read(struct omap_dss_device *dssdev, int channel, +		u8 *reqdata, int reqlen, u8 *buf, int buflen) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	int r; + +	r = dsi_vc_generic_send_read_request(dsidev, channel, reqdata, reqlen); +	if (r) +		return r; + +	r = dsi_vc_send_bta_sync(dssdev, channel); +	if (r) +		return r; + +	r = dsi_vc_read_rx_fifo(dsidev, channel, buf, buflen, +		DSS_DSI_CONTENT_GENERIC); +	if (r < 0) +		return r; + +	if (r != buflen) { +		r = -EIO; +		return r; +	} + +	return 0; +} + +static int dsi_vc_set_max_rx_packet_size(struct omap_dss_device *dssdev, int channel, +		u16 len) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); + +	return dsi_vc_send_short(dsidev, channel, +			MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, len, 0); +} + +static int dsi_enter_ulps(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	DECLARE_COMPLETION_ONSTACK(completion); +	int r, i; +	unsigned mask; + +	DSSDBG("Entering ULPS"); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	WARN_ON(dsi->ulps_enabled); + +	if (dsi->ulps_enabled) +		return 0; + +	/* DDR_CLK_ALWAYS_ON */ +	if (REG_GET(dsidev, DSI_CLK_CTRL, 13, 13)) { +		dsi_if_enable(dsidev, 0); +		REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 0, 13, 13); +		dsi_if_enable(dsidev, 1); +	} + +	dsi_sync_vc(dsidev, 0); +	dsi_sync_vc(dsidev, 1); +	dsi_sync_vc(dsidev, 2); +	dsi_sync_vc(dsidev, 3); + +	dsi_force_tx_stop_mode_io(dsidev); + +	dsi_vc_enable(dsidev, 0, false); +	dsi_vc_enable(dsidev, 1, false); +	dsi_vc_enable(dsidev, 2, false); +	dsi_vc_enable(dsidev, 3, false); + +	if (REG_GET(dsidev, DSI_COMPLEXIO_CFG2, 16, 16)) {	/* HS_BUSY */ +		DSSERR("HS busy when enabling ULPS\n"); +		return -EIO; +	} + +	if (REG_GET(dsidev, DSI_COMPLEXIO_CFG2, 17, 17)) {	/* LP_BUSY */ +		DSSERR("LP busy when enabling ULPS\n"); +		return -EIO; +	} + +	r = dsi_register_isr_cio(dsidev, dsi_completion_handler, &completion, +			DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); +	if (r) +		return r; + +	mask = 0; + +	for (i = 0; i < dsi->num_lanes_supported; ++i) { +		if (dsi->lanes[i].function == DSI_LANE_UNUSED) +			continue; +		mask |= 1 << i; +	} +	/* Assert TxRequestEsc for data lanes and TxUlpsClk for clk lane */ +	/* LANEx_ULPS_SIG2 */ +	REG_FLD_MOD(dsidev, DSI_COMPLEXIO_CFG2, mask, 9, 5); + +	/* flush posted write and wait for SCP interface to finish the write */ +	dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG2); + +	if (wait_for_completion_timeout(&completion, +				msecs_to_jiffies(1000)) == 0) { +		DSSERR("ULPS enable timeout\n"); +		r = -EIO; +		goto err; +	} + +	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, 9, 5); + +	/* flush posted write and wait for SCP interface to finish the write */ +	dsi_read_reg(dsidev, DSI_COMPLEXIO_CFG2); + +	dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ULPS); + +	dsi_if_enable(dsidev, false); + +	dsi->ulps_enabled = true; + +	return 0; + +err: +	dsi_unregister_isr_cio(dsidev, dsi_completion_handler, &completion, +			DSI_CIO_IRQ_ULPSACTIVENOT_ALL0); +	return r; +} + +static void dsi_set_lp_rx_timeout(struct platform_device *dsidev, +		unsigned ticks, bool x4, bool x16) +{ +	unsigned long fck; +	unsigned long total_ticks; +	u32 r; + +	BUG_ON(ticks > 0x1fff); + +	/* ticks in DSI_FCK */ +	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, 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 */ +	dsi_write_reg(dsidev, DSI_TIMING2, r); + +	total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + +	DSSDBG("LP_RX_TO %lu ticks (%#x%s%s) = %lu ns\n", +			total_ticks, +			ticks, x4 ? " x4" : "", x16 ? " x16" : "", +			(total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_ta_timeout(struct platform_device *dsidev, unsigned ticks, +		bool x8, bool x16) +{ +	unsigned long fck; +	unsigned long total_ticks; +	u32 r; + +	BUG_ON(ticks > 0x1fff); + +	/* ticks in DSI_FCK */ +	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, 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 */ +	dsi_write_reg(dsidev, DSI_TIMING1, r); + +	total_ticks = ticks * (x16 ? 16 : 1) * (x8 ? 8 : 1); + +	DSSDBG("TA_TO %lu ticks (%#x%s%s) = %lu ns\n", +			total_ticks, +			ticks, x8 ? " x8" : "", x16 ? " x16" : "", +			(total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_stop_state_counter(struct platform_device *dsidev, +		unsigned ticks, bool x4, bool x16) +{ +	unsigned long fck; +	unsigned long total_ticks; +	u32 r; + +	BUG_ON(ticks > 0x1fff); + +	/* ticks in DSI_FCK */ +	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, 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 */ +	dsi_write_reg(dsidev, DSI_TIMING1, r); + +	total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + +	DSSDBG("STOP_STATE_COUNTER %lu ticks (%#x%s%s) = %lu ns\n", +			total_ticks, +			ticks, x4 ? " x4" : "", x16 ? " x16" : "", +			(total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_set_hs_tx_timeout(struct platform_device *dsidev, +		unsigned ticks, bool x4, bool x16) +{ +	unsigned long fck; +	unsigned long total_ticks; +	u32 r; + +	BUG_ON(ticks > 0x1fff); + +	/* ticks in TxByteClkHS */ +	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, 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 */ +	dsi_write_reg(dsidev, DSI_TIMING2, r); + +	total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); + +	DSSDBG("HS_TX_TO %lu ticks (%#x%s%s) = %lu ns\n", +			total_ticks, +			ticks, x4 ? " x4" : "", x16 ? " x16" : "", +			(total_ticks * 1000) / (fck / 1000 / 1000)); +} + +static void dsi_config_vp_num_line_buffers(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int num_line_buffers; + +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		int bpp = dsi_get_pixel_size(dsi->pix_fmt); +		struct omap_video_timings *timings = &dsi->timings; +		/* +		 * Don't use line buffers if width is greater than the video +		 * port's line buffer size +		 */ +		if (dsi->line_buffer_size <= timings->x_res * bpp / 8) +			num_line_buffers = 0; +		else +			num_line_buffers = 2; +	} else { +		/* Use maximum number of line buffers in command mode */ +		num_line_buffers = 2; +	} + +	/* LINE_BUFFER */ +	REG_FLD_MOD(dsidev, DSI_CTRL, num_line_buffers, 13, 12); +} + +static void dsi_config_vp_sync_events(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	bool sync_end; +	u32 r; + +	if (dsi->vm_timings.trans_mode == OMAP_DSS_DSI_PULSE_MODE) +		sync_end = true; +	else +		sync_end = false; + +	r = dsi_read_reg(dsidev, DSI_CTRL); +	r = FLD_MOD(r, 1, 9, 9);		/* VP_DE_POL */ +	r = FLD_MOD(r, 1, 10, 10);		/* VP_HSYNC_POL */ +	r = FLD_MOD(r, 1, 11, 11);		/* VP_VSYNC_POL */ +	r = FLD_MOD(r, 1, 15, 15);		/* VP_VSYNC_START */ +	r = FLD_MOD(r, sync_end, 16, 16);	/* VP_VSYNC_END */ +	r = FLD_MOD(r, 1, 17, 17);		/* VP_HSYNC_START */ +	r = FLD_MOD(r, sync_end, 18, 18);	/* VP_HSYNC_END */ +	dsi_write_reg(dsidev, DSI_CTRL, r); +} + +static void dsi_config_blanking_modes(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int blanking_mode = dsi->vm_timings.blanking_mode; +	int hfp_blanking_mode = dsi->vm_timings.hfp_blanking_mode; +	int hbp_blanking_mode = dsi->vm_timings.hbp_blanking_mode; +	int hsa_blanking_mode = dsi->vm_timings.hsa_blanking_mode; +	u32 r; + +	/* +	 * 0 = TX FIFO packets sent or LPS in corresponding blanking periods +	 * 1 = Long blanking packets are sent in corresponding blanking periods +	 */ +	r = dsi_read_reg(dsidev, DSI_CTRL); +	r = FLD_MOD(r, blanking_mode, 20, 20);		/* BLANKING_MODE */ +	r = FLD_MOD(r, hfp_blanking_mode, 21, 21);	/* HFP_BLANKING */ +	r = FLD_MOD(r, hbp_blanking_mode, 22, 22);	/* HBP_BLANKING */ +	r = FLD_MOD(r, hsa_blanking_mode, 23, 23);	/* HSA_BLANKING */ +	dsi_write_reg(dsidev, DSI_CTRL, r); +} + +/* + * According to section 'HS Command Mode Interleaving' in OMAP TRM, Scenario 3 + * results in maximum transition time for data and clock lanes to enter and + * exit HS mode. Hence, this is the scenario where the least amount of command + * mode data can be interleaved. We program the minimum amount of TXBYTECLKHS + * clock cycles that can be used to interleave command mode data in HS so that + * all scenarios are satisfied. + */ +static int dsi_compute_interleave_hs(int blank, bool ddr_alwon, int enter_hs, +		int exit_hs, int exiths_clk, int ddr_pre, int ddr_post) +{ +	int transition; + +	/* +	 * If DDR_CLK_ALWAYS_ON is set, we need to consider HS mode transition +	 * time of data lanes only, if it isn't set, we need to consider HS +	 * transition time of both data and clock lanes. HS transition time +	 * of Scenario 3 is considered. +	 */ +	if (ddr_alwon) { +		transition = enter_hs + exit_hs + max(enter_hs, 2) + 1; +	} else { +		int trans1, trans2; +		trans1 = ddr_pre + enter_hs + exit_hs + max(enter_hs, 2) + 1; +		trans2 = ddr_pre + enter_hs + exiths_clk + ddr_post + ddr_pre + +				enter_hs + 1; +		transition = max(trans1, trans2); +	} + +	return blank > transition ? blank - transition : 0; +} + +/* + * According to section 'LP Command Mode Interleaving' in OMAP TRM, Scenario 1 + * results in maximum transition time for data lanes to enter and exit LP mode. + * Hence, this is the scenario where the least amount of command mode data can + * be interleaved. We program the minimum amount of bytes that can be + * interleaved in LP so that all scenarios are satisfied. + */ +static int dsi_compute_interleave_lp(int blank, int enter_hs, int exit_hs, +		int lp_clk_div, int tdsi_fclk) +{ +	int trans_lp;	/* time required for a LP transition, in TXBYTECLKHS */ +	int tlp_avail;	/* time left for interleaving commands, in CLKIN4DDR */ +	int ttxclkesc;	/* period of LP transmit escape clock, in CLKIN4DDR */ +	int thsbyte_clk = 16;	/* Period of TXBYTECLKHS clock, in CLKIN4DDR */ +	int lp_inter;	/* cmd mode data that can be interleaved, in bytes */ + +	/* maximum LP transition time according to Scenario 1 */ +	trans_lp = exit_hs + max(enter_hs, 2) + 1; + +	/* CLKIN4DDR = 16 * TXBYTECLKHS */ +	tlp_avail = thsbyte_clk * (blank - trans_lp); + +	ttxclkesc = tdsi_fclk * lp_clk_div; + +	lp_inter = ((tlp_avail - 8 * thsbyte_clk - 5 * tdsi_fclk) / ttxclkesc - +			26) / 16; + +	return max(lp_inter, 0); +} + +static void dsi_config_cmd_mode_interleaving(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int blanking_mode; +	int hfp_blanking_mode, hbp_blanking_mode, hsa_blanking_mode; +	int hsa, hfp, hbp, width_bytes, bllp, lp_clk_div; +	int ddr_clk_pre, ddr_clk_post, enter_hs_mode_lat, exit_hs_mode_lat; +	int tclk_trail, ths_exit, exiths_clk; +	bool ddr_alwon; +	struct omap_video_timings *timings = &dsi->timings; +	int bpp = dsi_get_pixel_size(dsi->pix_fmt); +	int ndl = dsi->num_lanes_used - 1; +	int dsi_fclk_hsdiv = dsi->user_dsi_cinfo.regm_dsi + 1; +	int hsa_interleave_hs = 0, hsa_interleave_lp = 0; +	int hfp_interleave_hs = 0, hfp_interleave_lp = 0; +	int hbp_interleave_hs = 0, hbp_interleave_lp = 0; +	int bl_interleave_hs = 0, bl_interleave_lp = 0; +	u32 r; + +	r = dsi_read_reg(dsidev, DSI_CTRL); +	blanking_mode = FLD_GET(r, 20, 20); +	hfp_blanking_mode = FLD_GET(r, 21, 21); +	hbp_blanking_mode = FLD_GET(r, 22, 22); +	hsa_blanking_mode = FLD_GET(r, 23, 23); + +	r = dsi_read_reg(dsidev, DSI_VM_TIMING1); +	hbp = FLD_GET(r, 11, 0); +	hfp = FLD_GET(r, 23, 12); +	hsa = FLD_GET(r, 31, 24); + +	r = dsi_read_reg(dsidev, DSI_CLK_TIMING); +	ddr_clk_post = FLD_GET(r, 7, 0); +	ddr_clk_pre = FLD_GET(r, 15, 8); + +	r = dsi_read_reg(dsidev, DSI_VM_TIMING7); +	exit_hs_mode_lat = FLD_GET(r, 15, 0); +	enter_hs_mode_lat = FLD_GET(r, 31, 16); + +	r = dsi_read_reg(dsidev, DSI_CLK_CTRL); +	lp_clk_div = FLD_GET(r, 12, 0); +	ddr_alwon = FLD_GET(r, 13, 13); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); +	ths_exit = FLD_GET(r, 7, 0); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); +	tclk_trail = FLD_GET(r, 15, 8); + +	exiths_clk = ths_exit + tclk_trail; + +	width_bytes = DIV_ROUND_UP(timings->x_res * bpp, 8); +	bllp = hbp + hfp + hsa + DIV_ROUND_UP(width_bytes + 6, ndl); + +	if (!hsa_blanking_mode) { +		hsa_interleave_hs = dsi_compute_interleave_hs(hsa, ddr_alwon, +					enter_hs_mode_lat, exit_hs_mode_lat, +					exiths_clk, ddr_clk_pre, ddr_clk_post); +		hsa_interleave_lp = dsi_compute_interleave_lp(hsa, +					enter_hs_mode_lat, exit_hs_mode_lat, +					lp_clk_div, dsi_fclk_hsdiv); +	} + +	if (!hfp_blanking_mode) { +		hfp_interleave_hs = dsi_compute_interleave_hs(hfp, ddr_alwon, +					enter_hs_mode_lat, exit_hs_mode_lat, +					exiths_clk, ddr_clk_pre, ddr_clk_post); +		hfp_interleave_lp = dsi_compute_interleave_lp(hfp, +					enter_hs_mode_lat, exit_hs_mode_lat, +					lp_clk_div, dsi_fclk_hsdiv); +	} + +	if (!hbp_blanking_mode) { +		hbp_interleave_hs = dsi_compute_interleave_hs(hbp, ddr_alwon, +					enter_hs_mode_lat, exit_hs_mode_lat, +					exiths_clk, ddr_clk_pre, ddr_clk_post); + +		hbp_interleave_lp = dsi_compute_interleave_lp(hbp, +					enter_hs_mode_lat, exit_hs_mode_lat, +					lp_clk_div, dsi_fclk_hsdiv); +	} + +	if (!blanking_mode) { +		bl_interleave_hs = dsi_compute_interleave_hs(bllp, ddr_alwon, +					enter_hs_mode_lat, exit_hs_mode_lat, +					exiths_clk, ddr_clk_pre, ddr_clk_post); + +		bl_interleave_lp = dsi_compute_interleave_lp(bllp, +					enter_hs_mode_lat, exit_hs_mode_lat, +					lp_clk_div, dsi_fclk_hsdiv); +	} + +	DSSDBG("DSI HS interleaving(TXBYTECLKHS) HSA %d, HFP %d, HBP %d, BLLP %d\n", +		hsa_interleave_hs, hfp_interleave_hs, hbp_interleave_hs, +		bl_interleave_hs); + +	DSSDBG("DSI LP interleaving(bytes) HSA %d, HFP %d, HBP %d, BLLP %d\n", +		hsa_interleave_lp, hfp_interleave_lp, hbp_interleave_lp, +		bl_interleave_lp); + +	r = dsi_read_reg(dsidev, DSI_VM_TIMING4); +	r = FLD_MOD(r, hsa_interleave_hs, 23, 16); +	r = FLD_MOD(r, hfp_interleave_hs, 15, 8); +	r = FLD_MOD(r, hbp_interleave_hs, 7, 0); +	dsi_write_reg(dsidev, DSI_VM_TIMING4, r); + +	r = dsi_read_reg(dsidev, DSI_VM_TIMING5); +	r = FLD_MOD(r, hsa_interleave_lp, 23, 16); +	r = FLD_MOD(r, hfp_interleave_lp, 15, 8); +	r = FLD_MOD(r, hbp_interleave_lp, 7, 0); +	dsi_write_reg(dsidev, DSI_VM_TIMING5, r); + +	r = dsi_read_reg(dsidev, DSI_VM_TIMING6); +	r = FLD_MOD(r, bl_interleave_hs, 31, 15); +	r = FLD_MOD(r, bl_interleave_lp, 16, 0); +	dsi_write_reg(dsidev, DSI_VM_TIMING6, r); +} + +static int dsi_proto_config(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u32 r; +	int buswidth = 0; + +	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); + +	/* 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); + +	switch (dsi_get_pixel_size(dsi->pix_fmt)) { +	case 16: +		buswidth = 0; +		break; +	case 18: +		buswidth = 1; +		break; +	case 24: +		buswidth = 2; +		break; +	default: +		BUG(); +		return -EINVAL; +	} + +	r = dsi_read_reg(dsidev, DSI_CTRL); +	r = FLD_MOD(r, 1, 1, 1);	/* CS_RX_EN */ +	r = FLD_MOD(r, 1, 2, 2);	/* ECC_RX_EN */ +	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, 14, 14);	/* TRIGGER_RESET_MODE */ +	r = FLD_MOD(r, 1, 19, 19);	/* EOT_ENABLE */ +	if (!dss_has_feature(FEAT_DSI_DCS_CMD_CONFIG_VC)) { +		r = FLD_MOD(r, 1, 24, 24);	/* DCS_CMD_ENABLE */ +		/* DCS_CMD_CODE, 1=start, 0=continue */ +		r = FLD_MOD(r, 0, 25, 25); +	} + +	dsi_write_reg(dsidev, DSI_CTRL, r); + +	dsi_config_vp_num_line_buffers(dsidev); + +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		dsi_config_vp_sync_events(dsidev); +		dsi_config_blanking_modes(dsidev); +		dsi_config_cmd_mode_interleaving(dsidev); +	} + +	dsi_vc_initial_config(dsidev, 0); +	dsi_vc_initial_config(dsidev, 1); +	dsi_vc_initial_config(dsidev, 2); +	dsi_vc_initial_config(dsidev, 3); + +	return 0; +} + +static void dsi_proto_timings(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	unsigned tlpx, tclk_zero, tclk_prepare, tclk_trail; +	unsigned tclk_pre, tclk_post; +	unsigned ths_prepare, ths_prepare_ths_zero, ths_zero; +	unsigned ths_trail, ths_exit; +	unsigned ddr_clk_pre, ddr_clk_post; +	unsigned enter_hs_mode_lat, exit_hs_mode_lat; +	unsigned ths_eot; +	int ndl = dsi->num_lanes_used - 1; +	u32 r; + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG0); +	ths_prepare = FLD_GET(r, 31, 24); +	ths_prepare_ths_zero = FLD_GET(r, 23, 16); +	ths_zero = ths_prepare_ths_zero - ths_prepare; +	ths_trail = FLD_GET(r, 15, 8); +	ths_exit = FLD_GET(r, 7, 0); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG1); +	tlpx = FLD_GET(r, 20, 16) * 2; +	tclk_trail = FLD_GET(r, 15, 8); +	tclk_zero = FLD_GET(r, 7, 0); + +	r = dsi_read_reg(dsidev, DSI_DSIPHY_CFG2); +	tclk_prepare = FLD_GET(r, 7, 0); + +	/* min 8*UI */ +	tclk_pre = 20; +	/* min 60ns + 52*UI */ +	tclk_post = ns2ddr(dsidev, 60) + 26; + +	ths_eot = DIV_ROUND_UP(4, ndl); + +	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; + +	BUG_ON(ddr_clk_pre == 0 || ddr_clk_pre > 255); +	BUG_ON(ddr_clk_post == 0 || ddr_clk_post > 255); + +	r = dsi_read_reg(dsidev, DSI_CLK_TIMING); +	r = FLD_MOD(r, ddr_clk_pre, 15, 8); +	r = FLD_MOD(r, ddr_clk_post, 7, 0); +	dsi_write_reg(dsidev, DSI_CLK_TIMING, r); + +	DSSDBG("ddr_clk_pre %u, ddr_clk_post %u\n", +			ddr_clk_pre, +			ddr_clk_post); + +	enter_hs_mode_lat = 1 + DIV_ROUND_UP(tlpx, 4) + +		DIV_ROUND_UP(ths_prepare, 4) + +		DIV_ROUND_UP(ths_zero + 3, 4); + +	exit_hs_mode_lat = DIV_ROUND_UP(ths_trail + ths_exit, 4) + 1 + ths_eot; + +	r = FLD_VAL(enter_hs_mode_lat, 31, 16) | +		FLD_VAL(exit_hs_mode_lat, 15, 0); +	dsi_write_reg(dsidev, DSI_VM_TIMING7, r); + +	DSSDBG("enter_hs_mode_lat %u, exit_hs_mode_lat %u\n", +			enter_hs_mode_lat, exit_hs_mode_lat); + +	 if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		/* TODO: Implement a video mode check_timings function */ +		int hsa = dsi->vm_timings.hsa; +		int hfp = dsi->vm_timings.hfp; +		int hbp = dsi->vm_timings.hbp; +		int vsa = dsi->vm_timings.vsa; +		int vfp = dsi->vm_timings.vfp; +		int vbp = dsi->vm_timings.vbp; +		int window_sync = dsi->vm_timings.window_sync; +		bool hsync_end; +		struct omap_video_timings *timings = &dsi->timings; +		int bpp = dsi_get_pixel_size(dsi->pix_fmt); +		int tl, t_he, width_bytes; + +		hsync_end = dsi->vm_timings.trans_mode == OMAP_DSS_DSI_PULSE_MODE; +		t_he = hsync_end ? +			((hsa == 0 && ndl == 3) ? 1 : DIV_ROUND_UP(4, ndl)) : 0; + +		width_bytes = DIV_ROUND_UP(timings->x_res * bpp, 8); + +		/* TL = t_HS + HSA + t_HE + HFP + ceil((WC + 6) / NDL) + HBP */ +		tl = DIV_ROUND_UP(4, ndl) + (hsync_end ? hsa : 0) + t_he + hfp + +			DIV_ROUND_UP(width_bytes + 6, ndl) + hbp; + +		DSSDBG("HBP: %d, HFP: %d, HSA: %d, TL: %d TXBYTECLKHS\n", hbp, +			hfp, hsync_end ? hsa : 0, tl); +		DSSDBG("VBP: %d, VFP: %d, VSA: %d, VACT: %d lines\n", vbp, vfp, +			vsa, timings->y_res); + +		r = dsi_read_reg(dsidev, DSI_VM_TIMING1); +		r = FLD_MOD(r, hbp, 11, 0);	/* HBP */ +		r = FLD_MOD(r, hfp, 23, 12);	/* HFP */ +		r = FLD_MOD(r, hsync_end ? hsa : 0, 31, 24);	/* HSA */ +		dsi_write_reg(dsidev, DSI_VM_TIMING1, r); + +		r = dsi_read_reg(dsidev, DSI_VM_TIMING2); +		r = FLD_MOD(r, vbp, 7, 0);	/* VBP */ +		r = FLD_MOD(r, vfp, 15, 8);	/* VFP */ +		r = FLD_MOD(r, vsa, 23, 16);	/* VSA */ +		r = FLD_MOD(r, window_sync, 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);	/* VACT */ +		r = FLD_MOD(r, tl, 31, 16);		/* TL */ +		dsi_write_reg(dsidev, DSI_VM_TIMING3, r); +	} +} + +static int dsi_configure_pins(struct omap_dss_device *dssdev, +		const struct omap_dsi_pin_config *pin_cfg) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int num_pins; +	const int *pins; +	struct dsi_lane_config lanes[DSI_MAX_NR_LANES]; +	int num_lanes; +	int i; + +	static const enum dsi_lane_function functions[] = { +		DSI_LANE_CLK, +		DSI_LANE_DATA1, +		DSI_LANE_DATA2, +		DSI_LANE_DATA3, +		DSI_LANE_DATA4, +	}; + +	num_pins = pin_cfg->num_pins; +	pins = pin_cfg->pins; + +	if (num_pins < 4 || num_pins > dsi->num_lanes_supported * 2 +			|| num_pins % 2 != 0) +		return -EINVAL; + +	for (i = 0; i < DSI_MAX_NR_LANES; ++i) +		lanes[i].function = DSI_LANE_UNUSED; + +	num_lanes = 0; + +	for (i = 0; i < num_pins; i += 2) { +		u8 lane, pol; +		int dx, dy; + +		dx = pins[i]; +		dy = pins[i + 1]; + +		if (dx < 0 || dx >= dsi->num_lanes_supported * 2) +			return -EINVAL; + +		if (dy < 0 || dy >= dsi->num_lanes_supported * 2) +			return -EINVAL; + +		if (dx & 1) { +			if (dy != dx - 1) +				return -EINVAL; +			pol = 1; +		} else { +			if (dy != dx + 1) +				return -EINVAL; +			pol = 0; +		} + +		lane = dx / 2; + +		lanes[lane].function = functions[i / 2]; +		lanes[lane].polarity = pol; +		num_lanes++; +	} + +	memcpy(dsi->lanes, lanes, sizeof(dsi->lanes)); +	dsi->num_lanes_used = num_lanes; + +	return 0; +} + +static int dsi_enable_video_output(struct omap_dss_device *dssdev, int channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct omap_overlay_manager *mgr = dsi->output.manager; +	int bpp = dsi_get_pixel_size(dsi->pix_fmt); +	struct omap_dss_device *out = &dsi->output; +	u8 data_type; +	u16 word_count; +	int r; + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		return -ENODEV; +	} + +	r = dsi_display_init_dispc(dsidev, mgr); +	if (r) +		goto err_init_dispc; + +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		switch (dsi->pix_fmt) { +		case OMAP_DSS_DSI_FMT_RGB888: +			data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; +			break; +		case OMAP_DSS_DSI_FMT_RGB666: +			data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; +			break; +		case OMAP_DSS_DSI_FMT_RGB666_PACKED: +			data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; +			break; +		case OMAP_DSS_DSI_FMT_RGB565: +			data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; +			break; +		default: +			r = -EINVAL; +			goto err_pix_fmt; +		} + +		dsi_if_enable(dsidev, false); +		dsi_vc_enable(dsidev, channel, false); + +		/* MODE, 1 = video mode */ +		REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 1, 4, 4); + +		word_count = DIV_ROUND_UP(dsi->timings.x_res * bpp, 8); + +		dsi_vc_write_long_header(dsidev, channel, data_type, +				word_count, 0); + +		dsi_vc_enable(dsidev, channel, true); +		dsi_if_enable(dsidev, true); +	} + +	r = dss_mgr_enable(mgr); +	if (r) +		goto err_mgr_enable; + +	return 0; + +err_mgr_enable: +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		dsi_if_enable(dsidev, false); +		dsi_vc_enable(dsidev, channel, false); +	} +err_pix_fmt: +	dsi_display_uninit_dispc(dsidev, mgr); +err_init_dispc: +	return r; +} + +static void dsi_disable_video_output(struct omap_dss_device *dssdev, int channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct omap_overlay_manager *mgr = dsi->output.manager; + +	if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { +		dsi_if_enable(dsidev, false); +		dsi_vc_enable(dsidev, channel, false); + +		/* MODE, 0 = command mode */ +		REG_FLD_MOD(dsidev, DSI_VC_CTRL(channel), 0, 4, 4); + +		dsi_vc_enable(dsidev, channel, true); +		dsi_if_enable(dsidev, true); +	} + +	dss_mgr_disable(mgr); + +	dsi_display_uninit_dispc(dsidev, mgr); +} + +static void dsi_update_screen_dispc(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct omap_overlay_manager *mgr = dsi->output.manager; +	unsigned bytespp; +	unsigned bytespl; +	unsigned bytespf; +	unsigned total_len; +	unsigned packet_payload; +	unsigned packet_len; +	u32 l; +	int r; +	const unsigned channel = dsi->update_channel; +	const unsigned line_buf_size = dsi->line_buffer_size; +	u16 w = dsi->timings.x_res; +	u16 h = dsi->timings.y_res; + +	DSSDBG("dsi_update_screen_dispc(%dx%d)\n", w, h); + +	dsi_vc_config_source(dsidev, channel, DSI_VC_SOURCE_VP); + +	bytespp	= dsi_get_pixel_size(dsi->pix_fmt) / 8; +	bytespl = w * bytespp; +	bytespf = bytespl * h; + +	/* NOTE: packet_payload has to be equal to N * bytespl, where N is +	 * number of lines in a packet.  See errata about VP_CLK_RATIO */ + +	if (bytespf < line_buf_size) +		packet_payload = bytespf; +	else +		packet_payload = (line_buf_size) / bytespl * bytespl; + +	packet_len = packet_payload + 1;	/* 1 byte for DCS cmd */ +	total_len = (bytespf / packet_payload) * packet_len; + +	if (bytespf % packet_payload) +		total_len += (bytespf % packet_payload) + 1; + +	l = FLD_VAL(total_len, 23, 0); /* TE_SIZE */ +	dsi_write_reg(dsidev, DSI_VC_TE(channel), l); + +	dsi_vc_write_long_header(dsidev, channel, MIPI_DSI_DCS_LONG_WRITE, +		packet_len, 0); + +	if (dsi->te_enabled) +		l = FLD_MOD(l, 1, 30, 30); /* TE_EN */ +	else +		l = FLD_MOD(l, 1, 31, 31); /* TE_START */ +	dsi_write_reg(dsidev, DSI_VC_TE(channel), l); + +	/* We put SIDLEMODE to no-idle for the duration of the transfer, +	 * because DSS interrupts are not capable of waking up the CPU and the +	 * framedone interrupt could be delayed for quite a long time. I think +	 * the same goes for any DSS interrupts, but for some reason I have not +	 * seen the problem anywhere else than here. +	 */ +	dispc_disable_sidle(); + +	dsi_perf_mark_start(dsidev); + +	r = schedule_delayed_work(&dsi->framedone_timeout_work, +		msecs_to_jiffies(250)); +	BUG_ON(r == 0); + +	dss_mgr_set_timings(mgr, &dsi->timings); + +	dss_mgr_start_update(mgr); + +	if (dsi->te_enabled) { +		/* disable LP_RX_TO, so that we can receive TE.  Time to wait +		 * for TE is longer than the timer allows */ +		REG_FLD_MOD(dsidev, DSI_TIMING2, 0, 15, 15); /* LP_RX_TO */ + +		dsi_vc_send_bta(dsidev, channel); + +#ifdef DSI_CATCH_MISSING_TE +		mod_timer(&dsi->te_timer, jiffies + msecs_to_jiffies(250)); +#endif +	} +} + +#ifdef DSI_CATCH_MISSING_TE +static void dsi_te_timeout(unsigned long arg) +{ +	DSSERR("TE not received for 250ms!\n"); +} +#endif + +static void dsi_handle_framedone(struct platform_device *dsidev, int error) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	/* SIDLEMODE back to smart-idle */ +	dispc_enable_sidle(); + +	if (dsi->te_enabled) { +		/* enable LP_RX_TO again after the TE */ +		REG_FLD_MOD(dsidev, DSI_TIMING2, 1, 15, 15); /* LP_RX_TO */ +	} + +	dsi->framedone_callback(error, dsi->framedone_data); + +	if (!error) +		dsi_perf_show(dsidev, "DISPC"); +} + +static void dsi_framedone_timeout_work_callback(struct work_struct *work) +{ +	struct dsi_data *dsi = container_of(work, struct dsi_data, +			framedone_timeout_work.work); +	/* XXX While extremely unlikely, we could get FRAMEDONE interrupt after +	 * 250ms which would conflict with this timeout work. What should be +	 * done is first cancel the transfer on the HW, and then cancel the +	 * possibly scheduled framedone work. However, cancelling the transfer +	 * on the HW is buggy, and would probably require resetting the whole +	 * DSI */ + +	DSSERR("Framedone not received for 250ms!\n"); + +	dsi_handle_framedone(dsi->pdev, -ETIMEDOUT); +} + +static void dsi_framedone_irq_callback(void *data) +{ +	struct platform_device *dsidev = (struct platform_device *) data; +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	/* Note: We get FRAMEDONE when DISPC has finished sending pixels and +	 * turns itself off. However, DSI still has the pixels in its buffers, +	 * and is sending the data. +	 */ + +	cancel_delayed_work(&dsi->framedone_timeout_work); + +	dsi_handle_framedone(dsidev, 0); +} + +static int dsi_update(struct omap_dss_device *dssdev, int channel, +		void (*callback)(int, void *), void *data) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	u16 dw, dh; + +	dsi_perf_mark_setup(dsidev); + +	dsi->update_channel = channel; + +	dsi->framedone_callback = callback; +	dsi->framedone_data = data; + +	dw = dsi->timings.x_res; +	dh = dsi->timings.y_res; + +#ifdef DSI_PERF_MEASURE +	dsi->update_bytes = dw * dh * +		dsi_get_pixel_size(dsi->pix_fmt) / 8; +#endif +	dsi_update_screen_dispc(dsidev); + +	return 0; +} + +/* Display funcs */ + +static int dsi_configure_dispc_clocks(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct dispc_clock_info dispc_cinfo; +	int r; +	unsigned long fck; + +	fck = dsi_get_pll_hsdiv_dispc_rate(dsidev); + +	dispc_cinfo.lck_div = dsi->user_dispc_cinfo.lck_div; +	dispc_cinfo.pck_div = dsi->user_dispc_cinfo.pck_div; + +	r = dispc_calc_clock_rates(fck, &dispc_cinfo); +	if (r) { +		DSSERR("Failed to calc dispc clocks\n"); +		return r; +	} + +	dsi->mgr_config.clock_info = dispc_cinfo; + +	return 0; +} + +static int dsi_display_init_dispc(struct platform_device *dsidev, +		struct omap_overlay_manager *mgr) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r; + +	dss_select_lcd_clk_source(mgr->id, dsi->module_id == 0 ? +			OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC : +			OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC); + +	if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) { +		r = dss_mgr_register_framedone_handler(mgr, +				dsi_framedone_irq_callback, dsidev); +		if (r) { +			DSSERR("can't register FRAMEDONE handler\n"); +			goto err; +		} + +		dsi->mgr_config.stallmode = true; +		dsi->mgr_config.fifohandcheck = true; +	} else { +		dsi->mgr_config.stallmode = false; +		dsi->mgr_config.fifohandcheck = false; +	} + +	/* +	 * override interlace, logic level and edge related parameters in +	 * omap_video_timings with default values +	 */ +	dsi->timings.interlace = false; +	dsi->timings.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; +	dsi->timings.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; +	dsi->timings.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; +	dsi->timings.de_level = OMAPDSS_SIG_ACTIVE_HIGH; +	dsi->timings.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + +	dss_mgr_set_timings(mgr, &dsi->timings); + +	r = dsi_configure_dispc_clocks(dsidev); +	if (r) +		goto err1; + +	dsi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; +	dsi->mgr_config.video_port_width = +			dsi_get_pixel_size(dsi->pix_fmt); +	dsi->mgr_config.lcden_sig_polarity = 0; + +	dss_mgr_set_lcd_config(mgr, &dsi->mgr_config); + +	return 0; +err1: +	if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) +		dss_mgr_unregister_framedone_handler(mgr, +				dsi_framedone_irq_callback, dsidev); +err: +	dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); +	return r; +} + +static void dsi_display_uninit_dispc(struct platform_device *dsidev, +		struct omap_overlay_manager *mgr) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (dsi->mode == OMAP_DSS_DSI_CMD_MODE) +		dss_mgr_unregister_framedone_handler(mgr, +				dsi_framedone_irq_callback, dsidev); + +	dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); +} + +static int dsi_configure_dsi_clocks(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct dsi_clock_info cinfo; +	int r; + +	cinfo = dsi->user_dsi_cinfo; + +	r = dsi_calc_clock_rates(dsidev, &cinfo); +	if (r) { +		DSSERR("Failed to calc dsi clocks\n"); +		return r; +	} + +	r = dsi_pll_set_clock_div(dsidev, &cinfo); +	if (r) { +		DSSERR("Failed to set dsi clocks\n"); +		return r; +	} + +	return 0; +} + +static int dsi_display_init_dsi(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r; + +	r = dsi_pll_init(dsidev, true, true); +	if (r) +		goto err0; + +	r = dsi_configure_dsi_clocks(dsidev); +	if (r) +		goto err1; + +	dss_select_dsi_clk_source(dsi->module_id, dsi->module_id == 0 ? +			OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI : +			OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI); + +	DSSDBG("PLL OK\n"); + +	r = dsi_cio_init(dsidev); +	if (r) +		goto err2; + +	_dsi_print_reset_status(dsidev); + +	dsi_proto_timings(dsidev); +	dsi_set_lp_clk_divisor(dsidev); + +	if (1) +		_dsi_print_reset_status(dsidev); + +	r = dsi_proto_config(dsidev); +	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); + +	return 0; +err3: +	dsi_cio_uninit(dsidev); +err2: +	dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK); +err1: +	dsi_pll_uninit(dsidev, true); +err0: +	return r; +} + +static void dsi_display_uninit_dsi(struct platform_device *dsidev, +		bool disconnect_lanes, bool enter_ulps) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (enter_ulps && !dsi->ulps_enabled) +		dsi_enter_ulps(dsidev); + +	/* disable interface */ +	dsi_if_enable(dsidev, 0); +	dsi_vc_enable(dsidev, 0, 0); +	dsi_vc_enable(dsidev, 1, 0); +	dsi_vc_enable(dsidev, 2, 0); +	dsi_vc_enable(dsidev, 3, 0); + +	dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK); +	dsi_cio_uninit(dsidev); +	dsi_pll_uninit(dsidev, disconnect_lanes); +} + +static int dsi_display_enable(struct omap_dss_device *dssdev) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int r = 0; + +	DSSDBG("dsi_display_enable\n"); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	mutex_lock(&dsi->lock); + +	r = dsi_runtime_get(dsidev); +	if (r) +		goto err_get_dsi; + +	dsi_enable_pll_clock(dsidev, 1); + +	_dsi_initialize_irq(dsidev); + +	r = dsi_display_init_dsi(dsidev); +	if (r) +		goto err_init_dsi; + +	mutex_unlock(&dsi->lock); + +	return 0; + +err_init_dsi: +	dsi_enable_pll_clock(dsidev, 0); +	dsi_runtime_put(dsidev); +err_get_dsi: +	mutex_unlock(&dsi->lock); +	DSSDBG("dsi_display_enable FAILED\n"); +	return r; +} + +static void dsi_display_disable(struct omap_dss_device *dssdev, +		bool disconnect_lanes, bool enter_ulps) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	DSSDBG("dsi_display_disable\n"); + +	WARN_ON(!dsi_bus_is_locked(dsidev)); + +	mutex_lock(&dsi->lock); + +	dsi_sync_vc(dsidev, 0); +	dsi_sync_vc(dsidev, 1); +	dsi_sync_vc(dsidev, 2); +	dsi_sync_vc(dsidev, 3); + +	dsi_display_uninit_dsi(dsidev, disconnect_lanes, enter_ulps); + +	dsi_runtime_put(dsidev); +	dsi_enable_pll_clock(dsidev, 0); + +	mutex_unlock(&dsi->lock); +} + +static int dsi_enable_te(struct omap_dss_device *dssdev, bool enable) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	dsi->te_enabled = enable; +	return 0; +} + +#ifdef PRINT_VERBOSE_VM_TIMINGS +static void print_dsi_vm(const char *str, +		const struct omap_dss_dsi_videomode_timings *t) +{ +	unsigned long byteclk = t->hsclk / 4; +	int bl, wc, pps, tot; + +	wc = DIV_ROUND_UP(t->hact * t->bitspp, 8); +	pps = DIV_ROUND_UP(wc + 6, t->ndl); /* pixel packet size */ +	bl = t->hss + t->hsa + t->hse + t->hbp + t->hfp; +	tot = bl + pps; + +#define TO_DSI_T(x) ((u32)div64_u64((u64)x * 1000000000llu, byteclk)) + +	pr_debug("%s bck %lu, %u/%u/%u/%u/%u/%u = %u+%u = %u, " +			"%u/%u/%u/%u/%u/%u = %u + %u = %u\n", +			str, +			byteclk, +			t->hss, t->hsa, t->hse, t->hbp, pps, t->hfp, +			bl, pps, tot, +			TO_DSI_T(t->hss), +			TO_DSI_T(t->hsa), +			TO_DSI_T(t->hse), +			TO_DSI_T(t->hbp), +			TO_DSI_T(pps), +			TO_DSI_T(t->hfp), + +			TO_DSI_T(bl), +			TO_DSI_T(pps), + +			TO_DSI_T(tot)); +#undef TO_DSI_T +} + +static void print_dispc_vm(const char *str, const struct omap_video_timings *t) +{ +	unsigned long pck = t->pixelclock; +	int hact, bl, tot; + +	hact = t->x_res; +	bl = t->hsw + t->hbp + t->hfp; +	tot = hact + bl; + +#define TO_DISPC_T(x) ((u32)div64_u64((u64)x * 1000000000llu, pck)) + +	pr_debug("%s pck %lu, %u/%u/%u/%u = %u+%u = %u, " +			"%u/%u/%u/%u = %u + %u = %u\n", +			str, +			pck, +			t->hsw, t->hbp, hact, t->hfp, +			bl, hact, tot, +			TO_DISPC_T(t->hsw), +			TO_DISPC_T(t->hbp), +			TO_DISPC_T(hact), +			TO_DISPC_T(t->hfp), +			TO_DISPC_T(bl), +			TO_DISPC_T(hact), +			TO_DISPC_T(tot)); +#undef TO_DISPC_T +} + +/* note: this is not quite accurate */ +static void print_dsi_dispc_vm(const char *str, +		const struct omap_dss_dsi_videomode_timings *t) +{ +	struct omap_video_timings vm = { 0 }; +	unsigned long byteclk = t->hsclk / 4; +	unsigned long pck; +	u64 dsi_tput; +	int dsi_hact, dsi_htot; + +	dsi_tput = (u64)byteclk * t->ndl * 8; +	pck = (u32)div64_u64(dsi_tput, t->bitspp); +	dsi_hact = DIV_ROUND_UP(DIV_ROUND_UP(t->hact * t->bitspp, 8) + 6, t->ndl); +	dsi_htot = t->hss + t->hsa + t->hse + t->hbp + dsi_hact + t->hfp; + +	vm.pixelclock = pck; +	vm.hsw = div64_u64((u64)(t->hsa + t->hse) * pck, byteclk); +	vm.hbp = div64_u64((u64)t->hbp * pck, byteclk); +	vm.hfp = div64_u64((u64)t->hfp * pck, byteclk); +	vm.x_res = t->hact; + +	print_dispc_vm(str, &vm); +} +#endif /* PRINT_VERBOSE_VM_TIMINGS */ + +static bool dsi_cm_calc_dispc_cb(int lckd, int pckd, unsigned long lck, +		unsigned long pck, void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; +	struct omap_video_timings *t = &ctx->dispc_vm; + +	ctx->dispc_cinfo.lck_div = lckd; +	ctx->dispc_cinfo.pck_div = pckd; +	ctx->dispc_cinfo.lck = lck; +	ctx->dispc_cinfo.pck = pck; + +	*t = *ctx->config->timings; +	t->pixelclock = pck; +	t->x_res = ctx->config->timings->x_res; +	t->y_res = ctx->config->timings->y_res; +	t->hsw = t->hfp = t->hbp = t->vsw = 1; +	t->vfp = t->vbp = 0; + +	return true; +} + +static bool dsi_cm_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, +		void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; + +	ctx->dsi_cinfo.regm_dispc = regm_dispc; +	ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + +	return dispc_div_calc(dispc, ctx->req_pck_min, ctx->req_pck_max, +			dsi_cm_calc_dispc_cb, ctx); +} + +static bool dsi_cm_calc_pll_cb(int regn, int regm, unsigned long fint, +		unsigned long pll, void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; + +	ctx->dsi_cinfo.regn = regn; +	ctx->dsi_cinfo.regm = regm; +	ctx->dsi_cinfo.fint = fint; +	ctx->dsi_cinfo.clkin4ddr = pll; + +	return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->req_pck_min, +			dsi_cm_calc_hsdiv_cb, ctx); +} + +static bool dsi_cm_calc(struct dsi_data *dsi, +		const struct omap_dss_dsi_config *cfg, +		struct dsi_clk_calc_ctx *ctx) +{ +	unsigned long clkin; +	int bitspp, ndl; +	unsigned long pll_min, pll_max; +	unsigned long pck, txbyteclk; + +	clkin = clk_get_rate(dsi->sys_clk); +	bitspp = dsi_get_pixel_size(cfg->pixel_format); +	ndl = dsi->num_lanes_used - 1; + +	/* +	 * Here we should calculate minimum txbyteclk to be able to send the +	 * frame in time, and also to handle TE. That's not very simple, though, +	 * especially as we go to LP between each pixel packet due to HW +	 * "feature". So let's just estimate very roughly and multiply by 1.5. +	 */ +	pck = cfg->timings->pixelclock; +	pck = pck * 3 / 2; +	txbyteclk = pck * bitspp / 8 / ndl; + +	memset(ctx, 0, sizeof(*ctx)); +	ctx->dsidev = dsi->pdev; +	ctx->config = cfg; +	ctx->req_pck_min = pck; +	ctx->req_pck_nom = pck; +	ctx->req_pck_max = pck * 3 / 2; +	ctx->dsi_cinfo.clkin = clkin; + +	pll_min = max(cfg->hs_clk_min * 4, txbyteclk * 4 * 4); +	pll_max = cfg->hs_clk_max * 4; + +	return dsi_pll_calc(dsi->pdev, clkin, +			pll_min, pll_max, +			dsi_cm_calc_pll_cb, ctx); +} + +static bool dsi_vm_calc_blanking(struct dsi_clk_calc_ctx *ctx) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(ctx->dsidev); +	const struct omap_dss_dsi_config *cfg = ctx->config; +	int bitspp = dsi_get_pixel_size(cfg->pixel_format); +	int ndl = dsi->num_lanes_used - 1; +	unsigned long hsclk = ctx->dsi_cinfo.clkin4ddr / 4; +	unsigned long byteclk = hsclk / 4; + +	unsigned long dispc_pck, req_pck_min, req_pck_nom, req_pck_max; +	int xres; +	int panel_htot, panel_hbl; /* pixels */ +	int dispc_htot, dispc_hbl; /* pixels */ +	int dsi_htot, dsi_hact, dsi_hbl, hss, hse; /* byteclks */ +	int hfp, hsa, hbp; +	const struct omap_video_timings *req_vm; +	struct omap_video_timings *dispc_vm; +	struct omap_dss_dsi_videomode_timings *dsi_vm; +	u64 dsi_tput, dispc_tput; + +	dsi_tput = (u64)byteclk * ndl * 8; + +	req_vm = cfg->timings; +	req_pck_min = ctx->req_pck_min; +	req_pck_max = ctx->req_pck_max; +	req_pck_nom = ctx->req_pck_nom; + +	dispc_pck = ctx->dispc_cinfo.pck; +	dispc_tput = (u64)dispc_pck * bitspp; + +	xres = req_vm->x_res; + +	panel_hbl = req_vm->hfp + req_vm->hbp + req_vm->hsw; +	panel_htot = xres + panel_hbl; + +	dsi_hact = DIV_ROUND_UP(DIV_ROUND_UP(xres * bitspp, 8) + 6, ndl); + +	/* +	 * When there are no line buffers, DISPC and DSI must have the +	 * same tput. Otherwise DISPC tput needs to be higher than DSI's. +	 */ +	if (dsi->line_buffer_size < xres * bitspp / 8) { +		if (dispc_tput != dsi_tput) +			return false; +	} else { +		if (dispc_tput < dsi_tput) +			return false; +	} + +	/* DSI tput must be over the min requirement */ +	if (dsi_tput < (u64)bitspp * req_pck_min) +		return false; + +	/* When non-burst mode, DSI tput must be below max requirement. */ +	if (cfg->trans_mode != OMAP_DSS_DSI_BURST_MODE) { +		if (dsi_tput > (u64)bitspp * req_pck_max) +			return false; +	} + +	hss = DIV_ROUND_UP(4, ndl); + +	if (cfg->trans_mode == OMAP_DSS_DSI_PULSE_MODE) { +		if (ndl == 3 && req_vm->hsw == 0) +			hse = 1; +		else +			hse = DIV_ROUND_UP(4, ndl); +	} else { +		hse = 0; +	} + +	/* DSI htot to match the panel's nominal pck */ +	dsi_htot = div64_u64((u64)panel_htot * byteclk, req_pck_nom); + +	/* fail if there would be no time for blanking */ +	if (dsi_htot < hss + hse + dsi_hact) +		return false; + +	/* total DSI blanking needed to achieve panel's TL */ +	dsi_hbl = dsi_htot - dsi_hact; + +	/* DISPC htot to match the DSI TL */ +	dispc_htot = div64_u64((u64)dsi_htot * dispc_pck, byteclk); + +	/* verify that the DSI and DISPC TLs are the same */ +	if ((u64)dsi_htot * dispc_pck != (u64)dispc_htot * byteclk) +		return false; + +	dispc_hbl = dispc_htot - xres; + +	/* setup DSI videomode */ + +	dsi_vm = &ctx->dsi_vm; +	memset(dsi_vm, 0, sizeof(*dsi_vm)); + +	dsi_vm->hsclk = hsclk; + +	dsi_vm->ndl = ndl; +	dsi_vm->bitspp = bitspp; + +	if (cfg->trans_mode != OMAP_DSS_DSI_PULSE_MODE) { +		hsa = 0; +	} else if (ndl == 3 && req_vm->hsw == 0) { +		hsa = 0; +	} else { +		hsa = div64_u64((u64)req_vm->hsw * byteclk, req_pck_nom); +		hsa = max(hsa - hse, 1); +	} + +	hbp = div64_u64((u64)req_vm->hbp * byteclk, req_pck_nom); +	hbp = max(hbp, 1); + +	hfp = dsi_hbl - (hss + hsa + hse + hbp); +	if (hfp < 1) { +		int t; +		/* we need to take cycles from hbp */ + +		t = 1 - hfp; +		hbp = max(hbp - t, 1); +		hfp = dsi_hbl - (hss + hsa + hse + hbp); + +		if (hfp < 1 && hsa > 0) { +			/* we need to take cycles from hsa */ +			t = 1 - hfp; +			hsa = max(hsa - t, 1); +			hfp = dsi_hbl - (hss + hsa + hse + hbp); +		} +	} + +	if (hfp < 1) +		return false; + +	dsi_vm->hss = hss; +	dsi_vm->hsa = hsa; +	dsi_vm->hse = hse; +	dsi_vm->hbp = hbp; +	dsi_vm->hact = xres; +	dsi_vm->hfp = hfp; + +	dsi_vm->vsa = req_vm->vsw; +	dsi_vm->vbp = req_vm->vbp; +	dsi_vm->vact = req_vm->y_res; +	dsi_vm->vfp = req_vm->vfp; + +	dsi_vm->trans_mode = cfg->trans_mode; + +	dsi_vm->blanking_mode = 0; +	dsi_vm->hsa_blanking_mode = 1; +	dsi_vm->hfp_blanking_mode = 1; +	dsi_vm->hbp_blanking_mode = 1; + +	dsi_vm->ddr_clk_always_on = cfg->ddr_clk_always_on; +	dsi_vm->window_sync = 4; + +	/* setup DISPC videomode */ + +	dispc_vm = &ctx->dispc_vm; +	*dispc_vm = *req_vm; +	dispc_vm->pixelclock = dispc_pck; + +	if (cfg->trans_mode == OMAP_DSS_DSI_PULSE_MODE) { +		hsa = div64_u64((u64)req_vm->hsw * dispc_pck, +				req_pck_nom); +		hsa = max(hsa, 1); +	} else { +		hsa = 1; +	} + +	hbp = div64_u64((u64)req_vm->hbp * dispc_pck, req_pck_nom); +	hbp = max(hbp, 1); + +	hfp = dispc_hbl - hsa - hbp; +	if (hfp < 1) { +		int t; +		/* we need to take cycles from hbp */ + +		t = 1 - hfp; +		hbp = max(hbp - t, 1); +		hfp = dispc_hbl - hsa - hbp; + +		if (hfp < 1) { +			/* we need to take cycles from hsa */ +			t = 1 - hfp; +			hsa = max(hsa - t, 1); +			hfp = dispc_hbl - hsa - hbp; +		} +	} + +	if (hfp < 1) +		return false; + +	dispc_vm->hfp = hfp; +	dispc_vm->hsw = hsa; +	dispc_vm->hbp = hbp; + +	return true; +} + + +static bool dsi_vm_calc_dispc_cb(int lckd, int pckd, unsigned long lck, +		unsigned long pck, void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; + +	ctx->dispc_cinfo.lck_div = lckd; +	ctx->dispc_cinfo.pck_div = pckd; +	ctx->dispc_cinfo.lck = lck; +	ctx->dispc_cinfo.pck = pck; + +	if (dsi_vm_calc_blanking(ctx) == false) +		return false; + +#ifdef PRINT_VERBOSE_VM_TIMINGS +	print_dispc_vm("dispc", &ctx->dispc_vm); +	print_dsi_vm("dsi  ", &ctx->dsi_vm); +	print_dispc_vm("req  ", ctx->config->timings); +	print_dsi_dispc_vm("act  ", &ctx->dsi_vm); +#endif + +	return true; +} + +static bool dsi_vm_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, +		void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; +	unsigned long pck_max; + +	ctx->dsi_cinfo.regm_dispc = regm_dispc; +	ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; + +	/* +	 * In burst mode we can let the dispc pck be arbitrarily high, but it +	 * limits our scaling abilities. So for now, don't aim too high. +	 */ + +	if (ctx->config->trans_mode == OMAP_DSS_DSI_BURST_MODE) +		pck_max = ctx->req_pck_max + 10000000; +	else +		pck_max = ctx->req_pck_max; + +	return dispc_div_calc(dispc, ctx->req_pck_min, pck_max, +			dsi_vm_calc_dispc_cb, ctx); +} + +static bool dsi_vm_calc_pll_cb(int regn, int regm, unsigned long fint, +		unsigned long pll, void *data) +{ +	struct dsi_clk_calc_ctx *ctx = data; + +	ctx->dsi_cinfo.regn = regn; +	ctx->dsi_cinfo.regm = regm; +	ctx->dsi_cinfo.fint = fint; +	ctx->dsi_cinfo.clkin4ddr = pll; + +	return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->req_pck_min, +			dsi_vm_calc_hsdiv_cb, ctx); +} + +static bool dsi_vm_calc(struct dsi_data *dsi, +		const struct omap_dss_dsi_config *cfg, +		struct dsi_clk_calc_ctx *ctx) +{ +	const struct omap_video_timings *t = cfg->timings; +	unsigned long clkin; +	unsigned long pll_min; +	unsigned long pll_max; +	int ndl = dsi->num_lanes_used - 1; +	int bitspp = dsi_get_pixel_size(cfg->pixel_format); +	unsigned long byteclk_min; + +	clkin = clk_get_rate(dsi->sys_clk); + +	memset(ctx, 0, sizeof(*ctx)); +	ctx->dsidev = dsi->pdev; +	ctx->config = cfg; + +	ctx->dsi_cinfo.clkin = clkin; + +	/* these limits should come from the panel driver */ +	ctx->req_pck_min = t->pixelclock - 1000; +	ctx->req_pck_nom = t->pixelclock; +	ctx->req_pck_max = t->pixelclock + 1000; + +	byteclk_min = div64_u64((u64)ctx->req_pck_min * bitspp, ndl * 8); +	pll_min = max(cfg->hs_clk_min * 4, byteclk_min * 4 * 4); + +	if (cfg->trans_mode == OMAP_DSS_DSI_BURST_MODE) { +		pll_max = cfg->hs_clk_max * 4; +	} else { +		unsigned long byteclk_max; +		byteclk_max = div64_u64((u64)ctx->req_pck_max * bitspp, +				ndl * 8); + +		pll_max = byteclk_max * 4 * 4; +	} + +	return dsi_pll_calc(dsi->pdev, clkin, +			pll_min, pll_max, +			dsi_vm_calc_pll_cb, ctx); +} + +static int dsi_set_config(struct omap_dss_device *dssdev, +		const struct omap_dss_dsi_config *config) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct dsi_clk_calc_ctx ctx; +	bool ok; +	int r; + +	mutex_lock(&dsi->lock); + +	dsi->pix_fmt = config->pixel_format; +	dsi->mode = config->mode; + +	if (config->mode == OMAP_DSS_DSI_VIDEO_MODE) +		ok = dsi_vm_calc(dsi, config, &ctx); +	else +		ok = dsi_cm_calc(dsi, config, &ctx); + +	if (!ok) { +		DSSERR("failed to find suitable DSI clock settings\n"); +		r = -EINVAL; +		goto err; +	} + +	dsi_pll_calc_dsi_fck(&ctx.dsi_cinfo); + +	r = dsi_lp_clock_calc(&ctx.dsi_cinfo, config->lp_clk_min, +			config->lp_clk_max); +	if (r) { +		DSSERR("failed to find suitable DSI LP clock settings\n"); +		goto err; +	} + +	dsi->user_dsi_cinfo = ctx.dsi_cinfo; +	dsi->user_dispc_cinfo = ctx.dispc_cinfo; + +	dsi->timings = ctx.dispc_vm; +	dsi->vm_timings = ctx.dsi_vm; + +	mutex_unlock(&dsi->lock); + +	return 0; +err: +	mutex_unlock(&dsi->lock); + +	return r; +} + +/* + * Return a hardcoded channel for the DSI output. This should work for + * current use cases, but this can be later expanded to either resolve + * the channel in some more dynamic manner, or get the channel as a user + * parameter. + */ +static enum omap_channel dsi_get_channel(int module_id) +{ +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP24xx: +	case OMAPDSS_VER_AM43xx: +		DSSWARN("DSI not supported\n"); +		return OMAP_DSS_CHANNEL_LCD; + +	case OMAPDSS_VER_OMAP34xx_ES1: +	case OMAPDSS_VER_OMAP34xx_ES3: +	case OMAPDSS_VER_OMAP3630: +	case OMAPDSS_VER_AM35xx: +		return OMAP_DSS_CHANNEL_LCD; + +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		switch (module_id) { +		case 0: +			return OMAP_DSS_CHANNEL_LCD; +		case 1: +			return OMAP_DSS_CHANNEL_LCD2; +		default: +			DSSWARN("unsupported module id\n"); +			return OMAP_DSS_CHANNEL_LCD; +		} + +	case OMAPDSS_VER_OMAP5: +		switch (module_id) { +		case 0: +			return OMAP_DSS_CHANNEL_LCD; +		case 1: +			return OMAP_DSS_CHANNEL_LCD3; +		default: +			DSSWARN("unsupported module id\n"); +			return OMAP_DSS_CHANNEL_LCD; +		} + +	default: +		DSSWARN("unsupported DSS version\n"); +		return OMAP_DSS_CHANNEL_LCD; +	} +} + +static int dsi_request_vc(struct omap_dss_device *dssdev, int *channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	int i; + +	for (i = 0; i < ARRAY_SIZE(dsi->vc); i++) { +		if (!dsi->vc[i].dssdev) { +			dsi->vc[i].dssdev = dssdev; +			*channel = i; +			return 0; +		} +	} + +	DSSERR("cannot get VC for display %s", dssdev->name); +	return -ENOSPC; +} + +static int dsi_set_vc_id(struct omap_dss_device *dssdev, int channel, int vc_id) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if (vc_id < 0 || vc_id > 3) { +		DSSERR("VC ID out of range\n"); +		return -EINVAL; +	} + +	if (channel < 0 || channel > 3) { +		DSSERR("Virtual Channel out of range\n"); +		return -EINVAL; +	} + +	if (dsi->vc[channel].dssdev != dssdev) { +		DSSERR("Virtual Channel not allocated to display %s\n", +			dssdev->name); +		return -EINVAL; +	} + +	dsi->vc[channel].vc_id = vc_id; + +	return 0; +} + +static void dsi_release_vc(struct omap_dss_device *dssdev, int channel) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	if ((channel >= 0 && channel <= 3) && +		dsi->vc[channel].dssdev == dssdev) { +		dsi->vc[channel].dssdev = NULL; +		dsi->vc[channel].vc_id = 0; +	} +} + +void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev) +{ +	if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 7, 1) != 1) +		DSSERR("%s (%s) not active\n", +			dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC), +			dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC)); +} + +void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev) +{ +	if (wait_for_bit_change(dsidev, DSI_PLL_STATUS, 8, 1) != 1) +		DSSERR("%s (%s) not active\n", +			dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI), +			dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI)); +} + +static void dsi_calc_clock_param_ranges(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	dsi->regn_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGN); +	dsi->regm_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM); +	dsi->regm_dispc_max = +		dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM_DISPC); +	dsi->regm_dsi_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_REGM_DSI); +	dsi->fint_min = dss_feat_get_param_min(FEAT_PARAM_DSIPLL_FINT); +	dsi->fint_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_FINT); +	dsi->lpdiv_max = dss_feat_get_param_max(FEAT_PARAM_DSIPLL_LPDIV); +} + +static int dsi_get_clocks(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct clk *clk; + +	clk = devm_clk_get(&dsidev->dev, "fck"); +	if (IS_ERR(clk)) { +		DSSERR("can't get fck\n"); +		return PTR_ERR(clk); +	} + +	dsi->dss_clk = clk; + +	clk = devm_clk_get(&dsidev->dev, "sys_clk"); +	if (IS_ERR(clk)) { +		DSSERR("can't get sys_clk\n"); +		return PTR_ERR(clk); +	} + +	dsi->sys_clk = clk; + +	return 0; +} + +static int dsi_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev); +	struct omap_overlay_manager *mgr; +	int r; + +	r = dsi_regulator_init(dsidev); +	if (r) +		return r; + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dssdev->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void dsi_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_dsi_ops dsi_ops = { +	.connect = dsi_connect, +	.disconnect = dsi_disconnect, + +	.bus_lock = dsi_bus_lock, +	.bus_unlock = dsi_bus_unlock, + +	.enable = dsi_display_enable, +	.disable = dsi_display_disable, + +	.enable_hs = dsi_vc_enable_hs, + +	.configure_pins = dsi_configure_pins, +	.set_config = dsi_set_config, + +	.enable_video_output = dsi_enable_video_output, +	.disable_video_output = dsi_disable_video_output, + +	.update = dsi_update, + +	.enable_te = dsi_enable_te, + +	.request_vc = dsi_request_vc, +	.set_vc_id = dsi_set_vc_id, +	.release_vc = dsi_release_vc, + +	.dcs_write = dsi_vc_dcs_write, +	.dcs_write_nosync = dsi_vc_dcs_write_nosync, +	.dcs_read = dsi_vc_dcs_read, + +	.gen_write = dsi_vc_generic_write, +	.gen_write_nosync = dsi_vc_generic_write_nosync, +	.gen_read = dsi_vc_generic_read, + +	.bta_sync = dsi_vc_send_bta_sync, + +	.set_max_rx_packet_size = dsi_vc_set_max_rx_packet_size, +}; + +static void dsi_init_output(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct omap_dss_device *out = &dsi->output; + +	out->dev = &dsidev->dev; +	out->id = dsi->module_id == 0 ? +			OMAP_DSS_OUTPUT_DSI1 : OMAP_DSS_OUTPUT_DSI2; + +	out->output_type = OMAP_DISPLAY_TYPE_DSI; +	out->name = dsi->module_id == 0 ? "dsi.0" : "dsi.1"; +	out->dispc_channel = dsi_get_channel(dsi->module_id); +	out->ops.dsi = &dsi_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void dsi_uninit_output(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); +	struct omap_dss_device *out = &dsi->output; + +	omapdss_unregister_output(out); +} + +static int dsi_probe_of(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); +	struct property *prop; +	u32 lane_arr[10]; +	int len, num_pins; +	int r, i; +	struct device_node *ep; +	struct omap_dsi_pin_config pin_cfg; + +	ep = omapdss_of_get_first_endpoint(node); +	if (!ep) +		return 0; + +	prop = of_find_property(ep, "lanes", &len); +	if (prop == NULL) { +		dev_err(&pdev->dev, "failed to find lane data\n"); +		r = -EINVAL; +		goto err; +	} + +	num_pins = len / sizeof(u32); + +	if (num_pins < 4 || num_pins % 2 != 0 || +		num_pins > dsi->num_lanes_supported * 2) { +		dev_err(&pdev->dev, "bad number of lanes\n"); +		r = -EINVAL; +		goto err; +	} + +	r = of_property_read_u32_array(ep, "lanes", lane_arr, num_pins); +	if (r) { +		dev_err(&pdev->dev, "failed to read lane data\n"); +		goto err; +	} + +	pin_cfg.num_pins = num_pins; +	for (i = 0; i < num_pins; ++i) +		pin_cfg.pins[i] = (int)lane_arr[i]; + +	r = dsi_configure_pins(&dsi->output, &pin_cfg); +	if (r) { +		dev_err(&pdev->dev, "failed to configure pins"); +		goto err; +	} + +	of_node_put(ep); + +	return 0; + +err: +	of_node_put(ep); +	return r; +} + +/* DSI1 HW IP initialisation */ +static int omap_dsihw_probe(struct platform_device *dsidev) +{ +	u32 rev; +	int r, i; +	struct dsi_data *dsi; +	struct resource *dsi_mem; +	struct resource *res; +	struct resource temp_res; + +	dsi = devm_kzalloc(&dsidev->dev, sizeof(*dsi), GFP_KERNEL); +	if (!dsi) +		return -ENOMEM; + +	dsi->pdev = dsidev; +	dev_set_drvdata(&dsidev->dev, dsi); + +	spin_lock_init(&dsi->irq_lock); +	spin_lock_init(&dsi->errors_lock); +	dsi->errors = 0; + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	spin_lock_init(&dsi->irq_stats_lock); +	dsi->irq_stats.last_reset = jiffies; +#endif + +	mutex_init(&dsi->lock); +	sema_init(&dsi->bus_lock, 1); + +	INIT_DEFERRABLE_WORK(&dsi->framedone_timeout_work, +			     dsi_framedone_timeout_work_callback); + +#ifdef DSI_CATCH_MISSING_TE +	init_timer(&dsi->te_timer); +	dsi->te_timer.function = dsi_te_timeout; +	dsi->te_timer.data = 0; +#endif + +	res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "proto"); +	if (!res) { +		res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); +		if (!res) { +			DSSERR("can't get IORESOURCE_MEM DSI\n"); +			return -EINVAL; +		} + +		temp_res.start = res->start; +		temp_res.end = temp_res.start + DSI_PROTO_SZ - 1; +		res = &temp_res; +	} + +	dsi_mem = res; + +	dsi->proto_base = devm_ioremap(&dsidev->dev, res->start, +		resource_size(res)); +	if (!dsi->proto_base) { +		DSSERR("can't ioremap DSI protocol engine\n"); +		return -ENOMEM; +	} + +	res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "phy"); +	if (!res) { +		res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); +		if (!res) { +			DSSERR("can't get IORESOURCE_MEM DSI\n"); +			return -EINVAL; +		} + +		temp_res.start = res->start + DSI_PHY_OFFSET; +		temp_res.end = temp_res.start + DSI_PHY_SZ - 1; +		res = &temp_res; +	} + +	dsi->phy_base = devm_ioremap(&dsidev->dev, res->start, +		resource_size(res)); +	if (!dsi->proto_base) { +		DSSERR("can't ioremap DSI PHY\n"); +		return -ENOMEM; +	} + +	res = platform_get_resource_byname(dsidev, IORESOURCE_MEM, "pll"); +	if (!res) { +		res = platform_get_resource(dsidev, IORESOURCE_MEM, 0); +		if (!res) { +			DSSERR("can't get IORESOURCE_MEM DSI\n"); +			return -EINVAL; +		} + +		temp_res.start = res->start + DSI_PLL_OFFSET; +		temp_res.end = temp_res.start + DSI_PLL_SZ - 1; +		res = &temp_res; +	} + +	dsi->pll_base = devm_ioremap(&dsidev->dev, res->start, +		resource_size(res)); +	if (!dsi->proto_base) { +		DSSERR("can't ioremap DSI PLL\n"); +		return -ENOMEM; +	} + +	dsi->irq = platform_get_irq(dsi->pdev, 0); +	if (dsi->irq < 0) { +		DSSERR("platform_get_irq failed\n"); +		return -ENODEV; +	} + +	r = devm_request_irq(&dsidev->dev, dsi->irq, omap_dsi_irq_handler, +			     IRQF_SHARED, dev_name(&dsidev->dev), dsi->pdev); +	if (r < 0) { +		DSSERR("request_irq failed\n"); +		return r; +	} + +	if (dsidev->dev.of_node) { +		const struct of_device_id *match; +		const struct dsi_module_id_data *d; + +		match = of_match_node(dsi_of_match, dsidev->dev.of_node); +		if (!match) { +			DSSERR("unsupported DSI module\n"); +			return -ENODEV; +		} + +		d = match->data; + +		while (d->address != 0 && d->address != dsi_mem->start) +			d++; + +		if (d->address == 0) { +			DSSERR("unsupported DSI module\n"); +			return -ENODEV; +		} + +		dsi->module_id = d->id; +	} else { +		dsi->module_id = dsidev->id; +	} + +	/* DSI VCs initialization */ +	for (i = 0; i < ARRAY_SIZE(dsi->vc); i++) { +		dsi->vc[i].source = DSI_VC_SOURCE_L4; +		dsi->vc[i].dssdev = NULL; +		dsi->vc[i].vc_id = 0; +	} + +	dsi_calc_clock_param_ranges(dsidev); + +	r = dsi_get_clocks(dsidev); +	if (r) +		return r; + +	pm_runtime_enable(&dsidev->dev); + +	r = dsi_runtime_get(dsidev); +	if (r) +		goto err_runtime_get; + +	rev = dsi_read_reg(dsidev, DSI_REVISION); +	dev_dbg(&dsidev->dev, "OMAP DSI rev %d.%d\n", +	       FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + +	/* DSI on OMAP3 doesn't have register DSI_GNQ, set number +	 * of data to 3 by default */ +	if (dss_has_feature(FEAT_DSI_GNQ)) +		/* NB_DATA_LANES */ +		dsi->num_lanes_supported = 1 + REG_GET(dsidev, DSI_GNQ, 11, 9); +	else +		dsi->num_lanes_supported = 3; + +	dsi->line_buffer_size = dsi_get_line_buf_size(dsidev); + +	dsi_init_output(dsidev); + +	if (dsidev->dev.of_node) { +		r = dsi_probe_of(dsidev); +		if (r) { +			DSSERR("Invalid DSI DT data\n"); +			goto err_probe_of; +		} + +		r = of_platform_populate(dsidev->dev.of_node, NULL, NULL, +			&dsidev->dev); +		if (r) +			DSSERR("Failed to populate DSI child devices: %d\n", r); +	} + +	dsi_runtime_put(dsidev); + +	if (dsi->module_id == 0) +		dss_debugfs_create_file("dsi1_regs", dsi1_dump_regs); +	else if (dsi->module_id == 1) +		dss_debugfs_create_file("dsi2_regs", dsi2_dump_regs); + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +	if (dsi->module_id == 0) +		dss_debugfs_create_file("dsi1_irqs", dsi1_dump_irqs); +	else if (dsi->module_id == 1) +		dss_debugfs_create_file("dsi2_irqs", dsi2_dump_irqs); +#endif + +	return 0; + +err_probe_of: +	dsi_uninit_output(dsidev); +	dsi_runtime_put(dsidev); + +err_runtime_get: +	pm_runtime_disable(&dsidev->dev); +	return r; +} + +static int dsi_unregister_child(struct device *dev, void *data) +{ +	struct platform_device *pdev = to_platform_device(dev); +	platform_device_unregister(pdev); +	return 0; +} + +static int __exit omap_dsihw_remove(struct platform_device *dsidev) +{ +	struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev); + +	device_for_each_child(&dsidev->dev, NULL, dsi_unregister_child); + +	WARN_ON(dsi->scp_clk_refcount > 0); + +	dsi_uninit_output(dsidev); + +	pm_runtime_disable(&dsidev->dev); + +	if (dsi->vdds_dsi_reg != NULL && dsi->vdds_dsi_enabled) { +		regulator_disable(dsi->vdds_dsi_reg); +		dsi->vdds_dsi_enabled = false; +	} + +	return 0; +} + +static int dsi_runtime_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); + +	dsi->is_enabled = false; +	/* ensure the irq handler sees the is_enabled value */ +	smp_wmb(); +	/* wait for current handler to finish before turning the DSI off */ +	synchronize_irq(dsi->irq); + +	dispc_runtime_put(); + +	return 0; +} + +static int dsi_runtime_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct dsi_data *dsi = dsi_get_dsidrv_data(pdev); +	int r; + +	r = dispc_runtime_get(); +	if (r) +		return r; + +	dsi->is_enabled = true; +	/* ensure the irq handler sees the is_enabled value */ +	smp_wmb(); + +	return 0; +} + +static const struct dev_pm_ops dsi_pm_ops = { +	.runtime_suspend = dsi_runtime_suspend, +	.runtime_resume = dsi_runtime_resume, +}; + +static const struct dsi_module_id_data dsi_of_data_omap3[] = { +	{ .address = 0x4804fc00, .id = 0, }, +	{ }, +}; + +static const struct dsi_module_id_data dsi_of_data_omap4[] = { +	{ .address = 0x58004000, .id = 0, }, +	{ .address = 0x58005000, .id = 1, }, +	{ }, +}; + +static const struct dsi_module_id_data dsi_of_data_omap5[] = { +	{ .address = 0x58004000, .id = 0, }, +	{ .address = 0x58009000, .id = 1, }, +	{ }, +}; + +static const struct of_device_id dsi_of_match[] = { +	{ .compatible = "ti,omap3-dsi", .data = dsi_of_data_omap3, }, +	{ .compatible = "ti,omap4-dsi", .data = dsi_of_data_omap4, }, +	{ .compatible = "ti,omap5-dsi", .data = dsi_of_data_omap5, }, +	{}, +}; + +static struct platform_driver omap_dsihw_driver = { +	.probe		= omap_dsihw_probe, +	.remove         = __exit_p(omap_dsihw_remove), +	.driver         = { +		.name   = "omapdss_dsi", +		.owner  = THIS_MODULE, +		.pm	= &dsi_pm_ops, +		.of_match_table = dsi_of_match, +	}, +}; + +int __init dsi_init_platform_driver(void) +{ +	return platform_driver_register(&omap_dsihw_driver); +} + +void __exit dsi_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_dsihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dss-of.c b/drivers/video/fbdev/omap2/dss/dss-of.c new file mode 100644 index 00000000000..a4b20aaf614 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss-of.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2013 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@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. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/seq_file.h> + +#include <video/omapdss.h> + +struct device_node * +omapdss_of_get_next_port(const struct device_node *parent, +			 struct device_node *prev) +{ +	struct device_node *port = NULL; + +	if (!parent) +		return NULL; + +	if (!prev) { +		struct device_node *ports; +		/* +		 * It's the first call, we have to find a port subnode +		 * within this node or within an optional 'ports' node. +		 */ +		ports = of_get_child_by_name(parent, "ports"); +		if (ports) +			parent = ports; + +		port = of_get_child_by_name(parent, "port"); + +		/* release the 'ports' node */ +		of_node_put(ports); +	} else { +		struct device_node *ports; + +		ports = of_get_parent(prev); +		if (!ports) +			return NULL; + +		do { +			port = of_get_next_child(ports, prev); +			if (!port) { +				of_node_put(ports); +				return NULL; +			} +			prev = port; +		} while (of_node_cmp(port->name, "port") != 0); +	} + +	return port; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_next_port); + +struct device_node * +omapdss_of_get_next_endpoint(const struct device_node *parent, +			     struct device_node *prev) +{ +	struct device_node *ep = NULL; + +	if (!parent) +		return NULL; + +	do { +		ep = of_get_next_child(parent, prev); +		if (!ep) +			return NULL; +		prev = ep; +	} while (of_node_cmp(ep->name, "endpoint") != 0); + +	return ep; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_next_endpoint); + +static struct device_node * +omapdss_of_get_remote_device_node(const struct device_node *node) +{ +	struct device_node *np; +	int i; + +	np = of_parse_phandle(node, "remote-endpoint", 0); + +	if (!np) +		return NULL; + +	np = of_get_next_parent(np); + +	for (i = 0; i < 3 && np; ++i) { +		struct property *prop; + +		prop = of_find_property(np, "compatible", NULL); + +		if (prop) +			return np; + +		np = of_get_next_parent(np); +	} + +	return NULL; +} + +struct device_node * +omapdss_of_get_first_endpoint(const struct device_node *parent) +{ +	struct device_node *port, *ep; + +	port = omapdss_of_get_next_port(parent, NULL); + +	if (!port) +		return NULL; + +	ep = omapdss_of_get_next_endpoint(port, NULL); + +	of_node_put(port); + +	return ep; +} +EXPORT_SYMBOL_GPL(omapdss_of_get_first_endpoint); + +struct omap_dss_device * +omapdss_of_find_source_for_first_ep(struct device_node *node) +{ +	struct device_node *ep; +	struct device_node *src_node; +	struct omap_dss_device *src; + +	ep = omapdss_of_get_first_endpoint(node); +	if (!ep) +		return ERR_PTR(-EINVAL); + +	src_node = omapdss_of_get_remote_device_node(ep); + +	of_node_put(ep); + +	if (!src_node) +		return ERR_PTR(-EINVAL); + +	src = omap_dss_find_output_by_node(src_node); + +	of_node_put(src_node); + +	if (!src) +		return ERR_PTR(-EPROBE_DEFER); + +	return src; +} +EXPORT_SYMBOL_GPL(omapdss_of_find_source_for_first_ep); diff --git a/drivers/video/fbdev/omap2/dss/dss.c b/drivers/video/fbdev/omap2/dss/dss.c new file mode 100644 index 00000000000..6daeb7ed44c --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss.c @@ -0,0 +1,980 @@ +/* + * linux/drivers/video/omap2/dss/dss.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "DSS" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/gfp.h> +#include <linux/sizes.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +#define DSS_SZ_REGS			SZ_512 + +struct dss_reg { +	u16 idx; +}; + +#define DSS_REG(idx)			((const struct dss_reg) { idx }) + +#define DSS_REVISION			DSS_REG(0x0000) +#define DSS_SYSCONFIG			DSS_REG(0x0010) +#define DSS_SYSSTATUS			DSS_REG(0x0014) +#define DSS_CONTROL			DSS_REG(0x0040) +#define DSS_SDI_CONTROL			DSS_REG(0x0044) +#define DSS_PLL_CONTROL			DSS_REG(0x0048) +#define DSS_SDI_STATUS			DSS_REG(0x005C) + +#define REG_GET(idx, start, end) \ +	FLD_GET(dss_read_reg(idx), start, end) + +#define REG_FLD_MOD(idx, val, start, end) \ +	dss_write_reg(idx, FLD_MOD(dss_read_reg(idx), val, start, end)) + +static int dss_runtime_get(void); +static void dss_runtime_put(void); + +struct dss_features { +	u8 fck_div_max; +	u8 dss_fck_multiplier; +	const char *parent_clk_name; +	int (*dpi_select_source)(enum omap_channel channel); +}; + +static struct { +	struct platform_device *pdev; +	void __iomem    *base; + +	struct clk	*parent_clk; +	struct clk	*dss_clk; +	unsigned long	dss_clk_rate; + +	unsigned long	cache_req_pck; +	unsigned long	cache_prate; +	struct dispc_clock_info cache_dispc_cinfo; + +	enum omap_dss_clk_source dsi_clk_source[MAX_NUM_DSI]; +	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)]; + +	const struct dss_features *feat; +} dss; + +static const char * const dss_generic_clk_source_names[] = { +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC]	= "DSI_PLL_HSDIV_DISPC", +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI]	= "DSI_PLL_HSDIV_DSI", +	[OMAP_DSS_CLK_SRC_FCK]			= "DSS_FCK", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC]	= "DSI_PLL2_HSDIV_DISPC", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI]	= "DSI_PLL2_HSDIV_DSI", +}; + +static inline void dss_write_reg(const struct dss_reg idx, u32 val) +{ +	__raw_writel(val, dss.base + idx.idx); +} + +static inline u32 dss_read_reg(const struct dss_reg idx) +{ +	return __raw_readl(dss.base + idx.idx); +} + +#define SR(reg) \ +	dss.ctx[(DSS_##reg).idx / sizeof(u32)] = dss_read_reg(DSS_##reg) +#define RR(reg) \ +	dss_write_reg(DSS_##reg, dss.ctx[(DSS_##reg).idx / sizeof(u32)]) + +static void dss_save_context(void) +{ +	DSSDBG("dss_save_context\n"); + +	SR(CONTROL); + +	if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & +			OMAP_DISPLAY_TYPE_SDI) { +		SR(SDI_CONTROL); +		SR(PLL_CONTROL); +	} + +	dss.ctx_valid = true; + +	DSSDBG("context saved\n"); +} + +static void dss_restore_context(void) +{ +	DSSDBG("dss_restore_context\n"); + +	if (!dss.ctx_valid) +		return; + +	RR(CONTROL); + +	if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & +			OMAP_DISPLAY_TYPE_SDI) { +		RR(SDI_CONTROL); +		RR(PLL_CONTROL); +	} + +	DSSDBG("context restored\n"); +} + +#undef SR +#undef RR + +void dss_sdi_init(int datapairs) +{ +	u32 l; + +	BUG_ON(datapairs > 3 || datapairs < 1); + +	l = dss_read_reg(DSS_SDI_CONTROL); +	l = FLD_MOD(l, 0xf, 19, 15);		/* SDI_PDIV */ +	l = FLD_MOD(l, datapairs-1, 3, 2);	/* SDI_PRSEL */ +	l = FLD_MOD(l, 2, 1, 0);		/* SDI_BWSEL */ +	dss_write_reg(DSS_SDI_CONTROL, l); + +	l = dss_read_reg(DSS_PLL_CONTROL); +	l = FLD_MOD(l, 0x7, 25, 22);	/* SDI_PLL_FREQSEL */ +	l = FLD_MOD(l, 0xb, 16, 11);	/* SDI_PLL_REGN */ +	l = FLD_MOD(l, 0xb4, 10, 1);	/* SDI_PLL_REGM */ +	dss_write_reg(DSS_PLL_CONTROL, l); +} + +int dss_sdi_enable(void) +{ +	unsigned long timeout; + +	dispc_pck_free_enable(1); + +	/* Reset SDI PLL */ +	REG_FLD_MOD(DSS_PLL_CONTROL, 1, 18, 18); /* SDI_PLL_SYSRESET */ +	udelay(1);	/* wait 2x PCLK */ + +	/* Lock SDI PLL */ +	REG_FLD_MOD(DSS_PLL_CONTROL, 1, 28, 28); /* SDI_PLL_GOBIT */ + +	/* Waiting for PLL lock request to complete */ +	timeout = jiffies + msecs_to_jiffies(500); +	while (dss_read_reg(DSS_SDI_STATUS) & (1 << 6)) { +		if (time_after_eq(jiffies, timeout)) { +			DSSERR("PLL lock request timed out\n"); +			goto err1; +		} +	} + +	/* Clearing PLL_GO bit */ +	REG_FLD_MOD(DSS_PLL_CONTROL, 0, 28, 28); + +	/* Waiting for PLL to lock */ +	timeout = jiffies + msecs_to_jiffies(500); +	while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 5))) { +		if (time_after_eq(jiffies, timeout)) { +			DSSERR("PLL lock timed out\n"); +			goto err1; +		} +	} + +	dispc_lcd_enable_signal(1); + +	/* Waiting for SDI reset to complete */ +	timeout = jiffies + msecs_to_jiffies(500); +	while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 2))) { +		if (time_after_eq(jiffies, timeout)) { +			DSSERR("SDI reset timed out\n"); +			goto err2; +		} +	} + +	return 0; + + err2: +	dispc_lcd_enable_signal(0); + err1: +	/* Reset SDI PLL */ +	REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ + +	dispc_pck_free_enable(0); + +	return -ETIMEDOUT; +} + +void dss_sdi_disable(void) +{ +	dispc_lcd_enable_signal(0); + +	dispc_pck_free_enable(0); + +	/* Reset SDI PLL */ +	REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ +} + +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) +{ +	const char *fclk_name, *fclk_real_name; +	unsigned long fclk_rate; + +	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 = clk_get_rate(dss.dss_clk); + +	seq_printf(s, "%s (%s) = %lu\n", +			fclk_name, fclk_real_name, +			fclk_rate); + +	dss_runtime_put(); +} + +static void dss_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dss_read_reg(r)) + +	if (dss_runtime_get()) +		return; + +	DUMPREG(DSS_REVISION); +	DUMPREG(DSS_SYSCONFIG); +	DUMPREG(DSS_SYSSTATUS); +	DUMPREG(DSS_CONTROL); + +	if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & +			OMAP_DISPLAY_TYPE_SDI) { +		DUMPREG(DSS_SDI_CONTROL); +		DUMPREG(DSS_PLL_CONTROL); +		DUMPREG(DSS_SDI_STATUS); +	} + +	dss_runtime_put(); +#undef DUMPREG +} + +static void dss_select_dispc_clk_source(enum omap_dss_clk_source clk_src) +{ +	struct platform_device *dsidev; +	int b; +	u8 start, end; + +	switch (clk_src) { +	case OMAP_DSS_CLK_SRC_FCK: +		b = 0; +		break; +	case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: +		b = 1; +		dsidev = dsi_get_dsidev_from_id(0); +		dsi_wait_pll_hsdiv_dispc_active(dsidev); +		break; +	case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: +		b = 2; +		dsidev = dsi_get_dsidev_from_id(1); +		dsi_wait_pll_hsdiv_dispc_active(dsidev); +		break; +	default: +		BUG(); +		return; +	} + +	dss_feat_get_reg_field(FEAT_REG_DISPC_CLK_SWITCH, &start, &end); + +	REG_FLD_MOD(DSS_CONTROL, b, start, end);	/* DISPC_CLK_SWITCH */ + +	dss.dispc_clk_source = clk_src; +} + +void dss_select_dsi_clk_source(int dsi_module, +		enum omap_dss_clk_source clk_src) +{ +	struct platform_device *dsidev; +	int b, pos; + +	switch (clk_src) { +	case OMAP_DSS_CLK_SRC_FCK: +		b = 0; +		break; +	case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI: +		BUG_ON(dsi_module != 0); +		b = 1; +		dsidev = dsi_get_dsidev_from_id(0); +		dsi_wait_pll_hsdiv_dsi_active(dsidev); +		break; +	case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI: +		BUG_ON(dsi_module != 1); +		b = 1; +		dsidev = dsi_get_dsidev_from_id(1); +		dsi_wait_pll_hsdiv_dsi_active(dsidev); +		break; +	default: +		BUG(); +		return; +	} + +	pos = dsi_module == 0 ? 1 : 10; +	REG_FLD_MOD(DSS_CONTROL, b, pos, pos);	/* DSIx_CLK_SWITCH */ + +	dss.dsi_clk_source[dsi_module] = clk_src; +} + +void dss_select_lcd_clk_source(enum omap_channel channel, +		enum omap_dss_clk_source clk_src) +{ +	struct platform_device *dsidev; +	int b, ix, pos; + +	if (!dss_has_feature(FEAT_LCD_CLK_SRC)) { +		dss_select_dispc_clk_source(clk_src); +		return; +	} + +	switch (clk_src) { +	case OMAP_DSS_CLK_SRC_FCK: +		b = 0; +		break; +	case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: +		BUG_ON(channel != OMAP_DSS_CHANNEL_LCD); +		b = 1; +		dsidev = dsi_get_dsidev_from_id(0); +		dsi_wait_pll_hsdiv_dispc_active(dsidev); +		break; +	case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: +		BUG_ON(channel != OMAP_DSS_CHANNEL_LCD2 && +		       channel != OMAP_DSS_CHANNEL_LCD3); +		b = 1; +		dsidev = dsi_get_dsidev_from_id(1); +		dsi_wait_pll_hsdiv_dispc_active(dsidev); +		break; +	default: +		BUG(); +		return; +	} + +	pos = channel == OMAP_DSS_CHANNEL_LCD ? 0 : +	     (channel == OMAP_DSS_CHANNEL_LCD2 ? 12 : 19); +	REG_FLD_MOD(DSS_CONTROL, b, pos, pos);	/* LCDx_CLK_SWITCH */ + +	ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : +	    (channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); +	dss.lcd_clk_source[ix] = clk_src; +} + +enum omap_dss_clk_source dss_get_dispc_clk_source(void) +{ +	return dss.dispc_clk_source; +} + +enum omap_dss_clk_source dss_get_dsi_clk_source(int dsi_module) +{ +	return dss.dsi_clk_source[dsi_module]; +} + +enum omap_dss_clk_source dss_get_lcd_clk_source(enum omap_channel channel) +{ +	if (dss_has_feature(FEAT_LCD_CLK_SRC)) { +		int ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : +			(channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); +		return dss.lcd_clk_source[ix]; +	} else { +		/* LCD_CLK source is the same as DISPC_FCLK source for +		 * OMAP2 and OMAP3 */ +		return dss.dispc_clk_source; +	} +} + +bool dss_div_calc(unsigned long pck, unsigned long fck_min, +		dss_div_calc_func func, void *data) +{ +	int fckd, fckd_start, fckd_stop; +	unsigned long fck; +	unsigned long fck_hw_max; +	unsigned long fckd_hw_max; +	unsigned long prate; +	unsigned m; + +	fck_hw_max = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + +	if (dss.parent_clk == NULL) { +		unsigned pckd; + +		pckd = fck_hw_max / pck; + +		fck = pck * pckd; + +		fck = clk_round_rate(dss.dss_clk, fck); + +		return func(fck, data); +	} + +	fckd_hw_max = dss.feat->fck_div_max; + +	m = dss.feat->dss_fck_multiplier; +	prate = clk_get_rate(dss.parent_clk); + +	fck_min = fck_min ? fck_min : 1; + +	fckd_start = min(prate * m / fck_min, fckd_hw_max); +	fckd_stop = max(DIV_ROUND_UP(prate * m, fck_hw_max), 1ul); + +	for (fckd = fckd_start; fckd >= fckd_stop; --fckd) { +		fck = DIV_ROUND_UP(prate, fckd) * m; + +		if (func(fck, data)) +			return true; +	} + +	return false; +} + +int dss_set_fck_rate(unsigned long rate) +{ +	int r; + +	DSSDBG("set fck to %lu\n", rate); + +	r = clk_set_rate(dss.dss_clk, rate); +	if (r) +		return r; + +	dss.dss_clk_rate = clk_get_rate(dss.dss_clk); + +	WARN_ONCE(dss.dss_clk_rate != rate, +			"clk rate mismatch: %lu != %lu", dss.dss_clk_rate, +			rate); + +	return 0; +} + +unsigned long dss_get_dispc_clk_rate(void) +{ +	return dss.dss_clk_rate; +} + +static int dss_setup_default_clock(void) +{ +	unsigned long max_dss_fck, prate; +	unsigned long fck; +	unsigned fck_div; +	int r; + +	max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); + +	if (dss.parent_clk == NULL) { +		fck = clk_round_rate(dss.dss_clk, max_dss_fck); +	} else { +		prate = clk_get_rate(dss.parent_clk); + +		fck_div = DIV_ROUND_UP(prate * dss.feat->dss_fck_multiplier, +				max_dss_fck); +		fck = DIV_ROUND_UP(prate, fck_div) * dss.feat->dss_fck_multiplier; +	} + +	r = dss_set_fck_rate(fck); +	if (r) +		return r; + +	return 0; +} + +void dss_set_venc_output(enum omap_dss_venc_type type) +{ +	int l = 0; + +	if (type == OMAP_DSS_VENC_TYPE_COMPOSITE) +		l = 0; +	else if (type == OMAP_DSS_VENC_TYPE_SVIDEO) +		l = 1; +	else +		BUG(); + +	/* venc out selection. 0 = comp, 1 = svideo */ +	REG_FLD_MOD(DSS_CONTROL, l, 6, 6); +} + +void dss_set_dac_pwrdn_bgz(bool enable) +{ +	REG_FLD_MOD(DSS_CONTROL, enable, 5, 5);	/* DAC Power-Down Control */ +} + +void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select src) +{ +	enum omap_display_type dp; +	dp = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); + +	/* Complain about invalid selections */ +	WARN_ON((src == DSS_VENC_TV_CLK) && !(dp & OMAP_DISPLAY_TYPE_VENC)); +	WARN_ON((src == DSS_HDMI_M_PCLK) && !(dp & OMAP_DISPLAY_TYPE_HDMI)); + +	/* Select only if we have options */ +	if ((dp & OMAP_DISPLAY_TYPE_VENC) && (dp & OMAP_DISPLAY_TYPE_HDMI)) +		REG_FLD_MOD(DSS_CONTROL, src, 15, 15);	/* VENC_HDMI_SWITCH */ +} + +enum dss_hdmi_venc_clk_source_select dss_get_hdmi_venc_clk_source(void) +{ +	enum omap_display_type displays; + +	displays = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); +	if ((displays & OMAP_DISPLAY_TYPE_HDMI) == 0) +		return DSS_VENC_TV_CLK; + +	if ((displays & OMAP_DISPLAY_TYPE_VENC) == 0) +		return DSS_HDMI_M_PCLK; + +	return REG_GET(DSS_CONTROL, 15, 15); +} + +static int dss_dpi_select_source_omap2_omap3(enum omap_channel channel) +{ +	if (channel != OMAP_DSS_CHANNEL_LCD) +		return -EINVAL; + +	return 0; +} + +static int dss_dpi_select_source_omap4(enum omap_channel channel) +{ +	int val; + +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD2: +		val = 0; +		break; +	case OMAP_DSS_CHANNEL_DIGIT: +		val = 1; +		break; +	default: +		return -EINVAL; +	} + +	REG_FLD_MOD(DSS_CONTROL, val, 17, 17); + +	return 0; +} + +static int dss_dpi_select_source_omap5(enum omap_channel channel) +{ +	int val; + +	switch (channel) { +	case OMAP_DSS_CHANNEL_LCD: +		val = 1; +		break; +	case OMAP_DSS_CHANNEL_LCD2: +		val = 2; +		break; +	case OMAP_DSS_CHANNEL_LCD3: +		val = 3; +		break; +	case OMAP_DSS_CHANNEL_DIGIT: +		val = 0; +		break; +	default: +		return -EINVAL; +	} + +	REG_FLD_MOD(DSS_CONTROL, val, 17, 16); + +	return 0; +} + +int dss_dpi_select_source(enum omap_channel channel) +{ +	return dss.feat->dpi_select_source(channel); +} + +static int dss_get_clocks(void) +{ +	struct clk *clk; + +	clk = devm_clk_get(&dss.pdev->dev, "fck"); +	if (IS_ERR(clk)) { +		DSSERR("can't get clock fck\n"); +		return PTR_ERR(clk); +	} + +	dss.dss_clk = clk; + +	if (dss.feat->parent_clk_name) { +		clk = clk_get(NULL, dss.feat->parent_clk_name); +		if (IS_ERR(clk)) { +			DSSERR("Failed to get %s\n", dss.feat->parent_clk_name); +			return PTR_ERR(clk); +		} +	} else { +		clk = NULL; +	} + +	dss.parent_clk = clk; + +	return 0; +} + +static void dss_put_clocks(void) +{ +	if (dss.parent_clk) +		clk_put(dss.parent_clk); +} + +static int dss_runtime_get(void) +{ +	int r; + +	DSSDBG("dss_runtime_get\n"); + +	r = pm_runtime_get_sync(&dss.pdev->dev); +	WARN_ON(r < 0); +	return r < 0 ? r : 0; +} + +static void dss_runtime_put(void) +{ +	int r; + +	DSSDBG("dss_runtime_put\n"); + +	r = pm_runtime_put_sync(&dss.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS && r != -EBUSY); +} + +/* DEBUGFS */ +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +void dss_debug_dump_clocks(struct seq_file *s) +{ +	dss_dump_clocks(s); +	dispc_dump_clocks(s); +#ifdef CONFIG_OMAP2_DSS_DSI +	dsi_dump_clocks(s); +#endif +} +#endif + +static const struct dss_features omap24xx_dss_feats __initconst = { +	/* +	 * fck div max is really 16, but the divider range has gaps. The range +	 * from 1 to 6 has no gaps, so let's use that as a max. +	 */ +	.fck_div_max		=	6, +	.dss_fck_multiplier	=	2, +	.parent_clk_name	=	"core_ck", +	.dpi_select_source	=	&dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap34xx_dss_feats __initconst = { +	.fck_div_max		=	16, +	.dss_fck_multiplier	=	2, +	.parent_clk_name	=	"dpll4_ck", +	.dpi_select_source	=	&dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap3630_dss_feats __initconst = { +	.fck_div_max		=	32, +	.dss_fck_multiplier	=	1, +	.parent_clk_name	=	"dpll4_ck", +	.dpi_select_source	=	&dss_dpi_select_source_omap2_omap3, +}; + +static const struct dss_features omap44xx_dss_feats __initconst = { +	.fck_div_max		=	32, +	.dss_fck_multiplier	=	1, +	.parent_clk_name	=	"dpll_per_x2_ck", +	.dpi_select_source	=	&dss_dpi_select_source_omap4, +}; + +static const struct dss_features omap54xx_dss_feats __initconst = { +	.fck_div_max		=	64, +	.dss_fck_multiplier	=	1, +	.parent_clk_name	=	"dpll_per_x2_ck", +	.dpi_select_source	=	&dss_dpi_select_source_omap5, +}; + +static const struct dss_features am43xx_dss_feats __initconst = { +	.fck_div_max		=	0, +	.dss_fck_multiplier	=	0, +	.parent_clk_name	=	NULL, +	.dpi_select_source	=	&dss_dpi_select_source_omap2_omap3, +}; + +static int __init dss_init_features(struct platform_device *pdev) +{ +	const struct dss_features *src; +	struct dss_features *dst; + +	dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); +	if (!dst) { +		dev_err(&pdev->dev, "Failed to allocate local DSS Features\n"); +		return -ENOMEM; +	} + +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP24xx: +		src = &omap24xx_dss_feats; +		break; + +	case OMAPDSS_VER_OMAP34xx_ES1: +	case OMAPDSS_VER_OMAP34xx_ES3: +	case OMAPDSS_VER_AM35xx: +		src = &omap34xx_dss_feats; +		break; + +	case OMAPDSS_VER_OMAP3630: +		src = &omap3630_dss_feats; +		break; + +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		src = &omap44xx_dss_feats; +		break; + +	case OMAPDSS_VER_OMAP5: +		src = &omap54xx_dss_feats; +		break; + +	case OMAPDSS_VER_AM43xx: +		src = &am43xx_dss_feats; +		break; + +	default: +		return -ENODEV; +	} + +	memcpy(dst, src, sizeof(*dst)); +	dss.feat = dst; + +	return 0; +} + +static int __init dss_init_ports(struct platform_device *pdev) +{ +	struct device_node *parent = pdev->dev.of_node; +	struct device_node *port; +	int r; + +	if (parent == NULL) +		return 0; + +	port = omapdss_of_get_next_port(parent, NULL); +	if (!port) +		return 0; + +	do { +		u32 reg; + +		r = of_property_read_u32(port, "reg", ®); +		if (r) +			reg = 0; + +#ifdef CONFIG_OMAP2_DSS_DPI +		if (reg == 0) +			dpi_init_port(pdev, port); +#endif + +#ifdef CONFIG_OMAP2_DSS_SDI +		if (reg == 1) +			sdi_init_port(pdev, port); +#endif + +	} while ((port = omapdss_of_get_next_port(parent, port)) != NULL); + +	return 0; +} + +static void __exit dss_uninit_ports(void) +{ +#ifdef CONFIG_OMAP2_DSS_DPI +	dpi_uninit_port(); +#endif + +#ifdef CONFIG_OMAP2_DSS_SDI +	sdi_uninit_port(); +#endif +} + +/* DSS HW IP initialisation */ +static int __init omap_dsshw_probe(struct platform_device *pdev) +{ +	struct resource *dss_mem; +	u32 rev; +	int r; + +	dss.pdev = pdev; + +	r = dss_init_features(dss.pdev); +	if (r) +		return r; + +	dss_mem = platform_get_resource(dss.pdev, IORESOURCE_MEM, 0); +	if (!dss_mem) { +		DSSERR("can't get IORESOURCE_MEM DSS\n"); +		return -EINVAL; +	} + +	dss.base = devm_ioremap(&pdev->dev, dss_mem->start, +				resource_size(dss_mem)); +	if (!dss.base) { +		DSSERR("can't ioremap DSS\n"); +		return -ENOMEM; +	} + +	r = dss_get_clocks(); +	if (r) +		return r; + +	r = dss_setup_default_clock(); +	if (r) +		goto err_setup_clocks; + +	pm_runtime_enable(&pdev->dev); + +	r = dss_runtime_get(); +	if (r) +		goto err_runtime_get; + +	dss.dss_clk_rate = clk_get_rate(dss.dss_clk); + +	/* Select DPLL */ +	REG_FLD_MOD(DSS_CONTROL, 0, 0, 0); + +	dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK); + +#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; + +	dss_init_ports(pdev); + +	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(); + +	dss_debugfs_create_file("dss", dss_dump_regs); + +	return 0; + +err_runtime_get: +	pm_runtime_disable(&pdev->dev); +err_setup_clocks: +	dss_put_clocks(); +	return r; +} + +static int __exit omap_dsshw_remove(struct platform_device *pdev) +{ +	dss_uninit_ports(); + +	pm_runtime_disable(&pdev->dev); + +	dss_put_clocks(); + +	return 0; +} + +static int dss_runtime_suspend(struct device *dev) +{ +	dss_save_context(); +	dss_set_min_bus_tput(dev, 0); +	return 0; +} + +static int dss_runtime_resume(struct device *dev) +{ +	int r; +	/* +	 * Set an arbitrarily high tput request to ensure OPP100. +	 * What we should really do is to make a request to stay in OPP100, +	 * without any tput requirements, but that is not currently possible +	 * via the PM layer. +	 */ + +	r = dss_set_min_bus_tput(dev, 1000000000); +	if (r) +		return r; + +	dss_restore_context(); +	return 0; +} + +static const struct dev_pm_ops dss_pm_ops = { +	.runtime_suspend = dss_runtime_suspend, +	.runtime_resume = dss_runtime_resume, +}; + +static const struct of_device_id dss_of_match[] = { +	{ .compatible = "ti,omap2-dss", }, +	{ .compatible = "ti,omap3-dss", }, +	{ .compatible = "ti,omap4-dss", }, +	{ .compatible = "ti,omap5-dss", }, +	{}, +}; + +MODULE_DEVICE_TABLE(of, dss_of_match); + +static struct platform_driver omap_dsshw_driver = { +	.remove         = __exit_p(omap_dsshw_remove), +	.driver         = { +		.name   = "omapdss_dss", +		.owner  = THIS_MODULE, +		.pm	= &dss_pm_ops, +		.of_match_table = dss_of_match, +	}, +}; + +int __init dss_init_platform_driver(void) +{ +	return platform_driver_probe(&omap_dsshw_driver, omap_dsshw_probe); +} + +void dss_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_dsshw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/dss.h b/drivers/video/fbdev/omap2/dss/dss.h new file mode 100644 index 00000000000..8ff22c134c6 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss.h @@ -0,0 +1,441 @@ +/* + * linux/drivers/video/omap2/dss/dss.h + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 __OMAP2_DSS_H +#define __OMAP2_DSS_H + +#include <linux/interrupt.h> + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#ifdef DSS_SUBSYS_NAME +#define pr_fmt(fmt) DSS_SUBSYS_NAME ": " fmt +#else +#define pr_fmt(fmt) fmt +#endif + +#define DSSDBG(format, ...) \ +	pr_debug(format, ## __VA_ARGS__) + +#ifdef DSS_SUBSYS_NAME +#define DSSERR(format, ...) \ +	printk(KERN_ERR "omapdss " DSS_SUBSYS_NAME " error: " format, \ +	## __VA_ARGS__) +#else +#define DSSERR(format, ...) \ +	printk(KERN_ERR "omapdss error: " format, ## __VA_ARGS__) +#endif + +#ifdef DSS_SUBSYS_NAME +#define DSSINFO(format, ...) \ +	printk(KERN_INFO "omapdss " DSS_SUBSYS_NAME ": " format, \ +	## __VA_ARGS__) +#else +#define DSSINFO(format, ...) \ +	printk(KERN_INFO "omapdss: " format, ## __VA_ARGS__) +#endif + +#ifdef DSS_SUBSYS_NAME +#define DSSWARN(format, ...) \ +	printk(KERN_WARNING "omapdss " DSS_SUBSYS_NAME ": " format, \ +	## __VA_ARGS__) +#else +#define DSSWARN(format, ...) \ +	printk(KERN_WARNING "omapdss: " format, ## __VA_ARGS__) +#endif + +/* OMAP TRM gives bitfields as start:end, where start is the higher bit +   number. For example 7:0 */ +#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end)) +#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)) + +enum dss_io_pad_mode { +	DSS_IO_PAD_MODE_RESET, +	DSS_IO_PAD_MODE_RFBI, +	DSS_IO_PAD_MODE_BYPASS, +}; + +enum dss_hdmi_venc_clk_source_select { +	DSS_VENC_TV_CLK = 0, +	DSS_HDMI_M_PCLK = 1, +}; + +enum dss_dsi_content_type { +	DSS_DSI_CONTENT_DCS, +	DSS_DSI_CONTENT_GENERIC, +}; + +enum dss_writeback_channel { +	DSS_WB_LCD1_MGR =	0, +	DSS_WB_LCD2_MGR =	1, +	DSS_WB_TV_MGR =		2, +	DSS_WB_OVL0 =		3, +	DSS_WB_OVL1 =		4, +	DSS_WB_OVL2 =		5, +	DSS_WB_OVL3 =		6, +	DSS_WB_LCD3_MGR =	7, +}; + +struct dispc_clock_info { +	/* rates that we get with dividers below */ +	unsigned long lck; +	unsigned long pck; + +	/* dividers */ +	u16 lck_div; +	u16 pck_div; +}; + +struct dsi_clock_info { +	/* rates that we get with dividers below */ +	unsigned long fint; +	unsigned long clkin4ddr; +	unsigned long clkin; +	unsigned long dsi_pll_hsdiv_dispc_clk;	/* OMAP3: DSI1_PLL_CLK +						 * OMAP4: PLLx_CLK1 */ +	unsigned long dsi_pll_hsdiv_dsi_clk;	/* OMAP3: DSI2_PLL_CLK +						 * OMAP4: PLLx_CLK2 */ +	unsigned long lp_clk; + +	/* dividers */ +	u16 regn; +	u16 regm; +	u16 regm_dispc;	/* OMAP3: REGM3 +			 * OMAP4: REGM4 */ +	u16 regm_dsi;	/* OMAP3: REGM4 +			 * OMAP4: REGM5 */ +	u16 lp_clk_div; +}; + +struct dss_lcd_mgr_config { +	enum dss_io_pad_mode io_pad_mode; + +	bool stallmode; +	bool fifohandcheck; + +	struct dispc_clock_info clock_info; + +	int video_port_width; + +	int lcden_sig_polarity; +}; + +struct seq_file; +struct platform_device; + +/* core */ +struct platform_device *dss_get_core_pdev(void); +int dss_dsi_enable_pads(int dsi_id, unsigned lane_mask); +void dss_dsi_disable_pads(int dsi_id, unsigned lane_mask); +int dss_set_min_bus_tput(struct device *dev, unsigned long tput); +int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *)); + +/* display */ +int dss_suspend_all_devices(void); +int dss_resume_all_devices(void); +void dss_disable_all_devices(void); + +int display_init_sysfs(struct platform_device *pdev); +void display_uninit_sysfs(struct platform_device *pdev); + +/* manager */ +int dss_init_overlay_managers(void); +void dss_uninit_overlay_managers(void); +int dss_init_overlay_managers_sysfs(struct platform_device *pdev); +void dss_uninit_overlay_managers_sysfs(struct platform_device *pdev); +int dss_mgr_simple_check(struct omap_overlay_manager *mgr, +		const struct omap_overlay_manager_info *info); +int dss_mgr_check_timings(struct omap_overlay_manager *mgr, +		const struct omap_video_timings *timings); +int dss_mgr_check(struct omap_overlay_manager *mgr, +		struct omap_overlay_manager_info *info, +		const struct omap_video_timings *mgr_timings, +		const struct dss_lcd_mgr_config *config, +		struct omap_overlay_info **overlay_infos); + +static inline bool dss_mgr_is_lcd(enum omap_channel id) +{ +	if (id == OMAP_DSS_CHANNEL_LCD || id == OMAP_DSS_CHANNEL_LCD2 || +			id == OMAP_DSS_CHANNEL_LCD3) +		return true; +	else +		return false; +} + +int dss_manager_kobj_init(struct omap_overlay_manager *mgr, +		struct platform_device *pdev); +void dss_manager_kobj_uninit(struct omap_overlay_manager *mgr); + +/* overlay */ +void dss_init_overlays(struct platform_device *pdev); +void dss_uninit_overlays(struct platform_device *pdev); +void dss_overlay_setup_dispc_manager(struct omap_overlay_manager *mgr); +int dss_ovl_simple_check(struct omap_overlay *ovl, +		const struct omap_overlay_info *info); +int dss_ovl_check(struct omap_overlay *ovl, struct omap_overlay_info *info, +		const struct omap_video_timings *mgr_timings); +bool dss_ovl_use_replication(struct dss_lcd_mgr_config config, +		enum omap_color_mode mode); +int dss_overlay_kobj_init(struct omap_overlay *ovl, +		struct platform_device *pdev); +void dss_overlay_kobj_uninit(struct omap_overlay *ovl); + +/* DSS */ +int dss_init_platform_driver(void) __init; +void dss_uninit_platform_driver(void); + +unsigned long dss_get_dispc_clk_rate(void); +int dss_dpi_select_source(enum omap_channel channel); +void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select); +enum dss_hdmi_venc_clk_source_select dss_get_hdmi_venc_clk_source(void); +const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src); +void dss_dump_clocks(struct seq_file *s); + +#if defined(CONFIG_OMAP2_DSS_DEBUGFS) +void dss_debug_dump_clocks(struct seq_file *s); +#endif + +void dss_sdi_init(int datapairs); +int dss_sdi_enable(void); +void dss_sdi_disable(void); + +void dss_select_dsi_clk_source(int dsi_module, +		enum omap_dss_clk_source clk_src); +void dss_select_lcd_clk_source(enum omap_channel channel, +		enum omap_dss_clk_source clk_src); +enum omap_dss_clk_source dss_get_dispc_clk_source(void); +enum omap_dss_clk_source dss_get_dsi_clk_source(int dsi_module); +enum omap_dss_clk_source dss_get_lcd_clk_source(enum omap_channel channel); + +void dss_set_venc_output(enum omap_dss_venc_type type); +void dss_set_dac_pwrdn_bgz(bool enable); + +int dss_set_fck_rate(unsigned long rate); + +typedef bool (*dss_div_calc_func)(unsigned long fck, void *data); +bool dss_div_calc(unsigned long pck, unsigned long fck_min, +		dss_div_calc_func func, void *data); + +/* SDI */ +int sdi_init_platform_driver(void) __init; +void sdi_uninit_platform_driver(void) __exit; + +int sdi_init_port(struct platform_device *pdev, struct device_node *port) __init; +void sdi_uninit_port(void) __exit; + +/* DSI */ + +typedef bool (*dsi_pll_calc_func)(int regn, int regm, unsigned long fint, +		unsigned long pll, void *data); +typedef bool (*dsi_hsdiv_calc_func)(int regm_dispc, unsigned long dispc, +		void *data); + +#ifdef CONFIG_OMAP2_DSS_DSI + +struct dentry; +struct file_operations; + +int dsi_init_platform_driver(void) __init; +void dsi_uninit_platform_driver(void) __exit; + +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_irq_handler(void); +u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt); + +unsigned long dsi_get_pll_clkin(struct platform_device *dsidev); + +bool dsi_hsdiv_calc(struct platform_device *dsidev, unsigned long pll, +		unsigned long out_min, dsi_hsdiv_calc_func func, void *data); +bool dsi_pll_calc(struct platform_device *dsidev, unsigned long clkin, +		unsigned long pll_min, unsigned long pll_max, +		dsi_pll_calc_func func, void *data); + +unsigned long dsi_get_pll_hsdiv_dispc_rate(struct platform_device *dsidev); +int dsi_pll_set_clock_div(struct platform_device *dsidev, +		struct dsi_clock_info *cinfo); +int dsi_pll_init(struct platform_device *dsidev, bool enable_hsclk, +		bool enable_hsdiv); +void dsi_pll_uninit(struct platform_device *dsidev, bool disconnect_lanes); +void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev); +void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev); +struct platform_device *dsi_get_dsidev_from_id(int module); +#else +static inline int dsi_runtime_get(struct platform_device *dsidev) +{ +	return 0; +} +static inline void dsi_runtime_put(struct platform_device *dsidev) +{ +} +static inline u8 dsi_get_pixel_size(enum omap_dss_dsi_pixel_format fmt) +{ +	WARN("%s: DSI not compiled in, returning pixel_size as 0\n", __func__); +	return 0; +} +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__); +	return 0; +} +static inline int dsi_pll_set_clock_div(struct platform_device *dsidev, +		struct dsi_clock_info *cinfo) +{ +	WARN("%s: DSI not compiled in\n", __func__); +	return -ENODEV; +} +static inline int dsi_pll_init(struct platform_device *dsidev, +		bool enable_hsclk, bool enable_hsdiv) +{ +	WARN("%s: DSI not compiled in\n", __func__); +	return -ENODEV; +} +static inline void dsi_pll_uninit(struct platform_device *dsidev, +		bool disconnect_lanes) +{ +} +static inline void dsi_wait_pll_hsdiv_dispc_active(struct platform_device *dsidev) +{ +} +static inline void dsi_wait_pll_hsdiv_dsi_active(struct platform_device *dsidev) +{ +} +static inline struct platform_device *dsi_get_dsidev_from_id(int module) +{ +	return NULL; +} + +static inline unsigned long dsi_get_pll_clkin(struct platform_device *dsidev) +{ +	return 0; +} + +static inline bool dsi_hsdiv_calc(struct platform_device *dsidev, +		unsigned long pll, unsigned long out_min, +		dsi_hsdiv_calc_func func, void *data) +{ +	return false; +} + +static inline bool dsi_pll_calc(struct platform_device *dsidev, +		unsigned long clkin, +		unsigned long pll_min, unsigned long pll_max, +		dsi_pll_calc_func func, void *data) +{ +	return false; +} + +#endif + +/* DPI */ +int dpi_init_platform_driver(void) __init; +void dpi_uninit_platform_driver(void) __exit; + +int dpi_init_port(struct platform_device *pdev, struct device_node *port) __init; +void dpi_uninit_port(void) __exit; + +/* DISPC */ +int dispc_init_platform_driver(void) __init; +void dispc_uninit_platform_driver(void) __exit; +void dispc_dump_clocks(struct seq_file *s); + +void dispc_enable_sidle(void); +void dispc_disable_sidle(void); + +void dispc_lcd_enable_signal(bool enable); +void dispc_pck_free_enable(bool enable); +void dispc_enable_fifomerge(bool enable); +void dispc_enable_gamma_table(bool enable); +void dispc_set_loadmode(enum omap_dss_load_mode mode); + +typedef bool (*dispc_div_calc_func)(int lckd, int pckd, unsigned long lck, +		unsigned long pck, void *data); +bool dispc_div_calc(unsigned long dispc, +		unsigned long pck_min, unsigned long pck_max, +		dispc_div_calc_func func, void *data); + +bool dispc_mgr_timings_ok(enum omap_channel channel, +		const struct omap_video_timings *timings); +unsigned long dispc_fclk_rate(void); +int dispc_calc_clock_rates(unsigned long dispc_fclk_rate, +		struct dispc_clock_info *cinfo); + + +void dispc_ovl_set_fifo_threshold(enum omap_plane plane, u32 low, u32 high); +void dispc_ovl_compute_fifo_thresholds(enum omap_plane plane, +		u32 *fifo_low, u32 *fifo_high, bool use_fifomerge, +		bool manual_update); + +unsigned long dispc_mgr_lclk_rate(enum omap_channel channel); +unsigned long dispc_mgr_pclk_rate(enum omap_channel channel); +unsigned long dispc_core_clk_rate(void); +void dispc_mgr_set_clock_div(enum omap_channel channel, +		const struct dispc_clock_info *cinfo); +int dispc_mgr_get_clock_div(enum omap_channel channel, +		struct dispc_clock_info *cinfo); +void dispc_set_tv_pclk(unsigned long pclk); + +u32 dispc_wb_get_framedone_irq(void); +bool dispc_wb_go_busy(void); +void dispc_wb_go(void); +void dispc_wb_enable(bool enable); +bool dispc_wb_is_enabled(void); +void dispc_wb_set_channel_in(enum dss_writeback_channel channel); +int dispc_wb_setup(const struct omap_dss_writeback_info *wi, +		bool mem_to_mem, const struct omap_video_timings *timings); + +/* VENC */ +int venc_init_platform_driver(void) __init; +void venc_uninit_platform_driver(void) __exit; + +/* HDMI */ +int hdmi4_init_platform_driver(void) __init; +void hdmi4_uninit_platform_driver(void) __exit; + +int hdmi5_init_platform_driver(void) __init; +void hdmi5_uninit_platform_driver(void) __exit; + +/* RFBI */ +int rfbi_init_platform_driver(void) __init; +void rfbi_uninit_platform_driver(void) __exit; + + +#ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS +static inline void dss_collect_irq_stats(u32 irqstatus, unsigned *irq_arr) +{ +	int b; +	for (b = 0; b < 32; ++b) { +		if (irqstatus & (1 << b)) +			irq_arr[b]++; +	} +} +#endif + +#endif diff --git a/drivers/video/fbdev/omap2/dss/dss_features.c b/drivers/video/fbdev/omap2/dss/dss_features.c new file mode 100644 index 00000000000..15088df7bd1 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss_features.c @@ -0,0 +1,1003 @@ +/* + * linux/drivers/video/omap2/dss/dss_features.c + * + * Copyright (C) 2010 Texas Instruments + * Author: Archit Taneja <archit@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/module.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +/* Defines a generic omap register field */ +struct dss_reg_field { +	u8 start, end; +}; + +struct dss_param_range { +	int min, max; +}; + +struct omap_dss_features { +	const struct dss_reg_field *reg_fields; +	const int num_reg_fields; + +	const enum dss_feat_id *features; +	const int num_features; + +	const int num_mgrs; +	const int num_ovls; +	const int num_wbs; +	const enum omap_display_type *supported_displays; +	const enum omap_dss_output_id *supported_outputs; +	const enum omap_color_mode *supported_color_modes; +	const enum omap_overlay_caps *overlay_caps; +	const char * const *clksrc_names; +	const struct dss_param_range *dss_params; + +	const enum omap_dss_rotation_type supported_rotation_types; + +	const u32 buffer_size_unit; +	const u32 burst_size_unit; +}; + +/* This struct is assigned to one of the below during initialization */ +static const struct omap_dss_features *omap_current_dss_features; + +static const struct dss_reg_field omap2_dss_reg_fields[] = { +	[FEAT_REG_FIRHINC]			= { 11, 0 }, +	[FEAT_REG_FIRVINC]			= { 27, 16 }, +	[FEAT_REG_FIFOLOWTHRESHOLD]		= { 8, 0 }, +	[FEAT_REG_FIFOHIGHTHRESHOLD]		= { 24, 16 }, +	[FEAT_REG_FIFOSIZE]			= { 8, 0 }, +	[FEAT_REG_HORIZONTALACCU]		= { 9, 0 }, +	[FEAT_REG_VERTICALACCU]			= { 25, 16 }, +	[FEAT_REG_DISPC_CLK_SWITCH]		= { 0, 0 }, +	[FEAT_REG_DSIPLL_REGN]			= { 0, 0 }, +	[FEAT_REG_DSIPLL_REGM]			= { 0, 0 }, +	[FEAT_REG_DSIPLL_REGM_DISPC]		= { 0, 0 }, +	[FEAT_REG_DSIPLL_REGM_DSI]		= { 0, 0 }, +}; + +static const struct dss_reg_field omap3_dss_reg_fields[] = { +	[FEAT_REG_FIRHINC]			= { 12, 0 }, +	[FEAT_REG_FIRVINC]			= { 28, 16 }, +	[FEAT_REG_FIFOLOWTHRESHOLD]		= { 11, 0 }, +	[FEAT_REG_FIFOHIGHTHRESHOLD]		= { 27, 16 }, +	[FEAT_REG_FIFOSIZE]			= { 10, 0 }, +	[FEAT_REG_HORIZONTALACCU]		= { 9, 0 }, +	[FEAT_REG_VERTICALACCU]			= { 25, 16 }, +	[FEAT_REG_DISPC_CLK_SWITCH]		= { 0, 0 }, +	[FEAT_REG_DSIPLL_REGN]			= { 7, 1 }, +	[FEAT_REG_DSIPLL_REGM]			= { 18, 8 }, +	[FEAT_REG_DSIPLL_REGM_DISPC]		= { 22, 19 }, +	[FEAT_REG_DSIPLL_REGM_DSI]		= { 26, 23 }, +}; + +static const struct dss_reg_field am43xx_dss_reg_fields[] = { +	[FEAT_REG_FIRHINC]			= { 12, 0 }, +	[FEAT_REG_FIRVINC]			= { 28, 16 }, +	[FEAT_REG_FIFOLOWTHRESHOLD]	= { 11, 0 }, +	[FEAT_REG_FIFOHIGHTHRESHOLD]		= { 27, 16 }, +	[FEAT_REG_FIFOSIZE]		= { 10, 0 }, +	[FEAT_REG_HORIZONTALACCU]		= { 9, 0 }, +	[FEAT_REG_VERTICALACCU]			= { 25, 16 }, +	[FEAT_REG_DISPC_CLK_SWITCH]		= { 0, 0 }, +}; + +static const struct dss_reg_field omap4_dss_reg_fields[] = { +	[FEAT_REG_FIRHINC]			= { 12, 0 }, +	[FEAT_REG_FIRVINC]			= { 28, 16 }, +	[FEAT_REG_FIFOLOWTHRESHOLD]		= { 15, 0 }, +	[FEAT_REG_FIFOHIGHTHRESHOLD]		= { 31, 16 }, +	[FEAT_REG_FIFOSIZE]			= { 15, 0 }, +	[FEAT_REG_HORIZONTALACCU]		= { 10, 0 }, +	[FEAT_REG_VERTICALACCU]			= { 26, 16 }, +	[FEAT_REG_DISPC_CLK_SWITCH]		= { 9, 8 }, +	[FEAT_REG_DSIPLL_REGN]			= { 8, 1 }, +	[FEAT_REG_DSIPLL_REGM]			= { 20, 9 }, +	[FEAT_REG_DSIPLL_REGM_DISPC]		= { 25, 21 }, +	[FEAT_REG_DSIPLL_REGM_DSI]		= { 30, 26 }, +}; + +static const struct dss_reg_field omap5_dss_reg_fields[] = { +	[FEAT_REG_FIRHINC]			= { 12, 0 }, +	[FEAT_REG_FIRVINC]			= { 28, 16 }, +	[FEAT_REG_FIFOLOWTHRESHOLD]		= { 15, 0 }, +	[FEAT_REG_FIFOHIGHTHRESHOLD]		= { 31, 16 }, +	[FEAT_REG_FIFOSIZE]			= { 15, 0 }, +	[FEAT_REG_HORIZONTALACCU]		= { 10, 0 }, +	[FEAT_REG_VERTICALACCU]			= { 26, 16 }, +	[FEAT_REG_DISPC_CLK_SWITCH]		= { 9, 7 }, +	[FEAT_REG_DSIPLL_REGN]			= { 8, 1 }, +	[FEAT_REG_DSIPLL_REGM]			= { 20, 9 }, +	[FEAT_REG_DSIPLL_REGM_DISPC]		= { 25, 21 }, +	[FEAT_REG_DSIPLL_REGM_DSI]		= { 30, 26 }, +}; + +static const enum omap_display_type omap2_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type omap3430_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | +	OMAP_DISPLAY_TYPE_SDI | OMAP_DISPLAY_TYPE_DSI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type omap3630_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | +	OMAP_DISPLAY_TYPE_DSI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DISPLAY_TYPE_VENC, +}; + +static const enum omap_display_type am43xx_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI, +}; + +static const enum omap_display_type omap4_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DBI | OMAP_DISPLAY_TYPE_DSI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DISPLAY_TYPE_VENC | OMAP_DISPLAY_TYPE_HDMI, + +	/* OMAP_DSS_CHANNEL_LCD2 */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | +	OMAP_DISPLAY_TYPE_DSI, +}; + +static const enum omap_display_type omap5_dss_supported_displays[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | +	OMAP_DISPLAY_TYPE_DSI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DISPLAY_TYPE_HDMI | OMAP_DISPLAY_TYPE_DPI, + +	/* OMAP_DSS_CHANNEL_LCD2 */ +	OMAP_DISPLAY_TYPE_DPI | OMAP_DISPLAY_TYPE_DBI | +	OMAP_DISPLAY_TYPE_DSI, +}; + +static const enum omap_dss_output_id omap2_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id omap3430_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_SDI | OMAP_DSS_OUTPUT_DSI1, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id omap3630_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_DSI1, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DSS_OUTPUT_VENC, +}; + +static const enum omap_dss_output_id am43xx_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI, +}; + +static const enum omap_dss_output_id omap4_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DBI | OMAP_DSS_OUTPUT_DSI1, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DSS_OUTPUT_VENC | OMAP_DSS_OUTPUT_HDMI, + +	/* OMAP_DSS_CHANNEL_LCD2 */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_DSI2, +}; + +static const enum omap_dss_output_id omap5_dss_supported_outputs[] = { +	/* OMAP_DSS_CHANNEL_LCD */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_DSI1 | OMAP_DSS_OUTPUT_DSI2, + +	/* OMAP_DSS_CHANNEL_DIGIT */ +	OMAP_DSS_OUTPUT_HDMI | OMAP_DSS_OUTPUT_DPI, + +	/* OMAP_DSS_CHANNEL_LCD2 */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_DSI1, + +	/* OMAP_DSS_CHANNEL_LCD3 */ +	OMAP_DSS_OUTPUT_DPI | OMAP_DSS_OUTPUT_DBI | +	OMAP_DSS_OUTPUT_DSI2, +}; + +static const enum omap_color_mode omap2_dss_supported_color_modes[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | +	OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | +	OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_RGB16 | +	OMAP_DSS_COLOR_RGB24U | OMAP_DSS_COLOR_RGB24P, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | +	OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | +	OMAP_DSS_COLOR_UYVY, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | +	OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | +	OMAP_DSS_COLOR_UYVY, +}; + +static const enum omap_color_mode omap3_dss_supported_color_modes[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | +	OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | +	OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | +	OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | +	OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_ARGB32 | +	OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_COLOR_RGB24U | OMAP_DSS_COLOR_RGB24P | +	OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_RGB16 | +	OMAP_DSS_COLOR_YUV2 | OMAP_DSS_COLOR_UYVY, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | +	OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | +	OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_YUV2 | +	OMAP_DSS_COLOR_UYVY | OMAP_DSS_COLOR_ARGB32 | +	OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32, +}; + +static const enum omap_color_mode omap4_dss_supported_color_modes[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_COLOR_CLUT1 | OMAP_DSS_COLOR_CLUT2 | +	OMAP_DSS_COLOR_CLUT4 | OMAP_DSS_COLOR_CLUT8 | +	OMAP_DSS_COLOR_RGB12U | OMAP_DSS_COLOR_ARGB16 | +	OMAP_DSS_COLOR_RGB16 | OMAP_DSS_COLOR_RGB24U | +	OMAP_DSS_COLOR_RGB24P | OMAP_DSS_COLOR_ARGB32 | +	OMAP_DSS_COLOR_RGBA32 | OMAP_DSS_COLOR_RGBX32 | +	OMAP_DSS_COLOR_ARGB16_1555 | OMAP_DSS_COLOR_RGBX16 | +	OMAP_DSS_COLOR_RGBA16 | OMAP_DSS_COLOR_XRGB16_1555, + +	/* OMAP_DSS_VIDEO1 */ +	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_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 | +	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_WB */ +	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, +}; + +static const enum omap_overlay_caps omap2_dss_overlay_caps[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3430_dss_overlay_caps[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | +		OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3630_dss_overlay_caps[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | +		OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | +		OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap4_dss_overlay_caps[] = { +	/* OMAP_DSS_GFX */ +	OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | +		OMAP_DSS_OVL_CAP_ZORDER | OMAP_DSS_OVL_CAP_POS | +		OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO1 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | +		OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | +		OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO2 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | +		OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | +		OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + +	/* OMAP_DSS_VIDEO3 */ +	OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | +		OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | +		OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const char * const omap2_dss_clk_source_names[] = { +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC]	= "N/A", +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI]	= "N/A", +	[OMAP_DSS_CLK_SRC_FCK]			= "DSS_FCLK1", +}; + +static const char * const omap3_dss_clk_source_names[] = { +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC]	= "DSI1_PLL_FCLK", +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI]	= "DSI2_PLL_FCLK", +	[OMAP_DSS_CLK_SRC_FCK]			= "DSS1_ALWON_FCLK", +}; + +static const char * const omap4_dss_clk_source_names[] = { +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC]	= "PLL1_CLK1", +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI]	= "PLL1_CLK2", +	[OMAP_DSS_CLK_SRC_FCK]			= "DSS_FCLK", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC]	= "PLL2_CLK1", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI]	= "PLL2_CLK2", +}; + +static const char * const omap5_dss_clk_source_names[] = { +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC]	= "DPLL_DSI1_A_CLK1", +	[OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI]	= "DPLL_DSI1_A_CLK2", +	[OMAP_DSS_CLK_SRC_FCK]			= "DSS_CLK", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC]	= "DPLL_DSI1_C_CLK1", +	[OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI]	= "DPLL_DSI1_C_CLK2", +}; + +static const struct dss_param_range omap2_dss_param_range[] = { +	[FEAT_PARAM_DSS_FCK]			= { 0, 133000000 }, +	[FEAT_PARAM_DSS_PCD]			= { 2, 255 }, +	[FEAT_PARAM_DSIPLL_REGN]		= { 0, 0 }, +	[FEAT_PARAM_DSIPLL_REGM]		= { 0, 0 }, +	[FEAT_PARAM_DSIPLL_REGM_DISPC]		= { 0, 0 }, +	[FEAT_PARAM_DSIPLL_REGM_DSI]		= { 0, 0 }, +	[FEAT_PARAM_DSIPLL_FINT]		= { 0, 0 }, +	[FEAT_PARAM_DSIPLL_LPDIV]		= { 0, 0 }, +	[FEAT_PARAM_DOWNSCALE]			= { 1, 2 }, +	/* +	 * Assuming the line width buffer to be 768 pixels as OMAP2 DISPC +	 * scaler cannot scale a image with width more than 768. +	 */ +	[FEAT_PARAM_LINEWIDTH]			= { 1, 768 }, +}; + +static const struct dss_param_range omap3_dss_param_range[] = { +	[FEAT_PARAM_DSS_FCK]			= { 0, 173000000 }, +	[FEAT_PARAM_DSS_PCD]			= { 1, 255 }, +	[FEAT_PARAM_DSIPLL_REGN]		= { 0, (1 << 7) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM]		= { 0, (1 << 11) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DISPC]		= { 0, (1 << 4) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DSI]		= { 0, (1 << 4) - 1 }, +	[FEAT_PARAM_DSIPLL_FINT]		= { 750000, 2100000 }, +	[FEAT_PARAM_DSIPLL_LPDIV]		= { 1, (1 << 13) - 1}, +	[FEAT_PARAM_DSI_FCK]			= { 0, 173000000 }, +	[FEAT_PARAM_DOWNSCALE]			= { 1, 4 }, +	[FEAT_PARAM_LINEWIDTH]			= { 1, 1024 }, +}; + +static const struct dss_param_range am43xx_dss_param_range[] = { +	[FEAT_PARAM_DSS_FCK]			= { 0, 200000000 }, +	[FEAT_PARAM_DSS_PCD]			= { 2, 255 }, +	[FEAT_PARAM_DOWNSCALE]			= { 1, 4 }, +	[FEAT_PARAM_LINEWIDTH]			= { 1, 1024 }, +}; + +static const struct dss_param_range omap4_dss_param_range[] = { +	[FEAT_PARAM_DSS_FCK]			= { 0, 186000000 }, +	[FEAT_PARAM_DSS_PCD]			= { 1, 255 }, +	[FEAT_PARAM_DSIPLL_REGN]		= { 0, (1 << 8) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM]		= { 0, (1 << 12) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DISPC]		= { 0, (1 << 5) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DSI]		= { 0, (1 << 5) - 1 }, +	[FEAT_PARAM_DSIPLL_FINT]		= { 500000, 2500000 }, +	[FEAT_PARAM_DSIPLL_LPDIV]		= { 0, (1 << 13) - 1 }, +	[FEAT_PARAM_DSI_FCK]			= { 0, 170000000 }, +	[FEAT_PARAM_DOWNSCALE]			= { 1, 4 }, +	[FEAT_PARAM_LINEWIDTH]			= { 1, 2048 }, +}; + +static const struct dss_param_range omap5_dss_param_range[] = { +	[FEAT_PARAM_DSS_FCK]			= { 0, 209250000 }, +	[FEAT_PARAM_DSS_PCD]			= { 1, 255 }, +	[FEAT_PARAM_DSIPLL_REGN]		= { 0, (1 << 8) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM]		= { 0, (1 << 12) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DISPC]		= { 0, (1 << 5) - 1 }, +	[FEAT_PARAM_DSIPLL_REGM_DSI]		= { 0, (1 << 5) - 1 }, +	[FEAT_PARAM_DSIPLL_FINT]		= { 150000, 52000000 }, +	[FEAT_PARAM_DSIPLL_LPDIV]		= { 0, (1 << 13) - 1 }, +	[FEAT_PARAM_DSI_FCK]			= { 0, 209250000 }, +	[FEAT_PARAM_DOWNSCALE]			= { 1, 4 }, +	[FEAT_PARAM_LINEWIDTH]			= { 1, 2048 }, +}; + +static const enum dss_feat_id omap2_dss_feat_list[] = { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +}; + +static const enum dss_feat_id omap3430_dss_feat_list[] = { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_LINEBUFFERSPLIT, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +	FEAT_DSI_PLL_FREQSEL, +	FEAT_DSI_REVERSE_TXCLKESC, +	FEAT_VENC_REQUIRES_TV_DAC_CLK, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FIXED_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_OMAP3_DSI_FIFO_BUG, +	FEAT_DPI_USES_VDDS_DSI, +}; + +static const enum dss_feat_id am35xx_dss_feat_list[] = { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_LINEBUFFERSPLIT, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +	FEAT_DSI_PLL_FREQSEL, +	FEAT_DSI_REVERSE_TXCLKESC, +	FEAT_VENC_REQUIRES_TV_DAC_CLK, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FIXED_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_OMAP3_DSI_FIFO_BUG, +}; + +static const enum dss_feat_id am43xx_dss_feat_list[] = { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_LINEBUFFERSPLIT, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FIXED_ZORDER, +	FEAT_FIFO_MERGE, +}; + +static const enum dss_feat_id omap3630_dss_feat_list[] = { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_LINEBUFFERSPLIT, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +	FEAT_DSI_PLL_PWR_BUG, +	FEAT_DSI_PLL_FREQSEL, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FIXED_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_OMAP3_DSI_FIFO_BUG, +	FEAT_DPI_USES_VDDS_DSI, +}; + +static const enum dss_feat_id omap4430_es1_0_dss_feat_list[] = { +	FEAT_MGR_LCD2, +	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_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FREE_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap4430_es2_0_1_2_dss_feat_list[] = { +	FEAT_MGR_LCD2, +	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_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FREE_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap4_dss_feat_list[] = { +	FEAT_MGR_LCD2, +	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_HDMI_AUDIO_USE_MCLK, +	FEAT_HANDLE_UV_SEPARATE, +	FEAT_ATTR2, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FREE_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_BURST_2D, +}; + +static const enum dss_feat_id omap5_dss_feat_list[] = { +	FEAT_MGR_LCD2, +	FEAT_MGR_LCD3, +	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_HDMI_AUDIO_USE_MCLK, +	FEAT_HANDLE_UV_SEPARATE, +	FEAT_ATTR2, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FREE_ZORDER, +	FEAT_FIFO_MERGE, +	FEAT_BURST_2D, +	FEAT_DSI_PLL_SELFREQDCO, +	FEAT_DSI_PLL_REFSEL, +	FEAT_DSI_PHY_DCC, +	FEAT_MFLAG, +}; + +/* OMAP2 DSS Features */ +static const struct omap_dss_features omap2_dss_features = { +	.reg_fields = omap2_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap2_dss_reg_fields), + +	.features = omap2_dss_feat_list, +	.num_features = ARRAY_SIZE(omap2_dss_feat_list), + +	.num_mgrs = 2, +	.num_ovls = 3, +	.supported_displays = omap2_dss_supported_displays, +	.supported_outputs = omap2_dss_supported_outputs, +	.supported_color_modes = omap2_dss_supported_color_modes, +	.overlay_caps = omap2_dss_overlay_caps, +	.clksrc_names = omap2_dss_clk_source_names, +	.dss_params = omap2_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, +	.buffer_size_unit = 1, +	.burst_size_unit = 8, +}; + +/* OMAP3 DSS Features */ +static const struct omap_dss_features omap3430_dss_features = { +	.reg_fields = omap3_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + +	.features = omap3430_dss_feat_list, +	.num_features = ARRAY_SIZE(omap3430_dss_feat_list), + +	.num_mgrs = 2, +	.num_ovls = 3, +	.supported_displays = omap3430_dss_supported_displays, +	.supported_outputs = omap3430_dss_supported_outputs, +	.supported_color_modes = omap3_dss_supported_color_modes, +	.overlay_caps = omap3430_dss_overlay_caps, +	.clksrc_names = omap3_dss_clk_source_names, +	.dss_params = omap3_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, +	.buffer_size_unit = 1, +	.burst_size_unit = 8, +}; + +/* + * AM35xx DSS Features. This is basically OMAP3 DSS Features without the + * vdds_dsi regulator. + */ +static const struct omap_dss_features am35xx_dss_features = { +	.reg_fields = omap3_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + +	.features = am35xx_dss_feat_list, +	.num_features = ARRAY_SIZE(am35xx_dss_feat_list), + +	.num_mgrs = 2, +	.num_ovls = 3, +	.supported_displays = omap3430_dss_supported_displays, +	.supported_outputs = omap3430_dss_supported_outputs, +	.supported_color_modes = omap3_dss_supported_color_modes, +	.overlay_caps = omap3430_dss_overlay_caps, +	.clksrc_names = omap3_dss_clk_source_names, +	.dss_params = omap3_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, +	.buffer_size_unit = 1, +	.burst_size_unit = 8, +}; + +static const struct omap_dss_features am43xx_dss_features = { +	.reg_fields = am43xx_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(am43xx_dss_reg_fields), + +	.features = am43xx_dss_feat_list, +	.num_features = ARRAY_SIZE(am43xx_dss_feat_list), + +	.num_mgrs = 1, +	.num_ovls = 3, +	.supported_displays = am43xx_dss_supported_displays, +	.supported_outputs = am43xx_dss_supported_outputs, +	.supported_color_modes = omap3_dss_supported_color_modes, +	.overlay_caps = omap3430_dss_overlay_caps, +	.clksrc_names = omap2_dss_clk_source_names, +	.dss_params = am43xx_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA, +	.buffer_size_unit = 1, +	.burst_size_unit = 8, +}; + +static const struct omap_dss_features omap3630_dss_features = { +	.reg_fields = omap3_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap3_dss_reg_fields), + +	.features = omap3630_dss_feat_list, +	.num_features = ARRAY_SIZE(omap3630_dss_feat_list), + +	.num_mgrs = 2, +	.num_ovls = 3, +	.supported_displays = omap3630_dss_supported_displays, +	.supported_outputs = omap3630_dss_supported_outputs, +	.supported_color_modes = omap3_dss_supported_color_modes, +	.overlay_caps = omap3630_dss_overlay_caps, +	.clksrc_names = omap3_dss_clk_source_names, +	.dss_params = omap3_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_VRFB, +	.buffer_size_unit = 1, +	.burst_size_unit = 8, +}; + +/* OMAP4 DSS Features */ +/* For OMAP4430 ES 1.0 revision */ +static const struct omap_dss_features omap4430_es1_0_dss_features  = { +	.reg_fields = omap4_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + +	.features = omap4430_es1_0_dss_feat_list, +	.num_features = ARRAY_SIZE(omap4430_es1_0_dss_feat_list), + +	.num_mgrs = 3, +	.num_ovls = 4, +	.num_wbs = 1, +	.supported_displays = omap4_dss_supported_displays, +	.supported_outputs = omap4_dss_supported_outputs, +	.supported_color_modes = omap4_dss_supported_color_modes, +	.overlay_caps = omap4_dss_overlay_caps, +	.clksrc_names = omap4_dss_clk_source_names, +	.dss_params = omap4_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, +	.buffer_size_unit = 16, +	.burst_size_unit = 16, +}; + +/* For OMAP4430 ES 2.0, 2.1 and 2.2 revisions */ +static const struct omap_dss_features omap4430_es2_0_1_2_dss_features = { +	.reg_fields = omap4_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + +	.features = omap4430_es2_0_1_2_dss_feat_list, +	.num_features = ARRAY_SIZE(omap4430_es2_0_1_2_dss_feat_list), + +	.num_mgrs = 3, +	.num_ovls = 4, +	.num_wbs = 1, +	.supported_displays = omap4_dss_supported_displays, +	.supported_outputs = omap4_dss_supported_outputs, +	.supported_color_modes = omap4_dss_supported_color_modes, +	.overlay_caps = omap4_dss_overlay_caps, +	.clksrc_names = omap4_dss_clk_source_names, +	.dss_params = omap4_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, +	.buffer_size_unit = 16, +	.burst_size_unit = 16, +}; + +/* For all the other OMAP4 versions */ +static const struct omap_dss_features omap4_dss_features = { +	.reg_fields = omap4_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap4_dss_reg_fields), + +	.features = omap4_dss_feat_list, +	.num_features = ARRAY_SIZE(omap4_dss_feat_list), + +	.num_mgrs = 3, +	.num_ovls = 4, +	.num_wbs = 1, +	.supported_displays = omap4_dss_supported_displays, +	.supported_outputs = omap4_dss_supported_outputs, +	.supported_color_modes = omap4_dss_supported_color_modes, +	.overlay_caps = omap4_dss_overlay_caps, +	.clksrc_names = omap4_dss_clk_source_names, +	.dss_params = omap4_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, +	.buffer_size_unit = 16, +	.burst_size_unit = 16, +}; + +/* OMAP5 DSS Features */ +static const struct omap_dss_features omap5_dss_features = { +	.reg_fields = omap5_dss_reg_fields, +	.num_reg_fields = ARRAY_SIZE(omap5_dss_reg_fields), + +	.features = omap5_dss_feat_list, +	.num_features = ARRAY_SIZE(omap5_dss_feat_list), + +	.num_mgrs = 4, +	.num_ovls = 4, +	.supported_displays = omap5_dss_supported_displays, +	.supported_outputs = omap5_dss_supported_outputs, +	.supported_color_modes = omap4_dss_supported_color_modes, +	.overlay_caps = omap4_dss_overlay_caps, +	.clksrc_names = omap5_dss_clk_source_names, +	.dss_params = omap5_dss_param_range, +	.supported_rotation_types = OMAP_DSS_ROT_DMA | OMAP_DSS_ROT_TILER, +	.buffer_size_unit = 16, +	.burst_size_unit = 16, +}; + +/* Functions returning values related to a DSS feature */ +int dss_feat_get_num_mgrs(void) +{ +	return omap_current_dss_features->num_mgrs; +} +EXPORT_SYMBOL(dss_feat_get_num_mgrs); + +int dss_feat_get_num_ovls(void) +{ +	return omap_current_dss_features->num_ovls; +} +EXPORT_SYMBOL(dss_feat_get_num_ovls); + +int dss_feat_get_num_wbs(void) +{ +	return omap_current_dss_features->num_wbs; +} + +unsigned long dss_feat_get_param_min(enum dss_range_param param) +{ +	return omap_current_dss_features->dss_params[param].min; +} + +unsigned long dss_feat_get_param_max(enum dss_range_param param) +{ +	return omap_current_dss_features->dss_params[param].max; +} + +enum omap_display_type dss_feat_get_supported_displays(enum omap_channel channel) +{ +	return omap_current_dss_features->supported_displays[channel]; +} +EXPORT_SYMBOL(dss_feat_get_supported_displays); + +enum omap_dss_output_id dss_feat_get_supported_outputs(enum omap_channel channel) +{ +	return omap_current_dss_features->supported_outputs[channel]; +} +EXPORT_SYMBOL(dss_feat_get_supported_outputs); + +enum omap_color_mode dss_feat_get_supported_color_modes(enum omap_plane plane) +{ +	return omap_current_dss_features->supported_color_modes[plane]; +} +EXPORT_SYMBOL(dss_feat_get_supported_color_modes); + +enum omap_overlay_caps dss_feat_get_overlay_caps(enum omap_plane plane) +{ +	return omap_current_dss_features->overlay_caps[plane]; +} + +bool dss_feat_color_mode_supported(enum omap_plane plane, +		enum omap_color_mode color_mode) +{ +	return omap_current_dss_features->supported_color_modes[plane] & +			color_mode; +} + +const char *dss_feat_get_clk_source_name(enum omap_dss_clk_source id) +{ +	return omap_current_dss_features->clksrc_names[id]; +} + +u32 dss_feat_get_buffer_size_unit(void) +{ +	return omap_current_dss_features->buffer_size_unit; +} + +u32 dss_feat_get_burst_size_unit(void) +{ +	return omap_current_dss_features->burst_size_unit; +} + +/* DSS has_feature check */ +bool dss_has_feature(enum dss_feat_id id) +{ +	int i; +	const enum dss_feat_id *features = omap_current_dss_features->features; +	const int num_features = omap_current_dss_features->num_features; + +	for (i = 0; i < num_features; i++) { +		if (features[i] == id) +			return true; +	} + +	return false; +} + +void dss_feat_get_reg_field(enum dss_feat_reg_field id, u8 *start, u8 *end) +{ +	if (id >= omap_current_dss_features->num_reg_fields) +		BUG(); + +	*start = omap_current_dss_features->reg_fields[id].start; +	*end = omap_current_dss_features->reg_fields[id].end; +} + +bool dss_feat_rotation_type_supported(enum omap_dss_rotation_type rot_type) +{ +	return omap_current_dss_features->supported_rotation_types & rot_type; +} + +void dss_features_init(enum omapdss_version version) +{ +	switch (version) { +	case OMAPDSS_VER_OMAP24xx: +		omap_current_dss_features = &omap2_dss_features; +		break; + +	case OMAPDSS_VER_OMAP34xx_ES1: +	case OMAPDSS_VER_OMAP34xx_ES3: +		omap_current_dss_features = &omap3430_dss_features; +		break; + +	case OMAPDSS_VER_OMAP3630: +		omap_current_dss_features = &omap3630_dss_features; +		break; + +	case OMAPDSS_VER_OMAP4430_ES1: +		omap_current_dss_features = &omap4430_es1_0_dss_features; +		break; + +	case OMAPDSS_VER_OMAP4430_ES2: +		omap_current_dss_features = &omap4430_es2_0_1_2_dss_features; +		break; + +	case OMAPDSS_VER_OMAP4: +		omap_current_dss_features = &omap4_dss_features; +		break; + +	case OMAPDSS_VER_OMAP5: +		omap_current_dss_features = &omap5_dss_features; +		break; + +	case OMAPDSS_VER_AM35xx: +		omap_current_dss_features = &am35xx_dss_features; +		break; + +	case OMAPDSS_VER_AM43xx: +		omap_current_dss_features = &am43xx_dss_features; +		break; + +	default: +		DSSWARN("Unsupported OMAP version"); +		break; +	} +} diff --git a/drivers/video/fbdev/omap2/dss/dss_features.h b/drivers/video/fbdev/omap2/dss/dss_features.h new file mode 100644 index 00000000000..e3ef3b71489 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/dss_features.h @@ -0,0 +1,117 @@ +/* + * linux/drivers/video/omap2/dss/dss_features.h + * + * Copyright (C) 2010 Texas Instruments + * Author: Archit Taneja <archit@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 __OMAP2_DSS_FEATURES_H +#define __OMAP2_DSS_FEATURES_H + +#define MAX_DSS_MANAGERS	4 +#define MAX_DSS_OVERLAYS	4 +#define MAX_DSS_LCD_MANAGERS	3 +#define MAX_NUM_DSI		2 + +/* DSS has feature id */ +enum dss_feat_id { +	FEAT_LCDENABLEPOL, +	FEAT_LCDENABLESIGNAL, +	FEAT_PCKFREEENABLE, +	FEAT_FUNCGATED, +	FEAT_MGR_LCD2, +	FEAT_MGR_LCD3, +	FEAT_LINEBUFFERSPLIT, +	FEAT_ROWREPEATENABLE, +	FEAT_RESIZECONF, +	/* Independent core clk divider */ +	FEAT_CORE_CLK_DIV, +	FEAT_LCD_CLK_SRC, +	/* DSI-PLL power command 0x3 is not working */ +	FEAT_DSI_PLL_PWR_BUG, +	FEAT_DSI_PLL_FREQSEL, +	FEAT_DSI_DCS_CMD_CONFIG_VC, +	FEAT_DSI_VC_OCP_WIDTH, +	FEAT_DSI_REVERSE_TXCLKESC, +	FEAT_DSI_GNQ, +	FEAT_DPI_USES_VDDS_DSI, +	FEAT_HDMI_CTS_SWMODE, +	FEAT_HDMI_AUDIO_USE_MCLK, +	FEAT_HANDLE_UV_SEPARATE, +	FEAT_ATTR2, +	FEAT_VENC_REQUIRES_TV_DAC_CLK, +	FEAT_CPR, +	FEAT_PRELOAD, +	FEAT_FIR_COEF_V, +	FEAT_ALPHA_FIXED_ZORDER, +	FEAT_ALPHA_FREE_ZORDER, +	FEAT_FIFO_MERGE, +	/* An unknown HW bug causing the normal FIFO thresholds not to work */ +	FEAT_OMAP3_DSI_FIFO_BUG, +	FEAT_BURST_2D, +	FEAT_DSI_PLL_SELFREQDCO, +	FEAT_DSI_PLL_REFSEL, +	FEAT_DSI_PHY_DCC, +	FEAT_MFLAG, +}; + +/* DSS register field id */ +enum dss_feat_reg_field { +	FEAT_REG_FIRHINC, +	FEAT_REG_FIRVINC, +	FEAT_REG_FIFOHIGHTHRESHOLD, +	FEAT_REG_FIFOLOWTHRESHOLD, +	FEAT_REG_FIFOSIZE, +	FEAT_REG_HORIZONTALACCU, +	FEAT_REG_VERTICALACCU, +	FEAT_REG_DISPC_CLK_SWITCH, +	FEAT_REG_DSIPLL_REGN, +	FEAT_REG_DSIPLL_REGM, +	FEAT_REG_DSIPLL_REGM_DISPC, +	FEAT_REG_DSIPLL_REGM_DSI, +}; + +enum dss_range_param { +	FEAT_PARAM_DSS_FCK, +	FEAT_PARAM_DSS_PCD, +	FEAT_PARAM_DSIPLL_REGN, +	FEAT_PARAM_DSIPLL_REGM, +	FEAT_PARAM_DSIPLL_REGM_DISPC, +	FEAT_PARAM_DSIPLL_REGM_DSI, +	FEAT_PARAM_DSIPLL_FINT, +	FEAT_PARAM_DSIPLL_LPDIV, +	FEAT_PARAM_DSI_FCK, +	FEAT_PARAM_DOWNSCALE, +	FEAT_PARAM_LINEWIDTH, +}; + +/* DSS Feature Functions */ +int dss_feat_get_num_wbs(void); +unsigned long dss_feat_get_param_min(enum dss_range_param param); +unsigned long dss_feat_get_param_max(enum dss_range_param param); +enum omap_overlay_caps dss_feat_get_overlay_caps(enum omap_plane plane); +bool dss_feat_color_mode_supported(enum omap_plane plane, +		enum omap_color_mode color_mode); +const char *dss_feat_get_clk_source_name(enum omap_dss_clk_source id); + +u32 dss_feat_get_buffer_size_unit(void);	/* in bytes */ +u32 dss_feat_get_burst_size_unit(void);		/* in bytes */ + +bool dss_feat_rotation_type_supported(enum omap_dss_rotation_type rot_type); + +bool dss_has_feature(enum dss_feat_id id); +void dss_feat_get_reg_field(enum dss_feat_reg_field id, u8 *start, u8 *end); +void dss_features_init(enum omapdss_version version); +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi.h b/drivers/video/fbdev/omap2/dss/hdmi.h new file mode 100644 index 00000000000..fbee0781633 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi.h @@ -0,0 +1,447 @@ +/* + * HDMI driver definition for TI OMAP4 Processor. + * + * 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 _HDMI_H +#define _HDMI_H + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" + +/* HDMI Wrapper */ + +#define HDMI_WP_REVISION			0x0 +#define HDMI_WP_SYSCONFIG			0x10 +#define HDMI_WP_IRQSTATUS_RAW			0x24 +#define HDMI_WP_IRQSTATUS			0x28 +#define HDMI_WP_IRQENABLE_SET			0x2C +#define HDMI_WP_IRQENABLE_CLR			0x30 +#define HDMI_WP_IRQWAKEEN			0x34 +#define HDMI_WP_PWR_CTRL			0x40 +#define HDMI_WP_DEBOUNCE			0x44 +#define HDMI_WP_VIDEO_CFG			0x50 +#define HDMI_WP_VIDEO_SIZE			0x60 +#define HDMI_WP_VIDEO_TIMING_H			0x68 +#define HDMI_WP_VIDEO_TIMING_V			0x6C +#define HDMI_WP_CLK				0x70 +#define HDMI_WP_AUDIO_CFG			0x80 +#define HDMI_WP_AUDIO_CFG2			0x84 +#define HDMI_WP_AUDIO_CTRL			0x88 +#define HDMI_WP_AUDIO_DATA			0x8C + +/* HDMI WP IRQ flags */ +#define HDMI_IRQ_CORE				(1 << 0) +#define HDMI_IRQ_OCP_TIMEOUT			(1 << 4) +#define HDMI_IRQ_AUDIO_FIFO_UNDERFLOW		(1 << 8) +#define HDMI_IRQ_AUDIO_FIFO_OVERFLOW		(1 << 9) +#define HDMI_IRQ_AUDIO_FIFO_SAMPLE_REQ		(1 << 10) +#define HDMI_IRQ_VIDEO_VSYNC			(1 << 16) +#define HDMI_IRQ_VIDEO_FRAME_DONE		(1 << 17) +#define HDMI_IRQ_PHY_LINE5V_ASSERT		(1 << 24) +#define HDMI_IRQ_LINK_CONNECT			(1 << 25) +#define HDMI_IRQ_LINK_DISCONNECT		(1 << 26) +#define HDMI_IRQ_PLL_LOCK			(1 << 29) +#define HDMI_IRQ_PLL_UNLOCK			(1 << 30) +#define HDMI_IRQ_PLL_RECAL			(1 << 31) + +/* HDMI PLL */ + +#define PLLCTRL_PLL_CONTROL			0x0 +#define PLLCTRL_PLL_STATUS			0x4 +#define PLLCTRL_PLL_GO				0x8 +#define PLLCTRL_CFG1				0xC +#define PLLCTRL_CFG2				0x10 +#define PLLCTRL_CFG3				0x14 +#define PLLCTRL_SSC_CFG1			0x18 +#define PLLCTRL_SSC_CFG2			0x1C +#define PLLCTRL_CFG4				0x20 + +/* HDMI PHY */ + +#define HDMI_TXPHY_TX_CTRL			0x0 +#define HDMI_TXPHY_DIGITAL_CTRL			0x4 +#define HDMI_TXPHY_POWER_CTRL			0x8 +#define HDMI_TXPHY_PAD_CFG_CTRL			0xC +#define HDMI_TXPHY_BIST_CONTROL			0x1C + +enum hdmi_pll_pwr { +	HDMI_PLLPWRCMD_ALLOFF = 0, +	HDMI_PLLPWRCMD_PLLONLY = 1, +	HDMI_PLLPWRCMD_BOTHON_ALLCLKS = 2, +	HDMI_PLLPWRCMD_BOTHON_NOPHYCLK = 3 +}; + +enum hdmi_phy_pwr { +	HDMI_PHYPWRCMD_OFF = 0, +	HDMI_PHYPWRCMD_LDOON = 1, +	HDMI_PHYPWRCMD_TXON = 2 +}; + +enum hdmi_core_hdmi_dvi { +	HDMI_DVI = 0, +	HDMI_HDMI = 1 +}; + +enum hdmi_clk_refsel { +	HDMI_REFSEL_PCLK = 0, +	HDMI_REFSEL_REF1 = 1, +	HDMI_REFSEL_REF2 = 2, +	HDMI_REFSEL_SYSCLK = 3 +}; + +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_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_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_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 +}; + +/* 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, +}; + +struct hdmi_cm { +	int	code; +	int	mode; +}; + +struct hdmi_video_format { +	enum hdmi_packing_mode	packing_mode; +	u32			y_res;	/* Line per panel */ +	u32			x_res;	/* pixel per line */ +}; + +struct hdmi_config { +	struct omap_video_timings timings; +	struct hdmi_cm cm; +}; + +/* HDMI PLL structure */ +struct hdmi_pll_info { +	u16 regn; +	u16 regm; +	u32 regmf; +	u16 regm2; +	u16 regsd; +	u16 dcofreq; +	enum hdmi_clk_refsel refsel; +}; + +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 in_length_bits; +	u8 justification; +	u8 sck_edge_mode; +	u8 vbit; +	u8 direction; +	u8 shift; +	u8 active_sds; +}; + +struct hdmi_core_audio_config { +	struct hdmi_core_audio_i2s_config	i2s_cfg; +	struct snd_aes_iec958			*iec60958_cfg; +	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; +}; + +/* + * Refer to section 8.2 in HDMI 1.3 specification for + * details about infoframe databytes + */ +struct hdmi_core_infoframe_avi { +	/* Y0, Y1 rgb,yCbCr */ +	u8	db1_format; +	/* A0  Active information Present */ +	u8	db1_active_info; +	/* B0, B1 Bar info data valid */ +	u8	db1_bar_info_dv; +	/* S0, S1 scan information */ +	u8	db1_scan_info; +	/* C0, C1 colorimetry */ +	u8	db2_colorimetry; +	/* M0, M1 Aspect ratio (4:3, 16:9) */ +	u8	db2_aspect_ratio; +	/* R0...R3 Active format aspect ratio */ +	u8	db2_active_fmt_ar; +	/* ITC IT content. */ +	u8	db3_itc; +	/* EC0, EC1, EC2 Extended colorimetry */ +	u8	db3_ec; +	/* Q1, Q0 Quantization range */ +	u8	db3_q_range; +	/* SC1, SC0 Non-uniform picture scaling */ +	u8	db3_nup_scaling; +	/* VIC0..6 Video format identification */ +	u8	db4_videocode; +	/* PR0..PR3 Pixel repetition factor */ +	u8	db5_pixel_repeat; +	/* Line number end of top bar */ +	u16	db6_7_line_eoftop; +	/* Line number start of bottom bar */ +	u16	db8_9_line_sofbottom; +	/* Pixel number end of left bar */ +	u16	db10_11_pixel_eofleft; +	/* Pixel number start of right bar */ +	u16	db12_13_pixel_sofright; +}; + +struct hdmi_wp_data { +	void __iomem *base; +}; + +struct hdmi_pll_data { +	void __iomem *base; + +	struct hdmi_pll_info info; +}; + +struct hdmi_phy_data { +	void __iomem *base; + +	u8 lane_function[4]; +	u8 lane_polarity[4]; +}; + +struct hdmi_core_data { +	void __iomem *base; + +	struct hdmi_core_infoframe_avi avi_cfg; +}; + +static inline void hdmi_write_reg(void __iomem *base_addr, const u32 idx, +		u32 val) +{ +	__raw_writel(val, base_addr + idx); +} + +static inline u32 hdmi_read_reg(void __iomem *base_addr, const u32 idx) +{ +	return __raw_readl(base_addr + idx); +} + +#define REG_FLD_MOD(base, idx, val, start, end) \ +	hdmi_write_reg(base, idx, FLD_MOD(hdmi_read_reg(base, idx),\ +							val, start, end)) +#define REG_GET(base, idx, start, end) \ +	FLD_GET(hdmi_read_reg(base, idx), start, end) + +static inline int hdmi_wait_for_bit_change(void __iomem *base_addr, +		const u32 idx, int b2, int b1, u32 val) +{ +	u32 t = 0, v; +	while (val != (v = REG_GET(base_addr, idx, b2, b1))) { +		if (t++ > 10000) +			return v; +		udelay(1); +	} +	return v; +} + +/* HDMI wrapper funcs */ +int hdmi_wp_video_start(struct hdmi_wp_data *wp); +void hdmi_wp_video_stop(struct hdmi_wp_data *wp); +void hdmi_wp_dump(struct hdmi_wp_data *wp, struct seq_file *s); +u32 hdmi_wp_get_irqstatus(struct hdmi_wp_data *wp); +void hdmi_wp_set_irqstatus(struct hdmi_wp_data *wp, u32 irqstatus); +void hdmi_wp_set_irqenable(struct hdmi_wp_data *wp, u32 mask); +void hdmi_wp_clear_irqenable(struct hdmi_wp_data *wp, u32 mask); +int hdmi_wp_set_phy_pwr(struct hdmi_wp_data *wp, enum hdmi_phy_pwr val); +int hdmi_wp_set_pll_pwr(struct hdmi_wp_data *wp, enum hdmi_pll_pwr val); +void hdmi_wp_video_config_format(struct hdmi_wp_data *wp, +		struct hdmi_video_format *video_fmt); +void hdmi_wp_video_config_interface(struct hdmi_wp_data *wp, +		struct omap_video_timings *timings); +void hdmi_wp_video_config_timing(struct hdmi_wp_data *wp, +		struct omap_video_timings *timings); +void hdmi_wp_init_vid_fmt_timings(struct hdmi_video_format *video_fmt, +		struct omap_video_timings *timings, struct hdmi_config *param); +int hdmi_wp_init(struct platform_device *pdev, struct hdmi_wp_data *wp); + +/* HDMI PLL funcs */ +int hdmi_pll_enable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp); +void hdmi_pll_disable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp); +void hdmi_pll_dump(struct hdmi_pll_data *pll, struct seq_file *s); +void hdmi_pll_compute(struct hdmi_pll_data *pll, unsigned long clkin, int phy); +int hdmi_pll_init(struct platform_device *pdev, struct hdmi_pll_data *pll); + +/* HDMI PHY funcs */ +int hdmi_phy_configure(struct hdmi_phy_data *phy, struct hdmi_config *cfg); +void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s); +int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy); +int hdmi_phy_parse_lanes(struct hdmi_phy_data *phy, const u32 *lanes); + +/* HDMI common funcs */ +const struct hdmi_config *hdmi_default_timing(void); +const struct hdmi_config *hdmi_get_timings(int mode, int code); +struct hdmi_cm hdmi_get_code(struct omap_video_timings *timing); +int hdmi_parse_lanes_of(struct platform_device *pdev, struct device_node *ep, +	struct hdmi_phy_data *phy); + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) || defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) +int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts); +int hdmi_wp_audio_enable(struct hdmi_wp_data *wp, bool enable); +int hdmi_wp_audio_core_req_enable(struct hdmi_wp_data *wp, bool enable); +void hdmi_wp_audio_config_format(struct hdmi_wp_data *wp, +		struct hdmi_audio_format *aud_fmt); +void hdmi_wp_audio_config_dma(struct hdmi_wp_data *wp, +		struct hdmi_audio_dma *aud_dma); +static inline bool hdmi_mode_has_audio(int mode) +{ +	return mode == HDMI_HDMI ? true : false; +} +#endif +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi4.c b/drivers/video/fbdev/omap2/dss/hdmi4.c new file mode 100644 index 00000000000..626aad2bef4 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4.c @@ -0,0 +1,804 @@ +/* + * HDMI interface DSS driver for TI's OMAP4 family of SoCs. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Yong Zhi + *	Mythri pk <mythripk@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 DSS_SUBSYS_NAME "HDMI" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <video/omapdss.h> + +#include "hdmi4_core.h" +#include "dss.h" +#include "dss_features.h" + +static struct { +	struct mutex lock; +	struct platform_device *pdev; + +	struct hdmi_wp_data	wp; +	struct hdmi_pll_data	pll; +	struct hdmi_phy_data	phy; +	struct hdmi_core_data	core; + +	struct hdmi_config cfg; + +	struct clk *sys_clk; +	struct regulator *vdda_hdmi_dac_reg; + +	bool core_enabled; + +	struct omap_dss_device output; +} hdmi; + +static int hdmi_runtime_get(void) +{ +	int r; + +	DSSDBG("hdmi_runtime_get\n"); + +	r = pm_runtime_get_sync(&hdmi.pdev->dev); +	WARN_ON(r < 0); +	if (r < 0) +		return r; + +	return 0; +} + +static void hdmi_runtime_put(void) +{ +	int r; + +	DSSDBG("hdmi_runtime_put\n"); + +	r = pm_runtime_put_sync(&hdmi.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} + +static irqreturn_t hdmi_irq_handler(int irq, void *data) +{ +	struct hdmi_wp_data *wp = data; +	u32 irqstatus; + +	irqstatus = hdmi_wp_get_irqstatus(wp); +	hdmi_wp_set_irqstatus(wp, irqstatus); + +	if ((irqstatus & HDMI_IRQ_LINK_CONNECT) && +			irqstatus & HDMI_IRQ_LINK_DISCONNECT) { +		/* +		 * If we get both connect and disconnect interrupts at the same +		 * time, turn off the PHY, clear interrupts, and restart, which +		 * raises connect interrupt if a cable is connected, or nothing +		 * if cable is not connected. +		 */ +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); + +		hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | +				HDMI_IRQ_LINK_DISCONNECT); + +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); +	} else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON); +	} else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); +	} + +	return IRQ_HANDLED; +} + +static int hdmi_init_regulator(void) +{ +	int r; +	struct regulator *reg; + +	if (hdmi.vdda_hdmi_dac_reg != NULL) +		return 0; + +	reg = devm_regulator_get(&hdmi.pdev->dev, "vdda"); + +	if (IS_ERR(reg)) { +		if (PTR_ERR(reg) != -EPROBE_DEFER) +			DSSERR("can't get VDDA regulator\n"); +		return PTR_ERR(reg); +	} + +	if (regulator_can_change_voltage(reg)) { +		r = regulator_set_voltage(reg, 1800000, 1800000); +		if (r) { +			devm_regulator_put(reg); +			DSSWARN("can't set the regulator voltage\n"); +			return r; +		} +	} + +	hdmi.vdda_hdmi_dac_reg = reg; + +	return 0; +} + +static int hdmi_power_on_core(struct omap_dss_device *dssdev) +{ +	int r; + +	r = regulator_enable(hdmi.vdda_hdmi_dac_reg); +	if (r) +		return r; + +	r = hdmi_runtime_get(); +	if (r) +		goto err_runtime_get; + +	/* Make selection of HDMI in DSS */ +	dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK); + +	hdmi.core_enabled = true; + +	return 0; + +err_runtime_get: +	regulator_disable(hdmi.vdda_hdmi_dac_reg); + +	return r; +} + +static void hdmi_power_off_core(struct omap_dss_device *dssdev) +{ +	hdmi.core_enabled = false; + +	hdmi_runtime_put(); +	regulator_disable(hdmi.vdda_hdmi_dac_reg); +} + +static int hdmi_power_on_full(struct omap_dss_device *dssdev) +{ +	int r; +	struct omap_video_timings *p; +	struct omap_overlay_manager *mgr = hdmi.output.manager; +	unsigned long phy; +	struct hdmi_wp_data *wp = &hdmi.wp; + +	r = hdmi_power_on_core(dssdev); +	if (r) +		return r; + +	/* disable and clear irqs */ +	hdmi_wp_clear_irqenable(wp, 0xffffffff); +	hdmi_wp_set_irqstatus(wp, 0xffffffff); + +	p = &hdmi.cfg.timings; + +	DSSDBG("hdmi_power_on x_res= %d y_res = %d\n", p->x_res, p->y_res); + +	/* the functions below use kHz pixel clock. TODO: change to Hz */ +	phy = p->pixelclock / 1000; + +	hdmi_pll_compute(&hdmi.pll, clk_get_rate(hdmi.sys_clk), phy); + +	/* config the PLL and PHY hdmi_set_pll_pwrfirst */ +	r = hdmi_pll_enable(&hdmi.pll, &hdmi.wp); +	if (r) { +		DSSDBG("Failed to lock PLL\n"); +		goto err_pll_enable; +	} + +	r = hdmi_phy_configure(&hdmi.phy, &hdmi.cfg); +	if (r) { +		DSSDBG("Failed to configure PHY\n"); +		goto err_phy_cfg; +	} + +	r = hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); +	if (r) +		goto err_phy_pwr; + +	hdmi4_configure(&hdmi.core, &hdmi.wp, &hdmi.cfg); + +	/* bypass TV gamma table */ +	dispc_enable_gamma_table(0); + +	/* tv size */ +	dss_mgr_set_timings(mgr, p); + +	r = hdmi_wp_video_start(&hdmi.wp); +	if (r) +		goto err_vid_enable; + +	r = dss_mgr_enable(mgr); +	if (r) +		goto err_mgr_enable; + +	hdmi_wp_set_irqenable(wp, +		HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); + +	return 0; + +err_mgr_enable: +	hdmi_wp_video_stop(&hdmi.wp); +err_vid_enable: +err_phy_cfg: +	hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF); +err_phy_pwr: +	hdmi_pll_disable(&hdmi.pll, &hdmi.wp); +err_pll_enable: +	hdmi_power_off_core(dssdev); +	return -EIO; +} + +static void hdmi_power_off_full(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = hdmi.output.manager; + +	hdmi_wp_clear_irqenable(&hdmi.wp, 0xffffffff); + +	dss_mgr_disable(mgr); + +	hdmi_wp_video_stop(&hdmi.wp); + +	hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF); + +	hdmi_pll_disable(&hdmi.pll, &hdmi.wp); + +	hdmi_power_off_core(dssdev); +} + +static int hdmi_display_check_timing(struct omap_dss_device *dssdev, +					struct omap_video_timings *timings) +{ +	struct omap_dss_device *out = &hdmi.output; + +	if (!dispc_mgr_timings_ok(out->dispc_channel, timings)) +		return -EINVAL; + +	return 0; +} + +static void hdmi_display_set_timing(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	struct hdmi_cm cm; +	const struct hdmi_config *t; + +	mutex_lock(&hdmi.lock); + +	cm = hdmi_get_code(timings); +	hdmi.cfg.cm = cm; + +	t = hdmi_get_timings(cm.mode, cm.code); +	if (t != NULL) { +		hdmi.cfg = *t; + +		dispc_set_tv_pclk(t->timings.pixelclock); +	} else { +		hdmi.cfg.timings = *timings; +		hdmi.cfg.cm.code = 0; +		hdmi.cfg.cm.mode = HDMI_DVI; + +		dispc_set_tv_pclk(timings->pixelclock); +	} + +	DSSDBG("using mode: %s, code %d\n", hdmi.cfg.cm.mode == HDMI_DVI ? +			"DVI" : "HDMI", hdmi.cfg.cm.code); + +	mutex_unlock(&hdmi.lock); +} + +static void hdmi_display_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	const struct hdmi_config *cfg; +	struct hdmi_cm cm = hdmi.cfg.cm; + +	cfg = hdmi_get_timings(cm.mode, cm.code); +	if (cfg == NULL) +		cfg = hdmi_default_timing(); + +	memcpy(timings, &cfg->timings, sizeof(cfg->timings)); +} + +static void hdmi_dump_regs(struct seq_file *s) +{ +	mutex_lock(&hdmi.lock); + +	if (hdmi_runtime_get()) { +		mutex_unlock(&hdmi.lock); +		return; +	} + +	hdmi_wp_dump(&hdmi.wp, s); +	hdmi_pll_dump(&hdmi.pll, s); +	hdmi_phy_dump(&hdmi.phy, s); +	hdmi4_core_dump(&hdmi.core, s); + +	hdmi_runtime_put(); +	mutex_unlock(&hdmi.lock); +} + +static int read_edid(u8 *buf, int len) +{ +	int r; + +	mutex_lock(&hdmi.lock); + +	r = hdmi_runtime_get(); +	BUG_ON(r); + +	r = hdmi4_read_edid(&hdmi.core,  buf, len); + +	hdmi_runtime_put(); +	mutex_unlock(&hdmi.lock); + +	return r; +} + +static int hdmi_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &hdmi.output; +	int r = 0; + +	DSSDBG("ENTER hdmi_display_enable\n"); + +	mutex_lock(&hdmi.lock); + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		r = -ENODEV; +		goto err0; +	} + +	r = hdmi_power_on_full(dssdev); +	if (r) { +		DSSERR("failed to power on device\n"); +		goto err0; +	} + +	mutex_unlock(&hdmi.lock); +	return 0; + +err0: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_display_disable(struct omap_dss_device *dssdev) +{ +	DSSDBG("Enter hdmi_display_disable\n"); + +	mutex_lock(&hdmi.lock); + +	hdmi_power_off_full(dssdev); + +	mutex_unlock(&hdmi.lock); +} + +static int hdmi_core_enable(struct omap_dss_device *dssdev) +{ +	int r = 0; + +	DSSDBG("ENTER omapdss_hdmi_core_enable\n"); + +	mutex_lock(&hdmi.lock); + +	r = hdmi_power_on_core(dssdev); +	if (r) { +		DSSERR("failed to power on device\n"); +		goto err0; +	} + +	mutex_unlock(&hdmi.lock); +	return 0; + +err0: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_core_disable(struct omap_dss_device *dssdev) +{ +	DSSDBG("Enter omapdss_hdmi_core_disable\n"); + +	mutex_lock(&hdmi.lock); + +	hdmi_power_off_core(dssdev); + +	mutex_unlock(&hdmi.lock); +} + +static int hdmi_get_clocks(struct platform_device *pdev) +{ +	struct clk *clk; + +	clk = devm_clk_get(&pdev->dev, "sys_clk"); +	if (IS_ERR(clk)) { +		DSSERR("can't get sys_clk\n"); +		return PTR_ERR(clk); +	} + +	hdmi.sys_clk = clk; + +	return 0; +} + +static int hdmi_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct omap_overlay_manager *mgr; +	int r; + +	r = hdmi_init_regulator(); +	if (r) +		return r; + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dst->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void hdmi_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static int hdmi_read_edid(struct omap_dss_device *dssdev, +		u8 *edid, int len) +{ +	bool need_enable; +	int r; + +	need_enable = hdmi.core_enabled == false; + +	if (need_enable) { +		r = hdmi_core_enable(dssdev); +		if (r) +			return r; +	} + +	r = read_edid(edid, len); + +	if (need_enable) +		hdmi_core_disable(dssdev); + +	return r; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ +	int r; + +	mutex_lock(&hdmi.lock); + +	if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { +		r = -EPERM; +		goto err; +	} + +	r = hdmi_wp_audio_enable(&hdmi.wp, true); +	if (r) +		goto err; + +	mutex_unlock(&hdmi.lock); +	return 0; + +err: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ +	hdmi_wp_audio_enable(&hdmi.wp, false); +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ +	return hdmi4_audio_start(&hdmi.core, &hdmi.wp); +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ +	hdmi4_audio_stop(&hdmi.core, &hdmi.wp); +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ +	bool r; + +	mutex_lock(&hdmi.lock); + +	r = hdmi_mode_has_audio(hdmi.cfg.cm.mode); + +	mutex_unlock(&hdmi.lock); +	return r; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, +		struct omap_dss_audio *audio) +{ +	int r; +	u32 pclk = hdmi.cfg.timings.pixelclock; + +	mutex_lock(&hdmi.lock); + +	if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { +		r = -EPERM; +		goto err; +	} + +	r = hdmi4_audio_config(&hdmi.core, &hdmi.wp, audio, pclk); +	if (r) +		goto err; + +	mutex_unlock(&hdmi.lock); +	return 0; + +err: +	mutex_unlock(&hdmi.lock); +	return r; +} +#else +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ +	return -EPERM; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ +	return -EPERM; +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ +	return false; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, +		struct omap_dss_audio *audio) +{ +	return -EPERM; +} +#endif + +static const struct omapdss_hdmi_ops hdmi_ops = { +	.connect		= hdmi_connect, +	.disconnect		= hdmi_disconnect, + +	.enable			= hdmi_display_enable, +	.disable		= hdmi_display_disable, + +	.check_timings		= hdmi_display_check_timing, +	.set_timings		= hdmi_display_set_timing, +	.get_timings		= hdmi_display_get_timings, + +	.read_edid		= hdmi_read_edid, + +	.audio_enable		= hdmi_audio_enable, +	.audio_disable		= hdmi_audio_disable, +	.audio_start		= hdmi_audio_start, +	.audio_stop		= hdmi_audio_stop, +	.audio_supported	= hdmi_audio_supported, +	.audio_config		= hdmi_audio_config, +}; + +static void hdmi_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &hdmi.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_HDMI; +	out->output_type = OMAP_DISPLAY_TYPE_HDMI; +	out->name = "hdmi.0"; +	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; +	out->ops.hdmi = &hdmi_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit hdmi_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &hdmi.output; + +	omapdss_unregister_output(out); +} + +static int hdmi_probe_of(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	struct device_node *ep; +	int r; + +	ep = omapdss_of_get_first_endpoint(node); +	if (!ep) +		return 0; + +	r = hdmi_parse_lanes_of(pdev, ep, &hdmi.phy); +	if (r) +		goto err; + +	of_node_put(ep); +	return 0; + +err: +	of_node_put(ep); +	return r; +} + +/* HDMI HW IP initialisation */ +static int omapdss_hdmihw_probe(struct platform_device *pdev) +{ +	int r; +	int irq; + +	hdmi.pdev = pdev; + +	mutex_init(&hdmi.lock); + +	if (pdev->dev.of_node) { +		r = hdmi_probe_of(pdev); +		if (r) +			return r; +	} + +	r = hdmi_wp_init(pdev, &hdmi.wp); +	if (r) +		return r; + +	r = hdmi_pll_init(pdev, &hdmi.pll); +	if (r) +		return r; + +	r = hdmi_phy_init(pdev, &hdmi.phy); +	if (r) +		return r; + +	r = hdmi4_core_init(pdev, &hdmi.core); +	if (r) +		return r; + +	r = hdmi_get_clocks(pdev); +	if (r) { +		DSSERR("can't get clocks\n"); +		return r; +	} + +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		DSSERR("platform_get_irq failed\n"); +		return -ENODEV; +	} + +	r = devm_request_threaded_irq(&pdev->dev, irq, +			NULL, hdmi_irq_handler, +			IRQF_ONESHOT, "OMAP HDMI", &hdmi.wp); +	if (r) { +		DSSERR("HDMI IRQ request failed\n"); +		return r; +	} + +	pm_runtime_enable(&pdev->dev); + +	hdmi_init_output(pdev); + +	dss_debugfs_create_file("hdmi", hdmi_dump_regs); + +	return 0; +} + +static int __exit omapdss_hdmihw_remove(struct platform_device *pdev) +{ +	hdmi_uninit_output(pdev); + +	pm_runtime_disable(&pdev->dev); + +	return 0; +} + +static int hdmi_runtime_suspend(struct device *dev) +{ +	clk_disable_unprepare(hdmi.sys_clk); + +	dispc_runtime_put(); + +	return 0; +} + +static int hdmi_runtime_resume(struct device *dev) +{ +	int r; + +	r = dispc_runtime_get(); +	if (r < 0) +		return r; + +	clk_prepare_enable(hdmi.sys_clk); + +	return 0; +} + +static const struct dev_pm_ops hdmi_pm_ops = { +	.runtime_suspend = hdmi_runtime_suspend, +	.runtime_resume = hdmi_runtime_resume, +}; + +static const struct of_device_id hdmi_of_match[] = { +	{ .compatible = "ti,omap4-hdmi", }, +	{}, +}; + +static struct platform_driver omapdss_hdmihw_driver = { +	.probe		= omapdss_hdmihw_probe, +	.remove         = __exit_p(omapdss_hdmihw_remove), +	.driver         = { +		.name   = "omapdss_hdmi", +		.owner  = THIS_MODULE, +		.pm	= &hdmi_pm_ops, +		.of_match_table = hdmi_of_match, +	}, +}; + +int __init hdmi4_init_platform_driver(void) +{ +	return platform_driver_register(&omapdss_hdmihw_driver); +} + +void __exit hdmi4_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omapdss_hdmihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi4_core.c b/drivers/video/fbdev/omap2/dss/hdmi4_core.c new file mode 100644 index 00000000000..8bde7b7e95f --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4_core.c @@ -0,0 +1,1018 @@ +/* + * ti_hdmi_4xxx_ip.c + * + * HDMI TI81xx, TI38xx, TI OMAP4 etc IP driver Library + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Yong Zhi + *	Mythri pk <mythripk@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 DSS_SUBSYS_NAME "HDMICORE" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +#include <sound/asound.h> +#include <sound/asoundef.h> +#endif + +#include "hdmi4_core.h" +#include "dss_features.h" + +#define HDMI_CORE_AV		0x500 + +static inline void __iomem *hdmi_av_base(struct hdmi_core_data *core) +{ +	return core->base + HDMI_CORE_AV; +} + +static int hdmi_core_ddc_init(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; + +	/* Turn on CLK for DDC */ +	REG_FLD_MOD(base, HDMI_CORE_AV_DPD, 0x7, 2, 0); + +	/* IN_PROG */ +	if (REG_GET(base, HDMI_CORE_DDC_STATUS, 4, 4) == 1) { +		/* Abort transaction */ +		REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0xf, 3, 0); +		/* IN_PROG */ +		if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, +					4, 4, 0) != 0) { +			DSSERR("Timeout aborting DDC transaction\n"); +			return -ETIMEDOUT; +		} +	} + +	/* Clk SCL Devices */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0xA, 3, 0); + +	/* HDMI_CORE_DDC_STATUS_IN_PROG */ +	if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, +				4, 4, 0) != 0) { +		DSSERR("Timeout starting SCL clock\n"); +		return -ETIMEDOUT; +	} + +	/* Clear FIFO */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x9, 3, 0); + +	/* HDMI_CORE_DDC_STATUS_IN_PROG */ +	if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, +				4, 4, 0) != 0) { +		DSSERR("Timeout clearing DDC fifo\n"); +		return -ETIMEDOUT; +	} + +	return 0; +} + +static int hdmi_core_ddc_edid(struct hdmi_core_data *core, +		u8 *pedid, int ext) +{ +	void __iomem *base = core->base; +	u32 i; +	char checksum; +	u32 offset = 0; + +	/* HDMI_CORE_DDC_STATUS_IN_PROG */ +	if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, +				4, 4, 0) != 0) { +		DSSERR("Timeout waiting DDC to be ready\n"); +		return -ETIMEDOUT; +	} + +	if (ext % 2 != 0) +		offset = 0x80; + +	/* Load Segment Address Register */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, ext / 2, 7, 0); + +	/* Load Slave Address Register */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1); + +	/* Load Offset Address Register */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, offset, 7, 0); + +	/* Load Byte Count */ +	REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, 0x80, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT2, 0x0, 1, 0); + +	/* Set DDC_CMD */ +	if (ext) +		REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0); +	else +		REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x2, 3, 0); + +	/* HDMI_CORE_DDC_STATUS_BUS_LOW */ +	if (REG_GET(base, HDMI_CORE_DDC_STATUS, 6, 6) == 1) { +		DSSERR("I2C Bus Low?\n"); +		return -EIO; +	} +	/* HDMI_CORE_DDC_STATUS_NO_ACK */ +	if (REG_GET(base, HDMI_CORE_DDC_STATUS, 5, 5) == 1) { +		DSSERR("I2C No Ack\n"); +		return -EIO; +	} + +	for (i = 0; i < 0x80; ++i) { +		int t; + +		/* IN_PROG */ +		if (REG_GET(base, HDMI_CORE_DDC_STATUS, 4, 4) == 0) { +			DSSERR("operation stopped when reading edid\n"); +			return -EIO; +		} + +		t = 0; +		/* FIFO_EMPTY */ +		while (REG_GET(base, HDMI_CORE_DDC_STATUS, 2, 2) == 1) { +			if (t++ > 10000) { +				DSSERR("timeout reading edid\n"); +				return -ETIMEDOUT; +			} +			udelay(1); +		} + +		pedid[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0); +	} + +	checksum = 0; +	for (i = 0; i < 0x80; ++i) +		checksum += pedid[i]; + +	if (checksum != 0) { +		DSSERR("E-EDID checksum failed!!\n"); +		return -EIO; +	} + +	return 0; +} + +int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len) +{ +	int r, l; + +	if (len < 128) +		return -EINVAL; + +	r = hdmi_core_ddc_init(core); +	if (r) +		return r; + +	r = hdmi_core_ddc_edid(core, edid, 0); +	if (r) +		return r; + +	l = 128; + +	if (len >= 128 * 2 && edid[0x7e] > 0) { +		r = hdmi_core_ddc_edid(core, edid + 0x80, 1); +		if (r) +			return r; +		l += 128; +	} + +	return l; +} + +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; +} + +static void hdmi_core_powerdown_disable(struct hdmi_core_data *core) +{ +	DSSDBG("Enter hdmi_core_powerdown_disable\n"); +	REG_FLD_MOD(core->base, HDMI_CORE_SYS_SYS_CTRL1, 0x0, 0, 0); +} + +static void hdmi_core_swreset_release(struct hdmi_core_data *core) +{ +	DSSDBG("Enter hdmi_core_swreset_release\n"); +	REG_FLD_MOD(core->base, HDMI_CORE_SYS_SRST, 0x0, 0, 0); +} + +static void hdmi_core_swreset_assert(struct hdmi_core_data *core) +{ +	DSSDBG("Enter hdmi_core_swreset_assert\n"); +	REG_FLD_MOD(core->base, HDMI_CORE_SYS_SRST, 0x1, 0, 0); +} + +/* HDMI_CORE_VIDEO_CONFIG */ +static void hdmi_core_video_config(struct hdmi_core_data *core, +				struct hdmi_core_video_config *cfg) +{ +	u32 r = 0; +	void __iomem *core_sys_base = core->base; +	void __iomem *core_av_base = hdmi_av_base(core); + +	/* sys_ctrl1 default configuration not tunable */ +	r = hdmi_read_reg(core_sys_base, HDMI_CORE_SYS_SYS_CTRL1); +	r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_VEN_FOLLOWVSYNC, 5, 5); +	r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_HEN_FOLLOWHSYNC, 4, 4); +	r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_BSEL_24BITBUS, 2, 2); +	r = FLD_MOD(r, HDMI_CORE_SYS_SYS_CTRL1_EDGE_RISINGEDGE, 1, 1); +	hdmi_write_reg(core_sys_base, HDMI_CORE_SYS_SYS_CTRL1, r); + +	REG_FLD_MOD(core_sys_base, +			HDMI_CORE_SYS_VID_ACEN, cfg->ip_bus_width, 7, 6); + +	/* Vid_Mode */ +	r = hdmi_read_reg(core_sys_base, 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); +	} +	hdmi_write_reg(core_sys_base, HDMI_CORE_SYS_VID_MODE, r); + +	/* HDMI_Ctrl */ +	r = hdmi_read_reg(core_av_base, 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(core_av_base, HDMI_CORE_AV_HDMI_CTRL, r); + +	/* TMDS_CTRL */ +	REG_FLD_MOD(core_sys_base, +			HDMI_CORE_SYS_TMDS_CTRL, cfg->tclk_sel_clkmult, 6, 5); +} + +static void hdmi_core_aux_infoframe_avi_config(struct hdmi_core_data *core) +{ +	u32 val; +	char sum = 0, checksum = 0; +	void __iomem *av_base = hdmi_av_base(core); +	struct hdmi_core_infoframe_avi info_avi = core->avi_cfg; + +	sum += 0x82 + 0x002 + 0x00D; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_TYPE, 0x082); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_VERS, 0x002); +	hdmi_write_reg(av_base, 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(av_base, 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(av_base, 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(av_base, HDMI_CORE_AV_AVI_DBYTE(2), val); +	sum += val; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(3), +					info_avi.db4_videocode); +	sum += info_avi.db4_videocode; + +	val = info_avi.db5_pixel_repeat; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(4), val); +	sum += val; + +	val = info_avi.db6_7_line_eoftop & 0x00FF; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(5), val); +	sum += val; + +	val = ((info_avi.db6_7_line_eoftop >> 8) & 0x00FF); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(6), val); +	sum += val; + +	val = info_avi.db8_9_line_sofbottom & 0x00FF; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(7), val); +	sum += val; + +	val = ((info_avi.db8_9_line_sofbottom >> 8) & 0x00FF); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(8), val); +	sum += val; + +	val = info_avi.db10_11_pixel_eofleft & 0x00FF; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(9), val); +	sum += val; + +	val = ((info_avi.db10_11_pixel_eofleft >> 8) & 0x00FF); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(10), val); +	sum += val; + +	val = info_avi.db12_13_pixel_sofright & 0x00FF; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(11), val); +	sum += val; + +	val = ((info_avi.db12_13_pixel_sofright >> 8) & 0x00FF); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_DBYTE(12), val); +	sum += val; + +	checksum = 0x100 - sum; +	hdmi_write_reg(av_base, HDMI_CORE_AV_AVI_CHSUM, checksum); +} + +static void hdmi_core_av_packet_config(struct hdmi_core_data *core, +		struct hdmi_core_packet_enable_repeat repeat_cfg) +{ +	/* enable/repeat the infoframe */ +	hdmi_write_reg(hdmi_av_base(core), 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_av_base(core), 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)); +} + +void hdmi4_configure(struct hdmi_core_data *core, +	struct hdmi_wp_data *wp, struct hdmi_config *cfg) +{ +	/* HDMI */ +	struct omap_video_timings video_timing; +	struct hdmi_video_format video_format; +	/* HDMI core */ +	struct hdmi_core_infoframe_avi *avi_cfg = &core->avi_cfg; +	struct hdmi_core_video_config v_core_cfg; +	struct hdmi_core_packet_enable_repeat repeat_cfg; + +	hdmi_core_init(&v_core_cfg, avi_cfg, &repeat_cfg); + +	hdmi_wp_init_vid_fmt_timings(&video_format, &video_timing, cfg); + +	hdmi_wp_video_config_timing(wp, &video_timing); + +	/* video config */ +	video_format.packing_mode = HDMI_PACK_24b_RGB_YUV444_YUV422; + +	hdmi_wp_video_config_format(wp, &video_format); + +	hdmi_wp_video_config_interface(wp, &video_timing); + +	/* +	 * configure core video part +	 * set software reset in the core +	 */ +	hdmi_core_swreset_assert(core); + +	/* power down off */ +	hdmi_core_powerdown_disable(core); + +	v_core_cfg.pkt_mode = HDMI_PACKETMODE24BITPERPIXEL; +	v_core_cfg.hdmi_dvi = cfg->cm.mode; + +	hdmi_core_video_config(core, &v_core_cfg); + +	/* release software reset in the core */ +	hdmi_core_swreset_release(core); + +	/* +	 * 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(core); + +	/* 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(core, repeat_cfg); +} + +void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s) +{ +	int i; + +#define CORE_REG(i, name) name(i) +#define DUMPCORE(r) seq_printf(s, "%-35s %08x\n", #r,\ +		hdmi_read_reg(core->base, r)) +#define DUMPCOREAV(r) seq_printf(s, "%-35s %08x\n", #r,\ +		hdmi_read_reg(hdmi_av_base(core), r)) +#define DUMPCOREAV2(i, r) seq_printf(s, "%s[%d]%*s %08x\n", #r, i, \ +		(i < 10) ? 32 - (int)strlen(#r) : 31 - (int)strlen(#r), " ", \ +		hdmi_read_reg(hdmi_av_base(core), CORE_REG(i, r))) + +	DUMPCORE(HDMI_CORE_SYS_VND_IDL); +	DUMPCORE(HDMI_CORE_SYS_DEV_IDL); +	DUMPCORE(HDMI_CORE_SYS_DEV_IDH); +	DUMPCORE(HDMI_CORE_SYS_DEV_REV); +	DUMPCORE(HDMI_CORE_SYS_SRST); +	DUMPCORE(HDMI_CORE_SYS_SYS_CTRL1); +	DUMPCORE(HDMI_CORE_SYS_SYS_STAT); +	DUMPCORE(HDMI_CORE_SYS_SYS_CTRL3); +	DUMPCORE(HDMI_CORE_SYS_DE_DLY); +	DUMPCORE(HDMI_CORE_SYS_DE_CTRL); +	DUMPCORE(HDMI_CORE_SYS_DE_TOP); +	DUMPCORE(HDMI_CORE_SYS_DE_CNTL); +	DUMPCORE(HDMI_CORE_SYS_DE_CNTH); +	DUMPCORE(HDMI_CORE_SYS_DE_LINL); +	DUMPCORE(HDMI_CORE_SYS_DE_LINH_1); +	DUMPCORE(HDMI_CORE_SYS_HRES_L); +	DUMPCORE(HDMI_CORE_SYS_HRES_H); +	DUMPCORE(HDMI_CORE_SYS_VRES_L); +	DUMPCORE(HDMI_CORE_SYS_VRES_H); +	DUMPCORE(HDMI_CORE_SYS_IADJUST); +	DUMPCORE(HDMI_CORE_SYS_POLDETECT); +	DUMPCORE(HDMI_CORE_SYS_HWIDTH1); +	DUMPCORE(HDMI_CORE_SYS_HWIDTH2); +	DUMPCORE(HDMI_CORE_SYS_VWIDTH); +	DUMPCORE(HDMI_CORE_SYS_VID_CTRL); +	DUMPCORE(HDMI_CORE_SYS_VID_ACEN); +	DUMPCORE(HDMI_CORE_SYS_VID_MODE); +	DUMPCORE(HDMI_CORE_SYS_VID_BLANK1); +	DUMPCORE(HDMI_CORE_SYS_VID_BLANK3); +	DUMPCORE(HDMI_CORE_SYS_VID_BLANK1); +	DUMPCORE(HDMI_CORE_SYS_DC_HEADER); +	DUMPCORE(HDMI_CORE_SYS_VID_DITHER); +	DUMPCORE(HDMI_CORE_SYS_RGB2XVYCC_CT); +	DUMPCORE(HDMI_CORE_SYS_R2Y_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_R2Y_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_G2Y_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_G2Y_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_B2Y_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_B2Y_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_R2CB_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_R2CB_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_G2CB_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_G2CB_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_B2CB_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_B2CB_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_R2CR_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_R2CR_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_G2CR_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_G2CR_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_B2CR_COEFF_LOW); +	DUMPCORE(HDMI_CORE_SYS_B2CR_COEFF_UP); +	DUMPCORE(HDMI_CORE_SYS_RGB_OFFSET_LOW); +	DUMPCORE(HDMI_CORE_SYS_RGB_OFFSET_UP); +	DUMPCORE(HDMI_CORE_SYS_Y_OFFSET_LOW); +	DUMPCORE(HDMI_CORE_SYS_Y_OFFSET_UP); +	DUMPCORE(HDMI_CORE_SYS_CBCR_OFFSET_LOW); +	DUMPCORE(HDMI_CORE_SYS_CBCR_OFFSET_UP); +	DUMPCORE(HDMI_CORE_SYS_INTR_STATE); +	DUMPCORE(HDMI_CORE_SYS_INTR1); +	DUMPCORE(HDMI_CORE_SYS_INTR2); +	DUMPCORE(HDMI_CORE_SYS_INTR3); +	DUMPCORE(HDMI_CORE_SYS_INTR4); +	DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK1); +	DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK2); +	DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK3); +	DUMPCORE(HDMI_CORE_SYS_INTR_UNMASK4); +	DUMPCORE(HDMI_CORE_SYS_INTR_CTRL); +	DUMPCORE(HDMI_CORE_SYS_TMDS_CTRL); + +	DUMPCORE(HDMI_CORE_DDC_ADDR); +	DUMPCORE(HDMI_CORE_DDC_SEGM); +	DUMPCORE(HDMI_CORE_DDC_OFFSET); +	DUMPCORE(HDMI_CORE_DDC_COUNT1); +	DUMPCORE(HDMI_CORE_DDC_COUNT2); +	DUMPCORE(HDMI_CORE_DDC_STATUS); +	DUMPCORE(HDMI_CORE_DDC_CMD); +	DUMPCORE(HDMI_CORE_DDC_DATA); + +	DUMPCOREAV(HDMI_CORE_AV_ACR_CTRL); +	DUMPCOREAV(HDMI_CORE_AV_FREQ_SVAL); +	DUMPCOREAV(HDMI_CORE_AV_N_SVAL1); +	DUMPCOREAV(HDMI_CORE_AV_N_SVAL2); +	DUMPCOREAV(HDMI_CORE_AV_N_SVAL3); +	DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL1); +	DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL2); +	DUMPCOREAV(HDMI_CORE_AV_CTS_SVAL3); +	DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL1); +	DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL2); +	DUMPCOREAV(HDMI_CORE_AV_CTS_HVAL3); +	DUMPCOREAV(HDMI_CORE_AV_AUD_MODE); +	DUMPCOREAV(HDMI_CORE_AV_SPDIF_CTRL); +	DUMPCOREAV(HDMI_CORE_AV_HW_SPDIF_FS); +	DUMPCOREAV(HDMI_CORE_AV_SWAP_I2S); +	DUMPCOREAV(HDMI_CORE_AV_SPDIF_ERTH); +	DUMPCOREAV(HDMI_CORE_AV_I2S_IN_MAP); +	DUMPCOREAV(HDMI_CORE_AV_I2S_IN_CTRL); +	DUMPCOREAV(HDMI_CORE_AV_I2S_CHST0); +	DUMPCOREAV(HDMI_CORE_AV_I2S_CHST1); +	DUMPCOREAV(HDMI_CORE_AV_I2S_CHST2); +	DUMPCOREAV(HDMI_CORE_AV_I2S_CHST4); +	DUMPCOREAV(HDMI_CORE_AV_I2S_CHST5); +	DUMPCOREAV(HDMI_CORE_AV_ASRC); +	DUMPCOREAV(HDMI_CORE_AV_I2S_IN_LEN); +	DUMPCOREAV(HDMI_CORE_AV_HDMI_CTRL); +	DUMPCOREAV(HDMI_CORE_AV_AUDO_TXSTAT); +	DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_1); +	DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_2); +	DUMPCOREAV(HDMI_CORE_AV_AUD_PAR_BUSCLK_3); +	DUMPCOREAV(HDMI_CORE_AV_TEST_TXCTRL); +	DUMPCOREAV(HDMI_CORE_AV_DPD); +	DUMPCOREAV(HDMI_CORE_AV_PB_CTRL1); +	DUMPCOREAV(HDMI_CORE_AV_PB_CTRL2); +	DUMPCOREAV(HDMI_CORE_AV_AVI_TYPE); +	DUMPCOREAV(HDMI_CORE_AV_AVI_VERS); +	DUMPCOREAV(HDMI_CORE_AV_AVI_LEN); +	DUMPCOREAV(HDMI_CORE_AV_AVI_CHSUM); + +	for (i = 0; i < HDMI_CORE_AV_AVI_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_AVI_DBYTE); + +	DUMPCOREAV(HDMI_CORE_AV_SPD_TYPE); +	DUMPCOREAV(HDMI_CORE_AV_SPD_VERS); +	DUMPCOREAV(HDMI_CORE_AV_SPD_LEN); +	DUMPCOREAV(HDMI_CORE_AV_SPD_CHSUM); + +	for (i = 0; i < HDMI_CORE_AV_SPD_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_SPD_DBYTE); + +	DUMPCOREAV(HDMI_CORE_AV_AUDIO_TYPE); +	DUMPCOREAV(HDMI_CORE_AV_AUDIO_VERS); +	DUMPCOREAV(HDMI_CORE_AV_AUDIO_LEN); +	DUMPCOREAV(HDMI_CORE_AV_AUDIO_CHSUM); + +	for (i = 0; i < HDMI_CORE_AV_AUD_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_AUD_DBYTE); + +	DUMPCOREAV(HDMI_CORE_AV_MPEG_TYPE); +	DUMPCOREAV(HDMI_CORE_AV_MPEG_VERS); +	DUMPCOREAV(HDMI_CORE_AV_MPEG_LEN); +	DUMPCOREAV(HDMI_CORE_AV_MPEG_CHSUM); + +	for (i = 0; i < HDMI_CORE_AV_MPEG_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_MPEG_DBYTE); + +	for (i = 0; i < HDMI_CORE_AV_GEN_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_GEN_DBYTE); + +	DUMPCOREAV(HDMI_CORE_AV_CP_BYTE1); + +	for (i = 0; i < HDMI_CORE_AV_GEN2_DBYTE_NELEMS; i++) +		DUMPCOREAV2(i, HDMI_CORE_AV_GEN2_DBYTE); + +	DUMPCOREAV(HDMI_CORE_AV_CEC_ADDR_ID); +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +static void hdmi_core_audio_config(struct hdmi_core_data *core, +					struct hdmi_core_audio_config *cfg) +{ +	u32 r; +	void __iomem *av_base = hdmi_av_base(core); + +	/* +	 * Parameters for generation of Audio Clock Recovery packets +	 */ +	REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL1, cfg->n, 7, 0); +	REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL2, cfg->n >> 8, 7, 0); +	REG_FLD_MOD(av_base, HDMI_CORE_AV_N_SVAL3, cfg->n >> 16, 7, 0); + +	if (cfg->cts_mode == HDMI_AUDIO_CTS_MODE_SW) { +		REG_FLD_MOD(av_base, HDMI_CORE_AV_CTS_SVAL1, cfg->cts, 7, 0); +		REG_FLD_MOD(av_base, +				HDMI_CORE_AV_CTS_SVAL2, cfg->cts >> 8, 7, 0); +		REG_FLD_MOD(av_base, +				HDMI_CORE_AV_CTS_SVAL3, cfg->cts >> 16, 7, 0); +	} else { +		REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_1, +				cfg->aud_par_busclk, 7, 0); +		REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_2, +				(cfg->aud_par_busclk >> 8), 7, 0); +		REG_FLD_MOD(av_base, HDMI_CORE_AV_AUD_PAR_BUSCLK_3, +				(cfg->aud_par_busclk >> 16), 7, 0); +	} + +	/* Set ACR clock divisor */ +	REG_FLD_MOD(av_base, +			HDMI_CORE_AV_FREQ_SVAL, cfg->mclk_mode, 2, 0); + +	r = hdmi_read_reg(av_base, HDMI_CORE_AV_ACR_CTRL); +	/* +	 * Use TMDS clock for ACR packets. For devices that use +	 * the MCLK, this is the first part of the MCLK initialization. +	 */ +	r = FLD_MOD(r, 0, 2, 2); + +	r = FLD_MOD(r, cfg->en_acr_pkt, 1, 1); +	r = FLD_MOD(r, cfg->cts_mode, 0, 0); +	hdmi_write_reg(av_base, HDMI_CORE_AV_ACR_CTRL, r); + +	/* For devices using MCLK, this completes its initialization. */ +	if (cfg->use_mclk) +		REG_FLD_MOD(av_base, HDMI_CORE_AV_ACR_CTRL, 1, 2, 2); + +	/* Override of SPDIF sample frequency with value in I2S_CHST4 */ +	REG_FLD_MOD(av_base, HDMI_CORE_AV_SPDIF_CTRL, +						cfg->fs_override, 1, 1); + +	/* +	 * Set IEC-60958-3 channel status word. It is passed to the IP +	 * just as it is received. The user of the driver is responsible +	 * for its contents. +	 */ +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST0, +		       cfg->iec60958_cfg->status[0]); +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST1, +		       cfg->iec60958_cfg->status[1]); +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST2, +		       cfg->iec60958_cfg->status[2]); +	/* yes, this is correct: status[3] goes to CHST4 register */ +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST4, +		       cfg->iec60958_cfg->status[3]); +	/* yes, this is correct: status[4] goes to CHST5 register */ +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_CHST5, +		       cfg->iec60958_cfg->status[4]); + +	/* set I2S parameters */ +	r = hdmi_read_reg(av_base, HDMI_CORE_AV_I2S_IN_CTRL); +	r = FLD_MOD(r, cfg->i2s_cfg.sck_edge_mode, 6, 6); +	r = FLD_MOD(r, cfg->i2s_cfg.vbit, 4, 4); +	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(av_base, HDMI_CORE_AV_I2S_IN_CTRL, r); + +	REG_FLD_MOD(av_base, HDMI_CORE_AV_I2S_IN_LEN, +			cfg->i2s_cfg.in_length_bits, 3, 0); + +	/* Audio channels and mode parameters */ +	REG_FLD_MOD(av_base, HDMI_CORE_AV_HDMI_CTRL, cfg->layout, 2, 1); +	r = hdmi_read_reg(av_base, 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(av_base, HDMI_CORE_AV_AUD_MODE, r); + +	/* Audio channel mappings */ +	/* TODO: Make channel mapping dynamic. For now, map channels +	 * in the ALSA order: FL/FR/RL/RR/C/LFE/SL/SR. Remapping is needed as +	 * HDMI speaker order is different. See CEA-861 Section 6.6.2. +	 */ +	hdmi_write_reg(av_base, HDMI_CORE_AV_I2S_IN_MAP, 0x78); +	REG_FLD_MOD(av_base, HDMI_CORE_AV_SWAP_I2S, 1, 5, 5); +} + +static void hdmi_core_audio_infoframe_cfg(struct hdmi_core_data *core, +		struct snd_cea_861_aud_if *info_aud) +{ +	u8 sum = 0, checksum = 0; +	void __iomem *av_base = hdmi_av_base(core); + +	/* +	 * 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(av_base, HDMI_CORE_AV_AUDIO_TYPE, 0x84); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUDIO_VERS, 0x01); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUDIO_LEN, 0x0a); +	sum += 0x84 + 0x001 + 0x00a; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(0), +		       info_aud->db1_ct_cc); +	sum += info_aud->db1_ct_cc; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(1), +		       info_aud->db2_sf_ss); +	sum += info_aud->db2_sf_ss; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(2), info_aud->db3); +	sum += info_aud->db3; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(3), info_aud->db4_ca); +	sum += info_aud->db4_ca; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(4), +		       info_aud->db5_dminh_lsv); +	sum += info_aud->db5_dminh_lsv; + +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(5), 0x00); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(6), 0x00); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(7), 0x00); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(8), 0x00); +	hdmi_write_reg(av_base, HDMI_CORE_AV_AUD_DBYTE(9), 0x00); + +	checksum = 0x100 - sum; +	hdmi_write_reg(av_base, +					HDMI_CORE_AV_AUDIO_CHSUM, checksum); + +	/* +	 * TODO: Add MPEG and SPD enable and repeat cfg when EDID parsing +	 * is available. +	 */ +} + +int hdmi4_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +		struct omap_dss_audio *audio, u32 pclk) +{ +	struct hdmi_audio_format audio_format; +	struct hdmi_audio_dma audio_dma; +	struct hdmi_core_audio_config acore; +	int err, n, cts, channel_count; +	unsigned int fs_nr; +	bool word_length_16b = false; + +	if (!audio || !audio->iec || !audio->cea || !core) +		return -EINVAL; + +	acore.iec60958_cfg = audio->iec; +	/* +	 * In the IEC-60958 status word, check if the audio sample word length +	 * is 16-bit as several optimizations can be performed in such case. +	 */ +	if (!(audio->iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24)) +		if (audio->iec->status[4] & IEC958_AES4_CON_WORDLEN_20_16) +			word_length_16b = true; + +	/* I2S configuration. See Phillips' specification */ +	if (word_length_16b) +		acore.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_LEFT; +	else +		acore.i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_RIGHT; +	/* +	 * The I2S input word length is twice the lenght given in the IEC-60958 +	 * status word. If the word size is greater than +	 * 20 bits, increment by one. +	 */ +	acore.i2s_cfg.in_length_bits = audio->iec->status[4] +		& IEC958_AES4_CON_WORDLEN; +	if (audio->iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24) +		acore.i2s_cfg.in_length_bits++; +	acore.i2s_cfg.sck_edge_mode = HDMI_AUDIO_I2S_SCK_EDGE_RISING; +	acore.i2s_cfg.vbit = HDMI_AUDIO_I2S_VBIT_FOR_PCM; +	acore.i2s_cfg.direction = HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST; +	acore.i2s_cfg.shift = HDMI_AUDIO_I2S_FIRST_BIT_SHIFT; + +	/* convert sample frequency to a number */ +	switch (audio->iec->status[3] & IEC958_AES3_CON_FS) { +	case IEC958_AES3_CON_FS_32000: +		fs_nr = 32000; +		break; +	case IEC958_AES3_CON_FS_44100: +		fs_nr = 44100; +		break; +	case IEC958_AES3_CON_FS_48000: +		fs_nr = 48000; +		break; +	case IEC958_AES3_CON_FS_88200: +		fs_nr = 88200; +		break; +	case IEC958_AES3_CON_FS_96000: +		fs_nr = 96000; +		break; +	case IEC958_AES3_CON_FS_176400: +		fs_nr = 176400; +		break; +	case IEC958_AES3_CON_FS_192000: +		fs_nr = 192000; +		break; +	default: +		return -EINVAL; +	} + +	err = hdmi_compute_acr(pclk, fs_nr, &n, &cts); + +	/* Audio clock regeneration settings */ +	acore.n = n; +	acore.cts = cts; +	if (dss_has_feature(FEAT_HDMI_CTS_SWMODE)) { +		acore.aud_par_busclk = 0; +		acore.cts_mode = HDMI_AUDIO_CTS_MODE_SW; +		acore.use_mclk = dss_has_feature(FEAT_HDMI_AUDIO_USE_MCLK); +	} else { +		acore.aud_par_busclk = (((128 * 31) - 1) << 8); +		acore.cts_mode = HDMI_AUDIO_CTS_MODE_HW; +		acore.use_mclk = true; +	} + +	if (acore.use_mclk) +		acore.mclk_mode = HDMI_AUDIO_MCLK_128FS; + +	/* Audio channels settings */ +	channel_count = (audio->cea->db1_ct_cc & +			 CEA861_AUDIO_INFOFRAME_DB1CC) + 1; + +	switch (channel_count) { +	case 2: +		audio_format.active_chnnls_msk = 0x03; +		break; +	case 3: +		audio_format.active_chnnls_msk = 0x07; +		break; +	case 4: +		audio_format.active_chnnls_msk = 0x0f; +		break; +	case 5: +		audio_format.active_chnnls_msk = 0x1f; +		break; +	case 6: +		audio_format.active_chnnls_msk = 0x3f; +		break; +	case 7: +		audio_format.active_chnnls_msk = 0x7f; +		break; +	case 8: +		audio_format.active_chnnls_msk = 0xff; +		break; +	default: +		return -EINVAL; +	} + +	/* +	 * the HDMI IP needs to enable four stereo channels when transmitting +	 * more than 2 audio channels +	 */ +	if (channel_count == 2) { +		audio_format.stereo_channels = HDMI_AUDIO_STEREO_ONECHANNEL; +		acore.i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN; +		acore.layout = HDMI_AUDIO_LAYOUT_2CH; +	} else { +		audio_format.stereo_channels = HDMI_AUDIO_STEREO_FOURCHANNELS; +		acore.i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN | +				HDMI_AUDIO_I2S_SD1_EN | HDMI_AUDIO_I2S_SD2_EN | +				HDMI_AUDIO_I2S_SD3_EN; +		acore.layout = HDMI_AUDIO_LAYOUT_8CH; +	} + +	acore.en_spdif = false; +	/* use sample frequency from channel status word */ +	acore.fs_override = true; +	/* enable ACR packets */ +	acore.en_acr_pkt = true; +	/* disable direct streaming digital audio */ +	acore.en_dsd_audio = false; +	/* use parallel audio interface */ +	acore.en_parallel_aud_input = true; + +	/* DMA settings */ +	if (word_length_16b) +		audio_dma.transfer_size = 0x10; +	else +		audio_dma.transfer_size = 0x20; +	audio_dma.block_size = 0xC0; +	audio_dma.mode = HDMI_AUDIO_TRANSF_DMA; +	audio_dma.fifo_threshold = 0x20; /* in number of samples */ + +	/* audio FIFO format settings */ +	if (word_length_16b) { +		audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_TWOSAMPLES; +		audio_format.sample_size = HDMI_AUDIO_SAMPLE_16BITS; +		audio_format.justification = HDMI_AUDIO_JUSTIFY_LEFT; +	} else { +		audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_ONESAMPLE; +		audio_format.sample_size = HDMI_AUDIO_SAMPLE_24BITS; +		audio_format.justification = HDMI_AUDIO_JUSTIFY_RIGHT; +	} +	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_ON; + +	/* configure DMA and audio FIFO format*/ +	hdmi_wp_audio_config_dma(wp, &audio_dma); +	hdmi_wp_audio_config_format(wp, &audio_format); + +	/* configure the core*/ +	hdmi_core_audio_config(core, &acore); + +	/* configure CEA 861 audio infoframe*/ +	hdmi_core_audio_infoframe_cfg(core, audio->cea); + +	return 0; +} + +int hdmi4_audio_start(struct hdmi_core_data *core, struct hdmi_wp_data *wp) +{ +	REG_FLD_MOD(hdmi_av_base(core), +		    HDMI_CORE_AV_AUD_MODE, true, 0, 0); + +	hdmi_wp_audio_core_req_enable(wp, true); + +	return 0; +} + +void hdmi4_audio_stop(struct hdmi_core_data *core, struct hdmi_wp_data *wp) +{ +	REG_FLD_MOD(hdmi_av_base(core), +		    HDMI_CORE_AV_AUD_MODE, false, 0, 0); + +	hdmi_wp_audio_core_req_enable(wp, false); +} + +int hdmi4_audio_get_dma_port(u32 *offset, u32 *size) +{ +	if (!offset || !size) +		return -EINVAL; +	*offset = HDMI_WP_AUDIO_DATA; +	*size = 4; +	return 0; +} + +#endif + +int hdmi4_core_init(struct platform_device *pdev, struct hdmi_core_data *core) +{ +	struct resource *res; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); +	if (!res) { +		DSSERR("can't get CORE mem resource\n"); +		return -EINVAL; +	} + +	core->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(core->base)) { +		DSSERR("can't ioremap CORE\n"); +		return PTR_ERR(core->base); +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi4_core.h b/drivers/video/fbdev/omap2/dss/hdmi4_core.h new file mode 100644 index 00000000000..bb646896fa8 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi4_core.h @@ -0,0 +1,276 @@ +/* + * HDMI header definition for OMAP4 HDMI core IP + * + * 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 _HDMI4_CORE_H_ +#define _HDMI4_CORE_H_ + +#include "hdmi.h" + +/* OMAP4 HDMI IP Core System */ + +#define HDMI_CORE_SYS_VND_IDL			0x0 +#define HDMI_CORE_SYS_DEV_IDL			0x8 +#define HDMI_CORE_SYS_DEV_IDH			0xC +#define HDMI_CORE_SYS_DEV_REV			0x10 +#define HDMI_CORE_SYS_SRST			0x14 +#define HDMI_CORE_SYS_SYS_CTRL1			0x20 +#define HDMI_CORE_SYS_SYS_STAT			0x24 +#define HDMI_CORE_SYS_SYS_CTRL3			0x28 +#define HDMI_CORE_SYS_DCTL			0x34 +#define HDMI_CORE_SYS_DE_DLY			0xC8 +#define HDMI_CORE_SYS_DE_CTRL			0xCC +#define HDMI_CORE_SYS_DE_TOP			0xD0 +#define HDMI_CORE_SYS_DE_CNTL			0xD8 +#define HDMI_CORE_SYS_DE_CNTH			0xDC +#define HDMI_CORE_SYS_DE_LINL			0xE0 +#define HDMI_CORE_SYS_DE_LINH_1			0xE4 +#define HDMI_CORE_SYS_HRES_L			0xE8 +#define HDMI_CORE_SYS_HRES_H			0xEC +#define HDMI_CORE_SYS_VRES_L			0xF0 +#define HDMI_CORE_SYS_VRES_H			0xF4 +#define HDMI_CORE_SYS_IADJUST			0xF8 +#define HDMI_CORE_SYS_POLDETECT			0xFC +#define HDMI_CORE_SYS_HWIDTH1			0x110 +#define HDMI_CORE_SYS_HWIDTH2			0x114 +#define HDMI_CORE_SYS_VWIDTH			0x11C +#define HDMI_CORE_SYS_VID_CTRL			0x120 +#define HDMI_CORE_SYS_VID_ACEN			0x124 +#define HDMI_CORE_SYS_VID_MODE			0x128 +#define HDMI_CORE_SYS_VID_BLANK1		0x12C +#define HDMI_CORE_SYS_VID_BLANK2		0x130 +#define HDMI_CORE_SYS_VID_BLANK3		0x134 +#define HDMI_CORE_SYS_DC_HEADER			0x138 +#define HDMI_CORE_SYS_VID_DITHER		0x13C +#define HDMI_CORE_SYS_RGB2XVYCC_CT		0x140 +#define HDMI_CORE_SYS_R2Y_COEFF_LOW		0x144 +#define HDMI_CORE_SYS_R2Y_COEFF_UP		0x148 +#define HDMI_CORE_SYS_G2Y_COEFF_LOW		0x14C +#define HDMI_CORE_SYS_G2Y_COEFF_UP		0x150 +#define HDMI_CORE_SYS_B2Y_COEFF_LOW		0x154 +#define HDMI_CORE_SYS_B2Y_COEFF_UP		0x158 +#define HDMI_CORE_SYS_R2CB_COEFF_LOW		0x15C +#define HDMI_CORE_SYS_R2CB_COEFF_UP		0x160 +#define HDMI_CORE_SYS_G2CB_COEFF_LOW		0x164 +#define HDMI_CORE_SYS_G2CB_COEFF_UP		0x168 +#define HDMI_CORE_SYS_B2CB_COEFF_LOW		0x16C +#define HDMI_CORE_SYS_B2CB_COEFF_UP		0x170 +#define HDMI_CORE_SYS_R2CR_COEFF_LOW		0x174 +#define HDMI_CORE_SYS_R2CR_COEFF_UP		0x178 +#define HDMI_CORE_SYS_G2CR_COEFF_LOW		0x17C +#define HDMI_CORE_SYS_G2CR_COEFF_UP		0x180 +#define HDMI_CORE_SYS_B2CR_COEFF_LOW		0x184 +#define HDMI_CORE_SYS_B2CR_COEFF_UP		0x188 +#define HDMI_CORE_SYS_RGB_OFFSET_LOW		0x18C +#define HDMI_CORE_SYS_RGB_OFFSET_UP		0x190 +#define HDMI_CORE_SYS_Y_OFFSET_LOW		0x194 +#define HDMI_CORE_SYS_Y_OFFSET_UP		0x198 +#define HDMI_CORE_SYS_CBCR_OFFSET_LOW		0x19C +#define HDMI_CORE_SYS_CBCR_OFFSET_UP		0x1A0 +#define HDMI_CORE_SYS_INTR_STATE		0x1C0 +#define HDMI_CORE_SYS_INTR1			0x1C4 +#define HDMI_CORE_SYS_INTR2			0x1C8 +#define HDMI_CORE_SYS_INTR3			0x1CC +#define HDMI_CORE_SYS_INTR4			0x1D0 +#define HDMI_CORE_SYS_INTR_UNMASK1		0x1D4 +#define HDMI_CORE_SYS_INTR_UNMASK2		0x1D8 +#define HDMI_CORE_SYS_INTR_UNMASK3		0x1DC +#define HDMI_CORE_SYS_INTR_UNMASK4		0x1E0 +#define HDMI_CORE_SYS_INTR_CTRL			0x1E4 +#define HDMI_CORE_SYS_TMDS_CTRL			0x208 + +/* value definitions for HDMI_CORE_SYS_SYS_CTRL1 fields */ +#define HDMI_CORE_SYS_SYS_CTRL1_VEN_FOLLOWVSYNC	0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_HEN_FOLLOWHSYNC	0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_BSEL_24BITBUS	0x1 +#define HDMI_CORE_SYS_SYS_CTRL1_EDGE_RISINGEDGE	0x1 + +/* HDMI DDC E-DID */ +#define HDMI_CORE_DDC_ADDR			0x3B4 +#define HDMI_CORE_DDC_SEGM			0x3B8 +#define HDMI_CORE_DDC_OFFSET			0x3BC +#define HDMI_CORE_DDC_COUNT1			0x3C0 +#define HDMI_CORE_DDC_COUNT2			0x3C4 +#define HDMI_CORE_DDC_STATUS			0x3C8 +#define HDMI_CORE_DDC_CMD			0x3CC +#define HDMI_CORE_DDC_DATA			0x3D0 + +/* HDMI IP Core Audio Video */ + +#define HDMI_CORE_AV_ACR_CTRL			0x4 +#define HDMI_CORE_AV_FREQ_SVAL			0x8 +#define HDMI_CORE_AV_N_SVAL1			0xC +#define HDMI_CORE_AV_N_SVAL2			0x10 +#define HDMI_CORE_AV_N_SVAL3			0x14 +#define HDMI_CORE_AV_CTS_SVAL1			0x18 +#define HDMI_CORE_AV_CTS_SVAL2			0x1C +#define HDMI_CORE_AV_CTS_SVAL3			0x20 +#define HDMI_CORE_AV_CTS_HVAL1			0x24 +#define HDMI_CORE_AV_CTS_HVAL2			0x28 +#define HDMI_CORE_AV_CTS_HVAL3			0x2C +#define HDMI_CORE_AV_AUD_MODE			0x50 +#define HDMI_CORE_AV_SPDIF_CTRL			0x54 +#define HDMI_CORE_AV_HW_SPDIF_FS		0x60 +#define HDMI_CORE_AV_SWAP_I2S			0x64 +#define HDMI_CORE_AV_SPDIF_ERTH			0x6C +#define HDMI_CORE_AV_I2S_IN_MAP			0x70 +#define HDMI_CORE_AV_I2S_IN_CTRL		0x74 +#define HDMI_CORE_AV_I2S_CHST0			0x78 +#define HDMI_CORE_AV_I2S_CHST1			0x7C +#define HDMI_CORE_AV_I2S_CHST2			0x80 +#define HDMI_CORE_AV_I2S_CHST4			0x84 +#define HDMI_CORE_AV_I2S_CHST5			0x88 +#define HDMI_CORE_AV_ASRC			0x8C +#define HDMI_CORE_AV_I2S_IN_LEN			0x90 +#define HDMI_CORE_AV_HDMI_CTRL			0xBC +#define HDMI_CORE_AV_AUDO_TXSTAT		0xC0 +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_1		0xCC +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_2		0xD0 +#define HDMI_CORE_AV_AUD_PAR_BUSCLK_3		0xD4 +#define HDMI_CORE_AV_TEST_TXCTRL		0xF0 +#define HDMI_CORE_AV_DPD			0xF4 +#define HDMI_CORE_AV_PB_CTRL1			0xF8 +#define HDMI_CORE_AV_PB_CTRL2			0xFC +#define HDMI_CORE_AV_AVI_TYPE			0x100 +#define HDMI_CORE_AV_AVI_VERS			0x104 +#define HDMI_CORE_AV_AVI_LEN			0x108 +#define HDMI_CORE_AV_AVI_CHSUM			0x10C +#define HDMI_CORE_AV_AVI_DBYTE(n)		(n * 4 + 0x110) +#define HDMI_CORE_AV_SPD_TYPE			0x180 +#define HDMI_CORE_AV_SPD_VERS			0x184 +#define HDMI_CORE_AV_SPD_LEN			0x188 +#define HDMI_CORE_AV_SPD_CHSUM			0x18C +#define HDMI_CORE_AV_SPD_DBYTE(n)		(n * 4 + 0x190) +#define HDMI_CORE_AV_AUDIO_TYPE			0x200 +#define HDMI_CORE_AV_AUDIO_VERS			0x204 +#define HDMI_CORE_AV_AUDIO_LEN			0x208 +#define HDMI_CORE_AV_AUDIO_CHSUM		0x20C +#define HDMI_CORE_AV_AUD_DBYTE(n)		(n * 4 + 0x210) +#define HDMI_CORE_AV_MPEG_TYPE			0x280 +#define HDMI_CORE_AV_MPEG_VERS			0x284 +#define HDMI_CORE_AV_MPEG_LEN			0x288 +#define HDMI_CORE_AV_MPEG_CHSUM			0x28C +#define HDMI_CORE_AV_MPEG_DBYTE(n)		(n * 4 + 0x290) +#define HDMI_CORE_AV_GEN_DBYTE(n)		(n * 4 + 0x300) +#define HDMI_CORE_AV_CP_BYTE1			0x37C +#define HDMI_CORE_AV_GEN2_DBYTE(n)		(n * 4 + 0x380) +#define HDMI_CORE_AV_CEC_ADDR_ID		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 + +#define HDMI_CORE_AV_AVI_DBYTE_NELEMS		15 +#define HDMI_CORE_AV_SPD_DBYTE_NELEMS		27 +#define HDMI_CORE_AV_AUD_DBYTE_NELEMS		10 +#define HDMI_CORE_AV_MPEG_DBYTE_NELEMS		27 +#define HDMI_CORE_AV_GEN_DBYTE_NELEMS		31 +#define HDMI_CORE_AV_GEN2_DBYTE_NELEMS		31 + +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_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 +}; + +enum hdmi_audio_i2s_config { +	HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST = 0, +	HDMI_AUDIO_I2S_LSB_SHIFTED_FIRST = 1, +	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_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, +}; + +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; +}; + +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; +}; + +int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len); +void hdmi4_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +		struct hdmi_config *cfg); +void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s); +int hdmi4_core_init(struct platform_device *pdev, struct hdmi_core_data *core); + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +int hdmi4_audio_start(struct hdmi_core_data *core, struct hdmi_wp_data *wp); +void hdmi4_audio_stop(struct hdmi_core_data *core, struct hdmi_wp_data *wp); +int hdmi4_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +		struct omap_dss_audio *audio, u32 pclk); +int hdmi4_audio_get_dma_port(u32 *offset, u32 *size); +#endif + +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi5.c b/drivers/video/fbdev/omap2/dss/hdmi5.c new file mode 100644 index 00000000000..c468b9e1f29 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi5.c @@ -0,0 +1,829 @@ +/* + * HDMI driver for OMAP5 + * + * Copyright (C) 2014 Texas Instruments Incorporated + * + * Authors: + *	Yong Zhi + *	Mythri pk + *	Archit Taneja <archit@ti.com> + *	Tomi Valkeinen <tomi.valkeinen@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 DSS_SUBSYS_NAME "HDMI" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <video/omapdss.h> + +#include "hdmi5_core.h" +#include "dss.h" +#include "dss_features.h" + +static struct { +	struct mutex lock; +	struct platform_device *pdev; + +	struct hdmi_wp_data	wp; +	struct hdmi_pll_data	pll; +	struct hdmi_phy_data	phy; +	struct hdmi_core_data	core; + +	struct hdmi_config cfg; + +	struct clk *sys_clk; +	struct regulator *vdda_reg; + +	bool core_enabled; + +	struct omap_dss_device output; +} hdmi; + +static int hdmi_runtime_get(void) +{ +	int r; + +	DSSDBG("hdmi_runtime_get\n"); + +	r = pm_runtime_get_sync(&hdmi.pdev->dev); +	WARN_ON(r < 0); +	if (r < 0) +		return r; + +	return 0; +} + +static void hdmi_runtime_put(void) +{ +	int r; + +	DSSDBG("hdmi_runtime_put\n"); + +	r = pm_runtime_put_sync(&hdmi.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} + +static irqreturn_t hdmi_irq_handler(int irq, void *data) +{ +	struct hdmi_wp_data *wp = data; +	u32 irqstatus; + +	irqstatus = hdmi_wp_get_irqstatus(wp); +	hdmi_wp_set_irqstatus(wp, irqstatus); + +	if ((irqstatus & HDMI_IRQ_LINK_CONNECT) && +			irqstatus & HDMI_IRQ_LINK_DISCONNECT) { +		u32 v; +		/* +		 * If we get both connect and disconnect interrupts at the same +		 * time, turn off the PHY, clear interrupts, and restart, which +		 * raises connect interrupt if a cable is connected, or nothing +		 * if cable is not connected. +		 */ + +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF); + +		/* +		 * We always get bogus CONNECT & DISCONNECT interrupts when +		 * setting the PHY to LDOON. To ignore those, we force the RXDET +		 * line to 0 until the PHY power state has been changed. +		 */ +		v = hdmi_read_reg(hdmi.phy.base, HDMI_TXPHY_PAD_CFG_CTRL); +		v = FLD_MOD(v, 1, 15, 15); /* FORCE_RXDET_HIGH */ +		v = FLD_MOD(v, 0, 14, 7); /* RXDET_LINE */ +		hdmi_write_reg(hdmi.phy.base, HDMI_TXPHY_PAD_CFG_CTRL, v); + +		hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT | +				HDMI_IRQ_LINK_DISCONNECT); + +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); + +		REG_FLD_MOD(hdmi.phy.base, HDMI_TXPHY_PAD_CFG_CTRL, 0, 15, 15); + +	} else if (irqstatus & HDMI_IRQ_LINK_CONNECT) { +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON); +	} else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) { +		hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON); +	} + +	return IRQ_HANDLED; +} + +static int hdmi_init_regulator(void) +{ +	int r; +	struct regulator *reg; + +	if (hdmi.vdda_reg != NULL) +		return 0; + +	reg = devm_regulator_get(&hdmi.pdev->dev, "vdda"); +	if (IS_ERR(reg)) { +		DSSERR("can't get VDDA regulator\n"); +		return PTR_ERR(reg); +	} + +	if (regulator_can_change_voltage(reg)) { +		r = regulator_set_voltage(reg, 1800000, 1800000); +		if (r) { +			devm_regulator_put(reg); +			DSSWARN("can't set the regulator voltage\n"); +			return r; +		} +	} + +	hdmi.vdda_reg = reg; + +	return 0; +} + +static int hdmi_power_on_core(struct omap_dss_device *dssdev) +{ +	int r; + +	r = regulator_enable(hdmi.vdda_reg); +	if (r) +		return r; + +	r = hdmi_runtime_get(); +	if (r) +		goto err_runtime_get; + +	/* Make selection of HDMI in DSS */ +	dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK); + +	hdmi.core_enabled = true; + +	return 0; + +err_runtime_get: +	regulator_disable(hdmi.vdda_reg); + +	return r; +} + +static void hdmi_power_off_core(struct omap_dss_device *dssdev) +{ +	hdmi.core_enabled = false; + +	hdmi_runtime_put(); +	regulator_disable(hdmi.vdda_reg); +} + +static int hdmi_power_on_full(struct omap_dss_device *dssdev) +{ +	int r; +	struct omap_video_timings *p; +	struct omap_overlay_manager *mgr = hdmi.output.manager; +	unsigned long phy; + +	r = hdmi_power_on_core(dssdev); +	if (r) +		return r; + +	p = &hdmi.cfg.timings; + +	DSSDBG("hdmi_power_on x_res= %d y_res = %d\n", p->x_res, p->y_res); + +	/* the functions below use kHz pixel clock. TODO: change to Hz */ +	phy = p->pixelclock / 1000; + +	hdmi_pll_compute(&hdmi.pll, clk_get_rate(hdmi.sys_clk), phy); + +	/* disable and clear irqs */ +	hdmi_wp_clear_irqenable(&hdmi.wp, 0xffffffff); +	hdmi_wp_set_irqstatus(&hdmi.wp, +			hdmi_wp_get_irqstatus(&hdmi.wp)); + +	/* config the PLL and PHY hdmi_set_pll_pwrfirst */ +	r = hdmi_pll_enable(&hdmi.pll, &hdmi.wp); +	if (r) { +		DSSDBG("Failed to lock PLL\n"); +		goto err_pll_enable; +	} + +	r = hdmi_phy_configure(&hdmi.phy, &hdmi.cfg); +	if (r) { +		DSSDBG("Failed to start PHY\n"); +		goto err_phy_cfg; +	} + +	r = hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_LDOON); +	if (r) +		goto err_phy_pwr; + +	hdmi5_configure(&hdmi.core, &hdmi.wp, &hdmi.cfg); + +	/* bypass TV gamma table */ +	dispc_enable_gamma_table(0); + +	/* tv size */ +	dss_mgr_set_timings(mgr, p); + +	r = hdmi_wp_video_start(&hdmi.wp); +	if (r) +		goto err_vid_enable; + +	r = dss_mgr_enable(mgr); +	if (r) +		goto err_mgr_enable; + +	hdmi_wp_set_irqenable(&hdmi.wp, +			HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT); + +	return 0; + +err_mgr_enable: +	hdmi_wp_video_stop(&hdmi.wp); +err_vid_enable: +	hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF); +err_phy_pwr: +err_phy_cfg: +	hdmi_pll_disable(&hdmi.pll, &hdmi.wp); +err_pll_enable: +	hdmi_power_off_core(dssdev); +	return -EIO; +} + +static void hdmi_power_off_full(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = hdmi.output.manager; + +	hdmi_wp_clear_irqenable(&hdmi.wp, 0xffffffff); + +	dss_mgr_disable(mgr); + +	hdmi_wp_video_stop(&hdmi.wp); + +	hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF); + +	hdmi_pll_disable(&hdmi.pll, &hdmi.wp); + +	hdmi_power_off_core(dssdev); +} + +static int hdmi_display_check_timing(struct omap_dss_device *dssdev, +					struct omap_video_timings *timings) +{ +	struct omap_dss_device *out = &hdmi.output; + +	if (!dispc_mgr_timings_ok(out->dispc_channel, timings)) +		return -EINVAL; + +	return 0; +} + +static void hdmi_display_set_timing(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	struct hdmi_cm cm; +	const struct hdmi_config *t; + +	mutex_lock(&hdmi.lock); + +	cm = hdmi_get_code(timings); +	hdmi.cfg.cm = cm; + +	t = hdmi_get_timings(cm.mode, cm.code); +	if (t != NULL) { +		hdmi.cfg = *t; + +		dispc_set_tv_pclk(t->timings.pixelclock); +	} else { +		hdmi.cfg.timings = *timings; +		hdmi.cfg.cm.code = 0; +		hdmi.cfg.cm.mode = HDMI_DVI; + +		dispc_set_tv_pclk(timings->pixelclock); +	} + +	DSSDBG("using mode: %s, code %d\n", hdmi.cfg.cm.mode == HDMI_DVI ? +			"DVI" : "HDMI", hdmi.cfg.cm.code); + +	mutex_unlock(&hdmi.lock); +} + +static void hdmi_display_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	const struct hdmi_config *cfg; +	struct hdmi_cm cm = hdmi.cfg.cm; + +	cfg = hdmi_get_timings(cm.mode, cm.code); +	if (cfg == NULL) +		cfg = hdmi_default_timing(); + +	memcpy(timings, &cfg->timings, sizeof(cfg->timings)); +} + +static void hdmi_dump_regs(struct seq_file *s) +{ +	mutex_lock(&hdmi.lock); + +	if (hdmi_runtime_get()) { +		mutex_unlock(&hdmi.lock); +		return; +	} + +	hdmi_wp_dump(&hdmi.wp, s); +	hdmi_pll_dump(&hdmi.pll, s); +	hdmi_phy_dump(&hdmi.phy, s); +	hdmi5_core_dump(&hdmi.core, s); + +	hdmi_runtime_put(); +	mutex_unlock(&hdmi.lock); +} + +static int read_edid(u8 *buf, int len) +{ +	int r; +	int idlemode; + +	mutex_lock(&hdmi.lock); + +	r = hdmi_runtime_get(); +	BUG_ON(r); + +	idlemode = REG_GET(hdmi.wp.base, HDMI_WP_SYSCONFIG, 3, 2); +	/* No-idle mode */ +	REG_FLD_MOD(hdmi.wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2); + +	r = hdmi5_read_edid(&hdmi.core,  buf, len); + +	REG_FLD_MOD(hdmi.wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2); + +	hdmi_runtime_put(); +	mutex_unlock(&hdmi.lock); + +	return r; +} + +static int hdmi_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &hdmi.output; +	int r = 0; + +	DSSDBG("ENTER hdmi_display_enable\n"); + +	mutex_lock(&hdmi.lock); + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		r = -ENODEV; +		goto err0; +	} + +	r = hdmi_power_on_full(dssdev); +	if (r) { +		DSSERR("failed to power on device\n"); +		goto err0; +	} + +	mutex_unlock(&hdmi.lock); +	return 0; + +err0: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_display_disable(struct omap_dss_device *dssdev) +{ +	DSSDBG("Enter hdmi_display_disable\n"); + +	mutex_lock(&hdmi.lock); + +	hdmi_power_off_full(dssdev); + +	mutex_unlock(&hdmi.lock); +} + +static int hdmi_core_enable(struct omap_dss_device *dssdev) +{ +	int r = 0; + +	DSSDBG("ENTER omapdss_hdmi_core_enable\n"); + +	mutex_lock(&hdmi.lock); + +	r = hdmi_power_on_core(dssdev); +	if (r) { +		DSSERR("failed to power on device\n"); +		goto err0; +	} + +	mutex_unlock(&hdmi.lock); +	return 0; + +err0: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_core_disable(struct omap_dss_device *dssdev) +{ +	DSSDBG("Enter omapdss_hdmi_core_disable\n"); + +	mutex_lock(&hdmi.lock); + +	hdmi_power_off_core(dssdev); + +	mutex_unlock(&hdmi.lock); +} + +static int hdmi_get_clocks(struct platform_device *pdev) +{ +	struct clk *clk; + +	clk = devm_clk_get(&pdev->dev, "sys_clk"); +	if (IS_ERR(clk)) { +		DSSERR("can't get sys_clk\n"); +		return PTR_ERR(clk); +	} + +	hdmi.sys_clk = clk; + +	return 0; +} + +static int hdmi_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct omap_overlay_manager *mgr; +	int r; + +	r = hdmi_init_regulator(); +	if (r) +		return r; + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dst->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void hdmi_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static int hdmi_read_edid(struct omap_dss_device *dssdev, +		u8 *edid, int len) +{ +	bool need_enable; +	int r; + +	need_enable = hdmi.core_enabled == false; + +	if (need_enable) { +		r = hdmi_core_enable(dssdev); +		if (r) +			return r; +	} + +	r = read_edid(edid, len); + +	if (need_enable) +		hdmi_core_disable(dssdev); + +	return r; +} + +#if defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ +	int r; + +	mutex_lock(&hdmi.lock); + +	if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { +		r = -EPERM; +		goto err; +	} + +	r = hdmi_wp_audio_enable(&hdmi.wp, true); +	if (r) +		goto err; + +	mutex_unlock(&hdmi.lock); +	return 0; + +err: +	mutex_unlock(&hdmi.lock); +	return r; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ +	hdmi_wp_audio_enable(&hdmi.wp, false); +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ +	return hdmi_wp_audio_core_req_enable(&hdmi.wp, true); +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ +	hdmi_wp_audio_core_req_enable(&hdmi.wp, false); +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ +	bool r; + +	mutex_lock(&hdmi.lock); + +	r = hdmi_mode_has_audio(hdmi.cfg.cm.mode); + +	mutex_unlock(&hdmi.lock); +	return r; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, +		struct omap_dss_audio *audio) +{ +	int r; +	u32 pclk = hdmi.cfg.timings.pixelclock; + +	mutex_lock(&hdmi.lock); + +	if (!hdmi_mode_has_audio(hdmi.cfg.cm.mode)) { +		r = -EPERM; +		goto err; +	} + +	r = hdmi5_audio_config(&hdmi.core, &hdmi.wp, audio, pclk); +	if (r) +		goto err; + +	mutex_unlock(&hdmi.lock); +	return 0; + +err: +	mutex_unlock(&hdmi.lock); +	return r; +} +#else +static int hdmi_audio_enable(struct omap_dss_device *dssdev) +{ +	return -EPERM; +} + +static void hdmi_audio_disable(struct omap_dss_device *dssdev) +{ +} + +static int hdmi_audio_start(struct omap_dss_device *dssdev) +{ +	return -EPERM; +} + +static void hdmi_audio_stop(struct omap_dss_device *dssdev) +{ +} + +static bool hdmi_audio_supported(struct omap_dss_device *dssdev) +{ +	return false; +} + +static int hdmi_audio_config(struct omap_dss_device *dssdev, +		struct omap_dss_audio *audio) +{ +	return -EPERM; +} +#endif + +static const struct omapdss_hdmi_ops hdmi_ops = { +	.connect		= hdmi_connect, +	.disconnect		= hdmi_disconnect, + +	.enable			= hdmi_display_enable, +	.disable		= hdmi_display_disable, + +	.check_timings		= hdmi_display_check_timing, +	.set_timings		= hdmi_display_set_timing, +	.get_timings		= hdmi_display_get_timings, + +	.read_edid		= hdmi_read_edid, + +	.audio_enable		= hdmi_audio_enable, +	.audio_disable		= hdmi_audio_disable, +	.audio_start		= hdmi_audio_start, +	.audio_stop		= hdmi_audio_stop, +	.audio_supported	= hdmi_audio_supported, +	.audio_config		= hdmi_audio_config, +}; + +static void hdmi_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &hdmi.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_HDMI; +	out->output_type = OMAP_DISPLAY_TYPE_HDMI; +	out->name = "hdmi.0"; +	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; +	out->ops.hdmi = &hdmi_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit hdmi_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &hdmi.output; + +	omapdss_unregister_output(out); +} + +static int hdmi_probe_of(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	struct device_node *ep; +	int r; + +	ep = omapdss_of_get_first_endpoint(node); +	if (!ep) +		return 0; + +	r = hdmi_parse_lanes_of(pdev, ep, &hdmi.phy); +	if (r) +		goto err; + +	of_node_put(ep); +	return 0; + +err: +	of_node_put(ep); +	return r; +} + +/* HDMI HW IP initialisation */ +static int omapdss_hdmihw_probe(struct platform_device *pdev) +{ +	int r; +	int irq; + +	hdmi.pdev = pdev; + +	mutex_init(&hdmi.lock); + +	if (pdev->dev.of_node) { +		r = hdmi_probe_of(pdev); +		if (r) +			return r; +	} + +	r = hdmi_wp_init(pdev, &hdmi.wp); +	if (r) +		return r; + +	r = hdmi_pll_init(pdev, &hdmi.pll); +	if (r) +		return r; + +	r = hdmi_phy_init(pdev, &hdmi.phy); +	if (r) +		return r; + +	r = hdmi5_core_init(pdev, &hdmi.core); +	if (r) +		return r; + +	r = hdmi_get_clocks(pdev); +	if (r) { +		DSSERR("can't get clocks\n"); +		return r; +	} + +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		DSSERR("platform_get_irq failed\n"); +		return -ENODEV; +	} + +	r = devm_request_threaded_irq(&pdev->dev, irq, +			NULL, hdmi_irq_handler, +			IRQF_ONESHOT, "OMAP HDMI", &hdmi.wp); +	if (r) { +		DSSERR("HDMI IRQ request failed\n"); +		return r; +	} + +	pm_runtime_enable(&pdev->dev); + +	hdmi_init_output(pdev); + +	dss_debugfs_create_file("hdmi", hdmi_dump_regs); + +	return 0; +} + +static int __exit omapdss_hdmihw_remove(struct platform_device *pdev) +{ +	hdmi_uninit_output(pdev); + +	pm_runtime_disable(&pdev->dev); + +	return 0; +} + +static int hdmi_runtime_suspend(struct device *dev) +{ +	clk_disable_unprepare(hdmi.sys_clk); + +	dispc_runtime_put(); + +	return 0; +} + +static int hdmi_runtime_resume(struct device *dev) +{ +	int r; + +	r = dispc_runtime_get(); +	if (r < 0) +		return r; + +	clk_prepare_enable(hdmi.sys_clk); + +	return 0; +} + +static const struct dev_pm_ops hdmi_pm_ops = { +	.runtime_suspend = hdmi_runtime_suspend, +	.runtime_resume = hdmi_runtime_resume, +}; + +static const struct of_device_id hdmi_of_match[] = { +	{ .compatible = "ti,omap5-hdmi", }, +	{}, +}; + +static struct platform_driver omapdss_hdmihw_driver = { +	.probe		= omapdss_hdmihw_probe, +	.remove         = __exit_p(omapdss_hdmihw_remove), +	.driver         = { +		.name   = "omapdss_hdmi5", +		.owner  = THIS_MODULE, +		.pm	= &hdmi_pm_ops, +		.of_match_table = hdmi_of_match, +	}, +}; + +int __init hdmi5_init_platform_driver(void) +{ +	return platform_driver_register(&omapdss_hdmihw_driver); +} + +void __exit hdmi5_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omapdss_hdmihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi5_core.c b/drivers/video/fbdev/omap2/dss/hdmi5_core.c new file mode 100644 index 00000000000..7528c7a42aa --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi5_core.c @@ -0,0 +1,922 @@ +/* + * OMAP5 HDMI CORE IP driver library + * + * Copyright (C) 2014 Texas Instruments Incorporated + * + * Authors: + *	Yong Zhi + *	Mythri pk + *	Archit Taneja <archit@ti.com> + *	Tomi Valkeinen <tomi.valkeinen@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/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#include <drm/drm_edid.h> +#if defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) +#include <sound/asound.h> +#include <sound/asoundef.h> +#endif + +#include "hdmi5_core.h" + +/* only 24 bit color depth used for now */ +static const struct csc_table csc_table_deepcolor[] = { +	/* HDMI_DEEP_COLOR_24BIT */ +	[0] = { 7036, 0, 0, 32, 0, 7036, 0, 32, 0, 0, 7036, 32, }, +	/* HDMI_DEEP_COLOR_30BIT */ +	[1] = { 7015, 0, 0, 128, 0, 7015, 0, 128, 0, 0, 7015, 128, }, +	/* HDMI_DEEP_COLOR_36BIT */ +	[2] = { 7010, 0, 0, 512, 0, 7010, 0, 512, 0, 0, 7010, 512, }, +	/* FULL RANGE */ +	[3] = { 8192, 0, 0, 0, 0, 8192, 0, 0, 0, 0, 8192, 0, }, +}; + +static void hdmi_core_ddc_init(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; +	const unsigned long long iclk = 266000000;	/* DSS L3 ICLK */ +	const unsigned ss_scl_high = 4000;		/* ns */ +	const unsigned ss_scl_low = 4700;		/* ns */ +	const unsigned fs_scl_high = 600;		/* ns */ +	const unsigned fs_scl_low = 1300;		/* ns */ +	const unsigned sda_hold = 300;			/* ns */ +	const unsigned sfr_div = 10; +	unsigned long long sfr; +	unsigned v; + +	sfr = iclk / sfr_div;	/* SFR_DIV */ +	sfr /= 1000;		/* SFR clock in kHz */ + +	/* Reset */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SOFTRSTZ, 0, 0, 0); +	if (hdmi_wait_for_bit_change(base, HDMI_CORE_I2CM_SOFTRSTZ, +				0, 0, 1) != 1) +		DSSERR("HDMI I2CM reset failed\n"); + +	/* Standard (0) or Fast (1) Mode */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_DIV, 0, 3, 3); + +	/* Standard Mode SCL High counter */ +	v = DIV_ROUND_UP_ULL(ss_scl_high * sfr, 1000000); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SS_SCL_HCNT_1_ADDR, +			(v >> 8) & 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SS_SCL_HCNT_0_ADDR, +			v & 0xff, 7, 0); + +	/* Standard Mode SCL Low counter */ +	v = DIV_ROUND_UP_ULL(ss_scl_low * sfr, 1000000); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SS_SCL_LCNT_1_ADDR, +			(v >> 8) & 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SS_SCL_LCNT_0_ADDR, +			v & 0xff, 7, 0); + +	/* Fast Mode SCL High Counter */ +	v = DIV_ROUND_UP_ULL(fs_scl_high * sfr, 1000000); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_FS_SCL_HCNT_1_ADDR, +			(v >> 8) & 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_FS_SCL_HCNT_0_ADDR, +			v & 0xff, 7, 0); + +	/* Fast Mode SCL Low Counter */ +	v = DIV_ROUND_UP_ULL(fs_scl_low * sfr, 1000000); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_FS_SCL_LCNT_1_ADDR, +			(v >> 8) & 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR, +			v & 0xff, 7, 0); + +	/* SDA Hold Time */ +	v = DIV_ROUND_UP_ULL(sda_hold * sfr, 1000000); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SDA_HOLD_ADDR, v & 0xff, 7, 0); + +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SLAVE, 0x50, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SEGADDR, 0x30, 6, 0); + +	/* NACK_POL to high */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 7, 7); + +	/* NACK_MASK to unmasked */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x0, 6, 6); + +	/* ARBITRATION_POL to high */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 3, 3); + +	/* ARBITRATION_MASK to unmasked */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x0, 2, 2); + +	/* DONE_POL to high */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 3, 3); + +	/* DONE_MASK to unmasked */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x0, 2, 2); +} + +static void hdmi_core_ddc_uninit(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; + +	/* Mask I2C interrupts */ +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 6, 6); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 2, 2); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2); +} + +static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext) +{ +	void __iomem *base = core->base; +	u8 cur_addr; +	char checksum = 0; +	const int retries = 1000; +	u8 seg_ptr = ext / 2; +	u8 edidbase = ((ext % 2) * 0x80); + +	REG_FLD_MOD(base, HDMI_CORE_I2CM_SEGPTR, seg_ptr, 7, 0); + +	/* +	 * TODO: We use polling here, although we probably should use proper +	 * interrupts. +	 */ +	for (cur_addr = 0; cur_addr < 128; ++cur_addr) { +		int i; + +		/* clear ERROR and DONE */ +		REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0x3, 1, 0); + +		REG_FLD_MOD(base, HDMI_CORE_I2CM_ADDRESS, +				edidbase + cur_addr, 7, 0); + +		if (seg_ptr) +			REG_FLD_MOD(base, HDMI_CORE_I2CM_OPERATION, 1, 1, 1); +		else +			REG_FLD_MOD(base, HDMI_CORE_I2CM_OPERATION, 1, 0, 0); + +		for (i = 0; i < retries; ++i) { +			u32 stat; + +			stat = REG_GET(base, HDMI_CORE_IH_I2CM_STAT0, 1, 0); + +			/* I2CM_ERROR */ +			if (stat & 1) { +				DSSERR("HDMI I2C Master Error\n"); +				return -EIO; +			} + +			/* I2CM_DONE */ +			if (stat & (1 << 1)) +				break; + +			usleep_range(250, 1000); +		} + +		if (i == retries) { +			DSSERR("HDMI I2C timeout reading EDID\n"); +			return -EIO; +		} + +		pedid[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0); +		checksum += pedid[cur_addr]; +	} + +	return 0; + +} + +int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len) +{ +	int r, n, i; +	int max_ext_blocks = (len / 128) - 1; + +	if (len < 128) +		return -EINVAL; + +	hdmi_core_ddc_init(core); + +	r = hdmi_core_ddc_edid(core, edid, 0); +	if (r) +		goto out; + +	n = edid[0x7e]; + +	if (n > max_ext_blocks) +		n = max_ext_blocks; + +	for (i = 1; i <= n; i++) { +		r = hdmi_core_ddc_edid(core, edid + i * EDID_LENGTH, i); +		if (r) +			goto out; +	} + +out: +	hdmi_core_ddc_uninit(core); + +	return r ? r : len; +} + +void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s) +{ + +#define DUMPCORE(r) seq_printf(s, "%-35s %08x\n", #r,\ +		hdmi_read_reg(core->base, r)) + +	DUMPCORE(HDMI_CORE_FC_INVIDCONF); +	DUMPCORE(HDMI_CORE_FC_INHACTIV0); +	DUMPCORE(HDMI_CORE_FC_INHACTIV1); +	DUMPCORE(HDMI_CORE_FC_INHBLANK0); +	DUMPCORE(HDMI_CORE_FC_INHBLANK1); +	DUMPCORE(HDMI_CORE_FC_INVACTIV0); +	DUMPCORE(HDMI_CORE_FC_INVACTIV1); +	DUMPCORE(HDMI_CORE_FC_INVBLANK); +	DUMPCORE(HDMI_CORE_FC_HSYNCINDELAY0); +	DUMPCORE(HDMI_CORE_FC_HSYNCINDELAY1); +	DUMPCORE(HDMI_CORE_FC_HSYNCINWIDTH0); +	DUMPCORE(HDMI_CORE_FC_HSYNCINWIDTH1); +	DUMPCORE(HDMI_CORE_FC_VSYNCINDELAY); +	DUMPCORE(HDMI_CORE_FC_VSYNCINWIDTH); +	DUMPCORE(HDMI_CORE_FC_CTRLDUR); +	DUMPCORE(HDMI_CORE_FC_EXCTRLDUR); +	DUMPCORE(HDMI_CORE_FC_EXCTRLSPAC); +	DUMPCORE(HDMI_CORE_FC_CH0PREAM); +	DUMPCORE(HDMI_CORE_FC_CH1PREAM); +	DUMPCORE(HDMI_CORE_FC_CH2PREAM); +	DUMPCORE(HDMI_CORE_FC_AVICONF0); +	DUMPCORE(HDMI_CORE_FC_AVICONF1); +	DUMPCORE(HDMI_CORE_FC_AVICONF2); +	DUMPCORE(HDMI_CORE_FC_AVIVID); +	DUMPCORE(HDMI_CORE_FC_PRCONF); + +	DUMPCORE(HDMI_CORE_MC_CLKDIS); +	DUMPCORE(HDMI_CORE_MC_SWRSTZREQ); +	DUMPCORE(HDMI_CORE_MC_FLOWCTRL); +	DUMPCORE(HDMI_CORE_MC_PHYRSTZ); +	DUMPCORE(HDMI_CORE_MC_LOCKONCLOCK); + +	DUMPCORE(HDMI_CORE_I2CM_SLAVE); +	DUMPCORE(HDMI_CORE_I2CM_ADDRESS); +	DUMPCORE(HDMI_CORE_I2CM_DATAO); +	DUMPCORE(HDMI_CORE_I2CM_DATAI); +	DUMPCORE(HDMI_CORE_I2CM_OPERATION); +	DUMPCORE(HDMI_CORE_I2CM_INT); +	DUMPCORE(HDMI_CORE_I2CM_CTLINT); +	DUMPCORE(HDMI_CORE_I2CM_DIV); +	DUMPCORE(HDMI_CORE_I2CM_SEGADDR); +	DUMPCORE(HDMI_CORE_I2CM_SOFTRSTZ); +	DUMPCORE(HDMI_CORE_I2CM_SEGPTR); +	DUMPCORE(HDMI_CORE_I2CM_SS_SCL_HCNT_1_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_SS_SCL_HCNT_0_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_SS_SCL_LCNT_1_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_SS_SCL_LCNT_0_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_HCNT_1_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_HCNT_0_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_1_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR); +	DUMPCORE(HDMI_CORE_I2CM_SDA_HOLD_ADDR); +} + +static void hdmi_core_init(struct hdmi_core_vid_config *video_cfg, +			struct hdmi_core_infoframe_avi *avi_cfg, +			struct hdmi_config *cfg) +{ +	DSSDBG("hdmi_core_init\n"); + +	/* video core */ +	video_cfg->data_enable_pol = 1; /* It is always 1*/ +	video_cfg->v_fc_config.timings.hsync_level = cfg->timings.hsync_level; +	video_cfg->v_fc_config.timings.x_res = cfg->timings.x_res; +	video_cfg->v_fc_config.timings.hsw = cfg->timings.hsw - 1; +	video_cfg->v_fc_config.timings.hbp = cfg->timings.hbp; +	video_cfg->v_fc_config.timings.hfp = cfg->timings.hfp; +	video_cfg->hblank = cfg->timings.hfp + +				cfg->timings.hbp + cfg->timings.hsw - 1; +	video_cfg->v_fc_config.timings.vsync_level = cfg->timings.vsync_level; +	video_cfg->v_fc_config.timings.y_res = cfg->timings.y_res; +	video_cfg->v_fc_config.timings.vsw = cfg->timings.vsw; +	video_cfg->v_fc_config.timings.vfp = cfg->timings.vfp; +	video_cfg->v_fc_config.timings.vbp = cfg->timings.vbp; +	video_cfg->vblank_osc = 0; /* Always 0 - need to confirm */ +	video_cfg->vblank = cfg->timings.vsw + +				cfg->timings.vfp + cfg->timings.vbp; +	video_cfg->v_fc_config.cm.mode = cfg->cm.mode; +	video_cfg->v_fc_config.timings.interlace = cfg->timings.interlace; + +	/* 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; +} + +/* DSS_HDMI_CORE_VIDEO_CONFIG */ +static void hdmi_core_video_config(struct hdmi_core_data *core, +			struct hdmi_core_vid_config *cfg) +{ +	void __iomem *base = core->base; +	unsigned char r = 0; +	bool vsync_pol, hsync_pol; + +	vsync_pol = +		cfg->v_fc_config.timings.vsync_level == OMAPDSS_SIG_ACTIVE_HIGH; +	hsync_pol = +		cfg->v_fc_config.timings.hsync_level == OMAPDSS_SIG_ACTIVE_HIGH; + +	/* Set hsync, vsync and data-enable polarity  */ +	r = hdmi_read_reg(base, HDMI_CORE_FC_INVIDCONF); +	r = FLD_MOD(r, vsync_pol, 6, 6); +	r = FLD_MOD(r, hsync_pol, 5, 5); +	r = FLD_MOD(r, cfg->data_enable_pol, 4, 4); +	r = FLD_MOD(r, cfg->vblank_osc, 1, 1); +	r = FLD_MOD(r, cfg->v_fc_config.timings.interlace, 0, 0); +	hdmi_write_reg(base, HDMI_CORE_FC_INVIDCONF, r); + +	/* set x resolution */ +	REG_FLD_MOD(base, HDMI_CORE_FC_INHACTIV1, +			cfg->v_fc_config.timings.x_res >> 8, 4, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_INHACTIV0, +			cfg->v_fc_config.timings.x_res & 0xFF, 7, 0); + +	/* set y resolution */ +	REG_FLD_MOD(base, HDMI_CORE_FC_INVACTIV1, +			cfg->v_fc_config.timings.y_res >> 8, 4, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_INVACTIV0, +			cfg->v_fc_config.timings.y_res & 0xFF, 7, 0); + +	/* set horizontal blanking pixels */ +	REG_FLD_MOD(base, HDMI_CORE_FC_INHBLANK1, cfg->hblank >> 8, 4, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_INHBLANK0, cfg->hblank & 0xFF, 7, 0); + +	/* set vertial blanking pixels */ +	REG_FLD_MOD(base, HDMI_CORE_FC_INVBLANK, cfg->vblank, 7, 0); + +	/* set horizontal sync offset */ +	REG_FLD_MOD(base, HDMI_CORE_FC_HSYNCINDELAY1, +			cfg->v_fc_config.timings.hfp >> 8, 4, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_HSYNCINDELAY0, +			cfg->v_fc_config.timings.hfp & 0xFF, 7, 0); + +	/* set vertical sync offset */ +	REG_FLD_MOD(base, HDMI_CORE_FC_VSYNCINDELAY, +			cfg->v_fc_config.timings.vfp, 7, 0); + +	/* set horizontal sync pulse width */ +	REG_FLD_MOD(base, HDMI_CORE_FC_HSYNCINWIDTH1, +			(cfg->v_fc_config.timings.hsw >> 8), 1, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_HSYNCINWIDTH0, +			cfg->v_fc_config.timings.hsw & 0xFF, 7, 0); + +	/*  set vertical sync pulse width */ +	REG_FLD_MOD(base, HDMI_CORE_FC_VSYNCINWIDTH, +			cfg->v_fc_config.timings.vsw, 5, 0); + +	/* select DVI mode */ +	REG_FLD_MOD(base, HDMI_CORE_FC_INVIDCONF, +			cfg->v_fc_config.cm.mode, 3, 3); +} + +static void hdmi_core_config_video_packetizer(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; +	int clr_depth = 0;	/* 24 bit color depth */ + +	/* COLOR_DEPTH */ +	REG_FLD_MOD(base, HDMI_CORE_VP_PR_CD, clr_depth, 7, 4); +	/* BYPASS_EN */ +	REG_FLD_MOD(base, HDMI_CORE_VP_CONF, clr_depth ? 0 : 1, 6, 6); +	/* PP_EN */ +	REG_FLD_MOD(base, HDMI_CORE_VP_CONF, clr_depth ? 1 : 0, 5, 5); +	/* YCC422_EN */ +	REG_FLD_MOD(base, HDMI_CORE_VP_CONF, 0, 3, 3); +	/* PP_STUFFING */ +	REG_FLD_MOD(base, HDMI_CORE_VP_STUFF, clr_depth ? 1 : 0, 1, 1); +	/* YCC422_STUFFING */ +	REG_FLD_MOD(base, HDMI_CORE_VP_STUFF, 1, 2, 2); +	/* OUTPUT_SELECTOR */ +	REG_FLD_MOD(base, HDMI_CORE_VP_CONF, clr_depth ? 0 : 2, 1, 0); +} + +static void hdmi_core_config_csc(struct hdmi_core_data *core) +{ +	int clr_depth = 0;	/* 24 bit color depth */ + +	/* CSC_COLORDEPTH */ +	REG_FLD_MOD(core->base, HDMI_CORE_CSC_SCALE, clr_depth, 7, 4); +} + +static void hdmi_core_config_video_sampler(struct hdmi_core_data *core) +{ +	int video_mapping = 1;	/* for 24 bit color depth */ + +	/* VIDEO_MAPPING */ +	REG_FLD_MOD(core->base, HDMI_CORE_TX_INVID0, video_mapping, 4, 0); +} + +static void hdmi_core_aux_infoframe_avi_config(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; +	struct hdmi_core_infoframe_avi avi = core->avi_cfg; + +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF0, avi.db1_format, 1, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF0, avi.db1_active_info, 6, 6); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF0, avi.db1_bar_info_dv, 3, 2); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF0, avi.db1_scan_info, 5, 4); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF1, avi.db2_colorimetry, 7, 6); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF1, avi.db2_aspect_ratio, 5, 4); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF1, avi.db2_active_fmt_ar, 3, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF2, avi.db3_itc, 7, 7); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF2, avi.db3_ec, 6, 4); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF2, avi.db3_q_range, 3, 2); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVICONF2, avi.db3_nup_scaling, 1, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_AVIVID, avi.db4_videocode, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_PRCONF, avi.db5_pixel_repeat, 3, 0); +} + +static void hdmi_core_csc_config(struct hdmi_core_data *core, +		struct csc_table csc_coeff) +{ +	void __iomem *base = core->base; + +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A1_MSB, csc_coeff.a1 >> 8 , 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A1_LSB, csc_coeff.a1, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A2_MSB, csc_coeff.a2 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A2_LSB, csc_coeff.a2, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A3_MSB, csc_coeff.a3 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A3_LSB, csc_coeff.a3, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A4_MSB, csc_coeff.a4 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_A4_LSB, csc_coeff.a4, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B1_MSB, csc_coeff.b1 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B1_LSB, csc_coeff.b1, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B2_MSB, csc_coeff.b2 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B2_LSB, csc_coeff.b2, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B3_MSB, csc_coeff.b3 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B3_LSB, csc_coeff.b3, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B4_MSB, csc_coeff.b4 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_B4_LSB, csc_coeff.b4, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C1_MSB, csc_coeff.c1 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C1_LSB, csc_coeff.c1, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C2_MSB, csc_coeff.c2 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C2_LSB, csc_coeff.c2, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C3_MSB, csc_coeff.c3 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C3_LSB, csc_coeff.c3, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C4_MSB, csc_coeff.c4 >> 8, 6, 0); +	REG_FLD_MOD(base, HDMI_CORE_CSC_COEF_C4_LSB, csc_coeff.c4, 7, 0); + +	REG_FLD_MOD(base, HDMI_CORE_MC_FLOWCTRL, 0x1, 0, 0); +} + +static void hdmi_core_configure_range(struct hdmi_core_data *core) +{ +	struct csc_table csc_coeff = { 0 }; + +	/* support limited range with 24 bit color depth for now */ +	csc_coeff = csc_table_deepcolor[0]; +	core->avi_cfg.db3_q_range = HDMI_INFOFRAME_AVI_DB3Q_LR; + +	hdmi_core_csc_config(core, csc_coeff); +	hdmi_core_aux_infoframe_avi_config(core); +} + +static void hdmi_core_enable_video_path(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; + +	DSSDBG("hdmi_core_enable_video_path\n"); + +	REG_FLD_MOD(base, HDMI_CORE_FC_CTRLDUR, 0x0C, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_EXCTRLDUR, 0x20, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_EXCTRLSPAC, 0x01, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_CH0PREAM, 0x0B, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_CH1PREAM, 0x16, 5, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_CH2PREAM, 0x21, 5, 0); +	REG_FLD_MOD(base, HDMI_CORE_MC_CLKDIS, 0x00, 0, 0); +	REG_FLD_MOD(base, HDMI_CORE_MC_CLKDIS, 0x00, 1, 1); +} + +static void hdmi_core_mask_interrupts(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; + +	/* Master IRQ mask */ +	REG_FLD_MOD(base, HDMI_CORE_IH_MUTE, 0x3, 1, 0); + +	/* Mask all the interrupts in HDMI core */ + +	REG_FLD_MOD(base, HDMI_CORE_VP_MASK, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_MASK0, 0xe7, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_MASK1, 0xfb, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_MASK2, 0x3, 1, 0); + +	REG_FLD_MOD(base, HDMI_CORE_AUD_INT, 0x3, 3, 2); +	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_MASK, 0x3, 1, 0); + +	REG_FLD_MOD(base, HDMI_CORE_CEC_MASK, 0x7f, 6, 0); + +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 6, 6); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_CTLINT, 0x1, 2, 2); +	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2); + +	REG_FLD_MOD(base, HDMI_CORE_PHY_MASK0, 0xf3, 7, 0); + +	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0); + +	/* Clear all the current interrupt bits */ + +	REG_FLD_MOD(base, HDMI_CORE_IH_VP_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT0, 0xe7, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT1, 0xfb, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT2, 0x3, 1, 0); + +	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0x7, 2, 0); + +	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0x7f, 6, 0); + +	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0x3, 1, 0); + +	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0); +} + +static void hdmi_core_enable_interrupts(struct hdmi_core_data *core) +{ +	/* Unmute interrupts */ +	REG_FLD_MOD(core->base, HDMI_CORE_IH_MUTE, 0x0, 1, 0); +} + +int hdmi5_core_handle_irqs(struct hdmi_core_data *core) +{ +	void __iomem *base = core->base; + +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT1, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_FC_STAT2, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_AS_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_PHY_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_I2CM_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_CEC_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_VP_STAT0, 0xff, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_IH_I2CMPHY_STAT0, 0xff, 7, 0); + +	return 0; +} + +void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +		struct hdmi_config *cfg) +{ +	struct omap_video_timings video_timing; +	struct hdmi_video_format video_format; +	struct hdmi_core_vid_config v_core_cfg; +	struct hdmi_core_infoframe_avi *avi_cfg = &core->avi_cfg; + +	hdmi_core_mask_interrupts(core); + +	hdmi_core_init(&v_core_cfg, avi_cfg, cfg); + +	hdmi_wp_init_vid_fmt_timings(&video_format, &video_timing, cfg); + +	hdmi_wp_video_config_timing(wp, &video_timing); + +	/* video config */ +	video_format.packing_mode = HDMI_PACK_24b_RGB_YUV444_YUV422; + +	hdmi_wp_video_config_format(wp, &video_format); + +	hdmi_wp_video_config_interface(wp, &video_timing); + +	hdmi_core_configure_range(core); + +	/* +	 * configure core video part, set software reset in the core +	 */ +	v_core_cfg.packet_mode = HDMI_PACKETMODE24BITPERPIXEL; + +	hdmi_core_video_config(core, &v_core_cfg); + +	hdmi_core_config_video_packetizer(core); +	hdmi_core_config_csc(core); +	hdmi_core_config_video_sampler(core); + +	/* +	 * 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(core); + +	hdmi_core_enable_video_path(core); + +	hdmi_core_enable_interrupts(core); +} + + +#if defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) + +static void hdmi5_core_audio_config(struct hdmi_core_data *core, +			struct hdmi_core_audio_config *cfg) +{ +	void __iomem *base = core->base; +	u8 val; + +	/* Mute audio before configuring */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCONF, 0xf, 7, 4); + +	/* Set the N parameter */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_N1, cfg->n, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_AUD_N2, cfg->n >> 8, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_AUD_N3, cfg->n >> 16, 3, 0); + +	/* +	 * CTS manual mode. Automatic mode is not supported when using audio +	 * parallel interface. +	 */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_CTS3, 1, 4, 4); +	REG_FLD_MOD(base, HDMI_CORE_AUD_CTS1, cfg->cts, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_AUD_CTS2, cfg->cts >> 8, 7, 0); +	REG_FLD_MOD(base, HDMI_CORE_AUD_CTS3, cfg->cts >> 16, 3, 0); + +	/* Layout of Audio Sample Packets: 2-channel or multichannels */ +	if (cfg->layout == HDMI_AUDIO_LAYOUT_2CH) +		REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCONF, 0, 0, 0); +	else +		REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCONF, 1, 0, 0); + +	/* Configure IEC-609580 Validity bits */ +	/* Channel 0 is valid */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, 0, 0, 0); +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, 0, 4, 4); + +	if (cfg->layout == HDMI_AUDIO_LAYOUT_2CH) +		val = 1; +	else +		val = 0; + +	/* Channels 1, 2 setting */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 1, 1); +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 5, 5); +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 2, 2); +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 6, 6); +	/* Channel 3 setting */ +	if (cfg->layout == HDMI_AUDIO_LAYOUT_6CH) +		val = 1; +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 3, 3); +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSV, val, 7, 7); + +	/* Configure IEC-60958 User bits */ +	/* TODO: should be set by user. */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSU, 0, 7, 0); + +	/* Configure IEC-60958 Channel Status word */ +	/* CGMSA */ +	val = cfg->iec60958_cfg->status[5] & IEC958_AES5_CON_CGMSA; +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(0), val, 5, 4); + +	/* Copyright */ +	val = (cfg->iec60958_cfg->status[0] & +			IEC958_AES0_CON_NOT_COPYRIGHT) >> 2; +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(0), val, 0, 0); + +	/* Category */ +	hdmi_write_reg(base, HDMI_CORE_FC_AUDSCHNLS(1), +		cfg->iec60958_cfg->status[1]); + +	/* PCM audio mode */ +	val = (cfg->iec60958_cfg->status[0] & IEC958_AES0_CON_MODE) >> 6; +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(2), val, 6, 4); + +	/* Source number */ +	val = cfg->iec60958_cfg->status[2] & IEC958_AES2_CON_SOURCE; +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(2), val, 3, 4); + +	/* Channel number right 0  */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(3), 2, 3, 0); +	/* Channel number right 1*/ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(3), 4, 7, 4); +	/* Channel number right 2  */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(4), 6, 3, 0); +	/* Channel number right 3*/ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(4), 8, 7, 4); +	/* Channel number left 0  */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(5), 1, 3, 0); +	/* Channel number left 1*/ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(5), 3, 7, 4); +	/* Channel number left 2  */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(6), 5, 3, 0); +	/* Channel number left 3*/ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCHNLS(6), 7, 7, 4); + +	/* Clock accuracy and sample rate */ +	hdmi_write_reg(base, HDMI_CORE_FC_AUDSCHNLS(7), +		cfg->iec60958_cfg->status[3]); + +	/* Original sample rate and word length */ +	hdmi_write_reg(base, HDMI_CORE_FC_AUDSCHNLS(8), +		cfg->iec60958_cfg->status[4]); + +	/* Enable FIFO empty and full interrupts */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_INT, 3, 3, 2); + +	/* Configure GPA */ +	/* select HBR/SPDIF interfaces */ +	if (cfg->layout == HDMI_AUDIO_LAYOUT_2CH) { +		/* select HBR/SPDIF interfaces */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_CONF0, 0, 5, 5); +		/* enable two channels in GPA */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_GP_CONF1, 3, 7, 0); +	} else if (cfg->layout == HDMI_AUDIO_LAYOUT_6CH) { +		/* select HBR/SPDIF interfaces */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_CONF0, 0, 5, 5); +		/* enable six channels in GPA */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_GP_CONF1, 0x3F, 7, 0); +	} else { +		/* select HBR/SPDIF interfaces */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_CONF0, 0, 5, 5); +		/* enable eight channels in GPA */ +		REG_FLD_MOD(base, HDMI_CORE_AUD_GP_CONF1, 0xFF, 7, 0); +	} + +	/* disable HBR */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_CONF2, 0, 0, 0); +	/* enable PCUV */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_CONF2, 1, 1, 1); +	/* enable GPA FIFO full and empty mask */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_MASK, 3, 1, 0); +	/* set polarity of GPA FIFO empty interrupts */ +	REG_FLD_MOD(base, HDMI_CORE_AUD_GP_POL, 1, 0, 0); + +	/* unmute audio */ +	REG_FLD_MOD(base, HDMI_CORE_FC_AUDSCONF, 0, 7, 4); +} + +static void hdmi5_core_audio_infoframe_cfg(struct hdmi_core_data *core, +	 struct snd_cea_861_aud_if *info_aud) +{ +	void __iomem *base = core->base; + +	/* channel count and coding type fields in AUDICONF0 are swapped */ +	hdmi_write_reg(base, HDMI_CORE_FC_AUDICONF0, +		(info_aud->db1_ct_cc & CEA861_AUDIO_INFOFRAME_DB1CC) << 4 | +		(info_aud->db1_ct_cc & CEA861_AUDIO_INFOFRAME_DB1CT) >> 4); + +	hdmi_write_reg(base, HDMI_CORE_FC_AUDICONF1, info_aud->db2_sf_ss); +	hdmi_write_reg(base, HDMI_CORE_FC_AUDICONF2, info_aud->db4_ca); +	hdmi_write_reg(base, HDMI_CORE_FC_AUDICONF3, info_aud->db5_dminh_lsv); +} + +int hdmi5_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +			struct omap_dss_audio *audio, u32 pclk) +{ +	struct hdmi_audio_format audio_format; +	struct hdmi_audio_dma audio_dma; +	struct hdmi_core_audio_config core_cfg; +	int err, n, cts, channel_count; +	unsigned int fs_nr; +	bool word_length_16b = false; + +	if (!audio || !audio->iec || !audio->cea || !core) +		return -EINVAL; + +	core_cfg.iec60958_cfg = audio->iec; + +	if (!(audio->iec->status[4] & IEC958_AES4_CON_MAX_WORDLEN_24) && +		(audio->iec->status[4] & IEC958_AES4_CON_WORDLEN_20_16)) +			word_length_16b = true; + +	/* only 16-bit word length supported atm */ +	if (!word_length_16b) +		return -EINVAL; + +	switch (audio->iec->status[3] & IEC958_AES3_CON_FS) { +	case IEC958_AES3_CON_FS_32000: +		fs_nr = 32000; +		break; +	case IEC958_AES3_CON_FS_44100: +		fs_nr = 44100; +		break; +	case IEC958_AES3_CON_FS_48000: +		fs_nr = 48000; +		break; +	case IEC958_AES3_CON_FS_88200: +		fs_nr = 88200; +		break; +	case IEC958_AES3_CON_FS_96000: +		fs_nr = 96000; +		break; +	case IEC958_AES3_CON_FS_176400: +		fs_nr = 176400; +		break; +	case IEC958_AES3_CON_FS_192000: +		fs_nr = 192000; +		break; +	default: +		return -EINVAL; +	} + +	err = hdmi_compute_acr(pclk, fs_nr, &n, &cts); +	core_cfg.n = n; +	core_cfg.cts = cts; + +	/* Audio channels settings */ +	channel_count = (audio->cea->db1_ct_cc & CEA861_AUDIO_INFOFRAME_DB1CC) +				+ 1; + +	if (channel_count == 2) +		core_cfg.layout = HDMI_AUDIO_LAYOUT_2CH; +	else if (channel_count == 6) +		core_cfg.layout = HDMI_AUDIO_LAYOUT_6CH; +	else +		core_cfg.layout = HDMI_AUDIO_LAYOUT_8CH; + +	/* DMA settings */ +	if (word_length_16b) +		audio_dma.transfer_size = 0x10; +	else +		audio_dma.transfer_size = 0x20; +	audio_dma.block_size = 0xC0; +	audio_dma.mode = HDMI_AUDIO_TRANSF_DMA; +	audio_dma.fifo_threshold = 0x20; /* in number of samples */ + +	/* audio FIFO format settings for 16-bit samples*/ +	audio_format.samples_per_word = HDMI_AUDIO_ONEWORD_TWOSAMPLES; +	audio_format.sample_size = HDMI_AUDIO_SAMPLE_16BITS; +	audio_format.justification = HDMI_AUDIO_JUSTIFY_LEFT; + +	/* only LPCM atm */ +	audio_format.type = HDMI_AUDIO_TYPE_LPCM; + +	/* disable start/stop signals of IEC 60958 blocks */ +	audio_format.en_sig_blk_strt_end = HDMI_AUDIO_BLOCK_SIG_STARTEND_ON; + +	/* configure DMA and audio FIFO format*/ +	hdmi_wp_audio_config_dma(wp, &audio_dma); +	hdmi_wp_audio_config_format(wp, &audio_format); + +	/* configure the core */ +	hdmi5_core_audio_config(core, &core_cfg); + +	/* configure CEA 861 audio infoframe */ +	hdmi5_core_audio_infoframe_cfg(core, audio->cea); + +	return 0; +} +#endif + +int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core) +{ +	struct resource *res; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); +	if (!res) { +		DSSERR("can't get CORE IORESOURCE_MEM HDMI\n"); +		return -EINVAL; +	} + +	core->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(core->base)) { +		DSSERR("can't ioremap HDMI core\n"); +		return PTR_ERR(core->base); +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi5_core.h b/drivers/video/fbdev/omap2/dss/hdmi5_core.h new file mode 100644 index 00000000000..ce7e9f376f0 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi5_core.h @@ -0,0 +1,306 @@ +/* + * HDMI driver definition for TI OMAP5 processors. + * + * Copyright (C) 2011-2012 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 _HDMI5_CORE_H_ +#define _HDMI5_CORE_H_ + +#include "hdmi.h" + +/* HDMI IP Core System */ + +/* HDMI Identification */ +#define HDMI_CORE_DESIGN_ID			0x00000 +#define HDMI_CORE_REVISION_ID			0x00004 +#define HDMI_CORE_PRODUCT_ID0			0x00008 +#define HDMI_CORE_PRODUCT_ID1			0x0000C +#define HDMI_CORE_CONFIG0_ID			0x00010 +#define HDMI_CORE_CONFIG1_ID			0x00014 +#define HDMI_CORE_CONFIG2_ID			0x00018 +#define HDMI_CORE_CONFIG3_ID			0x0001C + +/* HDMI Interrupt */ +#define HDMI_CORE_IH_FC_STAT0			0x00400 +#define HDMI_CORE_IH_FC_STAT1			0x00404 +#define HDMI_CORE_IH_FC_STAT2			0x00408 +#define HDMI_CORE_IH_AS_STAT0			0x0040C +#define HDMI_CORE_IH_PHY_STAT0			0x00410 +#define HDMI_CORE_IH_I2CM_STAT0			0x00414 +#define HDMI_CORE_IH_CEC_STAT0			0x00418 +#define HDMI_CORE_IH_VP_STAT0			0x0041C +#define HDMI_CORE_IH_I2CMPHY_STAT0		0x00420 +#define HDMI_CORE_IH_MUTE			0x007FC + +/* HDMI Video Sampler */ +#define HDMI_CORE_TX_INVID0			0x00800 +#define HDMI_CORE_TX_INSTUFFING			0x00804 +#define HDMI_CORE_TX_RGYDATA0			0x00808 +#define HDMI_CORE_TX_RGYDATA1			0x0080C +#define HDMI_CORE_TX_RCRDATA0			0x00810 +#define HDMI_CORE_TX_RCRDATA1			0x00814 +#define HDMI_CORE_TX_BCBDATA0			0x00818 +#define HDMI_CORE_TX_BCBDATA1			0x0081C + +/* HDMI Video Packetizer */ +#define HDMI_CORE_VP_STATUS			0x02000 +#define HDMI_CORE_VP_PR_CD			0x02004 +#define HDMI_CORE_VP_STUFF			0x02008 +#define HDMI_CORE_VP_REMAP			0x0200C +#define HDMI_CORE_VP_CONF			0x02010 +#define HDMI_CORE_VP_STAT			0x02014 +#define HDMI_CORE_VP_INT			0x02018 +#define HDMI_CORE_VP_MASK			0x0201C +#define HDMI_CORE_VP_POL			0x02020 + +/* Frame Composer */ +#define HDMI_CORE_FC_INVIDCONF			0x04000 +#define HDMI_CORE_FC_INHACTIV0			0x04004 +#define HDMI_CORE_FC_INHACTIV1			0x04008 +#define HDMI_CORE_FC_INHBLANK0			0x0400C +#define HDMI_CORE_FC_INHBLANK1			0x04010 +#define HDMI_CORE_FC_INVACTIV0			0x04014 +#define HDMI_CORE_FC_INVACTIV1			0x04018 +#define HDMI_CORE_FC_INVBLANK			0x0401C +#define HDMI_CORE_FC_HSYNCINDELAY0		0x04020 +#define HDMI_CORE_FC_HSYNCINDELAY1		0x04024 +#define HDMI_CORE_FC_HSYNCINWIDTH0		0x04028 +#define HDMI_CORE_FC_HSYNCINWIDTH1		0x0402C +#define HDMI_CORE_FC_VSYNCINDELAY		0x04030 +#define HDMI_CORE_FC_VSYNCINWIDTH		0x04034 +#define HDMI_CORE_FC_INFREQ0			0x04038 +#define HDMI_CORE_FC_INFREQ1			0x0403C +#define HDMI_CORE_FC_INFREQ2			0x04040 +#define HDMI_CORE_FC_CTRLDUR			0x04044 +#define HDMI_CORE_FC_EXCTRLDUR			0x04048 +#define HDMI_CORE_FC_EXCTRLSPAC			0x0404C +#define HDMI_CORE_FC_CH0PREAM			0x04050 +#define HDMI_CORE_FC_CH1PREAM			0x04054 +#define HDMI_CORE_FC_CH2PREAM			0x04058 +#define HDMI_CORE_FC_AVICONF3			0x0405C +#define HDMI_CORE_FC_GCP			0x04060 +#define HDMI_CORE_FC_AVICONF0			0x04064 +#define HDMI_CORE_FC_AVICONF1			0x04068 +#define HDMI_CORE_FC_AVICONF2			0x0406C +#define HDMI_CORE_FC_AVIVID			0x04070 +#define HDMI_CORE_FC_AVIETB0			0x04074 +#define HDMI_CORE_FC_AVIETB1			0x04078 +#define HDMI_CORE_FC_AVISBB0			0x0407C +#define HDMI_CORE_FC_AVISBB1			0x04080 +#define HDMI_CORE_FC_AVIELB0			0x04084 +#define HDMI_CORE_FC_AVIELB1			0x04088 +#define HDMI_CORE_FC_AVISRB0			0x0408C +#define HDMI_CORE_FC_AVISRB1			0x04090 +#define HDMI_CORE_FC_AUDICONF0			0x04094 +#define HDMI_CORE_FC_AUDICONF1			0x04098 +#define HDMI_CORE_FC_AUDICONF2			0x0409C +#define HDMI_CORE_FC_AUDICONF3			0x040A0 +#define HDMI_CORE_FC_VSDIEEEID0			0x040A4 +#define HDMI_CORE_FC_VSDSIZE			0x040A8 +#define HDMI_CORE_FC_VSDIEEEID1			0x040C0 +#define HDMI_CORE_FC_VSDIEEEID2			0x040C4 +#define HDMI_CORE_FC_VSDPAYLOAD(n)		(n * 4 + 0x040C8) +#define HDMI_CORE_FC_SPDVENDORNAME(n)		(n * 4 + 0x04128) +#define HDMI_CORE_FC_SPDPRODUCTNAME(n)		(n * 4 + 0x04148) +#define HDMI_CORE_FC_SPDDEVICEINF		0x04188 +#define HDMI_CORE_FC_AUDSCONF			0x0418C +#define HDMI_CORE_FC_AUDSSTAT			0x04190 +#define HDMI_CORE_FC_AUDSV			0x04194 +#define HDMI_CORE_FC_AUDSU			0x04198 +#define HDMI_CORE_FC_AUDSCHNLS(n)		(n * 4 + 0x0419C) +#define HDMI_CORE_FC_CTRLQHIGH			0x041CC +#define HDMI_CORE_FC_CTRLQLOW			0x041D0 +#define HDMI_CORE_FC_ACP0			0x041D4 +#define HDMI_CORE_FC_ACP(n)			((16-n) * 4 + 0x04208) +#define HDMI_CORE_FC_ISCR1_0			0x04248 +#define HDMI_CORE_FC_ISCR1(n)			((16-n) * 4 + 0x0424C) +#define HDMI_CORE_FC_ISCR2(n)			((15-n) * 4 + 0x0428C) +#define HDMI_CORE_FC_DATAUTO0			0x042CC +#define HDMI_CORE_FC_DATAUTO1			0x042D0 +#define HDMI_CORE_FC_DATAUTO2			0x042D4 +#define HDMI_CORE_FC_DATMAN			0x042D8 +#define HDMI_CORE_FC_DATAUTO3			0x042DC +#define HDMI_CORE_FC_RDRB(n)			(n * 4 + 0x042E0) +#define HDMI_CORE_FC_STAT0			0x04340 +#define HDMI_CORE_FC_INT0			0x04344 +#define HDMI_CORE_FC_MASK0			0x04348 +#define HDMI_CORE_FC_POL0			0x0434C +#define HDMI_CORE_FC_STAT1			0x04350 +#define HDMI_CORE_FC_INT1			0x04354 +#define HDMI_CORE_FC_MASK1			0x04358 +#define HDMI_CORE_FC_POL1			0x0435C +#define HDMI_CORE_FC_STAT2			0x04360 +#define HDMI_CORE_FC_INT2			0x04364 +#define HDMI_CORE_FC_MASK2			0x04368 +#define HDMI_CORE_FC_POL2			0x0436C +#define HDMI_CORE_FC_PRCONF			0x04380 +#define HDMI_CORE_FC_GMD_STAT			0x04400 +#define HDMI_CORE_FC_GMD_EN			0x04404 +#define HDMI_CORE_FC_GMD_UP			0x04408 +#define HDMI_CORE_FC_GMD_CONF			0x0440C +#define HDMI_CORE_FC_GMD_HB			0x04410 +#define HDMI_CORE_FC_GMD_PB(n)			(n * 4 + 0x04414) +#define HDMI_CORE_FC_DBGFORCE			0x04800 +#define HDMI_CORE_FC_DBGAUD0CH0			0x04804 +#define HDMI_CORE_FC_DBGAUD1CH0			0x04808 +#define HDMI_CORE_FC_DBGAUD2CH0			0x0480C +#define HDMI_CORE_FC_DBGAUD0CH1			0x04810 +#define HDMI_CORE_FC_DBGAUD1CH1			0x04814 +#define HDMI_CORE_FC_DBGAUD2CH1			0x04818 +#define HDMI_CORE_FC_DBGAUD0CH2			0x0481C +#define HDMI_CORE_FC_DBGAUD1CH2			0x04820 +#define HDMI_CORE_FC_DBGAUD2CH2			0x04824 +#define HDMI_CORE_FC_DBGAUD0CH3			0x04828 +#define HDMI_CORE_FC_DBGAUD1CH3			0x0482C +#define HDMI_CORE_FC_DBGAUD2CH3			0x04830 +#define HDMI_CORE_FC_DBGAUD0CH4			0x04834 +#define HDMI_CORE_FC_DBGAUD1CH4			0x04838 +#define HDMI_CORE_FC_DBGAUD2CH4			0x0483C +#define HDMI_CORE_FC_DBGAUD0CH5			0x04840 +#define HDMI_CORE_FC_DBGAUD1CH5			0x04844 +#define HDMI_CORE_FC_DBGAUD2CH5			0x04848 +#define HDMI_CORE_FC_DBGAUD0CH6			0x0484C +#define HDMI_CORE_FC_DBGAUD1CH6			0x04850 +#define HDMI_CORE_FC_DBGAUD2CH6			0x04854 +#define HDMI_CORE_FC_DBGAUD0CH7			0x04858 +#define HDMI_CORE_FC_DBGAUD1CH7			0x0485C +#define HDMI_CORE_FC_DBGAUD2CH7			0x04860 +#define HDMI_CORE_FC_DBGTMDS0			0x04864 +#define HDMI_CORE_FC_DBGTMDS1			0x04868 +#define HDMI_CORE_FC_DBGTMDS2			0x0486C +#define HDMI_CORE_PHY_MASK0			0x0C018 +#define HDMI_CORE_PHY_I2CM_INT_ADDR		0x0C09C +#define HDMI_CORE_PHY_I2CM_CTLINT_ADDR		0x0C0A0 + +/* HDMI Audio */ +#define HDMI_CORE_AUD_CONF0			0x0C400 +#define HDMI_CORE_AUD_CONF1			0x0C404 +#define HDMI_CORE_AUD_INT			0x0C408 +#define HDMI_CORE_AUD_N1			0x0C800 +#define HDMI_CORE_AUD_N2			0x0C804 +#define HDMI_CORE_AUD_N3			0x0C808 +#define HDMI_CORE_AUD_CTS1			0x0C80C +#define HDMI_CORE_AUD_CTS2			0x0C810 +#define HDMI_CORE_AUD_CTS3			0x0C814 +#define HDMI_CORE_AUD_INCLKFS			0x0C818 +#define HDMI_CORE_AUD_CC08			0x0CC08 +#define HDMI_CORE_AUD_GP_CONF0			0x0D400 +#define HDMI_CORE_AUD_GP_CONF1			0x0D404 +#define HDMI_CORE_AUD_GP_CONF2			0x0D408 +#define HDMI_CORE_AUD_D010			0x0D010 +#define HDMI_CORE_AUD_GP_STAT			0x0D40C +#define HDMI_CORE_AUD_GP_INT			0x0D410 +#define HDMI_CORE_AUD_GP_POL			0x0D414 +#define HDMI_CORE_AUD_GP_MASK			0x0D418 + +/* HDMI Main Controller */ +#define HDMI_CORE_MC_CLKDIS			0x10004 +#define HDMI_CORE_MC_SWRSTZREQ			0x10008 +#define HDMI_CORE_MC_FLOWCTRL			0x10010 +#define HDMI_CORE_MC_PHYRSTZ			0x10014 +#define HDMI_CORE_MC_LOCKONCLOCK		0x10018 + +/* HDMI COLOR SPACE CONVERTER */ +#define HDMI_CORE_CSC_CFG			0x10400 +#define HDMI_CORE_CSC_SCALE			0x10404 +#define HDMI_CORE_CSC_COEF_A1_MSB		0x10408 +#define HDMI_CORE_CSC_COEF_A1_LSB		0x1040C +#define HDMI_CORE_CSC_COEF_A2_MSB		0x10410 +#define HDMI_CORE_CSC_COEF_A2_LSB		0x10414 +#define HDMI_CORE_CSC_COEF_A3_MSB		0x10418 +#define HDMI_CORE_CSC_COEF_A3_LSB		0x1041C +#define HDMI_CORE_CSC_COEF_A4_MSB		0x10420 +#define HDMI_CORE_CSC_COEF_A4_LSB		0x10424 +#define HDMI_CORE_CSC_COEF_B1_MSB		0x10428 +#define HDMI_CORE_CSC_COEF_B1_LSB		0x1042C +#define HDMI_CORE_CSC_COEF_B2_MSB		0x10430 +#define HDMI_CORE_CSC_COEF_B2_LSB		0x10434 +#define HDMI_CORE_CSC_COEF_B3_MSB		0x10438 +#define HDMI_CORE_CSC_COEF_B3_LSB		0x1043C +#define HDMI_CORE_CSC_COEF_B4_MSB		0x10440 +#define HDMI_CORE_CSC_COEF_B4_LSB		0x10444 +#define HDMI_CORE_CSC_COEF_C1_MSB		0x10448 +#define HDMI_CORE_CSC_COEF_C1_LSB		0x1044C +#define HDMI_CORE_CSC_COEF_C2_MSB		0x10450 +#define HDMI_CORE_CSC_COEF_C2_LSB		0x10454 +#define HDMI_CORE_CSC_COEF_C3_MSB		0x10458 +#define HDMI_CORE_CSC_COEF_C3_LSB		0x1045C +#define HDMI_CORE_CSC_COEF_C4_MSB		0x10460 +#define HDMI_CORE_CSC_COEF_C4_LSB		0x10464 + +/* HDMI HDCP */ +#define HDMI_CORE_HDCP_MASK			0x14020 + +/* HDMI CEC */ +#define HDMI_CORE_CEC_MASK			0x17408 + +/* HDMI I2C Master */ +#define HDMI_CORE_I2CM_SLAVE			0x157C8 +#define HDMI_CORE_I2CM_ADDRESS			0x157CC +#define HDMI_CORE_I2CM_DATAO			0x157D0 +#define HDMI_CORE_I2CM_DATAI			0X157D4 +#define HDMI_CORE_I2CM_OPERATION		0x157D8 +#define HDMI_CORE_I2CM_INT			0x157DC +#define HDMI_CORE_I2CM_CTLINT			0x157E0 +#define HDMI_CORE_I2CM_DIV			0x157E4 +#define HDMI_CORE_I2CM_SEGADDR			0x157E8 +#define HDMI_CORE_I2CM_SOFTRSTZ			0x157EC +#define HDMI_CORE_I2CM_SEGPTR			0x157F0 +#define HDMI_CORE_I2CM_SS_SCL_HCNT_1_ADDR	0x157F4 +#define HDMI_CORE_I2CM_SS_SCL_HCNT_0_ADDR	0x157F8 +#define HDMI_CORE_I2CM_SS_SCL_LCNT_1_ADDR	0x157FC +#define HDMI_CORE_I2CM_SS_SCL_LCNT_0_ADDR	0x15800 +#define HDMI_CORE_I2CM_FS_SCL_HCNT_1_ADDR	0x15804 +#define HDMI_CORE_I2CM_FS_SCL_HCNT_0_ADDR	0x15808 +#define HDMI_CORE_I2CM_FS_SCL_LCNT_1_ADDR	0x1580C +#define HDMI_CORE_I2CM_FS_SCL_LCNT_0_ADDR	0x15810 +#define HDMI_CORE_I2CM_SDA_HOLD_ADDR		0x15814 + +enum hdmi_core_packet_mode { +	HDMI_PACKETMODERESERVEDVALUE = 0, +	HDMI_PACKETMODE24BITPERPIXEL = 4, +	HDMI_PACKETMODE30BITPERPIXEL = 5, +	HDMI_PACKETMODE36BITPERPIXEL = 6, +	HDMI_PACKETMODE48BITPERPIXEL = 7, +}; + +struct hdmi_core_vid_config { +	struct hdmi_config v_fc_config; +	enum hdmi_core_packet_mode packet_mode; +	int data_enable_pol; +	int vblank_osc; +	int hblank; +	int vblank; +}; + +struct csc_table { +	u16 a1, a2, a3, a4; +	u16 b1, b2, b3, b4; +	u16 c1, c2, c3, c4; +}; + +int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len); +void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s); +int hdmi5_core_handle_irqs(struct hdmi_core_data *core); +void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +			struct hdmi_config *cfg); +int hdmi5_core_init(struct platform_device *pdev, struct hdmi_core_data *core); + +#if defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) +int hdmi5_audio_config(struct hdmi_core_data *core, struct hdmi_wp_data *wp, +			struct omap_dss_audio *audio, u32 pclk); +#endif +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi_common.c b/drivers/video/fbdev/omap2/dss/hdmi_common.c new file mode 100644 index 00000000000..9a2c39cf297 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_common.c @@ -0,0 +1,466 @@ + +/* + * 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. + */ + +#define DSS_SUBSYS_NAME "HDMI" + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/of.h> +#include <video/omapdss.h> + +#include "hdmi.h" + +static const struct hdmi_config cea_timings[] = { +	{ +		{ 640, 480, 25200000, 96, 16, 48, 2, 10, 33, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 1, HDMI_HDMI }, +	}, +	{ +		{ 720, 480, 27027000, 62, 16, 60, 6, 9, 30, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 2, HDMI_HDMI }, +	}, +	{ +		{ 1280, 720, 74250000, 40, 110, 220, 5, 5, 20, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 4, HDMI_HDMI }, +	}, +	{ +		{ 1920, 540, 74250000, 44, 88, 148, 5, 2, 15, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			true, }, +		{ 5, HDMI_HDMI }, +	}, +	{ +		{ 1440, 240, 27027000, 124, 38, 114, 3, 4, 15, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			true, }, +		{ 6, HDMI_HDMI }, +	}, +	{ +		{ 1920, 1080, 148500000, 44, 88, 148, 5, 4, 36, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 16, HDMI_HDMI }, +	}, +	{ +		{ 720, 576, 27000000, 64, 12, 68, 5, 5, 39, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 17, HDMI_HDMI }, +	}, +	{ +		{ 1280, 720, 74250000, 40, 440, 220, 5, 5, 20, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 19, HDMI_HDMI }, +	}, +	{ +		{ 1920, 540, 74250000, 44, 528, 148, 5, 2, 15, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			true, }, +		{ 20, HDMI_HDMI }, +	}, +	{ +		{ 1440, 288, 27000000, 126, 24, 138, 3, 2, 19, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			true, }, +		{ 21, HDMI_HDMI }, +	}, +	{ +		{ 1440, 576, 54000000, 128, 24, 136, 5, 5, 39, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 29, HDMI_HDMI }, +	}, +	{ +		{ 1920, 1080, 148500000, 44, 528, 148, 5, 4, 36, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 31, HDMI_HDMI }, +	}, +	{ +		{ 1920, 1080, 74250000, 44, 638, 148, 5, 4, 36, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 32, HDMI_HDMI }, +	}, +	{ +		{ 2880, 480, 108108000, 248, 64, 240, 6, 9, 30, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 35, HDMI_HDMI }, +	}, +	{ +		{ 2880, 576, 108000000, 256, 48, 272, 5, 5, 39, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 37, HDMI_HDMI }, +	}, +}; + +static const struct hdmi_config vesa_timings[] = { +/* VESA From Here */ +	{ +		{ 640, 480, 25175000, 96, 16, 48, 2, 11, 31, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 4, HDMI_DVI }, +	}, +	{ +		{ 800, 600, 40000000, 128, 40, 88, 4, 1, 23, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 9, HDMI_DVI }, +	}, +	{ +		{ 848, 480, 33750000, 112, 16, 112, 8, 6, 23, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0xE, HDMI_DVI }, +	}, +	{ +		{ 1280, 768, 79500000, 128, 64, 192, 7, 3, 20, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x17, HDMI_DVI }, +	}, +	{ +		{ 1280, 800, 83500000, 128, 72, 200, 6, 3, 22, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x1C, HDMI_DVI }, +	}, +	{ +		{ 1360, 768, 85500000, 112, 64, 256, 6, 3, 18, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x27, HDMI_DVI }, +	}, +	{ +		{ 1280, 960, 108000000, 112, 96, 312, 3, 1, 36, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x20, HDMI_DVI }, +	}, +	{ +		{ 1280, 1024, 108000000, 112, 48, 248, 3, 1, 38, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x23, HDMI_DVI }, +	}, +	{ +		{ 1024, 768, 65000000, 136, 24, 160, 6, 3, 29, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x10, HDMI_DVI }, +	}, +	{ +		{ 1400, 1050, 121750000, 144, 88, 232, 4, 3, 32, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x2A, HDMI_DVI }, +	}, +	{ +		{ 1440, 900, 106500000, 152, 80, 232, 6, 3, 25, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x2F, HDMI_DVI }, +	}, +	{ +		{ 1680, 1050, 146250000, 176 , 104, 280, 6, 3, 30, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_LOW, +			false, }, +		{ 0x3A, HDMI_DVI }, +	}, +	{ +		{ 1366, 768, 85500000, 143, 70, 213, 3, 3, 24, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x51, HDMI_DVI }, +	}, +	{ +		{ 1920, 1080, 148500000, 44, 148, 80, 5, 4, 36, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x52, HDMI_DVI }, +	}, +	{ +		{ 1280, 768, 68250000, 32, 48, 80, 7, 3, 12, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x16, HDMI_DVI }, +	}, +	{ +		{ 1400, 1050, 101000000, 32, 48, 80, 4, 3, 23, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x29, HDMI_DVI }, +	}, +	{ +		{ 1680, 1050, 119000000, 32, 48, 80, 6, 3, 21, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x39, HDMI_DVI }, +	}, +	{ +		{ 1280, 800, 79500000, 32, 48, 80, 6, 3, 14, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x1B, HDMI_DVI }, +	}, +	{ +		{ 1280, 720, 74250000, 40, 110, 220, 5, 5, 20, +			OMAPDSS_SIG_ACTIVE_HIGH, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x55, HDMI_DVI }, +	}, +	{ +		{ 1920, 1200, 154000000, 32, 48, 80, 6, 3, 26, +			OMAPDSS_SIG_ACTIVE_LOW, OMAPDSS_SIG_ACTIVE_HIGH, +			false, }, +		{ 0x44, HDMI_DVI }, +	}, +}; + +const struct hdmi_config *hdmi_default_timing(void) +{ +	return &vesa_timings[0]; +} + +static const struct hdmi_config *hdmi_find_timing(int code, +			const struct hdmi_config *timings_arr, int len) +{ +	int i; + +	for (i = 0; i < len; i++) { +		if (timings_arr[i].cm.code == code) +			return &timings_arr[i]; +	} + +	return NULL; +} + +const struct hdmi_config *hdmi_get_timings(int mode, int code) +{ +	const struct hdmi_config *arr; +	int len; + +	if (mode == HDMI_DVI) { +		arr = vesa_timings; +		len = ARRAY_SIZE(vesa_timings); +	} else { +		arr = cea_timings; +		len = ARRAY_SIZE(cea_timings); +	} + +	return hdmi_find_timing(code, arr, len); +} + +static bool hdmi_timings_compare(struct omap_video_timings *timing1, +			const struct omap_video_timings *timing2) +{ +	int timing1_vsync, timing1_hsync, timing2_vsync, timing2_hsync; + +	if ((DIV_ROUND_CLOSEST(timing2->pixelclock, 1000000) == +			DIV_ROUND_CLOSEST(timing1->pixelclock, 1000000)) && +		(timing2->x_res == timing1->x_res) && +		(timing2->y_res == timing1->y_res)) { + +		timing2_hsync = timing2->hfp + timing2->hsw + timing2->hbp; +		timing1_hsync = timing1->hfp + timing1->hsw + timing1->hbp; +		timing2_vsync = timing2->vfp + timing2->vsw + timing2->vbp; +		timing1_vsync = timing1->vfp + timing1->vsw + timing1->vbp; + +		DSSDBG("timing1_hsync = %d timing1_vsync = %d"\ +			"timing2_hsync = %d timing2_vsync = %d\n", +			timing1_hsync, timing1_vsync, +			timing2_hsync, timing2_vsync); + +		if ((timing1_hsync == timing2_hsync) && +			(timing1_vsync == timing2_vsync)) { +			return true; +		} +	} +	return false; +} + +struct hdmi_cm hdmi_get_code(struct omap_video_timings *timing) +{ +	int i; +	struct hdmi_cm cm = {-1}; +	DSSDBG("hdmi_get_code\n"); + +	for (i = 0; i < ARRAY_SIZE(cea_timings); i++) { +		if (hdmi_timings_compare(timing, &cea_timings[i].timings)) { +			cm = cea_timings[i].cm; +			goto end; +		} +	} +	for (i = 0; i < ARRAY_SIZE(vesa_timings); i++) { +		if (hdmi_timings_compare(timing, &vesa_timings[i].timings)) { +			cm = vesa_timings[i].cm; +			goto end; +		} +	} + +end: +	return cm; +} + +int hdmi_parse_lanes_of(struct platform_device *pdev, struct device_node *ep, +	struct hdmi_phy_data *phy) +{ +	struct property *prop; +	int r, len; + +	prop = of_find_property(ep, "lanes", &len); +	if (prop) { +		u32 lanes[8]; + +		if (len / sizeof(u32) != ARRAY_SIZE(lanes)) { +			dev_err(&pdev->dev, "bad number of lanes\n"); +			return -EINVAL; +		} + +		r = of_property_read_u32_array(ep, "lanes", lanes, +			ARRAY_SIZE(lanes)); +		if (r) { +			dev_err(&pdev->dev, "failed to read lane data\n"); +			return r; +		} + +		r = hdmi_phy_parse_lanes(phy, lanes); +		if (r) { +			dev_err(&pdev->dev, "failed to parse lane data\n"); +			return r; +		} +	} else { +		static const u32 default_lanes[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + +		r = hdmi_phy_parse_lanes(phy, default_lanes); +		if (WARN_ON(r)) { +			dev_err(&pdev->dev, "failed to parse lane data\n"); +			return r; +		} +	} + +	return 0; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) +int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts) +{ +	u32 deep_color; +	bool deep_color_correct = false; + +	if (n == NULL || cts == NULL) +		return -EINVAL; + +	/* TODO: When implemented, query deep color mode here. */ +	deep_color = 100; + +	/* +	 * When using deep color, the default N value (as in the HDMI +	 * specification) yields to an non-integer CTS. Hence, we +	 * modify it while keeping the restrictions described in +	 * section 7.2.1 of the HDMI 1.4a specification. +	 */ +	switch (sample_freq) { +	case 32000: +	case 48000: +	case 96000: +	case 192000: +		if (deep_color == 125) +			if (pclk == 27027000 || pclk == 74250000) +				deep_color_correct = true; +		if (deep_color == 150) +			if (pclk == 27027000) +				deep_color_correct = true; +		break; +	case 44100: +	case 88200: +	case 176400: +		if (deep_color == 125) +			if (pclk == 27027000) +				deep_color_correct = true; +		break; +	default: +		return -EINVAL; +	} + +	if (deep_color_correct) { +		switch (sample_freq) { +		case 32000: +			*n = 8192; +			break; +		case 44100: +			*n = 12544; +			break; +		case 48000: +			*n = 8192; +			break; +		case 88200: +			*n = 25088; +			break; +		case 96000: +			*n = 16384; +			break; +		case 176400: +			*n = 50176; +			break; +		case 192000: +			*n = 32768; +			break; +		default: +			return -EINVAL; +		} +	} else { +		switch (sample_freq) { +		case 32000: +			*n = 4096; +			break; +		case 44100: +			*n = 6272; +			break; +		case 48000: +			*n = 6144; +			break; +		case 88200: +			*n = 12544; +			break; +		case 96000: +			*n = 12288; +			break; +		case 176400: +			*n = 25088; +			break; +		case 192000: +			*n = 24576; +			break; +		default: +			return -EINVAL; +		} +	} +	/* Calculate CTS. See HDMI 1.3a or 1.4a specifications */ +	*cts = (pclk/1000) * (*n / 128) * deep_color / (sample_freq / 10); + +	return 0; +} +#endif diff --git a/drivers/video/fbdev/omap2/dss/hdmi_phy.c b/drivers/video/fbdev/omap2/dss/hdmi_phy.c new file mode 100644 index 00000000000..e007ac892d7 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_phy.c @@ -0,0 +1,255 @@ +/* + * HDMI PHY + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +struct hdmi_phy_features { +	bool bist_ctrl; +	bool calc_freqout; +	bool ldo_voltage; +	unsigned long dcofreq_min; +	unsigned long max_phy; +}; + +static const struct hdmi_phy_features *phy_feat; + +void hdmi_phy_dump(struct hdmi_phy_data *phy, struct seq_file *s) +{ +#define DUMPPHY(r) seq_printf(s, "%-35s %08x\n", #r,\ +		hdmi_read_reg(phy->base, r)) + +	DUMPPHY(HDMI_TXPHY_TX_CTRL); +	DUMPPHY(HDMI_TXPHY_DIGITAL_CTRL); +	DUMPPHY(HDMI_TXPHY_POWER_CTRL); +	DUMPPHY(HDMI_TXPHY_PAD_CFG_CTRL); +	if (phy_feat->bist_ctrl) +		DUMPPHY(HDMI_TXPHY_BIST_CONTROL); +} + +int hdmi_phy_parse_lanes(struct hdmi_phy_data *phy, const u32 *lanes) +{ +	int i; + +	for (i = 0; i < 8; i += 2) { +		u8 lane, pol; +		int dx, dy; + +		dx = lanes[i]; +		dy = lanes[i + 1]; + +		if (dx < 0 || dx >= 8) +			return -EINVAL; + +		if (dy < 0 || dy >= 8) +			return -EINVAL; + +		if (dx & 1) { +			if (dy != dx - 1) +				return -EINVAL; +			pol = 1; +		} else { +			if (dy != dx + 1) +				return -EINVAL; +			pol = 0; +		} + +		lane = dx / 2; + +		phy->lane_function[lane] = i / 2; +		phy->lane_polarity[lane] = pol; +	} + +	return 0; +} + +static void hdmi_phy_configure_lanes(struct hdmi_phy_data *phy) +{ +	static const u16 pad_cfg_list[] = { +		0x0123, +		0x0132, +		0x0312, +		0x0321, +		0x0231, +		0x0213, +		0x1023, +		0x1032, +		0x3012, +		0x3021, +		0x2031, +		0x2013, +		0x1203, +		0x1302, +		0x3102, +		0x3201, +		0x2301, +		0x2103, +		0x1230, +		0x1320, +		0x3120, +		0x3210, +		0x2310, +		0x2130, +	}; + +	u16 lane_cfg = 0; +	int i; +	unsigned lane_cfg_val; +	u16 pol_val = 0; + +	for (i = 0; i < 4; ++i) +		lane_cfg |= phy->lane_function[i] << ((3 - i) * 4); + +	pol_val |= phy->lane_polarity[0] << 0; +	pol_val |= phy->lane_polarity[1] << 3; +	pol_val |= phy->lane_polarity[2] << 2; +	pol_val |= phy->lane_polarity[3] << 1; + +	for (i = 0; i < ARRAY_SIZE(pad_cfg_list); ++i) +		if (pad_cfg_list[i] == lane_cfg) +			break; + +	if (WARN_ON(i == ARRAY_SIZE(pad_cfg_list))) +		i = 0; + +	lane_cfg_val = i; + +	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, lane_cfg_val, 26, 22); +	REG_FLD_MOD(phy->base, HDMI_TXPHY_PAD_CFG_CTRL, pol_val, 30, 27); +} + +int hdmi_phy_configure(struct hdmi_phy_data *phy, struct hdmi_config *cfg) +{ +	u8 freqout; + +	/* +	 * Read address 0 in order to get the SCP reset done completed +	 * Dummy access performed to make sure reset is done +	 */ +	hdmi_read_reg(phy->base, HDMI_TXPHY_TX_CTRL); + +	/* +	 * In OMAP5+, the HFBITCLK must be divided by 2 before issuing the +	 * HDMI_PHYPWRCMD_LDOON command. +	*/ +	if (phy_feat->bist_ctrl) +		REG_FLD_MOD(phy->base, HDMI_TXPHY_BIST_CONTROL, 1, 11, 11); + +	if (phy_feat->calc_freqout) { +		/* DCOCLK/10 is pixel clock, compare pclk with DCOCLK_MIN/10 */ +		u32 dco_min = phy_feat->dcofreq_min / 10; +		u32 pclk = cfg->timings.pixelclock; + +		if (pclk < dco_min) +			freqout = 0; +		else if ((pclk >= dco_min) && (pclk < phy_feat->max_phy)) +			freqout = 1; +		else +			freqout = 2; +	} else { +		freqout = 1; +	} + +	/* +	 * Write to phy address 0 to configure the clock +	 * use HFBITCLK write HDMI_TXPHY_TX_CONTROL_FREQOUT field +	 */ +	REG_FLD_MOD(phy->base, HDMI_TXPHY_TX_CTRL, freqout, 31, 30); + +	/* Write to phy address 1 to start HDMI line (TXVALID and TMDSCLKEN) */ +	hdmi_write_reg(phy->base, HDMI_TXPHY_DIGITAL_CTRL, 0xF0000000); + +	/* Setup max LDO voltage */ +	if (phy_feat->ldo_voltage) +		REG_FLD_MOD(phy->base, HDMI_TXPHY_POWER_CTRL, 0xB, 3, 0); + +	hdmi_phy_configure_lanes(phy); + +	return 0; +} + +static const struct hdmi_phy_features omap44xx_phy_feats = { +	.bist_ctrl	=	false, +	.calc_freqout	=	false, +	.ldo_voltage	=	true, +	.dcofreq_min	=	500000000, +	.max_phy	=	185675000, +}; + +static const struct hdmi_phy_features omap54xx_phy_feats = { +	.bist_ctrl	=	true, +	.calc_freqout	=	true, +	.ldo_voltage	=	false, +	.dcofreq_min	=	750000000, +	.max_phy	=	186000000, +}; + +static int hdmi_phy_init_features(struct platform_device *pdev) +{ +	struct hdmi_phy_features *dst; +	const struct hdmi_phy_features *src; + +	dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); +	if (!dst) { +		dev_err(&pdev->dev, "Failed to allocate HDMI PHY Features\n"); +		return -ENOMEM; +	} + +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		src = &omap44xx_phy_feats; +		break; + +	case OMAPDSS_VER_OMAP5: +		src = &omap54xx_phy_feats; +		break; + +	default: +		return -ENODEV; +	} + +	memcpy(dst, src, sizeof(*dst)); +	phy_feat = dst; + +	return 0; +} + +int hdmi_phy_init(struct platform_device *pdev, struct hdmi_phy_data *phy) +{ +	int r; +	struct resource *res; + +	r = hdmi_phy_init_features(pdev); +	if (r) +		return r; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); +	if (!res) { +		DSSERR("can't get PHY mem resource\n"); +		return -EINVAL; +	} + +	phy->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(phy->base)) { +		DSSERR("can't ioremap TX PHY\n"); +		return PTR_ERR(phy->base); +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi_pll.c b/drivers/video/fbdev/omap2/dss/hdmi_pll.c new file mode 100644 index 00000000000..54df12a8d74 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_pll.c @@ -0,0 +1,291 @@ +/* + * HDMI PLL + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#define DSS_SUBSYS_NAME "HDMIPLL" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +#define HDMI_DEFAULT_REGN 16 +#define HDMI_DEFAULT_REGM2 1 + +struct hdmi_pll_features { +	bool sys_reset; +	/* this is a hack, need to replace it with a better computation of M2 */ +	bool bound_dcofreq; +	unsigned long fint_min, fint_max; +	u16 regm_max; +	unsigned long dcofreq_low_min, dcofreq_low_max; +	unsigned long dcofreq_high_min, dcofreq_high_max; +}; + +static const struct hdmi_pll_features *pll_feat; + +void hdmi_pll_dump(struct hdmi_pll_data *pll, struct seq_file *s) +{ +#define DUMPPLL(r) seq_printf(s, "%-35s %08x\n", #r,\ +		hdmi_read_reg(pll->base, r)) + +	DUMPPLL(PLLCTRL_PLL_CONTROL); +	DUMPPLL(PLLCTRL_PLL_STATUS); +	DUMPPLL(PLLCTRL_PLL_GO); +	DUMPPLL(PLLCTRL_CFG1); +	DUMPPLL(PLLCTRL_CFG2); +	DUMPPLL(PLLCTRL_CFG3); +	DUMPPLL(PLLCTRL_SSC_CFG1); +	DUMPPLL(PLLCTRL_SSC_CFG2); +	DUMPPLL(PLLCTRL_CFG4); +} + +void hdmi_pll_compute(struct hdmi_pll_data *pll, unsigned long clkin, int phy) +{ +	struct hdmi_pll_info *pi = &pll->info; +	unsigned long refclk; +	u32 mf; + +	/* use our funky units */ +	clkin /= 10000; + +	/* +	 * Input clock is predivided by N + 1 +	 * out put of which is reference clk +	 */ + +	pi->regn = HDMI_DEFAULT_REGN; + +	refclk = clkin / pi->regn; + +	/* temorary hack to make sure DCO freq isn't calculated too low */ +	if (pll_feat->bound_dcofreq && phy <= 65000) +		pi->regm2 = 3; +	else +		pi->regm2 = HDMI_DEFAULT_REGM2; + +	/* +	 * multiplier is pixel_clk/ref_clk +	 * Multiplying by 100 to avoid fractional part removal +	 */ +	pi->regm = phy * pi->regm2 / refclk; + +	/* +	 * fractional multiplier is remainder of the difference between +	 * multiplier and actual phy(required pixel clock thus should be +	 * multiplied by 2^18(262144) divided by the reference clock +	 */ +	mf = (phy - pi->regm / pi->regm2 * refclk) * 262144; +	pi->regmf = pi->regm2 * mf / refclk; + +	/* +	 * Dcofreq should be set to 1 if required pixel clock +	 * is greater than 1000MHz +	 */ +	pi->dcofreq = phy > 1000 * 100; +	pi->regsd = ((pi->regm * clkin / 10) / (pi->regn * 250) + 5) / 10; + +	/* Set the reference clock to sysclk reference */ +	pi->refsel = HDMI_REFSEL_SYSCLK; + +	DSSDBG("M = %d Mf = %d\n", pi->regm, pi->regmf); +	DSSDBG("range = %d sd = %d\n", pi->dcofreq, pi->regsd); +} + + +static int hdmi_pll_config(struct hdmi_pll_data *pll) +{ +	u32 r; +	struct hdmi_pll_info *fmt = &pll->info; + +	/* PLL start always use manual mode */ +	REG_FLD_MOD(pll->base, PLLCTRL_PLL_CONTROL, 0x0, 0, 0); + +	r = hdmi_read_reg(pll->base, PLLCTRL_CFG1); +	r = FLD_MOD(r, fmt->regm, 20, 9);	/* CFG1_PLL_REGM */ +	r = FLD_MOD(r, fmt->regn - 1, 8, 1);	/* CFG1_PLL_REGN */ +	hdmi_write_reg(pll->base, PLLCTRL_CFG1, r); + +	r = hdmi_read_reg(pll->base, 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 */ +	r = FLD_MOD(r, fmt->refsel, 22, 21);	/* REFSEL */ + +	if (fmt->dcofreq) { +		/* divider programming for frequency beyond 1000Mhz */ +		REG_FLD_MOD(pll->base, PLLCTRL_CFG3, fmt->regsd, 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(pll->base, PLLCTRL_CFG2, r); + +	r = hdmi_read_reg(pll->base, PLLCTRL_CFG4); +	r = FLD_MOD(r, fmt->regm2, 24, 18); +	r = FLD_MOD(r, fmt->regmf, 17, 0); +	hdmi_write_reg(pll->base, PLLCTRL_CFG4, r); + +	/* go now */ +	REG_FLD_MOD(pll->base, PLLCTRL_PLL_GO, 0x1, 0, 0); + +	/* wait for bit change */ +	if (hdmi_wait_for_bit_change(pll->base, 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(pll->base, +			PLLCTRL_PLL_STATUS, 1, 1, 1) != 1) { +		DSSERR("cannot lock PLL\n"); +		DSSERR("CFG1 0x%x\n", +			hdmi_read_reg(pll->base, PLLCTRL_CFG1)); +		DSSERR("CFG2 0x%x\n", +			hdmi_read_reg(pll->base, PLLCTRL_CFG2)); +		DSSERR("CFG4 0x%x\n", +			hdmi_read_reg(pll->base, PLLCTRL_CFG4)); +		return -ETIMEDOUT; +	} + +	DSSDBG("PLL locked!\n"); + +	return 0; +} + +static int hdmi_pll_reset(struct hdmi_pll_data *pll) +{ +	/* SYSRESET  controlled by power FSM */ +	REG_FLD_MOD(pll->base, PLLCTRL_PLL_CONTROL, pll_feat->sys_reset, 3, 3); + +	/* READ 0x0 reset is in progress */ +	if (hdmi_wait_for_bit_change(pll->base, PLLCTRL_PLL_STATUS, 0, 0, 1) +			!= 1) { +		DSSERR("Failed to sysreset PLL\n"); +		return -ETIMEDOUT; +	} + +	return 0; +} + +int hdmi_pll_enable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp) +{ +	u16 r = 0; + +	r = hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_ALLOFF); +	if (r) +		return r; + +	r = hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_BOTHON_ALLCLKS); +	if (r) +		return r; + +	r = hdmi_pll_reset(pll); +	if (r) +		return r; + +	r = hdmi_pll_config(pll); +	if (r) +		return r; + +	return 0; +} + +void hdmi_pll_disable(struct hdmi_pll_data *pll, struct hdmi_wp_data *wp) +{ +	hdmi_wp_set_pll_pwr(wp, HDMI_PLLPWRCMD_ALLOFF); +} + +static const struct hdmi_pll_features omap44xx_pll_feats = { +	.sys_reset		=	false, +	.bound_dcofreq		=	false, +	.fint_min		=	500000, +	.fint_max		=	2500000, +	.regm_max		=	4095, +	.dcofreq_low_min	=	500000000, +	.dcofreq_low_max	=	1000000000, +	.dcofreq_high_min	=	1000000000, +	.dcofreq_high_max	=	2000000000, +}; + +static const struct hdmi_pll_features omap54xx_pll_feats = { +	.sys_reset		=	true, +	.bound_dcofreq		=	true, +	.fint_min		=	620000, +	.fint_max		=	2500000, +	.regm_max		=	2046, +	.dcofreq_low_min	=	750000000, +	.dcofreq_low_max	=	1500000000, +	.dcofreq_high_min	=	1250000000, +	.dcofreq_high_max	=	2500000000UL, +}; + +static int hdmi_pll_init_features(struct platform_device *pdev) +{ +	struct hdmi_pll_features *dst; +	const struct hdmi_pll_features *src; + +	dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); +	if (!dst) { +		dev_err(&pdev->dev, "Failed to allocate HDMI PHY Features\n"); +		return -ENOMEM; +	} + +	switch (omapdss_get_version()) { +	case OMAPDSS_VER_OMAP4430_ES1: +	case OMAPDSS_VER_OMAP4430_ES2: +	case OMAPDSS_VER_OMAP4: +		src = &omap44xx_pll_feats; +		break; + +	case OMAPDSS_VER_OMAP5: +		src = &omap54xx_pll_feats; +		break; + +	default: +		return -ENODEV; +	} + +	memcpy(dst, src, sizeof(*dst)); +	pll_feat = dst; + +	return 0; +} + +int hdmi_pll_init(struct platform_device *pdev, struct hdmi_pll_data *pll) +{ +	int r; +	struct resource *res; + +	r = hdmi_pll_init_features(pdev); +	if (r) +		return r; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll"); +	if (!res) { +		DSSERR("can't get PLL mem resource\n"); +		return -EINVAL; +	} + +	pll->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(pll->base)) { +		DSSERR("can't ioremap PLLCTRL\n"); +		return PTR_ERR(pll->base); +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/hdmi_wp.c b/drivers/video/fbdev/omap2/dss/hdmi_wp.c new file mode 100644 index 00000000000..496327e2b21 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/hdmi_wp.c @@ -0,0 +1,258 @@ +/* + * HDMI wrapper + * + * Copyright (C) 2013 Texas Instruments Incorporated + * + * 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. + */ + +#define DSS_SUBSYS_NAME "HDMIWP" + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <video/omapdss.h> + +#include "dss.h" +#include "hdmi.h" + +void hdmi_wp_dump(struct hdmi_wp_data *wp, struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, hdmi_read_reg(wp->base, r)) + +	DUMPREG(HDMI_WP_REVISION); +	DUMPREG(HDMI_WP_SYSCONFIG); +	DUMPREG(HDMI_WP_IRQSTATUS_RAW); +	DUMPREG(HDMI_WP_IRQSTATUS); +	DUMPREG(HDMI_WP_IRQENABLE_SET); +	DUMPREG(HDMI_WP_IRQENABLE_CLR); +	DUMPREG(HDMI_WP_IRQWAKEEN); +	DUMPREG(HDMI_WP_PWR_CTRL); +	DUMPREG(HDMI_WP_DEBOUNCE); +	DUMPREG(HDMI_WP_VIDEO_CFG); +	DUMPREG(HDMI_WP_VIDEO_SIZE); +	DUMPREG(HDMI_WP_VIDEO_TIMING_H); +	DUMPREG(HDMI_WP_VIDEO_TIMING_V); +	DUMPREG(HDMI_WP_CLK); +	DUMPREG(HDMI_WP_AUDIO_CFG); +	DUMPREG(HDMI_WP_AUDIO_CFG2); +	DUMPREG(HDMI_WP_AUDIO_CTRL); +	DUMPREG(HDMI_WP_AUDIO_DATA); +} + +u32 hdmi_wp_get_irqstatus(struct hdmi_wp_data *wp) +{ +	return hdmi_read_reg(wp->base, HDMI_WP_IRQSTATUS); +} + +void hdmi_wp_set_irqstatus(struct hdmi_wp_data *wp, u32 irqstatus) +{ +	hdmi_write_reg(wp->base, HDMI_WP_IRQSTATUS, irqstatus); +	/* flush posted write */ +	hdmi_read_reg(wp->base, HDMI_WP_IRQSTATUS); +} + +void hdmi_wp_set_irqenable(struct hdmi_wp_data *wp, u32 mask) +{ +	hdmi_write_reg(wp->base, HDMI_WP_IRQENABLE_SET, mask); +} + +void hdmi_wp_clear_irqenable(struct hdmi_wp_data *wp, u32 mask) +{ +	hdmi_write_reg(wp->base, HDMI_WP_IRQENABLE_CLR, mask); +} + +/* PHY_PWR_CMD */ +int hdmi_wp_set_phy_pwr(struct hdmi_wp_data *wp, enum hdmi_phy_pwr val) +{ +	/* Return if already the state */ +	if (REG_GET(wp->base, HDMI_WP_PWR_CTRL, 5, 4) == val) +		return 0; + +	/* Command for power control of HDMI PHY */ +	REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 7, 6); + +	/* Status of the power control of HDMI PHY */ +	if (hdmi_wait_for_bit_change(wp->base, HDMI_WP_PWR_CTRL, 5, 4, val) +			!= val) { +		DSSERR("Failed to set PHY power mode to %d\n", val); +		return -ETIMEDOUT; +	} + +	return 0; +} + +/* PLL_PWR_CMD */ +int hdmi_wp_set_pll_pwr(struct hdmi_wp_data *wp, enum hdmi_pll_pwr val) +{ +	/* Command for power control of HDMI PLL */ +	REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 3, 2); + +	/* wait till PHY_PWR_STATUS is set */ +	if (hdmi_wait_for_bit_change(wp->base, HDMI_WP_PWR_CTRL, 1, 0, val) +			!= val) { +		DSSERR("Failed to set PLL_PWR_STATUS\n"); +		return -ETIMEDOUT; +	} + +	return 0; +} + +int hdmi_wp_video_start(struct hdmi_wp_data *wp) +{ +	REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, true, 31, 31); + +	return 0; +} + +void hdmi_wp_video_stop(struct hdmi_wp_data *wp) +{ +	REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, false, 31, 31); +} + +void hdmi_wp_video_config_format(struct hdmi_wp_data *wp, +		struct hdmi_video_format *video_fmt) +{ +	u32 l = 0; + +	REG_FLD_MOD(wp->base, 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(wp->base, HDMI_WP_VIDEO_SIZE, l); +} + +void hdmi_wp_video_config_interface(struct hdmi_wp_data *wp, +		struct omap_video_timings *timings) +{ +	u32 r; +	bool vsync_pol, hsync_pol; +	DSSDBG("Enter hdmi_wp_video_config_interface\n"); + +	vsync_pol = timings->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH; +	hsync_pol = timings->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH; + +	r = hdmi_read_reg(wp->base, HDMI_WP_VIDEO_CFG); +	r = FLD_MOD(r, vsync_pol, 7, 7); +	r = FLD_MOD(r, hsync_pol, 6, 6); +	r = FLD_MOD(r, timings->interlace, 3, 3); +	r = FLD_MOD(r, 1, 1, 0); /* HDMI_TIMING_MASTER_24BIT */ +	hdmi_write_reg(wp->base, HDMI_WP_VIDEO_CFG, r); +} + +void hdmi_wp_video_config_timing(struct hdmi_wp_data *wp, +		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(wp->base, 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(wp->base, HDMI_WP_VIDEO_TIMING_V, timing_v); +} + +void hdmi_wp_init_vid_fmt_timings(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->packing_mode = HDMI_PACK_10b_RGB_YUV444; +	video_fmt->y_res = param->timings.y_res; +	video_fmt->x_res = param->timings.x_res; +	if (param->timings.interlace) +		video_fmt->y_res /= 2; + +	timings->hbp = param->timings.hbp; +	timings->hfp = param->timings.hfp; +	timings->hsw = param->timings.hsw; +	timings->vbp = param->timings.vbp; +	timings->vfp = param->timings.vfp; +	timings->vsw = param->timings.vsw; +	timings->vsync_level = param->timings.vsync_level; +	timings->hsync_level = param->timings.hsync_level; +	timings->interlace = param->timings.interlace; +} + +#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO) || defined(CONFIG_OMAP5_DSS_HDMI_AUDIO) +void hdmi_wp_audio_config_format(struct hdmi_wp_data *wp, +		struct hdmi_audio_format *aud_fmt) +{ +	u32 r; + +	DSSDBG("Enter hdmi_wp_audio_config_format\n"); + +	r = hdmi_read_reg(wp->base, 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(wp->base, HDMI_WP_AUDIO_CFG, r); +} + +void hdmi_wp_audio_config_dma(struct hdmi_wp_data *wp, +		struct hdmi_audio_dma *aud_dma) +{ +	u32 r; + +	DSSDBG("Enter hdmi_wp_audio_config_dma\n"); + +	r = hdmi_read_reg(wp->base, 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(wp->base, HDMI_WP_AUDIO_CFG2, r); + +	r = hdmi_read_reg(wp->base, 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(wp->base, HDMI_WP_AUDIO_CTRL, r); +} + +int hdmi_wp_audio_enable(struct hdmi_wp_data *wp, bool enable) +{ +	REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 31, 31); + +	return 0; +} + +int hdmi_wp_audio_core_req_enable(struct hdmi_wp_data *wp, bool enable) +{ +	REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 30, 30); + +	return 0; +} +#endif + +int hdmi_wp_init(struct platform_device *pdev, struct hdmi_wp_data *wp) +{ +	struct resource *res; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wp"); +	if (!res) { +		DSSERR("can't get WP mem resource\n"); +		return -EINVAL; +	} + +	wp->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(wp->base)) { +		DSSERR("can't ioremap HDMI WP\n"); +		return PTR_ERR(wp->base); +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/manager-sysfs.c b/drivers/video/fbdev/omap2/dss/manager-sysfs.c new file mode 100644 index 00000000000..37b59fe28dc --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/manager-sysfs.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "MANAGER" + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static ssize_t manager_name_show(struct omap_overlay_manager *mgr, char *buf) +{ +	return snprintf(buf, PAGE_SIZE, "%s\n", mgr->name); +} + +static ssize_t manager_display_show(struct omap_overlay_manager *mgr, char *buf) +{ +	struct omap_dss_device *dssdev = mgr->get_device(mgr); + +	return snprintf(buf, PAGE_SIZE, "%s\n", dssdev ? +			dssdev->name : "<none>"); +} + +static ssize_t manager_display_store(struct omap_overlay_manager *mgr, +		const char *buf, size_t size) +{ +	int r = 0; +	size_t len = size; +	struct omap_dss_device *dssdev = NULL; +	struct omap_dss_device *old_dssdev; + +	int match(struct omap_dss_device *dssdev, void *data) +	{ +		const char *str = data; +		return sysfs_streq(dssdev->name, str); +	} + +	if (buf[size-1] == '\n') +		--len; + +	if (len > 0) +		dssdev = omap_dss_find_device((void *)buf, match); + +	if (len > 0 && dssdev == NULL) +		return -EINVAL; + +	if (dssdev) { +		DSSDBG("display %s found\n", dssdev->name); + +		if (omapdss_device_is_connected(dssdev)) { +			DSSERR("new display is already connected\n"); +			r = -EINVAL; +			goto put_device; +		} + +		if (omapdss_device_is_enabled(dssdev)) { +			DSSERR("new display is not disabled\n"); +			r = -EINVAL; +			goto put_device; +		} +	} + +	old_dssdev = mgr->get_device(mgr); +	if (old_dssdev) { +		if (omapdss_device_is_enabled(old_dssdev)) { +			DSSERR("old display is not disabled\n"); +			r = -EINVAL; +			goto put_device; +		} + +		old_dssdev->driver->disconnect(old_dssdev); +	} + +	if (dssdev) { +		r = dssdev->driver->connect(dssdev); +		if (r) { +			DSSERR("failed to connect new device\n"); +			goto put_device; +		} + +		old_dssdev = mgr->get_device(mgr); +		if (old_dssdev != dssdev) { +			DSSERR("failed to connect device to this manager\n"); +			dssdev->driver->disconnect(dssdev); +			goto put_device; +		} + +		r = mgr->apply(mgr); +		if (r) { +			DSSERR("failed to apply dispc config\n"); +			goto put_device; +		} +	} + +put_device: +	if (dssdev) +		omap_dss_put_device(dssdev); + +	return r ? r : size; +} + +static ssize_t manager_default_color_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, "%#x\n", info.default_color); +} + +static ssize_t manager_default_color_store(struct omap_overlay_manager *mgr, +					   const char *buf, size_t size) +{ +	struct omap_overlay_manager_info info; +	u32 color; +	int r; + +	r = kstrtouint(buf, 0, &color); +	if (r) +		return r; + +	mgr->get_manager_info(mgr, &info); + +	info.default_color = color; + +	r = mgr->set_manager_info(mgr, &info); +	if (r) +		return r; + +	r = mgr->apply(mgr); +	if (r) +		return r; + +	return size; +} + +static const char *trans_key_type_str[] = { +	"gfx-destination", +	"video-source", +}; + +static ssize_t manager_trans_key_type_show(struct omap_overlay_manager *mgr, +					   char *buf) +{ +	enum omap_dss_trans_key_type key_type; +	struct omap_overlay_manager_info info; + +	mgr->get_manager_info(mgr, &info); + +	key_type = info.trans_key_type; +	BUG_ON(key_type >= ARRAY_SIZE(trans_key_type_str)); + +	return snprintf(buf, PAGE_SIZE, "%s\n", trans_key_type_str[key_type]); +} + +static ssize_t manager_trans_key_type_store(struct omap_overlay_manager *mgr, +					    const char *buf, size_t size) +{ +	enum omap_dss_trans_key_type key_type; +	struct omap_overlay_manager_info info; +	int r; + +	for (key_type = OMAP_DSS_COLOR_KEY_GFX_DST; +			key_type < ARRAY_SIZE(trans_key_type_str); key_type++) { +		if (sysfs_streq(buf, trans_key_type_str[key_type])) +			break; +	} + +	if (key_type == ARRAY_SIZE(trans_key_type_str)) +		return -EINVAL; + +	mgr->get_manager_info(mgr, &info); + +	info.trans_key_type = key_type; + +	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_trans_key_value_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, "%#x\n", info.trans_key); +} + +static ssize_t manager_trans_key_value_store(struct omap_overlay_manager *mgr, +					     const char *buf, size_t size) +{ +	struct omap_overlay_manager_info info; +	u32 key_value; +	int r; + +	r = kstrtouint(buf, 0, &key_value); +	if (r) +		return r; + +	mgr->get_manager_info(mgr, &info); + +	info.trans_key = key_value; + +	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_trans_key_enabled_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\n", info.trans_enabled); +} + +static ssize_t manager_trans_key_enabled_store(struct omap_overlay_manager *mgr, +					       const char *buf, size_t size) +{ +	struct omap_overlay_manager_info info; +	bool enable; +	int r; + +	r = strtobool(buf, &enable); +	if (r) +		return r; + +	mgr->get_manager_info(mgr, &info); + +	info.trans_enabled = 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_alpha_blending_enabled_show( +		struct omap_overlay_manager *mgr, char *buf) +{ +	struct omap_overlay_manager_info info; + +	if(!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) +		return -ENODEV; + +	mgr->get_manager_info(mgr, &info); + +	return snprintf(buf, PAGE_SIZE, "%d\n", +		info.partial_alpha_enabled); +} + +static ssize_t manager_alpha_blending_enabled_store( +		struct omap_overlay_manager *mgr, +		const char *buf, size_t size) +{ +	struct omap_overlay_manager_info info; +	bool enable; +	int r; + +	if(!dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) +		return -ENODEV; + +	r = strtobool(buf, &enable); +	if (r) +		return r; + +	mgr->get_manager_info(mgr, &info); + +	info.partial_alpha_enabled = 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_enable_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\n", 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 r; +	bool enable; + +	if (!dss_has_feature(FEAT_CPR)) +		return -ENODEV; + +	r = strtobool(buf, &enable); +	if (r) +		return r; + +	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 *); +	ssize_t	(*store)(struct omap_overlay_manager *, const char *, size_t); +}; + +#define MANAGER_ATTR(_name, _mode, _show, _store) \ +	struct manager_attribute manager_attr_##_name = \ +	__ATTR(_name, _mode, _show, _store) + +static MANAGER_ATTR(name, S_IRUGO, manager_name_show, NULL); +static MANAGER_ATTR(display, S_IRUGO|S_IWUSR, +		manager_display_show, manager_display_store); +static MANAGER_ATTR(default_color, S_IRUGO|S_IWUSR, +		manager_default_color_show, manager_default_color_store); +static MANAGER_ATTR(trans_key_type, S_IRUGO|S_IWUSR, +		manager_trans_key_type_show, manager_trans_key_type_store); +static MANAGER_ATTR(trans_key_value, S_IRUGO|S_IWUSR, +		manager_trans_key_value_show, manager_trans_key_value_store); +static MANAGER_ATTR(trans_key_enabled, S_IRUGO|S_IWUSR, +		manager_trans_key_enabled_show, +		manager_trans_key_enabled_store); +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[] = { +	&manager_attr_name.attr, +	&manager_attr_display.attr, +	&manager_attr_default_color.attr, +	&manager_attr_trans_key_type.attr, +	&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 +}; + +static ssize_t manager_attr_show(struct kobject *kobj, struct attribute *attr, +		char *buf) +{ +	struct omap_overlay_manager *manager; +	struct manager_attribute *manager_attr; + +	manager = container_of(kobj, struct omap_overlay_manager, kobj); +	manager_attr = container_of(attr, struct manager_attribute, attr); + +	if (!manager_attr->show) +		return -ENOENT; + +	return manager_attr->show(manager, buf); +} + +static ssize_t manager_attr_store(struct kobject *kobj, struct attribute *attr, +		const char *buf, size_t size) +{ +	struct omap_overlay_manager *manager; +	struct manager_attribute *manager_attr; + +	manager = container_of(kobj, struct omap_overlay_manager, kobj); +	manager_attr = container_of(attr, struct manager_attribute, attr); + +	if (!manager_attr->store) +		return -ENOENT; + +	return manager_attr->store(manager, buf, size); +} + +static const struct sysfs_ops manager_sysfs_ops = { +	.show = manager_attr_show, +	.store = manager_attr_store, +}; + +static struct kobj_type manager_ktype = { +	.sysfs_ops = &manager_sysfs_ops, +	.default_attrs = manager_sysfs_attrs, +}; + +int dss_manager_kobj_init(struct omap_overlay_manager *mgr, +		struct platform_device *pdev) +{ +	return kobject_init_and_add(&mgr->kobj, &manager_ktype, +			&pdev->dev.kobj, "manager%d", mgr->id); +} + +void dss_manager_kobj_uninit(struct omap_overlay_manager *mgr) +{ +	kobject_del(&mgr->kobj); +	kobject_put(&mgr->kobj); + +	memset(&mgr->kobj, 0, sizeof(mgr->kobj)); +} diff --git a/drivers/video/fbdev/omap2/dss/manager.c b/drivers/video/fbdev/omap2/dss/manager.c new file mode 100644 index 00000000000..1aac9b4191a --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/manager.c @@ -0,0 +1,263 @@ +/* + * linux/drivers/video/omap2/dss/manager.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "MANAGER" + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static int num_managers; +static struct omap_overlay_manager *managers; + +int dss_init_overlay_managers(void) +{ +	int i; + +	num_managers = dss_feat_get_num_mgrs(); + +	managers = kzalloc(sizeof(struct omap_overlay_manager) * num_managers, +			GFP_KERNEL); + +	BUG_ON(managers == NULL); + +	for (i = 0; i < num_managers; ++i) { +		struct omap_overlay_manager *mgr = &managers[i]; + +		switch (i) { +		case 0: +			mgr->name = "lcd"; +			mgr->id = OMAP_DSS_CHANNEL_LCD; +			break; +		case 1: +			mgr->name = "tv"; +			mgr->id = OMAP_DSS_CHANNEL_DIGIT; +			break; +		case 2: +			mgr->name = "lcd2"; +			mgr->id = OMAP_DSS_CHANNEL_LCD2; +			break; +		case 3: +			mgr->name = "lcd3"; +			mgr->id = OMAP_DSS_CHANNEL_LCD3; +			break; +		} + +		mgr->caps = 0; +		mgr->supported_displays = +			dss_feat_get_supported_displays(mgr->id); +		mgr->supported_outputs = +			dss_feat_get_supported_outputs(mgr->id); + +		INIT_LIST_HEAD(&mgr->overlays); +	} + +	return 0; +} + +int dss_init_overlay_managers_sysfs(struct platform_device *pdev) +{ +	int i, r; + +	for (i = 0; i < num_managers; ++i) { +		struct omap_overlay_manager *mgr = &managers[i]; + +		r = dss_manager_kobj_init(mgr, pdev); +		if (r) +			DSSERR("failed to create sysfs file\n"); +	} + +	return 0; +} + +void dss_uninit_overlay_managers(void) +{ +	kfree(managers); +	managers = NULL; +	num_managers = 0; +} + +void dss_uninit_overlay_managers_sysfs(struct platform_device *pdev) +{ +	int i; + +	for (i = 0; i < num_managers; ++i) { +		struct omap_overlay_manager *mgr = &managers[i]; + +		dss_manager_kobj_uninit(mgr); +	} +} + +int omap_dss_get_num_overlay_managers(void) +{ +	return num_managers; +} +EXPORT_SYMBOL(omap_dss_get_num_overlay_managers); + +struct omap_overlay_manager *omap_dss_get_overlay_manager(int num) +{ +	if (num >= num_managers) +		return NULL; + +	return &managers[num]; +} +EXPORT_SYMBOL(omap_dss_get_overlay_manager); + +int dss_mgr_simple_check(struct omap_overlay_manager *mgr, +		const struct omap_overlay_manager_info *info) +{ +	if (dss_has_feature(FEAT_ALPHA_FIXED_ZORDER)) { +		/* +		 * OMAP3 supports only graphics source transparency color key +		 * and alpha blending simultaneously. See TRM 15.4.2.4.2.2 +		 * Alpha Mode. +		 */ +		if (info->partial_alpha_enabled && info->trans_enabled +			&& info->trans_key_type != OMAP_DSS_COLOR_KEY_GFX_DST) { +			DSSERR("check_manager: illegal transparency key\n"); +			return -EINVAL; +		} +	} + +	return 0; +} + +static int dss_mgr_check_zorder(struct omap_overlay_manager *mgr, +		struct omap_overlay_info **overlay_infos) +{ +	struct omap_overlay *ovl1, *ovl2; +	struct omap_overlay_info *info1, *info2; + +	list_for_each_entry(ovl1, &mgr->overlays, list) { +		info1 = overlay_infos[ovl1->id]; + +		if (info1 == NULL) +			continue; + +		list_for_each_entry(ovl2, &mgr->overlays, list) { +			if (ovl1 == ovl2) +				continue; + +			info2 = overlay_infos[ovl2->id]; + +			if (info2 == NULL) +				continue; + +			if (info1->zorder == info2->zorder) { +				DSSERR("overlays %d and %d have the same " +						"zorder %d\n", +					ovl1->id, ovl2->id, info1->zorder); +				return -EINVAL; +			} +		} +	} + +	return 0; +} + +int dss_mgr_check_timings(struct omap_overlay_manager *mgr, +		const struct omap_video_timings *timings) +{ +	if (!dispc_mgr_timings_ok(mgr->id, timings)) { +		DSSERR("check_manager: invalid timings\n"); +		return -EINVAL; +	} + +	return 0; +} + +static int dss_mgr_check_lcd_config(struct omap_overlay_manager *mgr, +		const struct dss_lcd_mgr_config *config) +{ +	struct dispc_clock_info cinfo = config->clock_info; +	int dl = config->video_port_width; +	bool stallmode = config->stallmode; +	bool fifohandcheck = config->fifohandcheck; + +	if (cinfo.lck_div < 1 || cinfo.lck_div > 255) +		return -EINVAL; + +	if (cinfo.pck_div < 1 || cinfo.pck_div > 255) +		return -EINVAL; + +	if (dl != 12 && dl != 16 && dl != 18 && dl != 24) +		return -EINVAL; + +	/* fifohandcheck should be used only with stallmode */ +	if (stallmode == false && fifohandcheck == true) +		return -EINVAL; + +	/* +	 * io pad mode can be only checked by using dssdev connected to the +	 * manager. Ignore checking these for now, add checks when manager +	 * is capable of holding information related to the connected interface +	 */ + +	return 0; +} + +int dss_mgr_check(struct omap_overlay_manager *mgr, +		struct omap_overlay_manager_info *info, +		const struct omap_video_timings *mgr_timings, +		const struct dss_lcd_mgr_config *lcd_config, +		struct omap_overlay_info **overlay_infos) +{ +	struct omap_overlay *ovl; +	int r; + +	if (dss_has_feature(FEAT_ALPHA_FREE_ZORDER)) { +		r = dss_mgr_check_zorder(mgr, overlay_infos); +		if (r) +			return r; +	} + +	r = dss_mgr_check_timings(mgr, mgr_timings); +	if (r) +		return r; + +	r = dss_mgr_check_lcd_config(mgr, lcd_config); +	if (r) +		return r; + +	list_for_each_entry(ovl, &mgr->overlays, list) { +		struct omap_overlay_info *oi; +		int r; + +		oi = overlay_infos[ovl->id]; + +		if (oi == NULL) +			continue; + +		r = dss_ovl_check(ovl, oi, mgr_timings); +		if (r) +			return r; +	} + +	return 0; +} diff --git a/drivers/video/fbdev/omap2/dss/omapdss-boot-init.c b/drivers/video/fbdev/omap2/dss/omapdss-boot-init.c new file mode 100644 index 00000000000..2f0822ee3ff --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/omapdss-boot-init.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@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/>. + */ + +/* + * As omapdss panel drivers are omapdss specific, but we want to define the + * DT-data in generic manner, we convert the compatible strings of the panel and + * encoder nodes from "panel-foo" to "omapdss,panel-foo". This way we can have + * both correct DT data and omapdss specific drivers. + * + * When we get generic panel drivers to the kernel, this file will be removed. + */ + +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/slab.h> +#include <linux/list.h> + +static struct list_head dss_conv_list __initdata; + +static const char prefix[] __initconst = "omapdss,"; + +struct dss_conv_node { +	struct list_head list; +	struct device_node *node; +	bool root; +}; + +static int __init omapdss_count_strings(const struct property *prop) +{ +	const char *p = prop->value; +	int l = 0, total = 0; +	int i; + +	for (i = 0; total < prop->length; total += l, p += l, i++) +		l = strlen(p) + 1; + +	return i; +} + +static void __init omapdss_update_prop(struct device_node *node, char *compat, +	int len) +{ +	struct property *prop; + +	prop = kzalloc(sizeof(*prop), GFP_KERNEL); +	if (!prop) +		return; + +	prop->name = "compatible"; +	prop->value = compat; +	prop->length = len; + +	of_update_property(node, prop); +} + +static void __init omapdss_prefix_strcpy(char *dst, int dst_len, +	const char *src, int src_len) +{ +	size_t total = 0; + +	while (total < src_len) { +		size_t l = strlen(src) + 1; + +		strcpy(dst, prefix); +		dst += strlen(prefix); + +		strcpy(dst, src); +		dst += l; + +		src += l; +		total += l; +	} +} + +/* prepend compatible property strings with "omapdss," */ +static void __init omapdss_omapify_node(struct device_node *node) +{ +	struct property *prop; +	char *new_compat; +	int num_strs; +	int new_len; + +	prop = of_find_property(node, "compatible", NULL); + +	if (!prop || !prop->value) +		return; + +	if (strnlen(prop->value, prop->length) >= prop->length) +		return; + +	/* is it already prefixed? */ +	if (strncmp(prefix, prop->value, strlen(prefix)) == 0) +		return; + +	num_strs = omapdss_count_strings(prop); + +	new_len = prop->length + strlen(prefix) * num_strs; +	new_compat = kmalloc(new_len, GFP_KERNEL); + +	omapdss_prefix_strcpy(new_compat, new_len, prop->value, prop->length); + +	omapdss_update_prop(node, new_compat, new_len); +} + +static void __init omapdss_add_to_list(struct device_node *node, bool root) +{ +	struct dss_conv_node *n = kmalloc(sizeof(struct dss_conv_node), +		GFP_KERNEL); +	if (n) { +		n->node = node; +		n->root = root; +		list_add(&n->list, &dss_conv_list); +	} +} + +static bool __init omapdss_list_contains(const struct device_node *node) +{ +	struct dss_conv_node *n; + +	list_for_each_entry(n, &dss_conv_list, list) { +		if (n->node == node) +			return true; +	} + +	return false; +} + +static void __init omapdss_walk_device(struct device_node *node, bool root) +{ +	struct device_node *n; + +	omapdss_add_to_list(node, root); + +	/* +	 * of_graph_get_remote_port_parent() prints an error if there is no +	 * port/ports node. To avoid that, check first that there's the node. +	 */ +	n = of_get_child_by_name(node, "ports"); +	if (!n) +		n = of_get_child_by_name(node, "port"); +	if (!n) +		return; + +	of_node_put(n); + +	n = NULL; +	while ((n = of_graph_get_next_endpoint(node, n)) != NULL) { +		struct device_node *pn; + +		pn = of_graph_get_remote_port_parent(n); + +		if (!pn) { +			of_node_put(n); +			continue; +		} + +		if (!of_device_is_available(pn) || omapdss_list_contains(pn)) { +			of_node_put(pn); +			of_node_put(n); +			continue; +		} + +		omapdss_walk_device(pn, false); + +		of_node_put(n); +	} +} + +static const struct of_device_id omapdss_of_match[] __initconst = { +	{ .compatible = "ti,omap2-dss", }, +	{ .compatible = "ti,omap3-dss", }, +	{ .compatible = "ti,omap4-dss", }, +	{ .compatible = "ti,omap5-dss", }, +	{}, +}; + +static int __init omapdss_boot_init(void) +{ +	struct device_node *dss, *child; + +	INIT_LIST_HEAD(&dss_conv_list); + +	dss = of_find_matching_node(NULL, omapdss_of_match); + +	if (dss == NULL || !of_device_is_available(dss)) +		return 0; + +	omapdss_walk_device(dss, true); + +	for_each_available_child_of_node(dss, child) { +		if (!of_find_property(child, "compatible", NULL)) { +			of_node_put(child); +			continue; +		} + +		omapdss_walk_device(child, true); +	} + +	while (!list_empty(&dss_conv_list)) { +		struct dss_conv_node *n; + +		n = list_first_entry(&dss_conv_list, struct dss_conv_node, +			list); + +		if (!n->root) +			omapdss_omapify_node(n->node); + +		list_del(&n->list); +		of_node_put(n->node); +		kfree(n); +	} + +	return 0; +} + +subsys_initcall(omapdss_boot_init); diff --git a/drivers/video/fbdev/omap2/dss/output.c b/drivers/video/fbdev/omap2/dss/output.c new file mode 100644 index 00000000000..2ab3afa615e --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/output.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2012 Texas Instruments Ltd + * Author: Archit Taneja <archit@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/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" + +static LIST_HEAD(output_list); +static DEFINE_MUTEX(output_lock); + +int omapdss_output_set_device(struct omap_dss_device *out, +		struct omap_dss_device *dssdev) +{ +	int r; + +	mutex_lock(&output_lock); + +	if (out->dst) { +		DSSERR("output already has device %s connected to it\n", +			out->dst->name); +		r = -EINVAL; +		goto err; +	} + +	if (out->output_type != dssdev->type) { +		DSSERR("output type and display type don't match\n"); +		r = -EINVAL; +		goto err; +	} + +	out->dst = dssdev; +	dssdev->src = out; + +	mutex_unlock(&output_lock); + +	return 0; +err: +	mutex_unlock(&output_lock); + +	return r; +} +EXPORT_SYMBOL(omapdss_output_set_device); + +int omapdss_output_unset_device(struct omap_dss_device *out) +{ +	int r; + +	mutex_lock(&output_lock); + +	if (!out->dst) { +		DSSERR("output doesn't have a device connected to it\n"); +		r = -EINVAL; +		goto err; +	} + +	if (out->dst->state != OMAP_DSS_DISPLAY_DISABLED) { +		DSSERR("device %s is not disabled, cannot unset device\n", +				out->dst->name); +		r = -EINVAL; +		goto err; +	} + +	out->dst->src = NULL; +	out->dst = NULL; + +	mutex_unlock(&output_lock); + +	return 0; +err: +	mutex_unlock(&output_lock); + +	return r; +} +EXPORT_SYMBOL(omapdss_output_unset_device); + +int omapdss_register_output(struct omap_dss_device *out) +{ +	list_add_tail(&out->list, &output_list); +	return 0; +} +EXPORT_SYMBOL(omapdss_register_output); + +void omapdss_unregister_output(struct omap_dss_device *out) +{ +	list_del(&out->list); +} +EXPORT_SYMBOL(omapdss_unregister_output); + +struct omap_dss_device *omap_dss_get_output(enum omap_dss_output_id id) +{ +	struct omap_dss_device *out; + +	list_for_each_entry(out, &output_list, list) { +		if (out->id == id) +			return out; +	} + +	return NULL; +} +EXPORT_SYMBOL(omap_dss_get_output); + +struct omap_dss_device *omap_dss_find_output(const char *name) +{ +	struct omap_dss_device *out; + +	list_for_each_entry(out, &output_list, list) { +		if (strcmp(out->name, name) == 0) +			return omap_dss_get_device(out); +	} + +	return NULL; +} +EXPORT_SYMBOL(omap_dss_find_output); + +struct omap_dss_device *omap_dss_find_output_by_node(struct device_node *node) +{ +	struct omap_dss_device *out; + +	list_for_each_entry(out, &output_list, list) { +		if (out->dev->of_node == node) +			return omap_dss_get_device(out); +	} + +	return NULL; +} +EXPORT_SYMBOL(omap_dss_find_output_by_node); + +struct omap_dss_device *omapdss_find_output_from_display(struct omap_dss_device *dssdev) +{ +	while (dssdev->src) +		dssdev = dssdev->src; + +	if (dssdev->id != 0) +		return omap_dss_get_device(dssdev); + +	return NULL; +} +EXPORT_SYMBOL(omapdss_find_output_from_display); + +struct omap_overlay_manager *omapdss_find_mgr_from_display(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out; +	struct omap_overlay_manager *mgr; + +	out = omapdss_find_output_from_display(dssdev); + +	if (out == NULL) +		return NULL; + +	mgr = out->manager; + +	omap_dss_put_device(out); + +	return mgr; +} +EXPORT_SYMBOL(omapdss_find_mgr_from_display); + +static const struct dss_mgr_ops *dss_mgr_ops; + +int dss_install_mgr_ops(const struct dss_mgr_ops *mgr_ops) +{ +	if (dss_mgr_ops) +		return -EBUSY; + +	dss_mgr_ops = mgr_ops; + +	return 0; +} +EXPORT_SYMBOL(dss_install_mgr_ops); + +void dss_uninstall_mgr_ops(void) +{ +	dss_mgr_ops = NULL; +} +EXPORT_SYMBOL(dss_uninstall_mgr_ops); + +int dss_mgr_connect(struct omap_overlay_manager *mgr, +		struct omap_dss_device *dst) +{ +	return dss_mgr_ops->connect(mgr, dst); +} +EXPORT_SYMBOL(dss_mgr_connect); + +void dss_mgr_disconnect(struct omap_overlay_manager *mgr, +		struct omap_dss_device *dst) +{ +	dss_mgr_ops->disconnect(mgr, dst); +} +EXPORT_SYMBOL(dss_mgr_disconnect); + +void dss_mgr_set_timings(struct omap_overlay_manager *mgr, +		const struct omap_video_timings *timings) +{ +	dss_mgr_ops->set_timings(mgr, timings); +} +EXPORT_SYMBOL(dss_mgr_set_timings); + +void dss_mgr_set_lcd_config(struct omap_overlay_manager *mgr, +		const struct dss_lcd_mgr_config *config) +{ +	dss_mgr_ops->set_lcd_config(mgr, config); +} +EXPORT_SYMBOL(dss_mgr_set_lcd_config); + +int dss_mgr_enable(struct omap_overlay_manager *mgr) +{ +	return dss_mgr_ops->enable(mgr); +} +EXPORT_SYMBOL(dss_mgr_enable); + +void dss_mgr_disable(struct omap_overlay_manager *mgr) +{ +	dss_mgr_ops->disable(mgr); +} +EXPORT_SYMBOL(dss_mgr_disable); + +void dss_mgr_start_update(struct omap_overlay_manager *mgr) +{ +	dss_mgr_ops->start_update(mgr); +} +EXPORT_SYMBOL(dss_mgr_start_update); + +int dss_mgr_register_framedone_handler(struct omap_overlay_manager *mgr, +		void (*handler)(void *), void *data) +{ +	return dss_mgr_ops->register_framedone_handler(mgr, handler, data); +} +EXPORT_SYMBOL(dss_mgr_register_framedone_handler); + +void dss_mgr_unregister_framedone_handler(struct omap_overlay_manager *mgr, +		void (*handler)(void *), void *data) +{ +	dss_mgr_ops->unregister_framedone_handler(mgr, handler, data); +} +EXPORT_SYMBOL(dss_mgr_unregister_framedone_handler); diff --git a/drivers/video/fbdev/omap2/dss/overlay-sysfs.c b/drivers/video/fbdev/omap2/dss/overlay-sysfs.c new file mode 100644 index 00000000000..4cc5ddebfb3 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/overlay-sysfs.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "OVERLAY" + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/platform_device.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static ssize_t overlay_name_show(struct omap_overlay *ovl, char *buf) +{ +	return snprintf(buf, PAGE_SIZE, "%s\n", ovl->name); +} + +static ssize_t overlay_manager_show(struct omap_overlay *ovl, char *buf) +{ +	return snprintf(buf, PAGE_SIZE, "%s\n", +			ovl->manager ? ovl->manager->name : "<none>"); +} + +static ssize_t overlay_manager_store(struct omap_overlay *ovl, const char *buf, +		size_t size) +{ +	int i, r; +	struct omap_overlay_manager *mgr = NULL; +	struct omap_overlay_manager *old_mgr; +	int len = size; + +	if (buf[size-1] == '\n') +		--len; + +	if (len > 0) { +		for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { +			mgr = omap_dss_get_overlay_manager(i); + +			if (sysfs_streq(buf, mgr->name)) +				break; + +			mgr = NULL; +		} +	} + +	if (len > 0 && mgr == NULL) +		return -EINVAL; + +	if (mgr) +		DSSDBG("manager %s found\n", mgr->name); + +	if (mgr == ovl->manager) +		return size; + +	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"); +			goto err; +		} + +		r = old_mgr->apply(old_mgr); +		if (r) +			goto err; +	} + +	if (mgr) { +		r = ovl->set_manager(ovl, mgr); +		if (r) { +			DSSERR("Failed to attach overlay\n"); +			goto err; +		} + +		r = mgr->apply(mgr); +		if (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) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d,%d\n", +			info.width, info.height); +} + +static ssize_t overlay_screen_width_show(struct omap_overlay *ovl, char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d\n", info.screen_width); +} + +static ssize_t overlay_position_show(struct omap_overlay *ovl, char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d,%d\n", +			info.pos_x, info.pos_y); +} + +static ssize_t overlay_position_store(struct omap_overlay *ovl, +		const char *buf, size_t size) +{ +	int r; +	char *last; +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	info.pos_x = simple_strtoul(buf, &last, 10); +	++last; +	if (last - buf >= size) +		return -EINVAL; + +	info.pos_y = simple_strtoul(last, &last, 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_output_size_show(struct omap_overlay *ovl, char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d,%d\n", +			info.out_width, info.out_height); +} + +static ssize_t overlay_output_size_store(struct omap_overlay *ovl, +		const char *buf, size_t size) +{ +	int r; +	char *last; +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	info.out_width = simple_strtoul(buf, &last, 10); +	++last; +	if (last - buf >= size) +		return -EINVAL; + +	info.out_height = simple_strtoul(last, &last, 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_enabled_show(struct omap_overlay *ovl, char *buf) +{ +	return snprintf(buf, PAGE_SIZE, "%d\n", ovl->is_enabled(ovl)); +} + +static ssize_t overlay_enabled_store(struct omap_overlay *ovl, const char *buf, +		size_t size) +{ +	int r; +	bool enable; + +	r = strtobool(buf, &enable); +	if (r) +		return r; + +	if (enable) +		r = ovl->enable(ovl); +	else +		r = ovl->disable(ovl); + +	if (r) +		return r; + +	return size; +} + +static ssize_t overlay_global_alpha_show(struct omap_overlay *ovl, char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d\n", +			info.global_alpha); +} + +static ssize_t overlay_global_alpha_store(struct omap_overlay *ovl, +		const char *buf, size_t size) +{ +	int r; +	u8 alpha; +	struct omap_overlay_info info; + +	if ((ovl->caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0) +		return -ENODEV; + +	r = kstrtou8(buf, 0, &alpha); +	if (r) +		return r; + +	ovl->get_overlay_info(ovl, &info); + +	info.global_alpha = alpha; + +	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_pre_mult_alpha_show(struct omap_overlay *ovl, +		char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d\n", +			info.pre_mult_alpha); +} + +static ssize_t overlay_pre_mult_alpha_store(struct omap_overlay *ovl, +		const char *buf, size_t size) +{ +	int r; +	u8 alpha; +	struct omap_overlay_info info; + +	if ((ovl->caps & OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA) == 0) +		return -ENODEV; + +	r = kstrtou8(buf, 0, &alpha); +	if (r) +		return r; + +	ovl->get_overlay_info(ovl, &info); + +	info.pre_mult_alpha = alpha; + +	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_zorder_show(struct omap_overlay *ovl, char *buf) +{ +	struct omap_overlay_info info; + +	ovl->get_overlay_info(ovl, &info); + +	return snprintf(buf, PAGE_SIZE, "%d\n", info.zorder); +} + +static ssize_t overlay_zorder_store(struct omap_overlay *ovl, +		const char *buf, size_t size) +{ +	int r; +	u8 zorder; +	struct omap_overlay_info info; + +	if ((ovl->caps & OMAP_DSS_OVL_CAP_ZORDER) == 0) +		return -ENODEV; + +	r = kstrtou8(buf, 0, &zorder); +	if (r) +		return r; + +	ovl->get_overlay_info(ovl, &info); + +	info.zorder = zorder; + +	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; +} + +struct overlay_attribute { +	struct attribute attr; +	ssize_t (*show)(struct omap_overlay *, char *); +	ssize_t	(*store)(struct omap_overlay *, const char *, size_t); +}; + +#define OVERLAY_ATTR(_name, _mode, _show, _store) \ +	struct overlay_attribute overlay_attr_##_name = \ +	__ATTR(_name, _mode, _show, _store) + +static OVERLAY_ATTR(name, S_IRUGO, overlay_name_show, NULL); +static OVERLAY_ATTR(manager, S_IRUGO|S_IWUSR, +		overlay_manager_show, overlay_manager_store); +static OVERLAY_ATTR(input_size, S_IRUGO, overlay_input_size_show, NULL); +static OVERLAY_ATTR(screen_width, S_IRUGO, overlay_screen_width_show, NULL); +static OVERLAY_ATTR(position, S_IRUGO|S_IWUSR, +		overlay_position_show, overlay_position_store); +static OVERLAY_ATTR(output_size, S_IRUGO|S_IWUSR, +		overlay_output_size_show, overlay_output_size_store); +static OVERLAY_ATTR(enabled, S_IRUGO|S_IWUSR, +		overlay_enabled_show, overlay_enabled_store); +static OVERLAY_ATTR(global_alpha, S_IRUGO|S_IWUSR, +		overlay_global_alpha_show, overlay_global_alpha_store); +static OVERLAY_ATTR(pre_mult_alpha, S_IRUGO|S_IWUSR, +		overlay_pre_mult_alpha_show, +		overlay_pre_mult_alpha_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, +	&overlay_attr_manager.attr, +	&overlay_attr_input_size.attr, +	&overlay_attr_screen_width.attr, +	&overlay_attr_position.attr, +	&overlay_attr_output_size.attr, +	&overlay_attr_enabled.attr, +	&overlay_attr_global_alpha.attr, +	&overlay_attr_pre_mult_alpha.attr, +	&overlay_attr_zorder.attr, +	NULL +}; + +static ssize_t overlay_attr_show(struct kobject *kobj, struct attribute *attr, +		char *buf) +{ +	struct omap_overlay *overlay; +	struct overlay_attribute *overlay_attr; + +	overlay = container_of(kobj, struct omap_overlay, kobj); +	overlay_attr = container_of(attr, struct overlay_attribute, attr); + +	if (!overlay_attr->show) +		return -ENOENT; + +	return overlay_attr->show(overlay, buf); +} + +static ssize_t overlay_attr_store(struct kobject *kobj, struct attribute *attr, +		const char *buf, size_t size) +{ +	struct omap_overlay *overlay; +	struct overlay_attribute *overlay_attr; + +	overlay = container_of(kobj, struct omap_overlay, kobj); +	overlay_attr = container_of(attr, struct overlay_attribute, attr); + +	if (!overlay_attr->store) +		return -ENOENT; + +	return overlay_attr->store(overlay, buf, size); +} + +static const struct sysfs_ops overlay_sysfs_ops = { +	.show = overlay_attr_show, +	.store = overlay_attr_store, +}; + +static struct kobj_type overlay_ktype = { +	.sysfs_ops = &overlay_sysfs_ops, +	.default_attrs = overlay_sysfs_attrs, +}; + +int dss_overlay_kobj_init(struct omap_overlay *ovl, +		struct platform_device *pdev) +{ +	return kobject_init_and_add(&ovl->kobj, &overlay_ktype, +			&pdev->dev.kobj, "overlay%d", ovl->id); +} + +void dss_overlay_kobj_uninit(struct omap_overlay *ovl) +{ +	kobject_del(&ovl->kobj); +	kobject_put(&ovl->kobj); +} diff --git a/drivers/video/fbdev/omap2/dss/overlay.c b/drivers/video/fbdev/omap2/dss/overlay.c new file mode 100644 index 00000000000..2f7cee985cd --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/overlay.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/video/omap2/dss/overlay.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "OVERLAY" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +static int num_overlays; +static struct omap_overlay *overlays; + +int omap_dss_get_num_overlays(void) +{ +	return num_overlays; +} +EXPORT_SYMBOL(omap_dss_get_num_overlays); + +struct omap_overlay *omap_dss_get_overlay(int num) +{ +	if (num >= num_overlays) +		return NULL; + +	return &overlays[num]; +} +EXPORT_SYMBOL(omap_dss_get_overlay); + +void dss_init_overlays(struct platform_device *pdev) +{ +	int i, r; + +	num_overlays = dss_feat_get_num_ovls(); + +	overlays = kzalloc(sizeof(struct omap_overlay) * num_overlays, +			GFP_KERNEL); + +	BUG_ON(overlays == NULL); + +	for (i = 0; i < num_overlays; ++i) { +		struct omap_overlay *ovl = &overlays[i]; + +		switch (i) { +		case 0: +			ovl->name = "gfx"; +			ovl->id = OMAP_DSS_GFX; +			break; +		case 1: +			ovl->name = "vid1"; +			ovl->id = OMAP_DSS_VIDEO1; +			break; +		case 2: +			ovl->name = "vid2"; +			ovl->id = OMAP_DSS_VIDEO2; +			break; +		case 3: +			ovl->name = "vid3"; +			ovl->id = OMAP_DSS_VIDEO3; +			break; +		} + +		ovl->caps = dss_feat_get_overlay_caps(ovl->id); +		ovl->supported_modes = +			dss_feat_get_supported_color_modes(ovl->id); + +		r = dss_overlay_kobj_init(ovl, pdev); +		if (r) +			DSSERR("failed to create sysfs file\n"); +	} +} + +void dss_uninit_overlays(struct platform_device *pdev) +{ +	int i; + +	for (i = 0; i < num_overlays; ++i) { +		struct omap_overlay *ovl = &overlays[i]; +		dss_overlay_kobj_uninit(ovl); +	} + +	kfree(overlays); +	overlays = NULL; +	num_overlays = 0; +} + +int dss_ovl_simple_check(struct omap_overlay *ovl, +		const struct omap_overlay_info *info) +{ +	if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { +		if (info->out_width != 0 && info->width != info->out_width) { +			DSSERR("check_overlay: overlay %d doesn't support " +					"scaling\n", ovl->id); +			return -EINVAL; +		} + +		if (info->out_height != 0 && info->height != info->out_height) { +			DSSERR("check_overlay: overlay %d doesn't support " +					"scaling\n", ovl->id); +			return -EINVAL; +		} +	} + +	if ((ovl->supported_modes & info->color_mode) == 0) { +		DSSERR("check_overlay: overlay %d doesn't support mode %d\n", +				ovl->id, info->color_mode); +		return -EINVAL; +	} + +	if (info->zorder >= omap_dss_get_num_overlays()) { +		DSSERR("check_overlay: zorder %d too high\n", info->zorder); +		return -EINVAL; +	} + +	if (dss_feat_rotation_type_supported(info->rotation_type) == 0) { +		DSSERR("check_overlay: rotation type %d not supported\n", +				info->rotation_type); +		return -EINVAL; +	} + +	return 0; +} + +int dss_ovl_check(struct omap_overlay *ovl, struct omap_overlay_info *info, +		const struct omap_video_timings *mgr_timings) +{ +	u16 outw, outh; +	u16 dw, dh; + +	dw = mgr_timings->x_res; +	dh = mgr_timings->y_res; + +	if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { +		outw = info->width; +		outh = info->height; +	} else { +		if (info->out_width == 0) +			outw = info->width; +		else +			outw = info->out_width; + +		if (info->out_height == 0) +			outh = info->height; +		else +			outh = info->out_height; +	} + +	if (dw < info->pos_x + outw) { +		DSSERR("overlay %d horizontally not inside the display area " +				"(%d + %d >= %d)\n", +				ovl->id, info->pos_x, outw, dw); +		return -EINVAL; +	} + +	if (dh < info->pos_y + outh) { +		DSSERR("overlay %d vertically not inside the display area " +				"(%d + %d >= %d)\n", +				ovl->id, info->pos_y, outh, dh); +		return -EINVAL; +	} + +	return 0; +} + +/* + * Checks if replication logic should be used. Only use when overlay is in + * RGB12U or RGB16 mode, and video port width interface is 18bpp or 24bpp + */ +bool dss_ovl_use_replication(struct dss_lcd_mgr_config config, +		enum omap_color_mode mode) +{ +	if (mode != OMAP_DSS_COLOR_RGB12U && mode != OMAP_DSS_COLOR_RGB16) +		return false; + +	return config.video_port_width > 16; +} diff --git a/drivers/video/fbdev/omap2/dss/rfbi.c b/drivers/video/fbdev/omap2/dss/rfbi.c new file mode 100644 index 00000000000..c8a81a2b879 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/rfbi.c @@ -0,0 +1,1058 @@ +/* + * linux/drivers/video/omap2/dss/rfbi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * 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 DSS_SUBSYS_NAME "RFBI" + +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/export.h> +#include <linux/vmalloc.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/kfifo.h> +#include <linux/ktime.h> +#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" + +struct rfbi_reg { u16 idx; }; + +#define RFBI_REG(idx)		((const struct rfbi_reg) { idx }) + +#define RFBI_REVISION		RFBI_REG(0x0000) +#define RFBI_SYSCONFIG		RFBI_REG(0x0010) +#define RFBI_SYSSTATUS		RFBI_REG(0x0014) +#define RFBI_CONTROL		RFBI_REG(0x0040) +#define RFBI_PIXEL_CNT		RFBI_REG(0x0044) +#define RFBI_LINE_NUMBER	RFBI_REG(0x0048) +#define RFBI_CMD		RFBI_REG(0x004c) +#define RFBI_PARAM		RFBI_REG(0x0050) +#define RFBI_DATA		RFBI_REG(0x0054) +#define RFBI_READ		RFBI_REG(0x0058) +#define RFBI_STATUS		RFBI_REG(0x005c) + +#define RFBI_CONFIG(n)		RFBI_REG(0x0060 + (n)*0x18) +#define RFBI_ONOFF_TIME(n)	RFBI_REG(0x0064 + (n)*0x18) +#define RFBI_CYCLE_TIME(n)	RFBI_REG(0x0068 + (n)*0x18) +#define RFBI_DATA_CYCLE1(n)	RFBI_REG(0x006c + (n)*0x18) +#define RFBI_DATA_CYCLE2(n)	RFBI_REG(0x0070 + (n)*0x18) +#define RFBI_DATA_CYCLE3(n)	RFBI_REG(0x0074 + (n)*0x18) + +#define RFBI_VSYNC_WIDTH	RFBI_REG(0x0090) +#define RFBI_HSYNC_WIDTH	RFBI_REG(0x0094) + +#define REG_FLD_MOD(idx, val, start, end) \ +	rfbi_write_reg(idx, FLD_MOD(rfbi_read_reg(idx), val, start, end)) + +enum omap_rfbi_cycleformat { +	OMAP_DSS_RFBI_CYCLEFORMAT_1_1 = 0, +	OMAP_DSS_RFBI_CYCLEFORMAT_2_1 = 1, +	OMAP_DSS_RFBI_CYCLEFORMAT_3_1 = 2, +	OMAP_DSS_RFBI_CYCLEFORMAT_3_2 = 3, +}; + +enum omap_rfbi_datatype { +	OMAP_DSS_RFBI_DATATYPE_12 = 0, +	OMAP_DSS_RFBI_DATATYPE_16 = 1, +	OMAP_DSS_RFBI_DATATYPE_18 = 2, +	OMAP_DSS_RFBI_DATATYPE_24 = 3, +}; + +enum omap_rfbi_parallelmode { +	OMAP_DSS_RFBI_PARALLELMODE_8 = 0, +	OMAP_DSS_RFBI_PARALLELMODE_9 = 1, +	OMAP_DSS_RFBI_PARALLELMODE_12 = 2, +	OMAP_DSS_RFBI_PARALLELMODE_16 = 3, +}; + +static int rfbi_convert_timings(struct rfbi_timings *t); +static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div); + +static struct { +	struct platform_device *pdev; +	void __iomem	*base; + +	unsigned long	l4_khz; + +	enum omap_rfbi_datatype datatype; +	enum omap_rfbi_parallelmode parallelmode; + +	enum omap_rfbi_te_mode te_mode; +	int te_enabled; + +	void (*framedone_callback)(void *data); +	void *framedone_callback_data; + +	struct omap_dss_device *dssdev[2]; + +	struct semaphore bus_lock; + +	struct omap_video_timings timings; +	int pixel_size; +	int data_lines; +	struct rfbi_timings intf_timings; + +	struct omap_dss_device output; +} rfbi; + +static inline void rfbi_write_reg(const struct rfbi_reg idx, u32 val) +{ +	__raw_writel(val, rfbi.base + idx.idx); +} + +static inline u32 rfbi_read_reg(const struct rfbi_reg idx) +{ +	return __raw_readl(rfbi.base + idx.idx); +} + +static int rfbi_runtime_get(void) +{ +	int r; + +	DSSDBG("rfbi_runtime_get\n"); + +	r = pm_runtime_get_sync(&rfbi.pdev->dev); +	WARN_ON(r < 0); +	return r < 0 ? r : 0; +} + +static void rfbi_runtime_put(void) +{ +	int r; + +	DSSDBG("rfbi_runtime_put\n"); + +	r = pm_runtime_put_sync(&rfbi.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} + +static void rfbi_bus_lock(void) +{ +	down(&rfbi.bus_lock); +} + +static void rfbi_bus_unlock(void) +{ +	up(&rfbi.bus_lock); +} + +static void rfbi_write_command(const void *buf, u32 len) +{ +	switch (rfbi.parallelmode) { +	case OMAP_DSS_RFBI_PARALLELMODE_8: +	{ +		const u8 *b = buf; +		for (; len; len--) +			rfbi_write_reg(RFBI_CMD, *b++); +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_16: +	{ +		const u16 *w = buf; +		BUG_ON(len & 1); +		for (; len; len -= 2) +			rfbi_write_reg(RFBI_CMD, *w++); +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_9: +	case OMAP_DSS_RFBI_PARALLELMODE_12: +	default: +		BUG(); +	} +} + +static void rfbi_read_data(void *buf, u32 len) +{ +	switch (rfbi.parallelmode) { +	case OMAP_DSS_RFBI_PARALLELMODE_8: +	{ +		u8 *b = buf; +		for (; len; len--) { +			rfbi_write_reg(RFBI_READ, 0); +			*b++ = rfbi_read_reg(RFBI_READ); +		} +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_16: +	{ +		u16 *w = buf; +		BUG_ON(len & ~1); +		for (; len; len -= 2) { +			rfbi_write_reg(RFBI_READ, 0); +			*w++ = rfbi_read_reg(RFBI_READ); +		} +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_9: +	case OMAP_DSS_RFBI_PARALLELMODE_12: +	default: +		BUG(); +	} +} + +static void rfbi_write_data(const void *buf, u32 len) +{ +	switch (rfbi.parallelmode) { +	case OMAP_DSS_RFBI_PARALLELMODE_8: +	{ +		const u8 *b = buf; +		for (; len; len--) +			rfbi_write_reg(RFBI_PARAM, *b++); +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_16: +	{ +		const u16 *w = buf; +		BUG_ON(len & 1); +		for (; len; len -= 2) +			rfbi_write_reg(RFBI_PARAM, *w++); +		break; +	} + +	case OMAP_DSS_RFBI_PARALLELMODE_9: +	case OMAP_DSS_RFBI_PARALLELMODE_12: +	default: +		BUG(); + +	} +} + +static void rfbi_write_pixels(const void __iomem *buf, int scr_width, +		u16 x, u16 y, +		u16 w, u16 h) +{ +	int start_offset = scr_width * y + x; +	int horiz_offset = scr_width - w; +	int i; + +	if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_16 && +	   rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_8) { +		const u16 __iomem *pd = buf; +		pd += start_offset; + +		for (; h; --h) { +			for (i = 0; i < w; ++i) { +				const u8 __iomem *b = (const u8 __iomem *)pd; +				rfbi_write_reg(RFBI_PARAM, __raw_readb(b+1)); +				rfbi_write_reg(RFBI_PARAM, __raw_readb(b+0)); +				++pd; +			} +			pd += horiz_offset; +		} +	} else if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_24 && +	   rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_8) { +		const u32 __iomem *pd = buf; +		pd += start_offset; + +		for (; h; --h) { +			for (i = 0; i < w; ++i) { +				const u8 __iomem *b = (const u8 __iomem *)pd; +				rfbi_write_reg(RFBI_PARAM, __raw_readb(b+2)); +				rfbi_write_reg(RFBI_PARAM, __raw_readb(b+1)); +				rfbi_write_reg(RFBI_PARAM, __raw_readb(b+0)); +				++pd; +			} +			pd += horiz_offset; +		} +	} else if (rfbi.datatype == OMAP_DSS_RFBI_DATATYPE_16 && +	   rfbi.parallelmode == OMAP_DSS_RFBI_PARALLELMODE_16) { +		const u16 __iomem *pd = buf; +		pd += start_offset; + +		for (; h; --h) { +			for (i = 0; i < w; ++i) { +				rfbi_write_reg(RFBI_PARAM, __raw_readw(pd)); +				++pd; +			} +			pd += horiz_offset; +		} +	} else { +		BUG(); +	} +} + +static int rfbi_transfer_area(struct omap_dss_device *dssdev, +		void (*callback)(void *data), void *data) +{ +	u32 l; +	int r; +	struct omap_overlay_manager *mgr = rfbi.output.manager; +	u16 width = rfbi.timings.x_res; +	u16 height = rfbi.timings.y_res; + +	/*BUG_ON(callback == 0);*/ +	BUG_ON(rfbi.framedone_callback != NULL); + +	DSSDBG("rfbi_transfer_area %dx%d\n", width, height); + +	dss_mgr_set_timings(mgr, &rfbi.timings); + +	r = dss_mgr_enable(mgr); +	if (r) +		return r; + +	rfbi.framedone_callback = callback; +	rfbi.framedone_callback_data = data; + +	rfbi_write_reg(RFBI_PIXEL_CNT, width * height); + +	l = rfbi_read_reg(RFBI_CONTROL); +	l = FLD_MOD(l, 1, 0, 0); /* enable */ +	if (!rfbi.te_enabled) +		l = FLD_MOD(l, 1, 4, 4); /* ITE */ + +	rfbi_write_reg(RFBI_CONTROL, l); + +	return 0; +} + +static void framedone_callback(void *data) +{ +	void (*callback)(void *data); + +	DSSDBG("FRAMEDONE\n"); + +	REG_FLD_MOD(RFBI_CONTROL, 0, 0, 0); + +	callback = rfbi.framedone_callback; +	rfbi.framedone_callback = NULL; + +	if (callback != NULL) +		callback(rfbi.framedone_callback_data); +} + +#if 1 /* VERBOSE */ +static void rfbi_print_timings(void) +{ +	u32 l; +	u32 time; + +	l = rfbi_read_reg(RFBI_CONFIG(0)); +	time = 1000000000 / rfbi.l4_khz; +	if (l & (1 << 4)) +		time *= 2; + +	DSSDBG("Tick time %u ps\n", time); +	l = rfbi_read_reg(RFBI_ONOFF_TIME(0)); +	DSSDBG("CSONTIME %d, CSOFFTIME %d, WEONTIME %d, WEOFFTIME %d, " +		"REONTIME %d, REOFFTIME %d\n", +		l & 0x0f, (l >> 4) & 0x3f, (l >> 10) & 0x0f, (l >> 14) & 0x3f, +		(l >> 20) & 0x0f, (l >> 24) & 0x3f); + +	l = rfbi_read_reg(RFBI_CYCLE_TIME(0)); +	DSSDBG("WECYCLETIME %d, RECYCLETIME %d, CSPULSEWIDTH %d, " +		"ACCESSTIME %d\n", +		(l & 0x3f), (l >> 6) & 0x3f, (l >> 12) & 0x3f, +		(l >> 22) & 0x3f); +} +#else +static void rfbi_print_timings(void) {} +#endif + + + + +static u32 extif_clk_period; + +static inline unsigned long round_to_extif_ticks(unsigned long ps, int div) +{ +	int bus_tick = extif_clk_period * div; +	return (ps + bus_tick - 1) / bus_tick * bus_tick; +} + +static int calc_reg_timing(struct rfbi_timings *t, int div) +{ +	t->clk_div = div; + +	t->cs_on_time = round_to_extif_ticks(t->cs_on_time, div); + +	t->we_on_time = round_to_extif_ticks(t->we_on_time, div); +	t->we_off_time = round_to_extif_ticks(t->we_off_time, div); +	t->we_cycle_time = round_to_extif_ticks(t->we_cycle_time, div); + +	t->re_on_time = round_to_extif_ticks(t->re_on_time, div); +	t->re_off_time = round_to_extif_ticks(t->re_off_time, div); +	t->re_cycle_time = round_to_extif_ticks(t->re_cycle_time, div); + +	t->access_time = round_to_extif_ticks(t->access_time, div); +	t->cs_off_time = round_to_extif_ticks(t->cs_off_time, div); +	t->cs_pulse_width = round_to_extif_ticks(t->cs_pulse_width, div); + +	DSSDBG("[reg]cson %d csoff %d reon %d reoff %d\n", +	       t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time); +	DSSDBG("[reg]weon %d weoff %d recyc %d wecyc %d\n", +	       t->we_on_time, t->we_off_time, t->re_cycle_time, +	       t->we_cycle_time); +	DSSDBG("[reg]rdaccess %d cspulse %d\n", +	       t->access_time, t->cs_pulse_width); + +	return rfbi_convert_timings(t); +} + +static int calc_extif_timings(struct rfbi_timings *t) +{ +	u32 max_clk_div; +	int div; + +	rfbi_get_clk_info(&extif_clk_period, &max_clk_div); +	for (div = 1; div <= max_clk_div; div++) { +		if (calc_reg_timing(t, div) == 0) +			break; +	} + +	if (div <= max_clk_div) +		return 0; + +	DSSERR("can't setup timings\n"); +	return -1; +} + + +static void rfbi_set_timings(int rfbi_module, struct rfbi_timings *t) +{ +	int r; + +	if (!t->converted) { +		r = calc_extif_timings(t); +		if (r < 0) +			DSSERR("Failed to calc timings\n"); +	} + +	BUG_ON(!t->converted); + +	rfbi_write_reg(RFBI_ONOFF_TIME(rfbi_module), t->tim[0]); +	rfbi_write_reg(RFBI_CYCLE_TIME(rfbi_module), t->tim[1]); + +	/* TIMEGRANULARITY */ +	REG_FLD_MOD(RFBI_CONFIG(rfbi_module), +		    (t->tim[2] ? 1 : 0), 4, 4); + +	rfbi_print_timings(); +} + +static int ps_to_rfbi_ticks(int time, int div) +{ +	unsigned long tick_ps; +	int ret; + +	/* Calculate in picosecs to yield more exact results */ +	tick_ps = 1000000000 / (rfbi.l4_khz) * div; + +	ret = (time + tick_ps - 1) / tick_ps; + +	return ret; +} + +static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div) +{ +	*clk_period = 1000000000 / rfbi.l4_khz; +	*max_clk_div = 2; +} + +static int rfbi_convert_timings(struct rfbi_timings *t) +{ +	u32 l; +	int reon, reoff, weon, weoff, cson, csoff, cs_pulse; +	int actim, recyc, wecyc; +	int div = t->clk_div; + +	if (div <= 0 || div > 2) +		return -1; + +	/* Make sure that after conversion it still holds that: +	 * weoff > weon, reoff > reon, recyc >= reoff, wecyc >= weoff, +	 * csoff > cson, csoff >= max(weoff, reoff), actim > reon +	 */ +	weon = ps_to_rfbi_ticks(t->we_on_time, div); +	weoff = ps_to_rfbi_ticks(t->we_off_time, div); +	if (weoff <= weon) +		weoff = weon + 1; +	if (weon > 0x0f) +		return -1; +	if (weoff > 0x3f) +		return -1; + +	reon = ps_to_rfbi_ticks(t->re_on_time, div); +	reoff = ps_to_rfbi_ticks(t->re_off_time, div); +	if (reoff <= reon) +		reoff = reon + 1; +	if (reon > 0x0f) +		return -1; +	if (reoff > 0x3f) +		return -1; + +	cson = ps_to_rfbi_ticks(t->cs_on_time, div); +	csoff = ps_to_rfbi_ticks(t->cs_off_time, div); +	if (csoff <= cson) +		csoff = cson + 1; +	if (csoff < max(weoff, reoff)) +		csoff = max(weoff, reoff); +	if (cson > 0x0f) +		return -1; +	if (csoff > 0x3f) +		return -1; + +	l =  cson; +	l |= csoff << 4; +	l |= weon  << 10; +	l |= weoff << 14; +	l |= reon  << 20; +	l |= reoff << 24; + +	t->tim[0] = l; + +	actim = ps_to_rfbi_ticks(t->access_time, div); +	if (actim <= reon) +		actim = reon + 1; +	if (actim > 0x3f) +		return -1; + +	wecyc = ps_to_rfbi_ticks(t->we_cycle_time, div); +	if (wecyc < weoff) +		wecyc = weoff; +	if (wecyc > 0x3f) +		return -1; + +	recyc = ps_to_rfbi_ticks(t->re_cycle_time, div); +	if (recyc < reoff) +		recyc = reoff; +	if (recyc > 0x3f) +		return -1; + +	cs_pulse = ps_to_rfbi_ticks(t->cs_pulse_width, div); +	if (cs_pulse > 0x3f) +		return -1; + +	l =  wecyc; +	l |= recyc    << 6; +	l |= cs_pulse << 12; +	l |= actim    << 22; + +	t->tim[1] = l; + +	t->tim[2] = div - 1; + +	t->converted = 1; + +	return 0; +} + +/* xxx FIX module selection missing */ +static int rfbi_setup_te(enum omap_rfbi_te_mode mode, +			     unsigned hs_pulse_time, unsigned vs_pulse_time, +			     int hs_pol_inv, int vs_pol_inv, int extif_div) +{ +	int hs, vs; +	int min; +	u32 l; + +	hs = ps_to_rfbi_ticks(hs_pulse_time, 1); +	vs = ps_to_rfbi_ticks(vs_pulse_time, 1); +	if (hs < 2) +		return -EDOM; +	if (mode == OMAP_DSS_RFBI_TE_MODE_2) +		min = 2; +	else /* OMAP_DSS_RFBI_TE_MODE_1 */ +		min = 4; +	if (vs < min) +		return -EDOM; +	if (vs == hs) +		return -EINVAL; +	rfbi.te_mode = mode; +	DSSDBG("setup_te: mode %d hs %d vs %d hs_inv %d vs_inv %d\n", +		mode, hs, vs, hs_pol_inv, vs_pol_inv); + +	rfbi_write_reg(RFBI_HSYNC_WIDTH, hs); +	rfbi_write_reg(RFBI_VSYNC_WIDTH, vs); + +	l = rfbi_read_reg(RFBI_CONFIG(0)); +	if (hs_pol_inv) +		l &= ~(1 << 21); +	else +		l |= 1 << 21; +	if (vs_pol_inv) +		l &= ~(1 << 20); +	else +		l |= 1 << 20; + +	return 0; +} + +/* xxx FIX module selection missing */ +static int rfbi_enable_te(bool enable, unsigned line) +{ +	u32 l; + +	DSSDBG("te %d line %d mode %d\n", enable, line, rfbi.te_mode); +	if (line > (1 << 11) - 1) +		return -EINVAL; + +	l = rfbi_read_reg(RFBI_CONFIG(0)); +	l &= ~(0x3 << 2); +	if (enable) { +		rfbi.te_enabled = 1; +		l |= rfbi.te_mode << 2; +	} else +		rfbi.te_enabled = 0; +	rfbi_write_reg(RFBI_CONFIG(0), l); +	rfbi_write_reg(RFBI_LINE_NUMBER, line); + +	return 0; +} + +static int rfbi_configure_bus(int rfbi_module, int bpp, int lines) +{ +	u32 l; +	int cycle1 = 0, cycle2 = 0, cycle3 = 0; +	enum omap_rfbi_cycleformat cycleformat; +	enum omap_rfbi_datatype datatype; +	enum omap_rfbi_parallelmode parallelmode; + +	switch (bpp) { +	case 12: +		datatype = OMAP_DSS_RFBI_DATATYPE_12; +		break; +	case 16: +		datatype = OMAP_DSS_RFBI_DATATYPE_16; +		break; +	case 18: +		datatype = OMAP_DSS_RFBI_DATATYPE_18; +		break; +	case 24: +		datatype = OMAP_DSS_RFBI_DATATYPE_24; +		break; +	default: +		BUG(); +		return 1; +	} +	rfbi.datatype = datatype; + +	switch (lines) { +	case 8: +		parallelmode = OMAP_DSS_RFBI_PARALLELMODE_8; +		break; +	case 9: +		parallelmode = OMAP_DSS_RFBI_PARALLELMODE_9; +		break; +	case 12: +		parallelmode = OMAP_DSS_RFBI_PARALLELMODE_12; +		break; +	case 16: +		parallelmode = OMAP_DSS_RFBI_PARALLELMODE_16; +		break; +	default: +		BUG(); +		return 1; +	} +	rfbi.parallelmode = parallelmode; + +	if ((bpp % lines) == 0) { +		switch (bpp / lines) { +		case 1: +			cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_1_1; +			break; +		case 2: +			cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_2_1; +			break; +		case 3: +			cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_3_1; +			break; +		default: +			BUG(); +			return 1; +		} +	} else if ((2 * bpp % lines) == 0) { +		if ((2 * bpp / lines) == 3) +			cycleformat = OMAP_DSS_RFBI_CYCLEFORMAT_3_2; +		else { +			BUG(); +			return 1; +		} +	} else { +		BUG(); +		return 1; +	} + +	switch (cycleformat) { +	case OMAP_DSS_RFBI_CYCLEFORMAT_1_1: +		cycle1 = lines; +		break; + +	case OMAP_DSS_RFBI_CYCLEFORMAT_2_1: +		cycle1 = lines; +		cycle2 = lines; +		break; + +	case OMAP_DSS_RFBI_CYCLEFORMAT_3_1: +		cycle1 = lines; +		cycle2 = lines; +		cycle3 = lines; +		break; + +	case OMAP_DSS_RFBI_CYCLEFORMAT_3_2: +		cycle1 = lines; +		cycle2 = (lines / 2) | ((lines / 2) << 16); +		cycle3 = (lines << 16); +		break; +	} + +	REG_FLD_MOD(RFBI_CONTROL, 0, 3, 2); /* clear CS */ + +	l = 0; +	l |= FLD_VAL(parallelmode, 1, 0); +	l |= FLD_VAL(0, 3, 2);		/* TRIGGERMODE: ITE */ +	l |= FLD_VAL(0, 4, 4);		/* TIMEGRANULARITY */ +	l |= FLD_VAL(datatype, 6, 5); +	/* l |= FLD_VAL(2, 8, 7); */	/* L4FORMAT, 2pix/L4 */ +	l |= FLD_VAL(0, 8, 7);	/* L4FORMAT, 1pix/L4 */ +	l |= FLD_VAL(cycleformat, 10, 9); +	l |= FLD_VAL(0, 12, 11);	/* UNUSEDBITS */ +	l |= FLD_VAL(0, 16, 16);	/* A0POLARITY */ +	l |= FLD_VAL(0, 17, 17);	/* REPOLARITY */ +	l |= FLD_VAL(0, 18, 18);	/* WEPOLARITY */ +	l |= FLD_VAL(0, 19, 19);	/* CSPOLARITY */ +	l |= FLD_VAL(1, 20, 20);	/* TE_VSYNC_POLARITY */ +	l |= FLD_VAL(1, 21, 21);	/* HSYNCPOLARITY */ +	rfbi_write_reg(RFBI_CONFIG(rfbi_module), l); + +	rfbi_write_reg(RFBI_DATA_CYCLE1(rfbi_module), cycle1); +	rfbi_write_reg(RFBI_DATA_CYCLE2(rfbi_module), cycle2); +	rfbi_write_reg(RFBI_DATA_CYCLE3(rfbi_module), cycle3); + + +	l = rfbi_read_reg(RFBI_CONTROL); +	l = FLD_MOD(l, rfbi_module+1, 3, 2); /* Select CSx */ +	l = FLD_MOD(l, 0, 1, 1); /* clear bypass */ +	rfbi_write_reg(RFBI_CONTROL, l); + + +	DSSDBG("RFBI config: bpp %d, lines %d, cycles: 0x%x 0x%x 0x%x\n", +	       bpp, lines, cycle1, cycle2, cycle3); + +	return 0; +} + +static int rfbi_configure(struct omap_dss_device *dssdev) +{ +	return rfbi_configure_bus(dssdev->phy.rfbi.channel, rfbi.pixel_size, +			rfbi.data_lines); +} + +static int rfbi_update(struct omap_dss_device *dssdev, void (*callback)(void *), +		void *data) +{ +	return rfbi_transfer_area(dssdev, callback, data); +} + +static void rfbi_set_size(struct omap_dss_device *dssdev, u16 w, u16 h) +{ +	rfbi.timings.x_res = w; +	rfbi.timings.y_res = h; +} + +static void rfbi_set_pixel_size(struct omap_dss_device *dssdev, int pixel_size) +{ +	rfbi.pixel_size = pixel_size; +} + +static void rfbi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) +{ +	rfbi.data_lines = data_lines; +} + +static void rfbi_set_interface_timings(struct omap_dss_device *dssdev, +		struct rfbi_timings *timings) +{ +	rfbi.intf_timings = *timings; +} + +static void rfbi_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, rfbi_read_reg(r)) + +	if (rfbi_runtime_get()) +		return; + +	DUMPREG(RFBI_REVISION); +	DUMPREG(RFBI_SYSCONFIG); +	DUMPREG(RFBI_SYSSTATUS); +	DUMPREG(RFBI_CONTROL); +	DUMPREG(RFBI_PIXEL_CNT); +	DUMPREG(RFBI_LINE_NUMBER); +	DUMPREG(RFBI_CMD); +	DUMPREG(RFBI_PARAM); +	DUMPREG(RFBI_DATA); +	DUMPREG(RFBI_READ); +	DUMPREG(RFBI_STATUS); + +	DUMPREG(RFBI_CONFIG(0)); +	DUMPREG(RFBI_ONOFF_TIME(0)); +	DUMPREG(RFBI_CYCLE_TIME(0)); +	DUMPREG(RFBI_DATA_CYCLE1(0)); +	DUMPREG(RFBI_DATA_CYCLE2(0)); +	DUMPREG(RFBI_DATA_CYCLE3(0)); + +	DUMPREG(RFBI_CONFIG(1)); +	DUMPREG(RFBI_ONOFF_TIME(1)); +	DUMPREG(RFBI_CYCLE_TIME(1)); +	DUMPREG(RFBI_DATA_CYCLE1(1)); +	DUMPREG(RFBI_DATA_CYCLE2(1)); +	DUMPREG(RFBI_DATA_CYCLE3(1)); + +	DUMPREG(RFBI_VSYNC_WIDTH); +	DUMPREG(RFBI_HSYNC_WIDTH); + +	rfbi_runtime_put(); +#undef DUMPREG +} + +static void rfbi_config_lcd_manager(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = rfbi.output.manager; +	struct dss_lcd_mgr_config mgr_config; + +	mgr_config.io_pad_mode = DSS_IO_PAD_MODE_RFBI; + +	mgr_config.stallmode = true; +	/* Do we need fifohandcheck for RFBI? */ +	mgr_config.fifohandcheck = false; + +	mgr_config.video_port_width = rfbi.pixel_size; +	mgr_config.lcden_sig_polarity = 0; + +	dss_mgr_set_lcd_config(mgr, &mgr_config); + +	/* +	 * Set rfbi.timings with default values, the x_res and y_res fields +	 * are expected to be already configured by the panel driver via +	 * omapdss_rfbi_set_size() +	 */ +	rfbi.timings.hsw = 1; +	rfbi.timings.hfp = 1; +	rfbi.timings.hbp = 1; +	rfbi.timings.vsw = 1; +	rfbi.timings.vfp = 0; +	rfbi.timings.vbp = 0; + +	rfbi.timings.interlace = false; +	rfbi.timings.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; +	rfbi.timings.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; +	rfbi.timings.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; +	rfbi.timings.de_level = OMAPDSS_SIG_ACTIVE_HIGH; +	rfbi.timings.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + +	dss_mgr_set_timings(mgr, &rfbi.timings); +} + +static int rfbi_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &rfbi.output; +	int r; + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		return -ENODEV; +	} + +	r = rfbi_runtime_get(); +	if (r) +		return r; + +	r = dss_mgr_register_framedone_handler(out->manager, +			framedone_callback, NULL); +	if (r) { +		DSSERR("can't get FRAMEDONE irq\n"); +		goto err1; +	} + +	rfbi_config_lcd_manager(dssdev); + +	rfbi_configure_bus(dssdev->phy.rfbi.channel, rfbi.pixel_size, +			rfbi.data_lines); + +	rfbi_set_timings(dssdev->phy.rfbi.channel, &rfbi.intf_timings); + +	return 0; +err1: +	rfbi_runtime_put(); +	return r; +} + +static void rfbi_display_disable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &rfbi.output; + +	dss_mgr_unregister_framedone_handler(out->manager, +			framedone_callback, NULL); + +	rfbi_runtime_put(); +} + +static int rfbi_init_display(struct omap_dss_device *dssdev) +{ +	rfbi.dssdev[dssdev->phy.rfbi.channel] = dssdev; +	return 0; +} + +static void rfbi_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &rfbi.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_DBI; +	out->output_type = OMAP_DISPLAY_TYPE_DBI; +	out->name = "rfbi.0"; +	out->dispc_channel = OMAP_DSS_CHANNEL_LCD; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit rfbi_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &rfbi.output; + +	omapdss_unregister_output(out); +} + +/* RFBI HW IP initialisation */ +static int omap_rfbihw_probe(struct platform_device *pdev) +{ +	u32 rev; +	struct resource *rfbi_mem; +	struct clk *clk; +	int r; + +	rfbi.pdev = pdev; + +	sema_init(&rfbi.bus_lock, 1); + +	rfbi_mem = platform_get_resource(rfbi.pdev, IORESOURCE_MEM, 0); +	if (!rfbi_mem) { +		DSSERR("can't get IORESOURCE_MEM RFBI\n"); +		return -EINVAL; +	} + +	rfbi.base = devm_ioremap(&pdev->dev, rfbi_mem->start, +				 resource_size(rfbi_mem)); +	if (!rfbi.base) { +		DSSERR("can't ioremap RFBI\n"); +		return -ENOMEM; +	} + +	clk = clk_get(&pdev->dev, "ick"); +	if (IS_ERR(clk)) { +		DSSERR("can't get ick\n"); +		return PTR_ERR(clk); +	} + +	rfbi.l4_khz = clk_get_rate(clk) / 1000; + +	clk_put(clk); + +	pm_runtime_enable(&pdev->dev); + +	r = rfbi_runtime_get(); +	if (r) +		goto err_runtime_get; + +	msleep(10); + +	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_runtime_put(); + +	dss_debugfs_create_file("rfbi", rfbi_dump_regs); + +	rfbi_init_output(pdev); + +	return 0; + +err_runtime_get: +	pm_runtime_disable(&pdev->dev); +	return r; +} + +static int __exit omap_rfbihw_remove(struct platform_device *pdev) +{ +	rfbi_uninit_output(pdev); + +	pm_runtime_disable(&pdev->dev); + +	return 0; +} + +static int rfbi_runtime_suspend(struct device *dev) +{ +	dispc_runtime_put(); + +	return 0; +} + +static int rfbi_runtime_resume(struct device *dev) +{ +	int r; + +	r = dispc_runtime_get(); +	if (r < 0) +		return r; + +	return 0; +} + +static const struct dev_pm_ops rfbi_pm_ops = { +	.runtime_suspend = rfbi_runtime_suspend, +	.runtime_resume = rfbi_runtime_resume, +}; + +static struct platform_driver omap_rfbihw_driver = { +	.probe		= omap_rfbihw_probe, +	.remove         = __exit_p(omap_rfbihw_remove), +	.driver         = { +		.name   = "omapdss_rfbi", +		.owner  = THIS_MODULE, +		.pm	= &rfbi_pm_ops, +	}, +}; + +int __init rfbi_init_platform_driver(void) +{ +	return platform_driver_register(&omap_rfbihw_driver); +} + +void __exit rfbi_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_rfbihw_driver); +} diff --git a/drivers/video/fbdev/omap2/dss/sdi.c b/drivers/video/fbdev/omap2/dss/sdi.c new file mode 100644 index 00000000000..911dcc9173a --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/sdi.c @@ -0,0 +1,433 @@ +/* + * linux/drivers/video/omap2/dss/sdi.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.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 DSS_SUBSYS_NAME "SDI" + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/regulator/consumer.h> +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/of.h> + +#include <video/omapdss.h> +#include "dss.h" + +static struct { +	struct platform_device *pdev; + +	bool update_enabled; +	struct regulator *vdds_sdi_reg; + +	struct dss_lcd_mgr_config mgr_config; +	struct omap_video_timings timings; +	int datapairs; + +	struct omap_dss_device output; + +	bool port_initialized; +} sdi; + +struct sdi_clk_calc_ctx { +	unsigned long pck_min, pck_max; + +	unsigned long fck; +	struct dispc_clock_info dispc_cinfo; +}; + +static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, +		unsigned long pck, void *data) +{ +	struct sdi_clk_calc_ctx *ctx = data; + +	ctx->dispc_cinfo.lck_div = lckd; +	ctx->dispc_cinfo.pck_div = pckd; +	ctx->dispc_cinfo.lck = lck; +	ctx->dispc_cinfo.pck = pck; + +	return true; +} + +static bool dpi_calc_dss_cb(unsigned long fck, void *data) +{ +	struct sdi_clk_calc_ctx *ctx = data; + +	ctx->fck = fck; + +	return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, +			dpi_calc_dispc_cb, ctx); +} + +static int sdi_calc_clock_div(unsigned long pclk, +		unsigned long *fck, +		struct dispc_clock_info *dispc_cinfo) +{ +	int i; +	struct sdi_clk_calc_ctx ctx; + +	/* +	 * DSS fclk gives us very few possibilities, so finding a good pixel +	 * clock may not be possible. We try multiple times to find the clock, +	 * each time widening the pixel clock range we look for, up to +	 * +/- 1MHz. +	 */ + +	for (i = 0; i < 10; ++i) { +		bool ok; + +		memset(&ctx, 0, sizeof(ctx)); +		if (pclk > 1000 * i * i * i) +			ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu); +		else +			ctx.pck_min = 0; +		ctx.pck_max = pclk + 1000 * i * i * i; + +		ok = dss_div_calc(pclk, ctx.pck_min, dpi_calc_dss_cb, &ctx); +		if (ok) { +			*fck = ctx.fck; +			*dispc_cinfo = ctx.dispc_cinfo; +			return 0; +		} +	} + +	return -EINVAL; +} + +static void sdi_config_lcd_manager(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = sdi.output.manager; + +	sdi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + +	sdi.mgr_config.stallmode = false; +	sdi.mgr_config.fifohandcheck = false; + +	sdi.mgr_config.video_port_width = 24; +	sdi.mgr_config.lcden_sig_polarity = 1; + +	dss_mgr_set_lcd_config(mgr, &sdi.mgr_config); +} + +static int sdi_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &sdi.output; +	struct omap_video_timings *t = &sdi.timings; +	unsigned long fck; +	struct dispc_clock_info dispc_cinfo; +	unsigned long pck; +	int r; + +	if (out == NULL || out->manager == NULL) { +		DSSERR("failed to enable display: no output/manager\n"); +		return -ENODEV; +	} + +	r = regulator_enable(sdi.vdds_sdi_reg); +	if (r) +		goto err_reg_enable; + +	r = dispc_runtime_get(); +	if (r) +		goto err_get_dispc; + +	/* 15.5.9.1.2 */ +	t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; +	t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + +	r = sdi_calc_clock_div(t->pixelclock, &fck, &dispc_cinfo); +	if (r) +		goto err_calc_clock_div; + +	sdi.mgr_config.clock_info = dispc_cinfo; + +	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div; + +	if (pck != t->pixelclock) { +		DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", +			t->pixelclock, pck); + +		t->pixelclock = pck; +	} + + +	dss_mgr_set_timings(out->manager, t); + +	r = dss_set_fck_rate(fck); +	if (r) +		goto err_set_dss_clock_div; + +	sdi_config_lcd_manager(dssdev); + +	/* +	 * LCLK and PCLK divisors are located in shadow registers, and we +	 * normally write them to DISPC registers when enabling the output. +	 * However, SDI uses pck-free as source clock for its PLL, and pck-free +	 * is affected by the divisors. And as we need the PLL before enabling +	 * the output, we need to write the divisors early. +	 * +	 * It seems just writing to the DISPC register is enough, and we don't +	 * need to care about the shadow register mechanism for pck-free. The +	 * exact reason for this is unknown. +	 */ +	dispc_mgr_set_clock_div(out->manager->id, &sdi.mgr_config.clock_info); + +	dss_sdi_init(sdi.datapairs); +	r = dss_sdi_enable(); +	if (r) +		goto err_sdi_enable; +	mdelay(2); + +	r = dss_mgr_enable(out->manager); +	if (r) +		goto err_mgr_enable; + +	return 0; + +err_mgr_enable: +	dss_sdi_disable(); +err_sdi_enable: +err_set_dss_clock_div: +err_calc_clock_div: +	dispc_runtime_put(); +err_get_dispc: +	regulator_disable(sdi.vdds_sdi_reg); +err_reg_enable: +	return r; +} + +static void sdi_display_disable(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = sdi.output.manager; + +	dss_mgr_disable(mgr); + +	dss_sdi_disable(); + +	dispc_runtime_put(); + +	regulator_disable(sdi.vdds_sdi_reg); +} + +static void sdi_set_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	sdi.timings = *timings; +} + +static void sdi_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	*timings = sdi.timings; +} + +static int sdi_check_timings(struct omap_dss_device *dssdev, +			struct omap_video_timings *timings) +{ +	struct omap_overlay_manager *mgr = sdi.output.manager; + +	if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) +		return -EINVAL; + +	if (timings->pixelclock == 0) +		return -EINVAL; + +	return 0; +} + +static void sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs) +{ +	sdi.datapairs = datapairs; +} + +static int sdi_init_regulator(void) +{ +	struct regulator *vdds_sdi; + +	if (sdi.vdds_sdi_reg) +		return 0; + +	vdds_sdi = devm_regulator_get(&sdi.pdev->dev, "vdds_sdi"); +	if (IS_ERR(vdds_sdi)) { +		if (PTR_ERR(vdds_sdi) != -EPROBE_DEFER) +			DSSERR("can't get VDDS_SDI regulator\n"); +		return PTR_ERR(vdds_sdi); +	} + +	sdi.vdds_sdi_reg = vdds_sdi; + +	return 0; +} + +static int sdi_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct omap_overlay_manager *mgr; +	int r; + +	r = sdi_init_regulator(); +	if (r) +		return r; + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dst->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void sdi_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_sdi_ops sdi_ops = { +	.connect = sdi_connect, +	.disconnect = sdi_disconnect, + +	.enable = sdi_display_enable, +	.disable = sdi_display_disable, + +	.check_timings = sdi_check_timings, +	.set_timings = sdi_set_timings, +	.get_timings = sdi_get_timings, + +	.set_datapairs = sdi_set_datapairs, +}; + +static void sdi_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &sdi.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_SDI; +	out->output_type = OMAP_DISPLAY_TYPE_SDI; +	out->name = "sdi.0"; +	out->dispc_channel = OMAP_DSS_CHANNEL_LCD; +	out->ops.sdi = &sdi_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit sdi_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &sdi.output; + +	omapdss_unregister_output(out); +} + +static int omap_sdi_probe(struct platform_device *pdev) +{ +	sdi.pdev = pdev; + +	sdi_init_output(pdev); + +	return 0; +} + +static int __exit omap_sdi_remove(struct platform_device *pdev) +{ +	sdi_uninit_output(pdev); + +	return 0; +} + +static struct platform_driver omap_sdi_driver = { +	.probe		= omap_sdi_probe, +	.remove         = __exit_p(omap_sdi_remove), +	.driver         = { +		.name   = "omapdss_sdi", +		.owner  = THIS_MODULE, +	}, +}; + +int __init sdi_init_platform_driver(void) +{ +	return platform_driver_register(&omap_sdi_driver); +} + +void __exit sdi_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_sdi_driver); +} + +int __init sdi_init_port(struct platform_device *pdev, struct device_node *port) +{ +	struct device_node *ep; +	u32 datapairs; +	int r; + +	ep = omapdss_of_get_next_endpoint(port, NULL); +	if (!ep) +		return 0; + +	r = of_property_read_u32(ep, "datapairs", &datapairs); +	if (r) { +		DSSERR("failed to parse datapairs\n"); +		goto err_datapairs; +	} + +	sdi.datapairs = datapairs; + +	of_node_put(ep); + +	sdi.pdev = pdev; + +	sdi_init_output(pdev); + +	sdi.port_initialized = true; + +	return 0; + +err_datapairs: +	of_node_put(ep); + +	return r; +} + +void __exit sdi_uninit_port(void) +{ +	if (!sdi.port_initialized) +		return; + +	sdi_uninit_output(sdi.pdev); +} diff --git a/drivers/video/fbdev/omap2/dss/venc.c b/drivers/video/fbdev/omap2/dss/venc.c new file mode 100644 index 00000000000..21d81113962 --- /dev/null +++ b/drivers/video/fbdev/omap2/dss/venc.c @@ -0,0 +1,980 @@ +/* + * linux/drivers/video/omap2/dss/venc.c + * + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * VENC settings from TI's DSS driver + * + * 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 DSS_SUBSYS_NAME "VENC" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> + +#include <video/omapdss.h> + +#include "dss.h" +#include "dss_features.h" + +/* Venc registers */ +#define VENC_REV_ID				0x00 +#define VENC_STATUS				0x04 +#define VENC_F_CONTROL				0x08 +#define VENC_VIDOUT_CTRL			0x10 +#define VENC_SYNC_CTRL				0x14 +#define VENC_LLEN				0x1C +#define VENC_FLENS				0x20 +#define VENC_HFLTR_CTRL				0x24 +#define VENC_CC_CARR_WSS_CARR			0x28 +#define VENC_C_PHASE				0x2C +#define VENC_GAIN_U				0x30 +#define VENC_GAIN_V				0x34 +#define VENC_GAIN_Y				0x38 +#define VENC_BLACK_LEVEL			0x3C +#define VENC_BLANK_LEVEL			0x40 +#define VENC_X_COLOR				0x44 +#define VENC_M_CONTROL				0x48 +#define VENC_BSTAMP_WSS_DATA			0x4C +#define VENC_S_CARR				0x50 +#define VENC_LINE21				0x54 +#define VENC_LN_SEL				0x58 +#define VENC_L21__WC_CTL			0x5C +#define VENC_HTRIGGER_VTRIGGER			0x60 +#define VENC_SAVID__EAVID			0x64 +#define VENC_FLEN__FAL				0x68 +#define VENC_LAL__PHASE_RESET			0x6C +#define VENC_HS_INT_START_STOP_X		0x70 +#define VENC_HS_EXT_START_STOP_X		0x74 +#define VENC_VS_INT_START_X			0x78 +#define VENC_VS_INT_STOP_X__VS_INT_START_Y	0x7C +#define VENC_VS_INT_STOP_Y__VS_EXT_START_X	0x80 +#define VENC_VS_EXT_STOP_X__VS_EXT_START_Y	0x84 +#define VENC_VS_EXT_STOP_Y			0x88 +#define VENC_AVID_START_STOP_X			0x90 +#define VENC_AVID_START_STOP_Y			0x94 +#define VENC_FID_INT_START_X__FID_INT_START_Y	0xA0 +#define VENC_FID_INT_OFFSET_Y__FID_EXT_START_X	0xA4 +#define VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y	0xA8 +#define VENC_TVDETGP_INT_START_STOP_X		0xB0 +#define VENC_TVDETGP_INT_START_STOP_Y		0xB4 +#define VENC_GEN_CTRL				0xB8 +#define VENC_OUTPUT_CONTROL			0xC4 +#define VENC_OUTPUT_TEST			0xC8 +#define VENC_DAC_B__DAC_C			0xC8 + +struct venc_config { +	u32 f_control; +	u32 vidout_ctrl; +	u32 sync_ctrl; +	u32 llen; +	u32 flens; +	u32 hfltr_ctrl; +	u32 cc_carr_wss_carr; +	u32 c_phase; +	u32 gain_u; +	u32 gain_v; +	u32 gain_y; +	u32 black_level; +	u32 blank_level; +	u32 x_color; +	u32 m_control; +	u32 bstamp_wss_data; +	u32 s_carr; +	u32 line21; +	u32 ln_sel; +	u32 l21__wc_ctl; +	u32 htrigger_vtrigger; +	u32 savid__eavid; +	u32 flen__fal; +	u32 lal__phase_reset; +	u32 hs_int_start_stop_x; +	u32 hs_ext_start_stop_x; +	u32 vs_int_start_x; +	u32 vs_int_stop_x__vs_int_start_y; +	u32 vs_int_stop_y__vs_ext_start_x; +	u32 vs_ext_stop_x__vs_ext_start_y; +	u32 vs_ext_stop_y; +	u32 avid_start_stop_x; +	u32 avid_start_stop_y; +	u32 fid_int_start_x__fid_int_start_y; +	u32 fid_int_offset_y__fid_ext_start_x; +	u32 fid_ext_start_y__fid_ext_offset_y; +	u32 tvdetgp_int_start_stop_x; +	u32 tvdetgp_int_start_stop_y; +	u32 gen_ctrl; +}; + +/* from TRM */ +static const struct venc_config venc_config_pal_trm = { +	.f_control				= 0, +	.vidout_ctrl				= 1, +	.sync_ctrl				= 0x40, +	.llen					= 0x35F, /* 863 */ +	.flens					= 0x270, /* 624 */ +	.hfltr_ctrl				= 0, +	.cc_carr_wss_carr			= 0x2F7225ED, +	.c_phase				= 0, +	.gain_u					= 0x111, +	.gain_v					= 0x181, +	.gain_y					= 0x140, +	.black_level				= 0x3B, +	.blank_level				= 0x3B, +	.x_color				= 0x7, +	.m_control				= 0x2, +	.bstamp_wss_data			= 0x3F, +	.s_carr					= 0x2A098ACB, +	.line21					= 0, +	.ln_sel					= 0x01290015, +	.l21__wc_ctl				= 0x0000F603, +	.htrigger_vtrigger			= 0, + +	.savid__eavid				= 0x06A70108, +	.flen__fal				= 0x00180270, +	.lal__phase_reset			= 0x00040135, +	.hs_int_start_stop_x			= 0x00880358, +	.hs_ext_start_stop_x			= 0x000F035F, +	.vs_int_start_x				= 0x01A70000, +	.vs_int_stop_x__vs_int_start_y		= 0x000001A7, +	.vs_int_stop_y__vs_ext_start_x		= 0x01AF0000, +	.vs_ext_stop_x__vs_ext_start_y		= 0x000101AF, +	.vs_ext_stop_y				= 0x00000025, +	.avid_start_stop_x			= 0x03530083, +	.avid_start_stop_y			= 0x026C002E, +	.fid_int_start_x__fid_int_start_y	= 0x0001008A, +	.fid_int_offset_y__fid_ext_start_x	= 0x002E0138, +	.fid_ext_start_y__fid_ext_offset_y	= 0x01380001, + +	.tvdetgp_int_start_stop_x		= 0x00140001, +	.tvdetgp_int_start_stop_y		= 0x00010001, +	.gen_ctrl				= 0x00FF0000, +}; + +/* from TRM */ +static const struct venc_config venc_config_ntsc_trm = { +	.f_control				= 0, +	.vidout_ctrl				= 1, +	.sync_ctrl				= 0x8040, +	.llen					= 0x359, +	.flens					= 0x20C, +	.hfltr_ctrl				= 0, +	.cc_carr_wss_carr			= 0x043F2631, +	.c_phase				= 0, +	.gain_u					= 0x102, +	.gain_v					= 0x16C, +	.gain_y					= 0x12F, +	.black_level				= 0x43, +	.blank_level				= 0x38, +	.x_color				= 0x7, +	.m_control				= 0x1, +	.bstamp_wss_data			= 0x38, +	.s_carr					= 0x21F07C1F, +	.line21					= 0, +	.ln_sel					= 0x01310011, +	.l21__wc_ctl				= 0x0000F003, +	.htrigger_vtrigger			= 0, + +	.savid__eavid				= 0x069300F4, +	.flen__fal				= 0x0016020C, +	.lal__phase_reset			= 0x00060107, +	.hs_int_start_stop_x			= 0x008E0350, +	.hs_ext_start_stop_x			= 0x000F0359, +	.vs_int_start_x				= 0x01A00000, +	.vs_int_stop_x__vs_int_start_y		= 0x020701A0, +	.vs_int_stop_y__vs_ext_start_x		= 0x01AC0024, +	.vs_ext_stop_x__vs_ext_start_y		= 0x020D01AC, +	.vs_ext_stop_y				= 0x00000006, +	.avid_start_stop_x			= 0x03480078, +	.avid_start_stop_y			= 0x02060024, +	.fid_int_start_x__fid_int_start_y	= 0x0001008A, +	.fid_int_offset_y__fid_ext_start_x	= 0x01AC0106, +	.fid_ext_start_y__fid_ext_offset_y	= 0x01060006, + +	.tvdetgp_int_start_stop_x		= 0x00140001, +	.tvdetgp_int_start_stop_y		= 0x00010001, +	.gen_ctrl				= 0x00F90000, +}; + +static const struct venc_config venc_config_pal_bdghi = { +	.f_control				= 0, +	.vidout_ctrl				= 0, +	.sync_ctrl				= 0, +	.hfltr_ctrl				= 0, +	.x_color				= 0, +	.line21					= 0, +	.ln_sel					= 21, +	.htrigger_vtrigger			= 0, +	.tvdetgp_int_start_stop_x		= 0x00140001, +	.tvdetgp_int_start_stop_y		= 0x00010001, +	.gen_ctrl				= 0x00FB0000, + +	.llen					= 864-1, +	.flens					= 625-1, +	.cc_carr_wss_carr			= 0x2F7625ED, +	.c_phase				= 0xDF, +	.gain_u					= 0x111, +	.gain_v					= 0x181, +	.gain_y					= 0x140, +	.black_level				= 0x3e, +	.blank_level				= 0x3e, +	.m_control				= 0<<2 | 1<<1, +	.bstamp_wss_data			= 0x42, +	.s_carr					= 0x2a098acb, +	.l21__wc_ctl				= 0<<13 | 0x16<<8 | 0<<0, +	.savid__eavid				= 0x06A70108, +	.flen__fal				= 23<<16 | 624<<0, +	.lal__phase_reset			= 2<<17 | 310<<0, +	.hs_int_start_stop_x			= 0x00920358, +	.hs_ext_start_stop_x			= 0x000F035F, +	.vs_int_start_x				= 0x1a7<<16, +	.vs_int_stop_x__vs_int_start_y		= 0x000601A7, +	.vs_int_stop_y__vs_ext_start_x		= 0x01AF0036, +	.vs_ext_stop_x__vs_ext_start_y		= 0x27101af, +	.vs_ext_stop_y				= 0x05, +	.avid_start_stop_x			= 0x03530082, +	.avid_start_stop_y			= 0x0270002E, +	.fid_int_start_x__fid_int_start_y	= 0x0005008A, +	.fid_int_offset_y__fid_ext_start_x	= 0x002E0138, +	.fid_ext_start_y__fid_ext_offset_y	= 0x01380005, +}; + +const struct omap_video_timings omap_dss_pal_timings = { +	.x_res		= 720, +	.y_res		= 574, +	.pixelclock	= 13500000, +	.hsw		= 64, +	.hfp		= 12, +	.hbp		= 68, +	.vsw		= 5, +	.vfp		= 5, +	.vbp		= 41, + +	.interlace	= true, +}; +EXPORT_SYMBOL(omap_dss_pal_timings); + +const struct omap_video_timings omap_dss_ntsc_timings = { +	.x_res		= 720, +	.y_res		= 482, +	.pixelclock	= 13500000, +	.hsw		= 64, +	.hfp		= 16, +	.hbp		= 58, +	.vsw		= 6, +	.vfp		= 6, +	.vbp		= 31, + +	.interlace	= true, +}; +EXPORT_SYMBOL(omap_dss_ntsc_timings); + +static struct { +	struct platform_device *pdev; +	void __iomem *base; +	struct mutex venc_lock; +	u32 wss_data; +	struct regulator *vdda_dac_reg; + +	struct clk	*tv_dac_clk; + +	struct omap_video_timings timings; +	enum omap_dss_venc_type type; +	bool invert_polarity; + +	struct omap_dss_device output; +} venc; + +static inline void venc_write_reg(int idx, u32 val) +{ +	__raw_writel(val, venc.base + idx); +} + +static inline u32 venc_read_reg(int idx) +{ +	u32 l = __raw_readl(venc.base + idx); +	return l; +} + +static void venc_write_config(const struct venc_config *config) +{ +	DSSDBG("write venc conf\n"); + +	venc_write_reg(VENC_LLEN, config->llen); +	venc_write_reg(VENC_FLENS, config->flens); +	venc_write_reg(VENC_CC_CARR_WSS_CARR, config->cc_carr_wss_carr); +	venc_write_reg(VENC_C_PHASE, config->c_phase); +	venc_write_reg(VENC_GAIN_U, config->gain_u); +	venc_write_reg(VENC_GAIN_V, config->gain_v); +	venc_write_reg(VENC_GAIN_Y, config->gain_y); +	venc_write_reg(VENC_BLACK_LEVEL, config->black_level); +	venc_write_reg(VENC_BLANK_LEVEL, config->blank_level); +	venc_write_reg(VENC_M_CONTROL, config->m_control); +	venc_write_reg(VENC_BSTAMP_WSS_DATA, config->bstamp_wss_data | +			venc.wss_data); +	venc_write_reg(VENC_S_CARR, config->s_carr); +	venc_write_reg(VENC_L21__WC_CTL, config->l21__wc_ctl); +	venc_write_reg(VENC_SAVID__EAVID, config->savid__eavid); +	venc_write_reg(VENC_FLEN__FAL, config->flen__fal); +	venc_write_reg(VENC_LAL__PHASE_RESET, config->lal__phase_reset); +	venc_write_reg(VENC_HS_INT_START_STOP_X, config->hs_int_start_stop_x); +	venc_write_reg(VENC_HS_EXT_START_STOP_X, config->hs_ext_start_stop_x); +	venc_write_reg(VENC_VS_INT_START_X, config->vs_int_start_x); +	venc_write_reg(VENC_VS_INT_STOP_X__VS_INT_START_Y, +		       config->vs_int_stop_x__vs_int_start_y); +	venc_write_reg(VENC_VS_INT_STOP_Y__VS_EXT_START_X, +		       config->vs_int_stop_y__vs_ext_start_x); +	venc_write_reg(VENC_VS_EXT_STOP_X__VS_EXT_START_Y, +		       config->vs_ext_stop_x__vs_ext_start_y); +	venc_write_reg(VENC_VS_EXT_STOP_Y, config->vs_ext_stop_y); +	venc_write_reg(VENC_AVID_START_STOP_X, config->avid_start_stop_x); +	venc_write_reg(VENC_AVID_START_STOP_Y, config->avid_start_stop_y); +	venc_write_reg(VENC_FID_INT_START_X__FID_INT_START_Y, +		       config->fid_int_start_x__fid_int_start_y); +	venc_write_reg(VENC_FID_INT_OFFSET_Y__FID_EXT_START_X, +		       config->fid_int_offset_y__fid_ext_start_x); +	venc_write_reg(VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y, +		       config->fid_ext_start_y__fid_ext_offset_y); + +	venc_write_reg(VENC_DAC_B__DAC_C,  venc_read_reg(VENC_DAC_B__DAC_C)); +	venc_write_reg(VENC_VIDOUT_CTRL, config->vidout_ctrl); +	venc_write_reg(VENC_HFLTR_CTRL, config->hfltr_ctrl); +	venc_write_reg(VENC_X_COLOR, config->x_color); +	venc_write_reg(VENC_LINE21, config->line21); +	venc_write_reg(VENC_LN_SEL, config->ln_sel); +	venc_write_reg(VENC_HTRIGGER_VTRIGGER, config->htrigger_vtrigger); +	venc_write_reg(VENC_TVDETGP_INT_START_STOP_X, +		       config->tvdetgp_int_start_stop_x); +	venc_write_reg(VENC_TVDETGP_INT_START_STOP_Y, +		       config->tvdetgp_int_start_stop_y); +	venc_write_reg(VENC_GEN_CTRL, config->gen_ctrl); +	venc_write_reg(VENC_F_CONTROL, config->f_control); +	venc_write_reg(VENC_SYNC_CTRL, config->sync_ctrl); +} + +static void venc_reset(void) +{ +	int t = 1000; + +	venc_write_reg(VENC_F_CONTROL, 1<<8); +	while (venc_read_reg(VENC_F_CONTROL) & (1<<8)) { +		if (--t == 0) { +			DSSERR("Failed to reset venc\n"); +			return; +		} +	} + +#ifdef CONFIG_OMAP2_DSS_SLEEP_AFTER_VENC_RESET +	/* the magical sleep that makes things work */ +	/* XXX more info? What bug this circumvents? */ +	msleep(20); +#endif +} + +static int venc_runtime_get(void) +{ +	int r; + +	DSSDBG("venc_runtime_get\n"); + +	r = pm_runtime_get_sync(&venc.pdev->dev); +	WARN_ON(r < 0); +	return r < 0 ? r : 0; +} + +static void venc_runtime_put(void) +{ +	int r; + +	DSSDBG("venc_runtime_put\n"); + +	r = pm_runtime_put_sync(&venc.pdev->dev); +	WARN_ON(r < 0 && r != -ENOSYS); +} + +static const struct venc_config *venc_timings_to_config( +		struct omap_video_timings *timings) +{ +	if (memcmp(&omap_dss_pal_timings, timings, sizeof(*timings)) == 0) +		return &venc_config_pal_trm; + +	if (memcmp(&omap_dss_ntsc_timings, timings, sizeof(*timings)) == 0) +		return &venc_config_ntsc_trm; + +	BUG(); +	return NULL; +} + +static int venc_power_on(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = venc.output.manager; +	u32 l; +	int r; + +	r = venc_runtime_get(); +	if (r) +		goto err0; + +	venc_reset(); +	venc_write_config(venc_timings_to_config(&venc.timings)); + +	dss_set_venc_output(venc.type); +	dss_set_dac_pwrdn_bgz(1); + +	l = 0; + +	if (venc.type == OMAP_DSS_VENC_TYPE_COMPOSITE) +		l |= 1 << 1; +	else /* S-Video */ +		l |= (1 << 0) | (1 << 2); + +	if (venc.invert_polarity == false) +		l |= 1 << 3; + +	venc_write_reg(VENC_OUTPUT_CONTROL, l); + +	dss_mgr_set_timings(mgr, &venc.timings); + +	r = regulator_enable(venc.vdda_dac_reg); +	if (r) +		goto err1; + +	r = dss_mgr_enable(mgr); +	if (r) +		goto err2; + +	return 0; + +err2: +	regulator_disable(venc.vdda_dac_reg); +err1: +	venc_write_reg(VENC_OUTPUT_CONTROL, 0); +	dss_set_dac_pwrdn_bgz(0); + +	venc_runtime_put(); +err0: +	return r; +} + +static void venc_power_off(struct omap_dss_device *dssdev) +{ +	struct omap_overlay_manager *mgr = venc.output.manager; + +	venc_write_reg(VENC_OUTPUT_CONTROL, 0); +	dss_set_dac_pwrdn_bgz(0); + +	dss_mgr_disable(mgr); + +	regulator_disable(venc.vdda_dac_reg); + +	venc_runtime_put(); +} + +static int venc_display_enable(struct omap_dss_device *dssdev) +{ +	struct omap_dss_device *out = &venc.output; +	int r; + +	DSSDBG("venc_display_enable\n"); + +	mutex_lock(&venc.venc_lock); + +	if (out == NULL || out->manager == NULL) { +		DSSERR("Failed to enable display: no output/manager\n"); +		r = -ENODEV; +		goto err0; +	} + +	r = venc_power_on(dssdev); +	if (r) +		goto err0; + +	venc.wss_data = 0; + +	mutex_unlock(&venc.venc_lock); + +	return 0; +err0: +	mutex_unlock(&venc.venc_lock); +	return r; +} + +static void venc_display_disable(struct omap_dss_device *dssdev) +{ +	DSSDBG("venc_display_disable\n"); + +	mutex_lock(&venc.venc_lock); + +	venc_power_off(dssdev); + +	mutex_unlock(&venc.venc_lock); +} + +static void venc_set_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	DSSDBG("venc_set_timings\n"); + +	mutex_lock(&venc.venc_lock); + +	/* Reset WSS data when the TV standard changes. */ +	if (memcmp(&venc.timings, timings, sizeof(*timings))) +		venc.wss_data = 0; + +	venc.timings = *timings; + +	dispc_set_tv_pclk(13500000); + +	mutex_unlock(&venc.venc_lock); +} + +static int venc_check_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	DSSDBG("venc_check_timings\n"); + +	if (memcmp(&omap_dss_pal_timings, timings, sizeof(*timings)) == 0) +		return 0; + +	if (memcmp(&omap_dss_ntsc_timings, timings, sizeof(*timings)) == 0) +		return 0; + +	return -EINVAL; +} + +static void venc_get_timings(struct omap_dss_device *dssdev, +		struct omap_video_timings *timings) +{ +	mutex_lock(&venc.venc_lock); + +	*timings = venc.timings; + +	mutex_unlock(&venc.venc_lock); +} + +static u32 venc_get_wss(struct omap_dss_device *dssdev) +{ +	/* Invert due to VENC_L21_WC_CTL:INV=1 */ +	return (venc.wss_data >> 8) ^ 0xfffff; +} + +static int venc_set_wss(struct omap_dss_device *dssdev, u32 wss) +{ +	const struct venc_config *config; +	int r; + +	DSSDBG("venc_set_wss\n"); + +	mutex_lock(&venc.venc_lock); + +	config = venc_timings_to_config(&venc.timings); + +	/* Invert due to VENC_L21_WC_CTL:INV=1 */ +	venc.wss_data = (wss ^ 0xfffff) << 8; + +	r = venc_runtime_get(); +	if (r) +		goto err; + +	venc_write_reg(VENC_BSTAMP_WSS_DATA, config->bstamp_wss_data | +			venc.wss_data); + +	venc_runtime_put(); + +err: +	mutex_unlock(&venc.venc_lock); + +	return r; +} + +static void venc_set_type(struct omap_dss_device *dssdev, +		enum omap_dss_venc_type type) +{ +	mutex_lock(&venc.venc_lock); + +	venc.type = type; + +	mutex_unlock(&venc.venc_lock); +} + +static void venc_invert_vid_out_polarity(struct omap_dss_device *dssdev, +		bool invert_polarity) +{ +	mutex_lock(&venc.venc_lock); + +	venc.invert_polarity = invert_polarity; + +	mutex_unlock(&venc.venc_lock); +} + +static int venc_init_regulator(void) +{ +	struct regulator *vdda_dac; + +	if (venc.vdda_dac_reg != NULL) +		return 0; + +	if (venc.pdev->dev.of_node) +		vdda_dac = devm_regulator_get(&venc.pdev->dev, "vdda"); +	else +		vdda_dac = devm_regulator_get(&venc.pdev->dev, "vdda_dac"); + +	if (IS_ERR(vdda_dac)) { +		if (PTR_ERR(vdda_dac) != -EPROBE_DEFER) +			DSSERR("can't get VDDA_DAC regulator\n"); +		return PTR_ERR(vdda_dac); +	} + +	venc.vdda_dac_reg = vdda_dac; + +	return 0; +} + +static void venc_dump_regs(struct seq_file *s) +{ +#define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, venc_read_reg(r)) + +	if (venc_runtime_get()) +		return; + +	DUMPREG(VENC_F_CONTROL); +	DUMPREG(VENC_VIDOUT_CTRL); +	DUMPREG(VENC_SYNC_CTRL); +	DUMPREG(VENC_LLEN); +	DUMPREG(VENC_FLENS); +	DUMPREG(VENC_HFLTR_CTRL); +	DUMPREG(VENC_CC_CARR_WSS_CARR); +	DUMPREG(VENC_C_PHASE); +	DUMPREG(VENC_GAIN_U); +	DUMPREG(VENC_GAIN_V); +	DUMPREG(VENC_GAIN_Y); +	DUMPREG(VENC_BLACK_LEVEL); +	DUMPREG(VENC_BLANK_LEVEL); +	DUMPREG(VENC_X_COLOR); +	DUMPREG(VENC_M_CONTROL); +	DUMPREG(VENC_BSTAMP_WSS_DATA); +	DUMPREG(VENC_S_CARR); +	DUMPREG(VENC_LINE21); +	DUMPREG(VENC_LN_SEL); +	DUMPREG(VENC_L21__WC_CTL); +	DUMPREG(VENC_HTRIGGER_VTRIGGER); +	DUMPREG(VENC_SAVID__EAVID); +	DUMPREG(VENC_FLEN__FAL); +	DUMPREG(VENC_LAL__PHASE_RESET); +	DUMPREG(VENC_HS_INT_START_STOP_X); +	DUMPREG(VENC_HS_EXT_START_STOP_X); +	DUMPREG(VENC_VS_INT_START_X); +	DUMPREG(VENC_VS_INT_STOP_X__VS_INT_START_Y); +	DUMPREG(VENC_VS_INT_STOP_Y__VS_EXT_START_X); +	DUMPREG(VENC_VS_EXT_STOP_X__VS_EXT_START_Y); +	DUMPREG(VENC_VS_EXT_STOP_Y); +	DUMPREG(VENC_AVID_START_STOP_X); +	DUMPREG(VENC_AVID_START_STOP_Y); +	DUMPREG(VENC_FID_INT_START_X__FID_INT_START_Y); +	DUMPREG(VENC_FID_INT_OFFSET_Y__FID_EXT_START_X); +	DUMPREG(VENC_FID_EXT_START_Y__FID_EXT_OFFSET_Y); +	DUMPREG(VENC_TVDETGP_INT_START_STOP_X); +	DUMPREG(VENC_TVDETGP_INT_START_STOP_Y); +	DUMPREG(VENC_GEN_CTRL); +	DUMPREG(VENC_OUTPUT_CONTROL); +	DUMPREG(VENC_OUTPUT_TEST); + +	venc_runtime_put(); + +#undef DUMPREG +} + +static int venc_get_clocks(struct platform_device *pdev) +{ +	struct clk *clk; + +	if (dss_has_feature(FEAT_VENC_REQUIRES_TV_DAC_CLK)) { +		clk = devm_clk_get(&pdev->dev, "tv_dac_clk"); +		if (IS_ERR(clk)) { +			DSSERR("can't get tv_dac_clk\n"); +			return PTR_ERR(clk); +		} +	} else { +		clk = NULL; +	} + +	venc.tv_dac_clk = clk; + +	return 0; +} + +static int venc_connect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	struct omap_overlay_manager *mgr; +	int r; + +	r = venc_init_regulator(); +	if (r) +		return r; + +	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); +	if (!mgr) +		return -ENODEV; + +	r = dss_mgr_connect(mgr, dssdev); +	if (r) +		return r; + +	r = omapdss_output_set_device(dssdev, dst); +	if (r) { +		DSSERR("failed to connect output to new device: %s\n", +				dst->name); +		dss_mgr_disconnect(mgr, dssdev); +		return r; +	} + +	return 0; +} + +static void venc_disconnect(struct omap_dss_device *dssdev, +		struct omap_dss_device *dst) +{ +	WARN_ON(dst != dssdev->dst); + +	if (dst != dssdev->dst) +		return; + +	omapdss_output_unset_device(dssdev); + +	if (dssdev->manager) +		dss_mgr_disconnect(dssdev->manager, dssdev); +} + +static const struct omapdss_atv_ops venc_ops = { +	.connect = venc_connect, +	.disconnect = venc_disconnect, + +	.enable = venc_display_enable, +	.disable = venc_display_disable, + +	.check_timings = venc_check_timings, +	.set_timings = venc_set_timings, +	.get_timings = venc_get_timings, + +	.set_type = venc_set_type, +	.invert_vid_out_polarity = venc_invert_vid_out_polarity, + +	.set_wss = venc_set_wss, +	.get_wss = venc_get_wss, +}; + +static void venc_init_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &venc.output; + +	out->dev = &pdev->dev; +	out->id = OMAP_DSS_OUTPUT_VENC; +	out->output_type = OMAP_DISPLAY_TYPE_VENC; +	out->name = "venc.0"; +	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; +	out->ops.atv = &venc_ops; +	out->owner = THIS_MODULE; + +	omapdss_register_output(out); +} + +static void __exit venc_uninit_output(struct platform_device *pdev) +{ +	struct omap_dss_device *out = &venc.output; + +	omapdss_unregister_output(out); +} + +static int venc_probe_of(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	struct device_node *ep; +	u32 channels; +	int r; + +	ep = omapdss_of_get_first_endpoint(node); +	if (!ep) +		return 0; + +	venc.invert_polarity = of_property_read_bool(ep, "ti,invert-polarity"); + +	r = of_property_read_u32(ep, "ti,channels", &channels); +	if (r) { +		dev_err(&pdev->dev, +			"failed to read property 'ti,channels': %d\n", r); +		goto err; +	} + +	switch (channels) { +	case 1: +		venc.type = OMAP_DSS_VENC_TYPE_COMPOSITE; +		break; +	case 2: +		venc.type = OMAP_DSS_VENC_TYPE_SVIDEO; +		break; +	default: +		dev_err(&pdev->dev, "bad channel propert '%d'\n", channels); +		r = -EINVAL; +		goto err; +	} + +	of_node_put(ep); + +	return 0; +err: +	of_node_put(ep); + +	return 0; +} + +/* 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; + +	mutex_init(&venc.venc_lock); + +	venc.wss_data = 0; + +	venc_mem = platform_get_resource(venc.pdev, IORESOURCE_MEM, 0); +	if (!venc_mem) { +		DSSERR("can't get IORESOURCE_MEM VENC\n"); +		return -EINVAL; +	} + +	venc.base = devm_ioremap(&pdev->dev, venc_mem->start, +				 resource_size(venc_mem)); +	if (!venc.base) { +		DSSERR("can't ioremap VENC\n"); +		return -ENOMEM; +	} + +	r = venc_get_clocks(pdev); +	if (r) +		return r; + +	pm_runtime_enable(&pdev->dev); + +	r = venc_runtime_get(); +	if (r) +		goto err_runtime_get; + +	rev_id = (u8)(venc_read_reg(VENC_REV_ID) & 0xff); +	dev_dbg(&pdev->dev, "OMAP VENC rev %d\n", rev_id); + +	venc_runtime_put(); + +	if (pdev->dev.of_node) { +		r = venc_probe_of(pdev); +		if (r) { +			DSSERR("Invalid DT data\n"); +			goto err_probe_of; +		} +	} + +	dss_debugfs_create_file("venc", venc_dump_regs); + +	venc_init_output(pdev); + +	return 0; + +err_probe_of: +err_runtime_get: +	pm_runtime_disable(&pdev->dev); +	return r; +} + +static int __exit omap_venchw_remove(struct platform_device *pdev) +{ +	venc_uninit_output(pdev); + +	pm_runtime_disable(&pdev->dev); + +	return 0; +} + +static int venc_runtime_suspend(struct device *dev) +{ +	if (venc.tv_dac_clk) +		clk_disable_unprepare(venc.tv_dac_clk); + +	dispc_runtime_put(); + +	return 0; +} + +static int venc_runtime_resume(struct device *dev) +{ +	int r; + +	r = dispc_runtime_get(); +	if (r < 0) +		return r; + +	if (venc.tv_dac_clk) +		clk_prepare_enable(venc.tv_dac_clk); + +	return 0; +} + +static const struct dev_pm_ops venc_pm_ops = { +	.runtime_suspend = venc_runtime_suspend, +	.runtime_resume = venc_runtime_resume, +}; + + +static const struct of_device_id venc_of_match[] = { +	{ .compatible = "ti,omap2-venc", }, +	{ .compatible = "ti,omap3-venc", }, +	{ .compatible = "ti,omap4-venc", }, +	{}, +}; + +static struct platform_driver omap_venchw_driver = { +	.probe		= omap_venchw_probe, +	.remove         = __exit_p(omap_venchw_remove), +	.driver         = { +		.name   = "omapdss_venc", +		.owner  = THIS_MODULE, +		.pm	= &venc_pm_ops, +		.of_match_table = venc_of_match, +	}, +}; + +int __init venc_init_platform_driver(void) +{ +	return platform_driver_register(&omap_venchw_driver); +} + +void __exit venc_uninit_platform_driver(void) +{ +	platform_driver_unregister(&omap_venchw_driver); +}  | 
