diff options
Diffstat (limited to 'drivers/gpu/host1x')
44 files changed, 11160 insertions, 0 deletions
diff --git a/drivers/gpu/host1x/Kconfig b/drivers/gpu/host1x/Kconfig new file mode 100644 index 00000000000..ccfd42b2360 --- /dev/null +++ b/drivers/gpu/host1x/Kconfig @@ -0,0 +1,24 @@ +config TEGRA_HOST1X +	tristate "NVIDIA Tegra host1x driver" +	depends on ARCH_TEGRA || ARCH_MULTIPLATFORM +	help +	  Driver for the NVIDIA Tegra host1x hardware. + +	  The Tegra host1x module is the DMA engine for register access to +	  Tegra's graphics- and multimedia-related modules. The modules served +	  by host1x are referred to as clients. host1x includes some other +	  functionality, such as synchronization. + +if TEGRA_HOST1X + +config TEGRA_HOST1X_FIREWALL +	bool "Enable HOST1X security firewall" +	default y +	help +	  Say yes if kernel should protect command streams from tampering. + +	  If unsure, choose Y. + +source "drivers/gpu/host1x/drm/Kconfig" + +endif diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile new file mode 100644 index 00000000000..3b037b6e029 --- /dev/null +++ b/drivers/gpu/host1x/Makefile @@ -0,0 +1,20 @@ +ccflags-y = -Idrivers/gpu/host1x + +host1x-y = \ +	syncpt.o \ +	dev.o \ +	intr.o \ +	cdma.o \ +	channel.o \ +	job.o \ +	debug.o \ +	hw/host1x01.o + +ccflags-y += -Iinclude/drm +ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG + +host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o +host1x-$(CONFIG_DRM_TEGRA) += drm/output.o drm/rgb.o drm/hdmi.o +host1x-$(CONFIG_DRM_TEGRA) += drm/gem.o +host1x-$(CONFIG_DRM_TEGRA) += drm/gr2d.o +obj-$(CONFIG_TEGRA_HOST1X) += host1x.o diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c new file mode 100644 index 00000000000..de72172d3b5 --- /dev/null +++ b/drivers/gpu/host1x/cdma.c @@ -0,0 +1,491 @@ +/* + * Tegra host1x Command DMA + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 <asm/cacheflush.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kfifo.h> +#include <linux/slab.h> +#include <trace/events/host1x.h> + +#include "cdma.h" +#include "channel.h" +#include "dev.h" +#include "debug.h" +#include "host1x_bo.h" +#include "job.h" + +/* + * push_buffer + * + * The push buffer is a circular array of words to be fetched by command DMA. + * Note that it works slightly differently to the sync queue; fence == pos + * means that the push buffer is full, not empty. + */ + +#define HOST1X_PUSHBUFFER_SLOTS	512 + +/* + * Clean up push buffer resources + */ +static void host1x_pushbuffer_destroy(struct push_buffer *pb) +{ +	struct host1x_cdma *cdma = pb_to_cdma(pb); +	struct host1x *host1x = cdma_to_host1x(cdma); + +	if (pb->phys != 0) +		dma_free_writecombine(host1x->dev, pb->size_bytes + 4, +				      pb->mapped, pb->phys); + +	pb->mapped = NULL; +	pb->phys = 0; +} + +/* + * Init push buffer resources + */ +static int host1x_pushbuffer_init(struct push_buffer *pb) +{ +	struct host1x_cdma *cdma = pb_to_cdma(pb); +	struct host1x *host1x = cdma_to_host1x(cdma); + +	pb->mapped = NULL; +	pb->phys = 0; +	pb->size_bytes = HOST1X_PUSHBUFFER_SLOTS * 8; + +	/* initialize buffer pointers */ +	pb->fence = pb->size_bytes - 8; +	pb->pos = 0; + +	/* allocate and map pushbuffer memory */ +	pb->mapped = dma_alloc_writecombine(host1x->dev, pb->size_bytes + 4, +					    &pb->phys, GFP_KERNEL); +	if (!pb->mapped) +		goto fail; + +	host1x_hw_pushbuffer_init(host1x, pb); + +	return 0; + +fail: +	host1x_pushbuffer_destroy(pb); +	return -ENOMEM; +} + +/* + * Push two words to the push buffer + * Caller must ensure push buffer is not full + */ +static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2) +{ +	u32 pos = pb->pos; +	u32 *p = (u32 *)((u32)pb->mapped + pos); +	WARN_ON(pos == pb->fence); +	*(p++) = op1; +	*(p++) = op2; +	pb->pos = (pos + 8) & (pb->size_bytes - 1); +} + +/* + * Pop a number of two word slots from the push buffer + * Caller must ensure push buffer is not empty + */ +static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots) +{ +	/* Advance the next write position */ +	pb->fence = (pb->fence + slots * 8) & (pb->size_bytes - 1); +} + +/* + * Return the number of two word slots free in the push buffer + */ +static u32 host1x_pushbuffer_space(struct push_buffer *pb) +{ +	return ((pb->fence - pb->pos) & (pb->size_bytes - 1)) / 8; +} + +/* + * Sleep (if necessary) until the requested event happens + *   - CDMA_EVENT_SYNC_QUEUE_EMPTY : sync queue is completely empty. + *     - Returns 1 + *   - CDMA_EVENT_PUSH_BUFFER_SPACE : there is space in the push buffer + *     - Return the amount of space (> 0) + * Must be called with the cdma lock held. + */ +unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma, +				     enum cdma_event event) +{ +	for (;;) { +		unsigned int space; + +		if (event == CDMA_EVENT_SYNC_QUEUE_EMPTY) +			space = list_empty(&cdma->sync_queue) ? 1 : 0; +		else if (event == CDMA_EVENT_PUSH_BUFFER_SPACE) { +			struct push_buffer *pb = &cdma->push_buffer; +			space = host1x_pushbuffer_space(pb); +		} else { +			WARN_ON(1); +			return -EINVAL; +		} + +		if (space) +			return space; + +		trace_host1x_wait_cdma(dev_name(cdma_to_channel(cdma)->dev), +				       event); + +		/* If somebody has managed to already start waiting, yield */ +		if (cdma->event != CDMA_EVENT_NONE) { +			mutex_unlock(&cdma->lock); +			schedule(); +			mutex_lock(&cdma->lock); +			continue; +		} +		cdma->event = event; + +		mutex_unlock(&cdma->lock); +		down(&cdma->sem); +		mutex_lock(&cdma->lock); +	} +	return 0; +} + +/* + * Start timer that tracks the time spent by the job. + * Must be called with the cdma lock held. + */ +static void cdma_start_timer_locked(struct host1x_cdma *cdma, +				    struct host1x_job *job) +{ +	struct host1x *host = cdma_to_host1x(cdma); + +	if (cdma->timeout.client) { +		/* timer already started */ +		return; +	} + +	cdma->timeout.client = job->client; +	cdma->timeout.syncpt = host1x_syncpt_get(host, job->syncpt_id); +	cdma->timeout.syncpt_val = job->syncpt_end; +	cdma->timeout.start_ktime = ktime_get(); + +	schedule_delayed_work(&cdma->timeout.wq, +			      msecs_to_jiffies(job->timeout)); +} + +/* + * Stop timer when a buffer submission completes. + * Must be called with the cdma lock held. + */ +static void stop_cdma_timer_locked(struct host1x_cdma *cdma) +{ +	cancel_delayed_work(&cdma->timeout.wq); +	cdma->timeout.client = 0; +} + +/* + * For all sync queue entries that have already finished according to the + * current sync point registers: + *  - unpin & unref their mems + *  - pop their push buffer slots + *  - remove them from the sync queue + * This is normally called from the host code's worker thread, but can be + * called manually if necessary. + * Must be called with the cdma lock held. + */ +static void update_cdma_locked(struct host1x_cdma *cdma) +{ +	bool signal = false; +	struct host1x *host1x = cdma_to_host1x(cdma); +	struct host1x_job *job, *n; + +	/* If CDMA is stopped, queue is cleared and we can return */ +	if (!cdma->running) +		return; + +	/* +	 * Walk the sync queue, reading the sync point registers as necessary, +	 * to consume as many sync queue entries as possible without blocking +	 */ +	list_for_each_entry_safe(job, n, &cdma->sync_queue, list) { +		struct host1x_syncpt *sp = +			host1x_syncpt_get(host1x, job->syncpt_id); + +		/* Check whether this syncpt has completed, and bail if not */ +		if (!host1x_syncpt_is_expired(sp, job->syncpt_end)) { +			/* Start timer on next pending syncpt */ +			if (job->timeout) +				cdma_start_timer_locked(cdma, job); +			break; +		} + +		/* Cancel timeout, when a buffer completes */ +		if (cdma->timeout.client) +			stop_cdma_timer_locked(cdma); + +		/* Unpin the memory */ +		host1x_job_unpin(job); + +		/* Pop push buffer slots */ +		if (job->num_slots) { +			struct push_buffer *pb = &cdma->push_buffer; +			host1x_pushbuffer_pop(pb, job->num_slots); +			if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE) +				signal = true; +		} + +		list_del(&job->list); +		host1x_job_put(job); +	} + +	if (cdma->event == CDMA_EVENT_SYNC_QUEUE_EMPTY && +	    list_empty(&cdma->sync_queue)) +		signal = true; + +	if (signal) { +		cdma->event = CDMA_EVENT_NONE; +		up(&cdma->sem); +	} +} + +void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma, +				   struct device *dev) +{ +	u32 restart_addr; +	u32 syncpt_incrs; +	struct host1x_job *job = NULL; +	u32 syncpt_val; +	struct host1x *host1x = cdma_to_host1x(cdma); + +	syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt); + +	dev_dbg(dev, "%s: starting cleanup (thresh %d)\n", +		__func__, syncpt_val); + +	/* +	 * Move the sync_queue read pointer to the first entry that hasn't +	 * completed based on the current HW syncpt value. It's likely there +	 * won't be any (i.e. we're still at the head), but covers the case +	 * where a syncpt incr happens just prior/during the teardown. +	 */ + +	dev_dbg(dev, "%s: skip completed buffers still in sync_queue\n", +		__func__); + +	list_for_each_entry(job, &cdma->sync_queue, list) { +		if (syncpt_val < job->syncpt_end) +			break; + +		host1x_job_dump(dev, job); +	} + +	/* +	 * Walk the sync_queue, first incrementing with the CPU syncpts that +	 * are partially executed (the first buffer) or fully skipped while +	 * still in the current context (slots are also NOP-ed). +	 * +	 * At the point contexts are interleaved, syncpt increments must be +	 * done inline with the pushbuffer from a GATHER buffer to maintain +	 * the order (slots are modified to be a GATHER of syncpt incrs). +	 * +	 * Note: save in restart_addr the location where the timed out buffer +	 * started in the PB, so we can start the refetch from there (with the +	 * modified NOP-ed PB slots). This lets things appear to have completed +	 * properly for this buffer and resources are freed. +	 */ + +	dev_dbg(dev, "%s: perform CPU incr on pending same ctx buffers\n", +		__func__); + +	if (!list_empty(&cdma->sync_queue)) +		restart_addr = job->first_get; +	else +		restart_addr = cdma->last_pos; + +	/* do CPU increments as long as this context continues */ +	list_for_each_entry_from(job, &cdma->sync_queue, list) { +		/* different context, gets us out of this loop */ +		if (job->client != cdma->timeout.client) +			break; + +		/* won't need a timeout when replayed */ +		job->timeout = 0; + +		syncpt_incrs = job->syncpt_end - syncpt_val; +		dev_dbg(dev, "%s: CPU incr (%d)\n", __func__, syncpt_incrs); + +		host1x_job_dump(dev, job); + +		/* safe to use CPU to incr syncpts */ +		host1x_hw_cdma_timeout_cpu_incr(host1x, cdma, job->first_get, +						syncpt_incrs, job->syncpt_end, +						job->num_slots); + +		syncpt_val += syncpt_incrs; +	} + +	/* The following sumbits from the same client may be dependent on the +	 * failed submit and therefore they may fail. Force a small timeout +	 * to make the queue cleanup faster */ + +	list_for_each_entry_from(job, &cdma->sync_queue, list) +		if (job->client == cdma->timeout.client) +			job->timeout = min_t(unsigned int, job->timeout, 500); + +	dev_dbg(dev, "%s: finished sync_queue modification\n", __func__); + +	/* roll back DMAGET and start up channel again */ +	host1x_hw_cdma_resume(host1x, cdma, restart_addr); +} + +/* + * Create a cdma + */ +int host1x_cdma_init(struct host1x_cdma *cdma) +{ +	int err; + +	mutex_init(&cdma->lock); +	sema_init(&cdma->sem, 0); + +	INIT_LIST_HEAD(&cdma->sync_queue); + +	cdma->event = CDMA_EVENT_NONE; +	cdma->running = false; +	cdma->torndown = false; + +	err = host1x_pushbuffer_init(&cdma->push_buffer); +	if (err) +		return err; +	return 0; +} + +/* + * Destroy a cdma + */ +int host1x_cdma_deinit(struct host1x_cdma *cdma) +{ +	struct push_buffer *pb = &cdma->push_buffer; +	struct host1x *host1x = cdma_to_host1x(cdma); + +	if (cdma->running) { +		pr_warn("%s: CDMA still running\n", __func__); +		return -EBUSY; +	} + +	host1x_pushbuffer_destroy(pb); +	host1x_hw_cdma_timeout_destroy(host1x, cdma); + +	return 0; +} + +/* + * Begin a cdma submit + */ +int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); + +	mutex_lock(&cdma->lock); + +	if (job->timeout) { +		/* init state on first submit with timeout value */ +		if (!cdma->timeout.initialized) { +			int err; +			err = host1x_hw_cdma_timeout_init(host1x, cdma, +							  job->syncpt_id); +			if (err) { +				mutex_unlock(&cdma->lock); +				return err; +			} +		} +	} +	if (!cdma->running) +		host1x_hw_cdma_start(host1x, cdma); + +	cdma->slots_free = 0; +	cdma->slots_used = 0; +	cdma->first_get = cdma->push_buffer.pos; + +	trace_host1x_cdma_begin(dev_name(job->channel->dev)); +	return 0; +} + +/* + * Push two words into a push buffer slot + * Blocks as necessary if the push buffer is full. + */ +void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); +	struct push_buffer *pb = &cdma->push_buffer; +	u32 slots_free = cdma->slots_free; + +	if (host1x_debug_trace_cmdbuf) +		trace_host1x_cdma_push(dev_name(cdma_to_channel(cdma)->dev), +				       op1, op2); + +	if (slots_free == 0) { +		host1x_hw_cdma_flush(host1x, cdma); +		slots_free = host1x_cdma_wait_locked(cdma, +						CDMA_EVENT_PUSH_BUFFER_SPACE); +	} +	cdma->slots_free = slots_free - 1; +	cdma->slots_used++; +	host1x_pushbuffer_push(pb, op1, op2); +} + +/* + * End a cdma submit + * Kick off DMA, add job to the sync queue, and a number of slots to be freed + * from the pushbuffer. The handles for a submit must all be pinned at the same + * time, but they can be unpinned in smaller chunks. + */ +void host1x_cdma_end(struct host1x_cdma *cdma, +		     struct host1x_job *job) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); +	bool idle = list_empty(&cdma->sync_queue); + +	host1x_hw_cdma_flush(host1x, cdma); + +	job->first_get = cdma->first_get; +	job->num_slots = cdma->slots_used; +	host1x_job_get(job); +	list_add_tail(&job->list, &cdma->sync_queue); + +	/* start timer on idle -> active transitions */ +	if (job->timeout && idle) +		cdma_start_timer_locked(cdma, job); + +	trace_host1x_cdma_end(dev_name(job->channel->dev)); +	mutex_unlock(&cdma->lock); +} + +/* + * Update cdma state according to current sync point values + */ +void host1x_cdma_update(struct host1x_cdma *cdma) +{ +	mutex_lock(&cdma->lock); +	update_cdma_locked(cdma); +	mutex_unlock(&cdma->lock); +} diff --git a/drivers/gpu/host1x/cdma.h b/drivers/gpu/host1x/cdma.h new file mode 100644 index 00000000000..313c4b78434 --- /dev/null +++ b/drivers/gpu/host1x/cdma.h @@ -0,0 +1,100 @@ +/* + * Tegra host1x Command DMA + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_CDMA_H +#define __HOST1X_CDMA_H + +#include <linux/sched.h> +#include <linux/semaphore.h> +#include <linux/list.h> + +struct host1x_syncpt; +struct host1x_userctx_timeout; +struct host1x_job; + +/* + * cdma + * + * This is in charge of a host command DMA channel. + * Sends ops to a push buffer, and takes responsibility for unpinning + * (& possibly freeing) of memory after those ops have completed. + * Producer: + *	begin + *		push - send ops to the push buffer + *	end - start command DMA and enqueue handles to be unpinned + * Consumer: + *	update - call to update sync queue and push buffer, unpin memory + */ + +struct push_buffer { +	u32 *mapped;			/* mapped pushbuffer memory */ +	dma_addr_t phys;		/* physical address of pushbuffer */ +	u32 fence;			/* index we've written */ +	u32 pos;			/* index to write to */ +	u32 size_bytes; +}; + +struct buffer_timeout { +	struct delayed_work wq;		/* work queue */ +	bool initialized;		/* timer one-time setup flag */ +	struct host1x_syncpt *syncpt;	/* buffer completion syncpt */ +	u32 syncpt_val;			/* syncpt value when completed */ +	ktime_t start_ktime;		/* starting time */ +	/* context timeout information */ +	int client; +}; + +enum cdma_event { +	CDMA_EVENT_NONE,		/* not waiting for any event */ +	CDMA_EVENT_SYNC_QUEUE_EMPTY,	/* wait for empty sync queue */ +	CDMA_EVENT_PUSH_BUFFER_SPACE	/* wait for space in push buffer */ +}; + +struct host1x_cdma { +	struct mutex lock;		/* controls access to shared state */ +	struct semaphore sem;		/* signalled when event occurs */ +	enum cdma_event event;		/* event that sem is waiting for */ +	unsigned int slots_used;	/* pb slots used in current submit */ +	unsigned int slots_free;	/* pb slots free in current submit */ +	unsigned int first_get;		/* DMAGET value, where submit begins */ +	unsigned int last_pos;		/* last value written to DMAPUT */ +	struct push_buffer push_buffer;	/* channel's push buffer */ +	struct list_head sync_queue;	/* job queue */ +	struct buffer_timeout timeout;	/* channel's timeout state/wq */ +	bool running; +	bool torndown; +}; + +#define cdma_to_channel(cdma) container_of(cdma, struct host1x_channel, cdma) +#define cdma_to_host1x(cdma) dev_get_drvdata(cdma_to_channel(cdma)->dev->parent) +#define pb_to_cdma(pb) container_of(pb, struct host1x_cdma, push_buffer) + +int host1x_cdma_init(struct host1x_cdma *cdma); +int host1x_cdma_deinit(struct host1x_cdma *cdma); +void host1x_cdma_stop(struct host1x_cdma *cdma); +int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job); +void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2); +void host1x_cdma_end(struct host1x_cdma *cdma, struct host1x_job *job); +void host1x_cdma_update(struct host1x_cdma *cdma); +void host1x_cdma_peek(struct host1x_cdma *cdma, u32 dmaget, int slot, +		      u32 *out); +unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma, +				     enum cdma_event event); +void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma, +				   struct device *dev); +#endif diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c new file mode 100644 index 00000000000..83ea51b9f0f --- /dev/null +++ b/drivers/gpu/host1x/channel.c @@ -0,0 +1,126 @@ +/* + * Tegra host1x Channel + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/slab.h> +#include <linux/module.h> + +#include "channel.h" +#include "dev.h" +#include "job.h" + +/* Constructor for the host1x device list */ +int host1x_channel_list_init(struct host1x *host) +{ +	INIT_LIST_HEAD(&host->chlist.list); +	mutex_init(&host->chlist_mutex); + +	if (host->info->nb_channels > BITS_PER_LONG) { +		WARN(1, "host1x hardware has more channels than supported by the driver\n"); +		return -ENOSYS; +	} + +	return 0; +} + +int host1x_job_submit(struct host1x_job *job) +{ +	struct host1x *host = dev_get_drvdata(job->channel->dev->parent); + +	return host1x_hw_channel_submit(host, job); +} + +struct host1x_channel *host1x_channel_get(struct host1x_channel *channel) +{ +	int err = 0; + +	mutex_lock(&channel->reflock); + +	if (channel->refcount == 0) +		err = host1x_cdma_init(&channel->cdma); + +	if (!err) +		channel->refcount++; + +	mutex_unlock(&channel->reflock); + +	return err ? NULL : channel; +} + +void host1x_channel_put(struct host1x_channel *channel) +{ +	mutex_lock(&channel->reflock); + +	if (channel->refcount == 1) { +		struct host1x *host = dev_get_drvdata(channel->dev->parent); + +		host1x_hw_cdma_stop(host, &channel->cdma); +		host1x_cdma_deinit(&channel->cdma); +	} + +	channel->refcount--; + +	mutex_unlock(&channel->reflock); +} + +struct host1x_channel *host1x_channel_request(struct device *dev) +{ +	struct host1x *host = dev_get_drvdata(dev->parent); +	int max_channels = host->info->nb_channels; +	struct host1x_channel *channel = NULL; +	int index, err; + +	mutex_lock(&host->chlist_mutex); + +	index = find_first_zero_bit(&host->allocated_channels, max_channels); +	if (index >= max_channels) +		goto fail; + +	channel = kzalloc(sizeof(*channel), GFP_KERNEL); +	if (!channel) +		goto fail; + +	err = host1x_hw_channel_init(host, channel, index); +	if (err < 0) +		goto fail; + +	/* Link device to host1x_channel */ +	channel->dev = dev; + +	/* Add to channel list */ +	list_add_tail(&channel->list, &host->chlist.list); + +	host->allocated_channels |= BIT(index); + +	mutex_unlock(&host->chlist_mutex); +	return channel; + +fail: +	dev_err(dev, "failed to init channel\n"); +	kfree(channel); +	mutex_unlock(&host->chlist_mutex); +	return NULL; +} + +void host1x_channel_free(struct host1x_channel *channel) +{ +	struct host1x *host = dev_get_drvdata(channel->dev->parent); + +	host->allocated_channels &= ~BIT(channel->id); +	list_del(&channel->list); +	kfree(channel); +} diff --git a/drivers/gpu/host1x/channel.h b/drivers/gpu/host1x/channel.h new file mode 100644 index 00000000000..48723b8eea4 --- /dev/null +++ b/drivers/gpu/host1x/channel.h @@ -0,0 +1,52 @@ +/* + * Tegra host1x Channel + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_CHANNEL_H +#define __HOST1X_CHANNEL_H + +#include <linux/io.h> + +#include "cdma.h" + +struct host1x; + +struct host1x_channel { +	struct list_head list; + +	unsigned int refcount; +	unsigned int id; +	struct mutex reflock; +	struct mutex submitlock; +	void __iomem *regs; +	struct device *dev; +	struct host1x_cdma cdma; +}; + +/* channel list operations */ +int host1x_channel_list_init(struct host1x *host); + +struct host1x_channel *host1x_channel_request(struct device *dev); +void host1x_channel_free(struct host1x_channel *channel); +struct host1x_channel *host1x_channel_get(struct host1x_channel *channel); +void host1x_channel_put(struct host1x_channel *channel); +int host1x_job_submit(struct host1x_job *job); + +#define host1x_for_each_channel(host, channel)				\ +	list_for_each_entry(channel, &host->chlist.list, list) + +#endif diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c new file mode 100644 index 00000000000..3ec7d77de24 --- /dev/null +++ b/drivers/gpu/host1x/debug.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Author: Erik Gilling <konkers@android.com> + * + * Copyright (C) 2011-2013 NVIDIA Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> + +#include <linux/io.h> + +#include "dev.h" +#include "debug.h" +#include "channel.h" + +unsigned int host1x_debug_trace_cmdbuf; + +static pid_t host1x_debug_force_timeout_pid; +static u32 host1x_debug_force_timeout_val; +static u32 host1x_debug_force_timeout_channel; + +void host1x_debug_output(struct output *o, const char *fmt, ...) +{ +	va_list args; +	int len; + +	va_start(args, fmt); +	len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); +	va_end(args); +	o->fn(o->ctx, o->buf, len); +} + +static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo) +{ +	struct host1x *m = dev_get_drvdata(ch->dev->parent); +	struct output *o = data; + +	mutex_lock(&ch->reflock); +	if (ch->refcount) { +		mutex_lock(&ch->cdma.lock); +		if (show_fifo) +			host1x_hw_show_channel_fifo(m, ch, o); +		host1x_hw_show_channel_cdma(m, ch, o); +		mutex_unlock(&ch->cdma.lock); +	} +	mutex_unlock(&ch->reflock); + +	return 0; +} + +static void show_syncpts(struct host1x *m, struct output *o) +{ +	int i; +	host1x_debug_output(o, "---- syncpts ----\n"); +	for (i = 0; i < host1x_syncpt_nb_pts(m); i++) { +		u32 max = host1x_syncpt_read_max(m->syncpt + i); +		u32 min = host1x_syncpt_load(m->syncpt + i); +		if (!min && !max) +			continue; +		host1x_debug_output(o, "id %d (%s) min %d max %d\n", +				    i, m->syncpt[i].name, min, max); +	} + +	for (i = 0; i < host1x_syncpt_nb_bases(m); i++) { +		u32 base_val; +		base_val = host1x_syncpt_load_wait_base(m->syncpt + i); +		if (base_val) +			host1x_debug_output(o, "waitbase id %d val %d\n", i, +					    base_val); +	} + +	host1x_debug_output(o, "\n"); +} + +static void show_all(struct host1x *m, struct output *o) +{ +	struct host1x_channel *ch; + +	host1x_hw_show_mlocks(m, o); +	show_syncpts(m, o); +	host1x_debug_output(o, "---- channels ----\n"); + +	host1x_for_each_channel(m, ch) +		show_channels(ch, o, true); +} + +#ifdef CONFIG_DEBUG_FS +static void show_all_no_fifo(struct host1x *host1x, struct output *o) +{ +	struct host1x_channel *ch; + +	host1x_hw_show_mlocks(host1x, o); +	show_syncpts(host1x, o); +	host1x_debug_output(o, "---- channels ----\n"); + +	host1x_for_each_channel(host1x, ch) +		show_channels(ch, o, false); +} + +static int host1x_debug_show_all(struct seq_file *s, void *unused) +{ +	struct output o = { +		.fn = write_to_seqfile, +		.ctx = s +	}; +	show_all(s->private, &o); +	return 0; +} + +static int host1x_debug_show(struct seq_file *s, void *unused) +{ +	struct output o = { +		.fn = write_to_seqfile, +		.ctx = s +	}; +	show_all_no_fifo(s->private, &o); +	return 0; +} + +static int host1x_debug_open_all(struct inode *inode, struct file *file) +{ +	return single_open(file, host1x_debug_show_all, inode->i_private); +} + +static const struct file_operations host1x_debug_all_fops = { +	.open		= host1x_debug_open_all, +	.read		= seq_read, +	.llseek		= seq_lseek, +	.release	= single_release, +}; + +static int host1x_debug_open(struct inode *inode, struct file *file) +{ +	return single_open(file, host1x_debug_show, inode->i_private); +} + +static const struct file_operations host1x_debug_fops = { +	.open		= host1x_debug_open, +	.read		= seq_read, +	.llseek		= seq_lseek, +	.release	= single_release, +}; + +void host1x_debug_init(struct host1x *host1x) +{ +	struct dentry *de = debugfs_create_dir("tegra-host1x", NULL); + +	if (!de) +		return; + +	/* Store the created entry */ +	host1x->debugfs = de; + +	debugfs_create_file("status", S_IRUGO, de, host1x, &host1x_debug_fops); +	debugfs_create_file("status_all", S_IRUGO, de, host1x, +			    &host1x_debug_all_fops); + +	debugfs_create_u32("trace_cmdbuf", S_IRUGO|S_IWUSR, de, +			   &host1x_debug_trace_cmdbuf); + +	host1x_hw_debug_init(host1x, de); + +	debugfs_create_u32("force_timeout_pid", S_IRUGO|S_IWUSR, de, +			   &host1x_debug_force_timeout_pid); +	debugfs_create_u32("force_timeout_val", S_IRUGO|S_IWUSR, de, +			   &host1x_debug_force_timeout_val); +	debugfs_create_u32("force_timeout_channel", S_IRUGO|S_IWUSR, de, +			   &host1x_debug_force_timeout_channel); +} + +void host1x_debug_deinit(struct host1x *host1x) +{ +	debugfs_remove_recursive(host1x->debugfs); +} +#else +void host1x_debug_init(struct host1x *host1x) +{ +} +void host1x_debug_deinit(struct host1x *host1x) +{ +} +#endif + +void host1x_debug_dump(struct host1x *host1x) +{ +	struct output o = { +		.fn = write_to_printk +	}; +	show_all(host1x, &o); +} + +void host1x_debug_dump_syncpts(struct host1x *host1x) +{ +	struct output o = { +		.fn = write_to_printk +	}; +	show_syncpts(host1x, &o); +} diff --git a/drivers/gpu/host1x/debug.h b/drivers/gpu/host1x/debug.h new file mode 100644 index 00000000000..4595b2e0799 --- /dev/null +++ b/drivers/gpu/host1x/debug.h @@ -0,0 +1,51 @@ +/* + * Tegra host1x Debug + * + * Copyright (c) 2011-2013 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_DEBUG_H +#define __HOST1X_DEBUG_H + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +struct host1x; + +struct output { +	void (*fn)(void *ctx, const char *str, size_t len); +	void *ctx; +	char buf[256]; +}; + +static inline void write_to_seqfile(void *ctx, const char *str, size_t len) +{ +	seq_write((struct seq_file *)ctx, str, len); +} + +static inline void write_to_printk(void *ctx, const char *str, size_t len) +{ +	pr_info("%s", str); +} + +void __printf(2, 3) host1x_debug_output(struct output *o, const char *fmt, ...); + +extern unsigned int host1x_debug_trace_cmdbuf; + +void host1x_debug_init(struct host1x *host1x); +void host1x_debug_deinit(struct host1x *host1x); +void host1x_debug_dump(struct host1x *host1x); +void host1x_debug_dump_syncpts(struct host1x *host1x); + +#endif diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c new file mode 100644 index 00000000000..28e28a23d44 --- /dev/null +++ b/drivers/gpu/host1x/dev.c @@ -0,0 +1,246 @@ +/* + * Tegra host1x driver + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/io.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/host1x.h> + +#include "dev.h" +#include "intr.h" +#include "channel.h" +#include "debug.h" +#include "hw/host1x01.h" +#include "host1x_client.h" + +void host1x_set_drm_data(struct device *dev, void *data) +{ +	struct host1x *host1x = dev_get_drvdata(dev); +	host1x->drm_data = data; +} + +void *host1x_get_drm_data(struct device *dev) +{ +	struct host1x *host1x = dev_get_drvdata(dev); +	return host1x->drm_data; +} + +void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r) +{ +	void __iomem *sync_regs = host1x->regs + host1x->info->sync_offset; + +	writel(v, sync_regs + r); +} + +u32 host1x_sync_readl(struct host1x *host1x, u32 r) +{ +	void __iomem *sync_regs = host1x->regs + host1x->info->sync_offset; + +	return readl(sync_regs + r); +} + +void host1x_ch_writel(struct host1x_channel *ch, u32 v, u32 r) +{ +	writel(v, ch->regs + r); +} + +u32 host1x_ch_readl(struct host1x_channel *ch, u32 r) +{ +	return readl(ch->regs + r); +} + +static const struct host1x_info host1x01_info = { +	.nb_channels	= 8, +	.nb_pts		= 32, +	.nb_mlocks	= 16, +	.nb_bases	= 8, +	.init		= host1x01_init, +	.sync_offset	= 0x3000, +}; + +static struct of_device_id host1x_of_match[] = { +	{ .compatible = "nvidia,tegra30-host1x", .data = &host1x01_info, }, +	{ .compatible = "nvidia,tegra20-host1x", .data = &host1x01_info, }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, host1x_of_match); + +static int host1x_probe(struct platform_device *pdev) +{ +	const struct of_device_id *id; +	struct host1x *host; +	struct resource *regs; +	int syncpt_irq; +	int err; + +	id = of_match_device(host1x_of_match, &pdev->dev); +	if (!id) +		return -EINVAL; + +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!regs) { +		dev_err(&pdev->dev, "failed to get registers\n"); +		return -ENXIO; +	} + +	syncpt_irq = platform_get_irq(pdev, 0); +	if (syncpt_irq < 0) { +		dev_err(&pdev->dev, "failed to get IRQ\n"); +		return -ENXIO; +	} + +	host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); +	if (!host) +		return -ENOMEM; + +	host->dev = &pdev->dev; +	host->info = id->data; + +	/* set common host1x device data */ +	platform_set_drvdata(pdev, host); + +	host->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(host->regs)) +		return PTR_ERR(host->regs); + +	if (host->info->init) { +		err = host->info->init(host); +		if (err) +			return err; +	} + +	host->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(host->clk)) { +		dev_err(&pdev->dev, "failed to get clock\n"); +		err = PTR_ERR(host->clk); +		return err; +	} + +	err = host1x_channel_list_init(host); +	if (err) { +		dev_err(&pdev->dev, "failed to initialize channel list\n"); +		return err; +	} + +	err = clk_prepare_enable(host->clk); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to enable clock\n"); +		return err; +	} + +	err = host1x_syncpt_init(host); +	if (err) { +		dev_err(&pdev->dev, "failed to initialize syncpts\n"); +		return err; +	} + +	err = host1x_intr_init(host, syncpt_irq); +	if (err) { +		dev_err(&pdev->dev, "failed to initialize interrupts\n"); +		goto fail_deinit_syncpt; +	} + +	host1x_debug_init(host); + +	host1x_drm_alloc(pdev); + +	return 0; + +fail_deinit_syncpt: +	host1x_syncpt_deinit(host); +	return err; +} + +static int __exit host1x_remove(struct platform_device *pdev) +{ +	struct host1x *host = platform_get_drvdata(pdev); + +	host1x_intr_deinit(host); +	host1x_syncpt_deinit(host); +	clk_disable_unprepare(host->clk); + +	return 0; +} + +static struct platform_driver tegra_host1x_driver = { +	.probe = host1x_probe, +	.remove = __exit_p(host1x_remove), +	.driver = { +		.owner = THIS_MODULE, +		.name = "tegra-host1x", +		.of_match_table = host1x_of_match, +	}, +}; + +static int __init tegra_host1x_init(void) +{ +	int err; + +	err = platform_driver_register(&tegra_host1x_driver); +	if (err < 0) +		return err; + +#ifdef CONFIG_DRM_TEGRA +	err = platform_driver_register(&tegra_dc_driver); +	if (err < 0) +		goto unregister_host1x; + +	err = platform_driver_register(&tegra_hdmi_driver); +	if (err < 0) +		goto unregister_dc; + +	err = platform_driver_register(&tegra_gr2d_driver); +	if (err < 0) +		goto unregister_hdmi; +#endif + +	return 0; + +#ifdef CONFIG_DRM_TEGRA +unregister_hdmi: +	platform_driver_unregister(&tegra_hdmi_driver); +unregister_dc: +	platform_driver_unregister(&tegra_dc_driver); +unregister_host1x: +	platform_driver_unregister(&tegra_host1x_driver); +	return err; +#endif +} +module_init(tegra_host1x_init); + +static void __exit tegra_host1x_exit(void) +{ +#ifdef CONFIG_DRM_TEGRA +	platform_driver_unregister(&tegra_gr2d_driver); +	platform_driver_unregister(&tegra_hdmi_driver); +	platform_driver_unregister(&tegra_dc_driver); +#endif +	platform_driver_unregister(&tegra_host1x_driver); +} +module_exit(tegra_host1x_exit); + +MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>"); +MODULE_AUTHOR("Terje Bergstrom <tbergstrom@nvidia.com>"); +MODULE_DESCRIPTION("Host1x driver for Tegra products"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h new file mode 100644 index 00000000000..a1607d6e135 --- /dev/null +++ b/drivers/gpu/host1x/dev.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 HOST1X_DEV_H +#define HOST1X_DEV_H + +#include <linux/platform_device.h> +#include <linux/device.h> + +#include "channel.h" +#include "syncpt.h" +#include "intr.h" +#include "cdma.h" +#include "job.h" + +struct host1x_syncpt; +struct host1x_channel; +struct host1x_cdma; +struct host1x_job; +struct push_buffer; +struct output; +struct dentry; + +struct host1x_channel_ops { +	int (*init)(struct host1x_channel *channel, struct host1x *host, +		    unsigned int id); +	int (*submit)(struct host1x_job *job); +}; + +struct host1x_cdma_ops { +	void (*start)(struct host1x_cdma *cdma); +	void (*stop)(struct host1x_cdma *cdma); +	void (*flush)(struct  host1x_cdma *cdma); +	int (*timeout_init)(struct host1x_cdma *cdma, u32 syncpt_id); +	void (*timeout_destroy)(struct host1x_cdma *cdma); +	void (*freeze)(struct host1x_cdma *cdma); +	void (*resume)(struct host1x_cdma *cdma, u32 getptr); +	void (*timeout_cpu_incr)(struct host1x_cdma *cdma, u32 getptr, +				 u32 syncpt_incrs, u32 syncval, u32 nr_slots); +}; + +struct host1x_pushbuffer_ops { +	void (*init)(struct push_buffer *pb); +}; + +struct host1x_debug_ops { +	void (*debug_init)(struct dentry *de); +	void (*show_channel_cdma)(struct host1x *host, +				  struct host1x_channel *ch, +				  struct output *o); +	void (*show_channel_fifo)(struct host1x *host, +				  struct host1x_channel *ch, +				  struct output *o); +	void (*show_mlocks)(struct host1x *host, struct output *output); + +}; + +struct host1x_syncpt_ops { +	void (*restore)(struct host1x_syncpt *syncpt); +	void (*restore_wait_base)(struct host1x_syncpt *syncpt); +	void (*load_wait_base)(struct host1x_syncpt *syncpt); +	u32 (*load)(struct host1x_syncpt *syncpt); +	void (*cpu_incr)(struct host1x_syncpt *syncpt); +	int (*patch_wait)(struct host1x_syncpt *syncpt, void *patch_addr); +}; + +struct host1x_intr_ops { +	int (*init_host_sync)(struct host1x *host, u32 cpm, +		void (*syncpt_thresh_work)(struct work_struct *work)); +	void (*set_syncpt_threshold)( +		struct host1x *host, u32 id, u32 thresh); +	void (*enable_syncpt_intr)(struct host1x *host, u32 id); +	void (*disable_syncpt_intr)(struct host1x *host, u32 id); +	void (*disable_all_syncpt_intrs)(struct host1x *host); +	int (*free_syncpt_irq)(struct host1x *host); +}; + +struct host1x_info { +	int	nb_channels;		/* host1x: num channels supported */ +	int	nb_pts;			/* host1x: num syncpoints supported */ +	int	nb_bases;		/* host1x: num syncpoints supported */ +	int	nb_mlocks;		/* host1x: number of mlocks */ +	int	(*init)(struct host1x *); /* initialize per SoC ops */ +	int	sync_offset; +}; + +struct host1x { +	const struct host1x_info *info; + +	void __iomem *regs; +	struct host1x_syncpt *syncpt; +	struct device *dev; +	struct clk *clk; + +	struct mutex intr_mutex; +	struct workqueue_struct *intr_wq; +	int intr_syncpt_irq; + +	const struct host1x_syncpt_ops *syncpt_op; +	const struct host1x_intr_ops *intr_op; +	const struct host1x_channel_ops *channel_op; +	const struct host1x_cdma_ops *cdma_op; +	const struct host1x_pushbuffer_ops *cdma_pb_op; +	const struct host1x_debug_ops *debug_op; + +	struct host1x_syncpt *nop_sp; + +	struct mutex chlist_mutex; +	struct host1x_channel chlist; +	unsigned long allocated_channels; +	unsigned int num_allocated_channels; + +	struct dentry *debugfs; + +	void *drm_data; +}; + +void host1x_sync_writel(struct host1x *host1x, u32 r, u32 v); +u32 host1x_sync_readl(struct host1x *host1x, u32 r); +void host1x_ch_writel(struct host1x_channel *ch, u32 r, u32 v); +u32 host1x_ch_readl(struct host1x_channel *ch, u32 r); + +static inline void host1x_hw_syncpt_restore(struct host1x *host, +					    struct host1x_syncpt *sp) +{ +	host->syncpt_op->restore(sp); +} + +static inline void host1x_hw_syncpt_restore_wait_base(struct host1x *host, +						      struct host1x_syncpt *sp) +{ +	host->syncpt_op->restore_wait_base(sp); +} + +static inline void host1x_hw_syncpt_load_wait_base(struct host1x *host, +						   struct host1x_syncpt *sp) +{ +	host->syncpt_op->load_wait_base(sp); +} + +static inline u32 host1x_hw_syncpt_load(struct host1x *host, +					struct host1x_syncpt *sp) +{ +	return host->syncpt_op->load(sp); +} + +static inline void host1x_hw_syncpt_cpu_incr(struct host1x *host, +					     struct host1x_syncpt *sp) +{ +	host->syncpt_op->cpu_incr(sp); +} + +static inline int host1x_hw_syncpt_patch_wait(struct host1x *host, +					      struct host1x_syncpt *sp, +					      void *patch_addr) +{ +	return host->syncpt_op->patch_wait(sp, patch_addr); +} + +static inline int host1x_hw_intr_init_host_sync(struct host1x *host, u32 cpm, +			void (*syncpt_thresh_work)(struct work_struct *)) +{ +	return host->intr_op->init_host_sync(host, cpm, syncpt_thresh_work); +} + +static inline void host1x_hw_intr_set_syncpt_threshold(struct host1x *host, +						       u32 id, u32 thresh) +{ +	host->intr_op->set_syncpt_threshold(host, id, thresh); +} + +static inline void host1x_hw_intr_enable_syncpt_intr(struct host1x *host, +						     u32 id) +{ +	host->intr_op->enable_syncpt_intr(host, id); +} + +static inline void host1x_hw_intr_disable_syncpt_intr(struct host1x *host, +						      u32 id) +{ +	host->intr_op->disable_syncpt_intr(host, id); +} + +static inline void host1x_hw_intr_disable_all_syncpt_intrs(struct host1x *host) +{ +	host->intr_op->disable_all_syncpt_intrs(host); +} + +static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host) +{ +	return host->intr_op->free_syncpt_irq(host); +} + +static inline int host1x_hw_channel_init(struct host1x *host, +					 struct host1x_channel *channel, +					 int chid) +{ +	return host->channel_op->init(channel, host, chid); +} + +static inline int host1x_hw_channel_submit(struct host1x *host, +					   struct host1x_job *job) +{ +	return host->channel_op->submit(job); +} + +static inline void host1x_hw_cdma_start(struct host1x *host, +					struct host1x_cdma *cdma) +{ +	host->cdma_op->start(cdma); +} + +static inline void host1x_hw_cdma_stop(struct host1x *host, +				       struct host1x_cdma *cdma) +{ +	host->cdma_op->stop(cdma); +} + +static inline void host1x_hw_cdma_flush(struct host1x *host, +					struct host1x_cdma *cdma) +{ +	host->cdma_op->flush(cdma); +} + +static inline int host1x_hw_cdma_timeout_init(struct host1x *host, +					      struct host1x_cdma *cdma, +					      u32 syncpt_id) +{ +	return host->cdma_op->timeout_init(cdma, syncpt_id); +} + +static inline void host1x_hw_cdma_timeout_destroy(struct host1x *host, +						  struct host1x_cdma *cdma) +{ +	host->cdma_op->timeout_destroy(cdma); +} + +static inline void host1x_hw_cdma_freeze(struct host1x *host, +					 struct host1x_cdma *cdma) +{ +	host->cdma_op->freeze(cdma); +} + +static inline void host1x_hw_cdma_resume(struct host1x *host, +					 struct host1x_cdma *cdma, u32 getptr) +{ +	host->cdma_op->resume(cdma, getptr); +} + +static inline void host1x_hw_cdma_timeout_cpu_incr(struct host1x *host, +						   struct host1x_cdma *cdma, +						   u32 getptr, +						   u32 syncpt_incrs, +						   u32 syncval, u32 nr_slots) +{ +	host->cdma_op->timeout_cpu_incr(cdma, getptr, syncpt_incrs, syncval, +					nr_slots); +} + +static inline void host1x_hw_pushbuffer_init(struct host1x *host, +					     struct push_buffer *pb) +{ +	host->cdma_pb_op->init(pb); +} + +static inline void host1x_hw_debug_init(struct host1x *host, struct dentry *de) +{ +	if (host->debug_op && host->debug_op->debug_init) +		host->debug_op->debug_init(de); +} + +static inline void host1x_hw_show_channel_cdma(struct host1x *host, +					       struct host1x_channel *channel, +					       struct output *o) +{ +	host->debug_op->show_channel_cdma(host, channel, o); +} + +static inline void host1x_hw_show_channel_fifo(struct host1x *host, +					       struct host1x_channel *channel, +					       struct output *o) +{ +	host->debug_op->show_channel_fifo(host, channel, o); +} + +static inline void host1x_hw_show_mlocks(struct host1x *host, struct output *o) +{ +	host->debug_op->show_mlocks(host, o); +} + +extern struct platform_driver tegra_hdmi_driver; +extern struct platform_driver tegra_dc_driver; +extern struct platform_driver tegra_gr2d_driver; + +#endif diff --git a/drivers/gpu/host1x/drm/Kconfig b/drivers/gpu/host1x/drm/Kconfig new file mode 100644 index 00000000000..69853a4de40 --- /dev/null +++ b/drivers/gpu/host1x/drm/Kconfig @@ -0,0 +1,29 @@ +config DRM_TEGRA +	bool "NVIDIA Tegra DRM" +	depends on DRM +	select DRM_KMS_HELPER +	select FB_SYS_FILLRECT +	select FB_SYS_COPYAREA +	select FB_SYS_IMAGEBLIT +	help +	  Choose this option if you have an NVIDIA Tegra SoC. + +	  To compile this driver as a module, choose M here: the module +	  will be called tegra-drm. + +if DRM_TEGRA + +config DRM_TEGRA_STAGING +	bool "Enable HOST1X interface" +	depends on STAGING +	help +	  Say yes if HOST1X should be available for userspace DRM users. + +	  If unsure, choose N. + +config DRM_TEGRA_DEBUG +	bool "NVIDIA Tegra DRM debug support" +	help +	  Say yes here to enable debugging support. + +endif diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c new file mode 100644 index 00000000000..1e2060324f0 --- /dev/null +++ b/drivers/gpu/host1x/drm/dc.c @@ -0,0 +1,1200 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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/clk.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/clk/tegra.h> + +#include "host1x_client.h" +#include "dc.h" +#include "drm.h" +#include "gem.h" + +struct tegra_plane { +	struct drm_plane base; +	unsigned int index; +}; + +static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) +{ +	return container_of(plane, struct tegra_plane, base); +} + +static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, +			      struct drm_framebuffer *fb, int crtc_x, +			      int crtc_y, unsigned int crtc_w, +			      unsigned int crtc_h, uint32_t src_x, +			      uint32_t src_y, uint32_t src_w, uint32_t src_h) +{ +	struct tegra_plane *p = to_tegra_plane(plane); +	struct tegra_dc *dc = to_tegra_dc(crtc); +	struct tegra_dc_window window; +	unsigned int i; + +	memset(&window, 0, sizeof(window)); +	window.src.x = src_x >> 16; +	window.src.y = src_y >> 16; +	window.src.w = src_w >> 16; +	window.src.h = src_h >> 16; +	window.dst.x = crtc_x; +	window.dst.y = crtc_y; +	window.dst.w = crtc_w; +	window.dst.h = crtc_h; +	window.format = tegra_dc_format(fb->pixel_format); +	window.bits_per_pixel = fb->bits_per_pixel; + +	for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { +		struct tegra_bo *bo = tegra_fb_get_plane(fb, i); + +		window.base[i] = bo->paddr + fb->offsets[i]; + +		/* +		 * Tegra doesn't support different strides for U and V planes +		 * so we display a warning if the user tries to display a +		 * framebuffer with such a configuration. +		 */ +		if (i >= 2) { +			if (fb->pitches[i] != window.stride[1]) +				DRM_ERROR("unsupported UV-plane configuration\n"); +		} else { +			window.stride[i] = fb->pitches[i]; +		} +	} + +	return tegra_dc_setup_window(dc, p->index, &window); +} + +static int tegra_plane_disable(struct drm_plane *plane) +{ +	struct tegra_dc *dc = to_tegra_dc(plane->crtc); +	struct tegra_plane *p = to_tegra_plane(plane); +	unsigned long value; + +	value = WINDOW_A_SELECT << p->index; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); + +	value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); +	value &= ~WIN_ENABLE; +	tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + +	tegra_dc_writel(dc, WIN_A_UPDATE << p->index, DC_CMD_STATE_CONTROL); +	tegra_dc_writel(dc, WIN_A_ACT_REQ << p->index, DC_CMD_STATE_CONTROL); + +	return 0; +} + +static void tegra_plane_destroy(struct drm_plane *plane) +{ +	tegra_plane_disable(plane); +	drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs tegra_plane_funcs = { +	.update_plane = tegra_plane_update, +	.disable_plane = tegra_plane_disable, +	.destroy = tegra_plane_destroy, +}; + +static const uint32_t plane_formats[] = { +	DRM_FORMAT_XBGR8888, +	DRM_FORMAT_XRGB8888, +	DRM_FORMAT_RGB565, +	DRM_FORMAT_UYVY, +	DRM_FORMAT_YUV420, +	DRM_FORMAT_YUV422, +}; + +static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) +{ +	unsigned int i; +	int err = 0; + +	for (i = 0; i < 2; i++) { +		struct tegra_plane *plane; + +		plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL); +		if (!plane) +			return -ENOMEM; + +		plane->index = 1 + i; + +		err = drm_plane_init(drm, &plane->base, 1 << dc->pipe, +				     &tegra_plane_funcs, plane_formats, +				     ARRAY_SIZE(plane_formats), false); +		if (err < 0) +			return err; +	} + +	return 0; +} + +static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, +			     struct drm_framebuffer *fb) +{ +	struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); +	unsigned long value; + +	tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); + +	value = fb->offsets[0] + y * fb->pitches[0] + +		x * fb->bits_per_pixel / 8; + +	tegra_dc_writel(dc, bo->paddr + value, DC_WINBUF_START_ADDR); +	tegra_dc_writel(dc, fb->pitches[0], DC_WIN_LINE_STRIDE); + +	value = GENERAL_UPDATE | WIN_A_UPDATE; +	tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + +	value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; +	tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + +	return 0; +} + +void tegra_dc_enable_vblank(struct tegra_dc *dc) +{ +	unsigned long value, flags; + +	spin_lock_irqsave(&dc->lock, flags); + +	value = tegra_dc_readl(dc, DC_CMD_INT_MASK); +	value |= VBLANK_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + +	spin_unlock_irqrestore(&dc->lock, flags); +} + +void tegra_dc_disable_vblank(struct tegra_dc *dc) +{ +	unsigned long value, flags; + +	spin_lock_irqsave(&dc->lock, flags); + +	value = tegra_dc_readl(dc, DC_CMD_INT_MASK); +	value &= ~VBLANK_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + +	spin_unlock_irqrestore(&dc->lock, flags); +} + +static void tegra_dc_finish_page_flip(struct tegra_dc *dc) +{ +	struct drm_device *drm = dc->base.dev; +	struct drm_crtc *crtc = &dc->base; +	unsigned long flags, base; +	struct tegra_bo *bo; + +	if (!dc->event) +		return; + +	bo = tegra_fb_get_plane(crtc->fb, 0); + +	/* check if new start address has been latched */ +	tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); +	base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR); +	tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + +	if (base == bo->paddr + crtc->fb->offsets[0]) { +		spin_lock_irqsave(&drm->event_lock, flags); +		drm_send_vblank_event(drm, dc->pipe, dc->event); +		drm_vblank_put(drm, dc->pipe); +		dc->event = NULL; +		spin_unlock_irqrestore(&drm->event_lock, flags); +	} +} + +void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) +{ +	struct tegra_dc *dc = to_tegra_dc(crtc); +	struct drm_device *drm = crtc->dev; +	unsigned long flags; + +	spin_lock_irqsave(&drm->event_lock, flags); + +	if (dc->event && dc->event->base.file_priv == file) { +		dc->event->base.destroy(&dc->event->base); +		drm_vblank_put(drm, dc->pipe); +		dc->event = NULL; +	} + +	spin_unlock_irqrestore(&drm->event_lock, flags); +} + +static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, +			      struct drm_pending_vblank_event *event) +{ +	struct tegra_dc *dc = to_tegra_dc(crtc); +	struct drm_device *drm = crtc->dev; + +	if (dc->event) +		return -EBUSY; + +	if (event) { +		event->pipe = dc->pipe; +		dc->event = event; +		drm_vblank_get(drm, dc->pipe); +	} + +	tegra_dc_set_base(dc, 0, 0, fb); +	crtc->fb = fb; + +	return 0; +} + +static const struct drm_crtc_funcs tegra_crtc_funcs = { +	.page_flip = tegra_dc_page_flip, +	.set_config = drm_crtc_helper_set_config, +	.destroy = drm_crtc_cleanup, +}; + +static void tegra_crtc_disable(struct drm_crtc *crtc) +{ +	struct drm_device *drm = crtc->dev; +	struct drm_plane *plane; + +	list_for_each_entry(plane, &drm->mode_config.plane_list, head) { +		if (plane->crtc == crtc) { +			tegra_plane_disable(plane); +			plane->crtc = NULL; + +			if (plane->fb) { +				drm_framebuffer_unreference(plane->fb); +				plane->fb = NULL; +			} +		} +	} +} + +static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, +				  const struct drm_display_mode *mode, +				  struct drm_display_mode *adjusted) +{ +	return true; +} + +static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v, +				  unsigned int bpp) +{ +	fixed20_12 outf = dfixed_init(out); +	fixed20_12 inf = dfixed_init(in); +	u32 dda_inc; +	int max; + +	if (v) +		max = 15; +	else { +		switch (bpp) { +		case 2: +			max = 8; +			break; + +		default: +			WARN_ON_ONCE(1); +			/* fallthrough */ +		case 4: +			max = 4; +			break; +		} +	} + +	outf.full = max_t(u32, outf.full - dfixed_const(1), dfixed_const(1)); +	inf.full -= dfixed_const(1); + +	dda_inc = dfixed_div(inf, outf); +	dda_inc = min_t(u32, dda_inc, dfixed_const(max)); + +	return dda_inc; +} + +static inline u32 compute_initial_dda(unsigned int in) +{ +	fixed20_12 inf = dfixed_init(in); +	return dfixed_frac(inf); +} + +static int tegra_dc_set_timings(struct tegra_dc *dc, +				struct drm_display_mode *mode) +{ +	/* TODO: For HDMI compliance, h & v ref_to_sync should be set to 1 */ +	unsigned int h_ref_to_sync = 0; +	unsigned int v_ref_to_sync = 0; +	unsigned long value; + +	tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS); + +	value = (v_ref_to_sync << 16) | h_ref_to_sync; +	tegra_dc_writel(dc, value, DC_DISP_REF_TO_SYNC); + +	value = ((mode->vsync_end - mode->vsync_start) << 16) | +		((mode->hsync_end - mode->hsync_start) <<  0); +	tegra_dc_writel(dc, value, DC_DISP_SYNC_WIDTH); + +	value = ((mode->vtotal - mode->vsync_end) << 16) | +		((mode->htotal - mode->hsync_end) <<  0); +	tegra_dc_writel(dc, value, DC_DISP_BACK_PORCH); + +	value = ((mode->vsync_start - mode->vdisplay) << 16) | +		((mode->hsync_start - mode->hdisplay) <<  0); +	tegra_dc_writel(dc, value, DC_DISP_FRONT_PORCH); + +	value = (mode->vdisplay << 16) | mode->hdisplay; +	tegra_dc_writel(dc, value, DC_DISP_ACTIVE); + +	return 0; +} + +static int tegra_crtc_setup_clk(struct drm_crtc *crtc, +				struct drm_display_mode *mode, +				unsigned long *div) +{ +	unsigned long pclk = mode->clock * 1000, rate; +	struct tegra_dc *dc = to_tegra_dc(crtc); +	struct tegra_output *output = NULL; +	struct drm_encoder *encoder; +	long err; + +	list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list, head) +		if (encoder->crtc == crtc) { +			output = encoder_to_output(encoder); +			break; +		} + +	if (!output) +		return -ENODEV; + +	/* +	 * This assumes that the display controller will divide its parent +	 * clock by 2 to generate the pixel clock. +	 */ +	err = tegra_output_setup_clock(output, dc->clk, pclk * 2); +	if (err < 0) { +		dev_err(dc->dev, "failed to setup clock: %ld\n", err); +		return err; +	} + +	rate = clk_get_rate(dc->clk); +	*div = (rate * 2 / pclk) - 2; + +	DRM_DEBUG_KMS("rate: %lu, div: %lu\n", rate, *div); + +	return 0; +} + +static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) +{ +	switch (format) { +	case WIN_COLOR_DEPTH_YCbCr422: +	case WIN_COLOR_DEPTH_YUV422: +		if (planar) +			*planar = false; + +		return true; + +	case WIN_COLOR_DEPTH_YCbCr420P: +	case WIN_COLOR_DEPTH_YUV420P: +	case WIN_COLOR_DEPTH_YCbCr422P: +	case WIN_COLOR_DEPTH_YUV422P: +	case WIN_COLOR_DEPTH_YCbCr422R: +	case WIN_COLOR_DEPTH_YUV422R: +	case WIN_COLOR_DEPTH_YCbCr422RA: +	case WIN_COLOR_DEPTH_YUV422RA: +		if (planar) +			*planar = true; + +		return true; +	} + +	return false; +} + +int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, +			  const struct tegra_dc_window *window) +{ +	unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; +	unsigned long value; +	bool yuv, planar; + +	/* +	 * For YUV planar modes, the number of bytes per pixel takes into +	 * account only the luma component and therefore is 1. +	 */ +	yuv = tegra_dc_format_is_yuv(window->format, &planar); +	if (!yuv) +		bpp = window->bits_per_pixel / 8; +	else +		bpp = planar ? 1 : 2; + +	value = WINDOW_A_SELECT << index; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); + +	tegra_dc_writel(dc, window->format, DC_WIN_COLOR_DEPTH); +	tegra_dc_writel(dc, 0, DC_WIN_BYTE_SWAP); + +	value = V_POSITION(window->dst.y) | H_POSITION(window->dst.x); +	tegra_dc_writel(dc, value, DC_WIN_POSITION); + +	value = V_SIZE(window->dst.h) | H_SIZE(window->dst.w); +	tegra_dc_writel(dc, value, DC_WIN_SIZE); + +	h_offset = window->src.x * bpp; +	v_offset = window->src.y; +	h_size = window->src.w * bpp; +	v_size = window->src.h; + +	value = V_PRESCALED_SIZE(v_size) | H_PRESCALED_SIZE(h_size); +	tegra_dc_writel(dc, value, DC_WIN_PRESCALED_SIZE); + +	/* +	 * For DDA computations the number of bytes per pixel for YUV planar +	 * modes needs to take into account all Y, U and V components. +	 */ +	if (yuv && planar) +		bpp = 2; + +	h_dda = compute_dda_inc(window->src.w, window->dst.w, false, bpp); +	v_dda = compute_dda_inc(window->src.h, window->dst.h, true, bpp); + +	value = V_DDA_INC(v_dda) | H_DDA_INC(h_dda); +	tegra_dc_writel(dc, value, DC_WIN_DDA_INC); + +	h_dda = compute_initial_dda(window->src.x); +	v_dda = compute_initial_dda(window->src.y); + +	tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); +	tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); + +	tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); +	tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); + +	tegra_dc_writel(dc, window->base[0], DC_WINBUF_START_ADDR); + +	if (yuv && planar) { +		tegra_dc_writel(dc, window->base[1], DC_WINBUF_START_ADDR_U); +		tegra_dc_writel(dc, window->base[2], DC_WINBUF_START_ADDR_V); +		value = window->stride[1] << 16 | window->stride[0]; +		tegra_dc_writel(dc, value, DC_WIN_LINE_STRIDE); +	} else { +		tegra_dc_writel(dc, window->stride[0], DC_WIN_LINE_STRIDE); +	} + +	tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); +	tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); + +	value = WIN_ENABLE; + +	if (yuv) { +		/* setup default colorspace conversion coefficients */ +		tegra_dc_writel(dc, 0x00f0, DC_WIN_CSC_YOF); +		tegra_dc_writel(dc, 0x012a, DC_WIN_CSC_KYRGB); +		tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KUR); +		tegra_dc_writel(dc, 0x0198, DC_WIN_CSC_KVR); +		tegra_dc_writel(dc, 0x039b, DC_WIN_CSC_KUG); +		tegra_dc_writel(dc, 0x032f, DC_WIN_CSC_KVG); +		tegra_dc_writel(dc, 0x0204, DC_WIN_CSC_KUB); +		tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KVB); + +		value |= CSC_ENABLE; +	} else if (window->bits_per_pixel < 24) { +		value |= COLOR_EXPAND; +	} + +	tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + +	/* +	 * Disable blending and assume Window A is the bottom-most window, +	 * Window C is the top-most window and Window B is in the middle. +	 */ +	tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_NOKEY); +	tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_1WIN); + +	switch (index) { +	case 0: +		tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_X); +		tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); +		tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); +		break; + +	case 1: +		tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); +		tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); +		tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); +		break; + +	case 2: +		tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); +		tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_Y); +		tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_3WIN_XY); +		break; +	} + +	tegra_dc_writel(dc, WIN_A_UPDATE << index, DC_CMD_STATE_CONTROL); +	tegra_dc_writel(dc, WIN_A_ACT_REQ << index, DC_CMD_STATE_CONTROL); + +	return 0; +} + +unsigned int tegra_dc_format(uint32_t format) +{ +	switch (format) { +	case DRM_FORMAT_XBGR8888: +		return WIN_COLOR_DEPTH_R8G8B8A8; + +	case DRM_FORMAT_XRGB8888: +		return WIN_COLOR_DEPTH_B8G8R8A8; + +	case DRM_FORMAT_RGB565: +		return WIN_COLOR_DEPTH_B5G6R5; + +	case DRM_FORMAT_UYVY: +		return WIN_COLOR_DEPTH_YCbCr422; + +	case DRM_FORMAT_YUV420: +		return WIN_COLOR_DEPTH_YCbCr420P; + +	case DRM_FORMAT_YUV422: +		return WIN_COLOR_DEPTH_YCbCr422P; + +	default: +		break; +	} + +	WARN(1, "unsupported pixel format %u, using default\n", format); +	return WIN_COLOR_DEPTH_B8G8R8A8; +} + +static int tegra_crtc_mode_set(struct drm_crtc *crtc, +			       struct drm_display_mode *mode, +			       struct drm_display_mode *adjusted, +			       int x, int y, struct drm_framebuffer *old_fb) +{ +	struct tegra_bo *bo = tegra_fb_get_plane(crtc->fb, 0); +	struct tegra_dc *dc = to_tegra_dc(crtc); +	struct tegra_dc_window window; +	unsigned long div, value; +	int err; + +	drm_vblank_pre_modeset(crtc->dev, dc->pipe); + +	err = tegra_crtc_setup_clk(crtc, mode, &div); +	if (err) { +		dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err); +		return err; +	} + +	/* program display mode */ +	tegra_dc_set_timings(dc, mode); + +	value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL; +	tegra_dc_writel(dc, value, DC_DISP_DATA_ENABLE_OPTIONS); + +	value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(1)); +	value &= ~LVS_OUTPUT_POLARITY_LOW; +	value &= ~LHS_OUTPUT_POLARITY_LOW; +	tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(1)); + +	value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB | +		DISP_ORDER_RED_BLUE; +	tegra_dc_writel(dc, value, DC_DISP_DISP_INTERFACE_CONTROL); + +	tegra_dc_writel(dc, 0x00010001, DC_DISP_SHIFT_CLOCK_OPTIONS); + +	value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; +	tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); + +	/* setup window parameters */ +	memset(&window, 0, sizeof(window)); +	window.src.x = 0; +	window.src.y = 0; +	window.src.w = mode->hdisplay; +	window.src.h = mode->vdisplay; +	window.dst.x = 0; +	window.dst.y = 0; +	window.dst.w = mode->hdisplay; +	window.dst.h = mode->vdisplay; +	window.format = tegra_dc_format(crtc->fb->pixel_format); +	window.bits_per_pixel = crtc->fb->bits_per_pixel; +	window.stride[0] = crtc->fb->pitches[0]; +	window.base[0] = bo->paddr; + +	err = tegra_dc_setup_window(dc, 0, &window); +	if (err < 0) +		dev_err(dc->dev, "failed to enable root plane\n"); + +	return 0; +} + +static int tegra_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, +				    struct drm_framebuffer *old_fb) +{ +	struct tegra_dc *dc = to_tegra_dc(crtc); + +	return tegra_dc_set_base(dc, x, y, crtc->fb); +} + +static void tegra_crtc_prepare(struct drm_crtc *crtc) +{ +	struct tegra_dc *dc = to_tegra_dc(crtc); +	unsigned int syncpt; +	unsigned long value; + +	/* hardware initialization */ +	tegra_periph_reset_deassert(dc->clk); +	usleep_range(10000, 20000); + +	if (dc->pipe) +		syncpt = SYNCPT_VBLANK1; +	else +		syncpt = SYNCPT_VBLANK0; + +	/* initialize display controller */ +	tegra_dc_writel(dc, 0x00000100, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); +	tegra_dc_writel(dc, 0x100 | syncpt, DC_CMD_CONT_SYNCPT_VSYNC); + +	value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | WIN_A_OF_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); + +	value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | +		WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); + +	value = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | +		PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + +	value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); +	value |= DISP_CTRL_MODE_C_DISPLAY; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + +	/* initialize timer */ +	value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | +		WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); +	tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY); + +	value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) | +		WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); +	tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); + +	value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); + +	value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; +	tegra_dc_writel(dc, value, DC_CMD_INT_MASK); +} + +static void tegra_crtc_commit(struct drm_crtc *crtc) +{ +	struct tegra_dc *dc = to_tegra_dc(crtc); +	unsigned long value; + +	value = GENERAL_UPDATE | WIN_A_UPDATE; +	tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + +	value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; +	tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + +	drm_vblank_post_modeset(crtc->dev, dc->pipe); +} + +static void tegra_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { +	.disable = tegra_crtc_disable, +	.mode_fixup = tegra_crtc_mode_fixup, +	.mode_set = tegra_crtc_mode_set, +	.mode_set_base = tegra_crtc_mode_set_base, +	.prepare = tegra_crtc_prepare, +	.commit = tegra_crtc_commit, +	.load_lut = tegra_crtc_load_lut, +}; + +static irqreturn_t tegra_dc_irq(int irq, void *data) +{ +	struct tegra_dc *dc = data; +	unsigned long status; + +	status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); +	tegra_dc_writel(dc, status, DC_CMD_INT_STATUS); + +	if (status & FRAME_END_INT) { +		/* +		dev_dbg(dc->dev, "%s(): frame end\n", __func__); +		*/ +	} + +	if (status & VBLANK_INT) { +		/* +		dev_dbg(dc->dev, "%s(): vertical blank\n", __func__); +		*/ +		drm_handle_vblank(dc->base.dev, dc->pipe); +		tegra_dc_finish_page_flip(dc); +	} + +	if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) { +		/* +		dev_dbg(dc->dev, "%s(): underflow\n", __func__); +		*/ +	} + +	return IRQ_HANDLED; +} + +static int tegra_dc_show_regs(struct seq_file *s, void *data) +{ +	struct drm_info_node *node = s->private; +	struct tegra_dc *dc = node->info_ent->data; + +#define DUMP_REG(name)						\ +	seq_printf(s, "%-40s %#05x %08lx\n", #name, name,	\ +		   tegra_dc_readl(dc, name)) + +	DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT); +	DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); +	DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT_ERROR); +	DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT); +	DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT_CNTRL); +	DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT_ERROR); +	DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT); +	DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT_CNTRL); +	DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT_ERROR); +	DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT); +	DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT_CNTRL); +	DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT_ERROR); +	DUMP_REG(DC_CMD_CONT_SYNCPT_VSYNC); +	DUMP_REG(DC_CMD_DISPLAY_COMMAND_OPTION0); +	DUMP_REG(DC_CMD_DISPLAY_COMMAND); +	DUMP_REG(DC_CMD_SIGNAL_RAISE); +	DUMP_REG(DC_CMD_DISPLAY_POWER_CONTROL); +	DUMP_REG(DC_CMD_INT_STATUS); +	DUMP_REG(DC_CMD_INT_MASK); +	DUMP_REG(DC_CMD_INT_ENABLE); +	DUMP_REG(DC_CMD_INT_TYPE); +	DUMP_REG(DC_CMD_INT_POLARITY); +	DUMP_REG(DC_CMD_SIGNAL_RAISE1); +	DUMP_REG(DC_CMD_SIGNAL_RAISE2); +	DUMP_REG(DC_CMD_SIGNAL_RAISE3); +	DUMP_REG(DC_CMD_STATE_ACCESS); +	DUMP_REG(DC_CMD_STATE_CONTROL); +	DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER); +	DUMP_REG(DC_CMD_REG_ACT_CONTROL); +	DUMP_REG(DC_COM_CRC_CONTROL); +	DUMP_REG(DC_COM_CRC_CHECKSUM); +	DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(0)); +	DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(1)); +	DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(2)); +	DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(3)); +	DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(0)); +	DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(1)); +	DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(2)); +	DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(3)); +	DUMP_REG(DC_COM_PIN_OUTPUT_DATA(0)); +	DUMP_REG(DC_COM_PIN_OUTPUT_DATA(1)); +	DUMP_REG(DC_COM_PIN_OUTPUT_DATA(2)); +	DUMP_REG(DC_COM_PIN_OUTPUT_DATA(3)); +	DUMP_REG(DC_COM_PIN_INPUT_ENABLE(0)); +	DUMP_REG(DC_COM_PIN_INPUT_ENABLE(1)); +	DUMP_REG(DC_COM_PIN_INPUT_ENABLE(2)); +	DUMP_REG(DC_COM_PIN_INPUT_ENABLE(3)); +	DUMP_REG(DC_COM_PIN_INPUT_DATA(0)); +	DUMP_REG(DC_COM_PIN_INPUT_DATA(1)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(0)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(1)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(2)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(3)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(4)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(5)); +	DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(6)); +	DUMP_REG(DC_COM_PIN_MISC_CONTROL); +	DUMP_REG(DC_COM_PIN_PM0_CONTROL); +	DUMP_REG(DC_COM_PIN_PM0_DUTY_CYCLE); +	DUMP_REG(DC_COM_PIN_PM1_CONTROL); +	DUMP_REG(DC_COM_PIN_PM1_DUTY_CYCLE); +	DUMP_REG(DC_COM_SPI_CONTROL); +	DUMP_REG(DC_COM_SPI_START_BYTE); +	DUMP_REG(DC_COM_HSPI_WRITE_DATA_AB); +	DUMP_REG(DC_COM_HSPI_WRITE_DATA_CD); +	DUMP_REG(DC_COM_HSPI_CS_DC); +	DUMP_REG(DC_COM_SCRATCH_REGISTER_A); +	DUMP_REG(DC_COM_SCRATCH_REGISTER_B); +	DUMP_REG(DC_COM_GPIO_CTRL); +	DUMP_REG(DC_COM_GPIO_DEBOUNCE_COUNTER); +	DUMP_REG(DC_COM_CRC_CHECKSUM_LATCHED); +	DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS0); +	DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS1); +	DUMP_REG(DC_DISP_DISP_WIN_OPTIONS); +	DUMP_REG(DC_DISP_DISP_MEM_HIGH_PRIORITY); +	DUMP_REG(DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); +	DUMP_REG(DC_DISP_DISP_TIMING_OPTIONS); +	DUMP_REG(DC_DISP_REF_TO_SYNC); +	DUMP_REG(DC_DISP_SYNC_WIDTH); +	DUMP_REG(DC_DISP_BACK_PORCH); +	DUMP_REG(DC_DISP_ACTIVE); +	DUMP_REG(DC_DISP_FRONT_PORCH); +	DUMP_REG(DC_DISP_H_PULSE0_CONTROL); +	DUMP_REG(DC_DISP_H_PULSE0_POSITION_A); +	DUMP_REG(DC_DISP_H_PULSE0_POSITION_B); +	DUMP_REG(DC_DISP_H_PULSE0_POSITION_C); +	DUMP_REG(DC_DISP_H_PULSE0_POSITION_D); +	DUMP_REG(DC_DISP_H_PULSE1_CONTROL); +	DUMP_REG(DC_DISP_H_PULSE1_POSITION_A); +	DUMP_REG(DC_DISP_H_PULSE1_POSITION_B); +	DUMP_REG(DC_DISP_H_PULSE1_POSITION_C); +	DUMP_REG(DC_DISP_H_PULSE1_POSITION_D); +	DUMP_REG(DC_DISP_H_PULSE2_CONTROL); +	DUMP_REG(DC_DISP_H_PULSE2_POSITION_A); +	DUMP_REG(DC_DISP_H_PULSE2_POSITION_B); +	DUMP_REG(DC_DISP_H_PULSE2_POSITION_C); +	DUMP_REG(DC_DISP_H_PULSE2_POSITION_D); +	DUMP_REG(DC_DISP_V_PULSE0_CONTROL); +	DUMP_REG(DC_DISP_V_PULSE0_POSITION_A); +	DUMP_REG(DC_DISP_V_PULSE0_POSITION_B); +	DUMP_REG(DC_DISP_V_PULSE0_POSITION_C); +	DUMP_REG(DC_DISP_V_PULSE1_CONTROL); +	DUMP_REG(DC_DISP_V_PULSE1_POSITION_A); +	DUMP_REG(DC_DISP_V_PULSE1_POSITION_B); +	DUMP_REG(DC_DISP_V_PULSE1_POSITION_C); +	DUMP_REG(DC_DISP_V_PULSE2_CONTROL); +	DUMP_REG(DC_DISP_V_PULSE2_POSITION_A); +	DUMP_REG(DC_DISP_V_PULSE3_CONTROL); +	DUMP_REG(DC_DISP_V_PULSE3_POSITION_A); +	DUMP_REG(DC_DISP_M0_CONTROL); +	DUMP_REG(DC_DISP_M1_CONTROL); +	DUMP_REG(DC_DISP_DI_CONTROL); +	DUMP_REG(DC_DISP_PP_CONTROL); +	DUMP_REG(DC_DISP_PP_SELECT_A); +	DUMP_REG(DC_DISP_PP_SELECT_B); +	DUMP_REG(DC_DISP_PP_SELECT_C); +	DUMP_REG(DC_DISP_PP_SELECT_D); +	DUMP_REG(DC_DISP_DISP_CLOCK_CONTROL); +	DUMP_REG(DC_DISP_DISP_INTERFACE_CONTROL); +	DUMP_REG(DC_DISP_DISP_COLOR_CONTROL); +	DUMP_REG(DC_DISP_SHIFT_CLOCK_OPTIONS); +	DUMP_REG(DC_DISP_DATA_ENABLE_OPTIONS); +	DUMP_REG(DC_DISP_SERIAL_INTERFACE_OPTIONS); +	DUMP_REG(DC_DISP_LCD_SPI_OPTIONS); +	DUMP_REG(DC_DISP_BORDER_COLOR); +	DUMP_REG(DC_DISP_COLOR_KEY0_LOWER); +	DUMP_REG(DC_DISP_COLOR_KEY0_UPPER); +	DUMP_REG(DC_DISP_COLOR_KEY1_LOWER); +	DUMP_REG(DC_DISP_COLOR_KEY1_UPPER); +	DUMP_REG(DC_DISP_CURSOR_FOREGROUND); +	DUMP_REG(DC_DISP_CURSOR_BACKGROUND); +	DUMP_REG(DC_DISP_CURSOR_START_ADDR); +	DUMP_REG(DC_DISP_CURSOR_START_ADDR_NS); +	DUMP_REG(DC_DISP_CURSOR_POSITION); +	DUMP_REG(DC_DISP_CURSOR_POSITION_NS); +	DUMP_REG(DC_DISP_INIT_SEQ_CONTROL); +	DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_A); +	DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_B); +	DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_C); +	DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_D); +	DUMP_REG(DC_DISP_DC_MCCIF_FIFOCTRL); +	DUMP_REG(DC_DISP_MCCIF_DISPLAY0A_HYST); +	DUMP_REG(DC_DISP_MCCIF_DISPLAY0B_HYST); +	DUMP_REG(DC_DISP_MCCIF_DISPLAY1A_HYST); +	DUMP_REG(DC_DISP_MCCIF_DISPLAY1B_HYST); +	DUMP_REG(DC_DISP_DAC_CRT_CTRL); +	DUMP_REG(DC_DISP_DISP_MISC_CONTROL); +	DUMP_REG(DC_DISP_SD_CONTROL); +	DUMP_REG(DC_DISP_SD_CSC_COEFF); +	DUMP_REG(DC_DISP_SD_LUT(0)); +	DUMP_REG(DC_DISP_SD_LUT(1)); +	DUMP_REG(DC_DISP_SD_LUT(2)); +	DUMP_REG(DC_DISP_SD_LUT(3)); +	DUMP_REG(DC_DISP_SD_LUT(4)); +	DUMP_REG(DC_DISP_SD_LUT(5)); +	DUMP_REG(DC_DISP_SD_LUT(6)); +	DUMP_REG(DC_DISP_SD_LUT(7)); +	DUMP_REG(DC_DISP_SD_LUT(8)); +	DUMP_REG(DC_DISP_SD_FLICKER_CONTROL); +	DUMP_REG(DC_DISP_DC_PIXEL_COUNT); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(0)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(1)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(2)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(3)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(4)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(5)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(6)); +	DUMP_REG(DC_DISP_SD_HISTOGRAM(7)); +	DUMP_REG(DC_DISP_SD_BL_TF(0)); +	DUMP_REG(DC_DISP_SD_BL_TF(1)); +	DUMP_REG(DC_DISP_SD_BL_TF(2)); +	DUMP_REG(DC_DISP_SD_BL_TF(3)); +	DUMP_REG(DC_DISP_SD_BL_CONTROL); +	DUMP_REG(DC_DISP_SD_HW_K_VALUES); +	DUMP_REG(DC_DISP_SD_MAN_K_VALUES); +	DUMP_REG(DC_WIN_WIN_OPTIONS); +	DUMP_REG(DC_WIN_BYTE_SWAP); +	DUMP_REG(DC_WIN_BUFFER_CONTROL); +	DUMP_REG(DC_WIN_COLOR_DEPTH); +	DUMP_REG(DC_WIN_POSITION); +	DUMP_REG(DC_WIN_SIZE); +	DUMP_REG(DC_WIN_PRESCALED_SIZE); +	DUMP_REG(DC_WIN_H_INITIAL_DDA); +	DUMP_REG(DC_WIN_V_INITIAL_DDA); +	DUMP_REG(DC_WIN_DDA_INC); +	DUMP_REG(DC_WIN_LINE_STRIDE); +	DUMP_REG(DC_WIN_BUF_STRIDE); +	DUMP_REG(DC_WIN_UV_BUF_STRIDE); +	DUMP_REG(DC_WIN_BUFFER_ADDR_MODE); +	DUMP_REG(DC_WIN_DV_CONTROL); +	DUMP_REG(DC_WIN_BLEND_NOKEY); +	DUMP_REG(DC_WIN_BLEND_1WIN); +	DUMP_REG(DC_WIN_BLEND_2WIN_X); +	DUMP_REG(DC_WIN_BLEND_2WIN_Y); +	DUMP_REG(DC_WIN_BLEND_3WIN_XY); +	DUMP_REG(DC_WIN_HP_FETCH_CONTROL); +	DUMP_REG(DC_WINBUF_START_ADDR); +	DUMP_REG(DC_WINBUF_START_ADDR_NS); +	DUMP_REG(DC_WINBUF_START_ADDR_U); +	DUMP_REG(DC_WINBUF_START_ADDR_U_NS); +	DUMP_REG(DC_WINBUF_START_ADDR_V); +	DUMP_REG(DC_WINBUF_START_ADDR_V_NS); +	DUMP_REG(DC_WINBUF_ADDR_H_OFFSET); +	DUMP_REG(DC_WINBUF_ADDR_H_OFFSET_NS); +	DUMP_REG(DC_WINBUF_ADDR_V_OFFSET); +	DUMP_REG(DC_WINBUF_ADDR_V_OFFSET_NS); +	DUMP_REG(DC_WINBUF_UFLOW_STATUS); +	DUMP_REG(DC_WINBUF_AD_UFLOW_STATUS); +	DUMP_REG(DC_WINBUF_BD_UFLOW_STATUS); +	DUMP_REG(DC_WINBUF_CD_UFLOW_STATUS); + +#undef DUMP_REG + +	return 0; +} + +static struct drm_info_list debugfs_files[] = { +	{ "regs", tegra_dc_show_regs, 0, NULL }, +}; + +static int tegra_dc_debugfs_init(struct tegra_dc *dc, struct drm_minor *minor) +{ +	unsigned int i; +	char *name; +	int err; + +	name = kasprintf(GFP_KERNEL, "dc.%d", dc->pipe); +	dc->debugfs = debugfs_create_dir(name, minor->debugfs_root); +	kfree(name); + +	if (!dc->debugfs) +		return -ENOMEM; + +	dc->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), +				    GFP_KERNEL); +	if (!dc->debugfs_files) { +		err = -ENOMEM; +		goto remove; +	} + +	for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) +		dc->debugfs_files[i].data = dc; + +	err = drm_debugfs_create_files(dc->debugfs_files, +				       ARRAY_SIZE(debugfs_files), +				       dc->debugfs, minor); +	if (err < 0) +		goto free; + +	dc->minor = minor; + +	return 0; + +free: +	kfree(dc->debugfs_files); +	dc->debugfs_files = NULL; +remove: +	debugfs_remove(dc->debugfs); +	dc->debugfs = NULL; + +	return err; +} + +static int tegra_dc_debugfs_exit(struct tegra_dc *dc) +{ +	drm_debugfs_remove_files(dc->debugfs_files, ARRAY_SIZE(debugfs_files), +				 dc->minor); +	dc->minor = NULL; + +	kfree(dc->debugfs_files); +	dc->debugfs_files = NULL; + +	debugfs_remove(dc->debugfs); +	dc->debugfs = NULL; + +	return 0; +} + +static int tegra_dc_drm_init(struct host1x_client *client, +			     struct drm_device *drm) +{ +	struct tegra_dc *dc = host1x_client_to_dc(client); +	int err; + +	dc->pipe = drm->mode_config.num_crtc; + +	drm_crtc_init(drm, &dc->base, &tegra_crtc_funcs); +	drm_mode_crtc_set_gamma_size(&dc->base, 256); +	drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs); + +	err = tegra_dc_rgb_init(drm, dc); +	if (err < 0 && err != -ENODEV) { +		dev_err(dc->dev, "failed to initialize RGB output: %d\n", err); +		return err; +	} + +	err = tegra_dc_add_planes(drm, dc); +	if (err < 0) +		return err; + +	if (IS_ENABLED(CONFIG_DEBUG_FS)) { +		err = tegra_dc_debugfs_init(dc, drm->primary); +		if (err < 0) +			dev_err(dc->dev, "debugfs setup failed: %d\n", err); +	} + +	err = devm_request_irq(dc->dev, dc->irq, tegra_dc_irq, 0, +			       dev_name(dc->dev), dc); +	if (err < 0) { +		dev_err(dc->dev, "failed to request IRQ#%u: %d\n", dc->irq, +			err); +		return err; +	} + +	return 0; +} + +static int tegra_dc_drm_exit(struct host1x_client *client) +{ +	struct tegra_dc *dc = host1x_client_to_dc(client); +	int err; + +	devm_free_irq(dc->dev, dc->irq, dc); + +	if (IS_ENABLED(CONFIG_DEBUG_FS)) { +		err = tegra_dc_debugfs_exit(dc); +		if (err < 0) +			dev_err(dc->dev, "debugfs cleanup failed: %d\n", err); +	} + +	err = tegra_dc_rgb_exit(dc); +	if (err) { +		dev_err(dc->dev, "failed to shutdown RGB output: %d\n", err); +		return err; +	} + +	return 0; +} + +static const struct host1x_client_ops dc_client_ops = { +	.drm_init = tegra_dc_drm_init, +	.drm_exit = tegra_dc_drm_exit, +}; + +static int tegra_dc_probe(struct platform_device *pdev) +{ +	struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent); +	struct resource *regs; +	struct tegra_dc *dc; +	int err; + +	dc = devm_kzalloc(&pdev->dev, sizeof(*dc), GFP_KERNEL); +	if (!dc) +		return -ENOMEM; + +	spin_lock_init(&dc->lock); +	INIT_LIST_HEAD(&dc->list); +	dc->dev = &pdev->dev; + +	dc->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(dc->clk)) { +		dev_err(&pdev->dev, "failed to get clock\n"); +		return PTR_ERR(dc->clk); +	} + +	err = clk_prepare_enable(dc->clk); +	if (err < 0) +		return err; + +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!regs) { +		dev_err(&pdev->dev, "failed to get registers\n"); +		return -ENXIO; +	} + +	dc->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(dc->regs)) +		return PTR_ERR(dc->regs); + +	dc->irq = platform_get_irq(pdev, 0); +	if (dc->irq < 0) { +		dev_err(&pdev->dev, "failed to get IRQ\n"); +		return -ENXIO; +	} + +	INIT_LIST_HEAD(&dc->client.list); +	dc->client.ops = &dc_client_ops; +	dc->client.dev = &pdev->dev; + +	err = tegra_dc_rgb_probe(dc); +	if (err < 0 && err != -ENODEV) { +		dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err); +		return err; +	} + +	err = host1x_register_client(host1x, &dc->client); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to register host1x client: %d\n", +			err); +		return err; +	} + +	platform_set_drvdata(pdev, dc); + +	return 0; +} + +static int tegra_dc_remove(struct platform_device *pdev) +{ +	struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent); +	struct tegra_dc *dc = platform_get_drvdata(pdev); +	int err; + +	err = host1x_unregister_client(host1x, &dc->client); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", +			err); +		return err; +	} + +	clk_disable_unprepare(dc->clk); + +	return 0; +} + +static struct of_device_id tegra_dc_of_match[] = { +	{ .compatible = "nvidia,tegra30-dc", }, +	{ .compatible = "nvidia,tegra20-dc", }, +	{ }, +}; + +struct platform_driver tegra_dc_driver = { +	.driver = { +		.name = "tegra-dc", +		.owner = THIS_MODULE, +		.of_match_table = tegra_dc_of_match, +	}, +	.probe = tegra_dc_probe, +	.remove = tegra_dc_remove, +}; diff --git a/drivers/gpu/host1x/drm/dc.h b/drivers/gpu/host1x/drm/dc.h new file mode 100644 index 00000000000..79eaec9aac7 --- /dev/null +++ b/drivers/gpu/host1x/drm/dc.h @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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. + */ + +#ifndef TEGRA_DC_H +#define TEGRA_DC_H 1 + +#define DC_CMD_GENERAL_INCR_SYNCPT		0x000 +#define DC_CMD_GENERAL_INCR_SYNCPT_CNTRL	0x001 +#define DC_CMD_GENERAL_INCR_SYNCPT_ERROR	0x002 +#define DC_CMD_WIN_A_INCR_SYNCPT		0x008 +#define DC_CMD_WIN_A_INCR_SYNCPT_CNTRL		0x009 +#define DC_CMD_WIN_A_INCR_SYNCPT_ERROR		0x00a +#define DC_CMD_WIN_B_INCR_SYNCPT		0x010 +#define DC_CMD_WIN_B_INCR_SYNCPT_CNTRL		0x011 +#define DC_CMD_WIN_B_INCR_SYNCPT_ERROR		0x012 +#define DC_CMD_WIN_C_INCR_SYNCPT		0x018 +#define DC_CMD_WIN_C_INCR_SYNCPT_CNTRL		0x019 +#define DC_CMD_WIN_C_INCR_SYNCPT_ERROR		0x01a +#define DC_CMD_CONT_SYNCPT_VSYNC		0x028 +#define DC_CMD_DISPLAY_COMMAND_OPTION0		0x031 +#define DC_CMD_DISPLAY_COMMAND			0x032 +#define DISP_CTRL_MODE_STOP (0 << 5) +#define DISP_CTRL_MODE_C_DISPLAY (1 << 5) +#define DISP_CTRL_MODE_NC_DISPLAY (2 << 5) +#define DC_CMD_SIGNAL_RAISE			0x033 +#define DC_CMD_DISPLAY_POWER_CONTROL		0x036 +#define PW0_ENABLE (1 <<  0) +#define PW1_ENABLE (1 <<  2) +#define PW2_ENABLE (1 <<  4) +#define PW3_ENABLE (1 <<  6) +#define PW4_ENABLE (1 <<  8) +#define PM0_ENABLE (1 << 16) +#define PM1_ENABLE (1 << 18) + +#define DC_CMD_INT_STATUS			0x037 +#define DC_CMD_INT_MASK				0x038 +#define DC_CMD_INT_ENABLE			0x039 +#define DC_CMD_INT_TYPE				0x03a +#define DC_CMD_INT_POLARITY			0x03b +#define CTXSW_INT     (1 << 0) +#define FRAME_END_INT (1 << 1) +#define VBLANK_INT    (1 << 2) +#define WIN_A_UF_INT  (1 << 8) +#define WIN_B_UF_INT  (1 << 9) +#define WIN_C_UF_INT  (1 << 10) +#define WIN_A_OF_INT  (1 << 14) +#define WIN_B_OF_INT  (1 << 15) +#define WIN_C_OF_INT  (1 << 16) + +#define DC_CMD_SIGNAL_RAISE1			0x03c +#define DC_CMD_SIGNAL_RAISE2			0x03d +#define DC_CMD_SIGNAL_RAISE3			0x03e + +#define DC_CMD_STATE_ACCESS			0x040 +#define READ_MUX  (1 << 0) +#define WRITE_MUX (1 << 2) + +#define DC_CMD_STATE_CONTROL			0x041 +#define GENERAL_ACT_REQ (1 <<  0) +#define WIN_A_ACT_REQ   (1 <<  1) +#define WIN_B_ACT_REQ   (1 <<  2) +#define WIN_C_ACT_REQ   (1 <<  3) +#define GENERAL_UPDATE  (1 <<  8) +#define WIN_A_UPDATE    (1 <<  9) +#define WIN_B_UPDATE    (1 << 10) +#define WIN_C_UPDATE    (1 << 11) +#define NC_HOST_TRIG    (1 << 24) + +#define DC_CMD_DISPLAY_WINDOW_HEADER		0x042 +#define WINDOW_A_SELECT (1 << 4) +#define WINDOW_B_SELECT (1 << 5) +#define WINDOW_C_SELECT (1 << 6) + +#define DC_CMD_REG_ACT_CONTROL			0x043 + +#define DC_COM_CRC_CONTROL			0x300 +#define DC_COM_CRC_CHECKSUM			0x301 +#define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x)) +#define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x)) +#define LVS_OUTPUT_POLARITY_LOW (1 << 28) +#define LHS_OUTPUT_POLARITY_LOW (1 << 30) +#define DC_COM_PIN_OUTPUT_DATA(x) (0x30a + (x)) +#define DC_COM_PIN_INPUT_ENABLE(x) (0x30e + (x)) +#define DC_COM_PIN_INPUT_DATA(x) (0x312 + (x)) +#define DC_COM_PIN_OUTPUT_SELECT(x) (0x314 + (x)) + +#define DC_COM_PIN_MISC_CONTROL			0x31b +#define DC_COM_PIN_PM0_CONTROL			0x31c +#define DC_COM_PIN_PM0_DUTY_CYCLE		0x31d +#define DC_COM_PIN_PM1_CONTROL			0x31e +#define DC_COM_PIN_PM1_DUTY_CYCLE		0x31f + +#define DC_COM_SPI_CONTROL			0x320 +#define DC_COM_SPI_START_BYTE			0x321 +#define DC_COM_HSPI_WRITE_DATA_AB		0x322 +#define DC_COM_HSPI_WRITE_DATA_CD		0x323 +#define DC_COM_HSPI_CS_DC			0x324 +#define DC_COM_SCRATCH_REGISTER_A		0x325 +#define DC_COM_SCRATCH_REGISTER_B		0x326 +#define DC_COM_GPIO_CTRL			0x327 +#define DC_COM_GPIO_DEBOUNCE_COUNTER		0x328 +#define DC_COM_CRC_CHECKSUM_LATCHED		0x329 + +#define DC_DISP_DISP_SIGNAL_OPTIONS0		0x400 +#define H_PULSE_0_ENABLE (1 <<  8) +#define H_PULSE_1_ENABLE (1 << 10) +#define H_PULSE_2_ENABLE (1 << 12) + +#define DC_DISP_DISP_SIGNAL_OPTIONS1		0x401 + +#define DC_DISP_DISP_WIN_OPTIONS		0x402 +#define HDMI_ENABLE (1 << 30) + +#define DC_DISP_DISP_MEM_HIGH_PRIORITY		0x403 +#define CURSOR_THRESHOLD(x)   (((x) & 0x03) << 24) +#define WINDOW_A_THRESHOLD(x) (((x) & 0x7f) << 16) +#define WINDOW_B_THRESHOLD(x) (((x) & 0x7f) <<  8) +#define WINDOW_C_THRESHOLD(x) (((x) & 0xff) <<  0) + +#define DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER	0x404 +#define CURSOR_DELAY(x)   (((x) & 0x3f) << 24) +#define WINDOW_A_DELAY(x) (((x) & 0x3f) << 16) +#define WINDOW_B_DELAY(x) (((x) & 0x3f) <<  8) +#define WINDOW_C_DELAY(x) (((x) & 0x3f) <<  0) + +#define DC_DISP_DISP_TIMING_OPTIONS		0x405 +#define VSYNC_H_POSITION(x) ((x) & 0xfff) + +#define DC_DISP_REF_TO_SYNC			0x406 +#define DC_DISP_SYNC_WIDTH			0x407 +#define DC_DISP_BACK_PORCH			0x408 +#define DC_DISP_ACTIVE				0x409 +#define DC_DISP_FRONT_PORCH			0x40a +#define DC_DISP_H_PULSE0_CONTROL		0x40b +#define DC_DISP_H_PULSE0_POSITION_A		0x40c +#define DC_DISP_H_PULSE0_POSITION_B		0x40d +#define DC_DISP_H_PULSE0_POSITION_C		0x40e +#define DC_DISP_H_PULSE0_POSITION_D		0x40f +#define DC_DISP_H_PULSE1_CONTROL		0x410 +#define DC_DISP_H_PULSE1_POSITION_A		0x411 +#define DC_DISP_H_PULSE1_POSITION_B		0x412 +#define DC_DISP_H_PULSE1_POSITION_C		0x413 +#define DC_DISP_H_PULSE1_POSITION_D		0x414 +#define DC_DISP_H_PULSE2_CONTROL		0x415 +#define DC_DISP_H_PULSE2_POSITION_A		0x416 +#define DC_DISP_H_PULSE2_POSITION_B		0x417 +#define DC_DISP_H_PULSE2_POSITION_C		0x418 +#define DC_DISP_H_PULSE2_POSITION_D		0x419 +#define DC_DISP_V_PULSE0_CONTROL		0x41a +#define DC_DISP_V_PULSE0_POSITION_A		0x41b +#define DC_DISP_V_PULSE0_POSITION_B		0x41c +#define DC_DISP_V_PULSE0_POSITION_C		0x41d +#define DC_DISP_V_PULSE1_CONTROL		0x41e +#define DC_DISP_V_PULSE1_POSITION_A		0x41f +#define DC_DISP_V_PULSE1_POSITION_B		0x420 +#define DC_DISP_V_PULSE1_POSITION_C		0x421 +#define DC_DISP_V_PULSE2_CONTROL		0x422 +#define DC_DISP_V_PULSE2_POSITION_A		0x423 +#define DC_DISP_V_PULSE3_CONTROL		0x424 +#define DC_DISP_V_PULSE3_POSITION_A		0x425 +#define DC_DISP_M0_CONTROL			0x426 +#define DC_DISP_M1_CONTROL			0x427 +#define DC_DISP_DI_CONTROL			0x428 +#define DC_DISP_PP_CONTROL			0x429 +#define DC_DISP_PP_SELECT_A			0x42a +#define DC_DISP_PP_SELECT_B			0x42b +#define DC_DISP_PP_SELECT_C			0x42c +#define DC_DISP_PP_SELECT_D			0x42d + +#define PULSE_MODE_NORMAL    (0 << 3) +#define PULSE_MODE_ONE_CLOCK (1 << 3) +#define PULSE_POLARITY_HIGH  (0 << 4) +#define PULSE_POLARITY_LOW   (1 << 4) +#define PULSE_QUAL_ALWAYS    (0 << 6) +#define PULSE_QUAL_VACTIVE   (2 << 6) +#define PULSE_QUAL_VACTIVE1  (3 << 6) +#define PULSE_LAST_START_A   (0 << 8) +#define PULSE_LAST_END_A     (1 << 8) +#define PULSE_LAST_START_B   (2 << 8) +#define PULSE_LAST_END_B     (3 << 8) +#define PULSE_LAST_START_C   (4 << 8) +#define PULSE_LAST_END_C     (5 << 8) +#define PULSE_LAST_START_D   (6 << 8) +#define PULSE_LAST_END_D     (7 << 8) + +#define PULSE_START(x) (((x) & 0xfff) <<  0) +#define PULSE_END(x)   (((x) & 0xfff) << 16) + +#define DC_DISP_DISP_CLOCK_CONTROL		0x42e +#define PIXEL_CLK_DIVIDER_PCD1  (0 << 8) +#define PIXEL_CLK_DIVIDER_PCD1H (1 << 8) +#define PIXEL_CLK_DIVIDER_PCD2  (2 << 8) +#define PIXEL_CLK_DIVIDER_PCD3  (3 << 8) +#define PIXEL_CLK_DIVIDER_PCD4  (4 << 8) +#define PIXEL_CLK_DIVIDER_PCD6  (5 << 8) +#define PIXEL_CLK_DIVIDER_PCD8  (6 << 8) +#define PIXEL_CLK_DIVIDER_PCD9  (7 << 8) +#define PIXEL_CLK_DIVIDER_PCD12 (8 << 8) +#define PIXEL_CLK_DIVIDER_PCD16 (9 << 8) +#define PIXEL_CLK_DIVIDER_PCD18 (10 << 8) +#define PIXEL_CLK_DIVIDER_PCD24 (11 << 8) +#define PIXEL_CLK_DIVIDER_PCD13 (12 << 8) +#define SHIFT_CLK_DIVIDER(x)    ((x) & 0xff) + +#define DC_DISP_DISP_INTERFACE_CONTROL		0x42f +#define DISP_DATA_FORMAT_DF1P1C    (0 << 0) +#define DISP_DATA_FORMAT_DF1P2C24B (1 << 0) +#define DISP_DATA_FORMAT_DF1P2C18B (2 << 0) +#define DISP_DATA_FORMAT_DF1P2C16B (3 << 0) +#define DISP_DATA_FORMAT_DF2S      (4 << 0) +#define DISP_DATA_FORMAT_DF3S      (5 << 0) +#define DISP_DATA_FORMAT_DFSPI     (6 << 0) +#define DISP_DATA_FORMAT_DF1P3C24B (7 << 0) +#define DISP_DATA_FORMAT_DF1P3C18B (8 << 0) +#define DISP_ALIGNMENT_MSB         (0 << 8) +#define DISP_ALIGNMENT_LSB         (1 << 8) +#define DISP_ORDER_RED_BLUE        (0 << 9) +#define DISP_ORDER_BLUE_RED        (1 << 9) + +#define DC_DISP_DISP_COLOR_CONTROL		0x430 +#define BASE_COLOR_SIZE666     (0 << 0) +#define BASE_COLOR_SIZE111     (1 << 0) +#define BASE_COLOR_SIZE222     (2 << 0) +#define BASE_COLOR_SIZE333     (3 << 0) +#define BASE_COLOR_SIZE444     (4 << 0) +#define BASE_COLOR_SIZE555     (5 << 0) +#define BASE_COLOR_SIZE565     (6 << 0) +#define BASE_COLOR_SIZE332     (7 << 0) +#define BASE_COLOR_SIZE888     (8 << 0) +#define DITHER_CONTROL_DISABLE (0 << 8) +#define DITHER_CONTROL_ORDERED (2 << 8) +#define DITHER_CONTROL_ERRDIFF (3 << 8) + +#define DC_DISP_SHIFT_CLOCK_OPTIONS		0x431 + +#define DC_DISP_DATA_ENABLE_OPTIONS		0x432 +#define DE_SELECT_ACTIVE_BLANK  (0 << 0) +#define DE_SELECT_ACTIVE        (1 << 0) +#define DE_SELECT_ACTIVE_IS     (2 << 0) +#define DE_CONTROL_ONECLK       (0 << 2) +#define DE_CONTROL_NORMAL       (1 << 2) +#define DE_CONTROL_EARLY_EXT    (2 << 2) +#define DE_CONTROL_EARLY        (3 << 2) +#define DE_CONTROL_ACTIVE_BLANK (4 << 2) + +#define DC_DISP_SERIAL_INTERFACE_OPTIONS	0x433 +#define DC_DISP_LCD_SPI_OPTIONS			0x434 +#define DC_DISP_BORDER_COLOR			0x435 +#define DC_DISP_COLOR_KEY0_LOWER		0x436 +#define DC_DISP_COLOR_KEY0_UPPER		0x437 +#define DC_DISP_COLOR_KEY1_LOWER		0x438 +#define DC_DISP_COLOR_KEY1_UPPER		0x439 + +#define DC_DISP_CURSOR_FOREGROUND		0x43c +#define DC_DISP_CURSOR_BACKGROUND		0x43d + +#define DC_DISP_CURSOR_START_ADDR		0x43e +#define DC_DISP_CURSOR_START_ADDR_NS		0x43f + +#define DC_DISP_CURSOR_POSITION			0x440 +#define DC_DISP_CURSOR_POSITION_NS		0x441 + +#define DC_DISP_INIT_SEQ_CONTROL		0x442 +#define DC_DISP_SPI_INIT_SEQ_DATA_A		0x443 +#define DC_DISP_SPI_INIT_SEQ_DATA_B		0x444 +#define DC_DISP_SPI_INIT_SEQ_DATA_C		0x445 +#define DC_DISP_SPI_INIT_SEQ_DATA_D		0x446 + +#define DC_DISP_DC_MCCIF_FIFOCTRL		0x480 +#define DC_DISP_MCCIF_DISPLAY0A_HYST		0x481 +#define DC_DISP_MCCIF_DISPLAY0B_HYST		0x482 +#define DC_DISP_MCCIF_DISPLAY1A_HYST		0x483 +#define DC_DISP_MCCIF_DISPLAY1B_HYST		0x484 + +#define DC_DISP_DAC_CRT_CTRL			0x4c0 +#define DC_DISP_DISP_MISC_CONTROL		0x4c1 +#define DC_DISP_SD_CONTROL			0x4c2 +#define DC_DISP_SD_CSC_COEFF			0x4c3 +#define DC_DISP_SD_LUT(x)			(0x4c4 + (x)) +#define DC_DISP_SD_FLICKER_CONTROL		0x4cd +#define DC_DISP_DC_PIXEL_COUNT			0x4ce +#define DC_DISP_SD_HISTOGRAM(x)			(0x4cf + (x)) +#define DC_DISP_SD_BL_PARAMETERS		0x4d7 +#define DC_DISP_SD_BL_TF(x)			(0x4d8 + (x)) +#define DC_DISP_SD_BL_CONTROL			0x4dc +#define DC_DISP_SD_HW_K_VALUES			0x4dd +#define DC_DISP_SD_MAN_K_VALUES			0x4de + +#define DC_WIN_CSC_YOF				0x611 +#define DC_WIN_CSC_KYRGB			0x612 +#define DC_WIN_CSC_KUR				0x613 +#define DC_WIN_CSC_KVR				0x614 +#define DC_WIN_CSC_KUG				0x615 +#define DC_WIN_CSC_KVG				0x616 +#define DC_WIN_CSC_KUB				0x617 +#define DC_WIN_CSC_KVB				0x618 + +#define DC_WIN_WIN_OPTIONS			0x700 +#define COLOR_EXPAND (1 <<  6) +#define CSC_ENABLE   (1 << 18) +#define WIN_ENABLE   (1 << 30) + +#define DC_WIN_BYTE_SWAP			0x701 +#define BYTE_SWAP_NOSWAP  (0 << 0) +#define BYTE_SWAP_SWAP2   (1 << 0) +#define BYTE_SWAP_SWAP4   (2 << 0) +#define BYTE_SWAP_SWAP4HW (3 << 0) + +#define DC_WIN_BUFFER_CONTROL			0x702 +#define BUFFER_CONTROL_HOST  (0 << 0) +#define BUFFER_CONTROL_VI    (1 << 0) +#define BUFFER_CONTROL_EPP   (2 << 0) +#define BUFFER_CONTROL_MPEGE (3 << 0) +#define BUFFER_CONTROL_SB2D  (4 << 0) + +#define DC_WIN_COLOR_DEPTH			0x703 +#define WIN_COLOR_DEPTH_P1              0 +#define WIN_COLOR_DEPTH_P2              1 +#define WIN_COLOR_DEPTH_P4              2 +#define WIN_COLOR_DEPTH_P8              3 +#define WIN_COLOR_DEPTH_B4G4R4A4        4 +#define WIN_COLOR_DEPTH_B5G5R5A         5 +#define WIN_COLOR_DEPTH_B5G6R5          6 +#define WIN_COLOR_DEPTH_AB5G5R5         7 +#define WIN_COLOR_DEPTH_B8G8R8A8       12 +#define WIN_COLOR_DEPTH_R8G8B8A8       13 +#define WIN_COLOR_DEPTH_B6x2G6x2R6x2A8 14 +#define WIN_COLOR_DEPTH_R6x2G6x2B6x2A8 15 +#define WIN_COLOR_DEPTH_YCbCr422       16 +#define WIN_COLOR_DEPTH_YUV422         17 +#define WIN_COLOR_DEPTH_YCbCr420P      18 +#define WIN_COLOR_DEPTH_YUV420P        19 +#define WIN_COLOR_DEPTH_YCbCr422P      20 +#define WIN_COLOR_DEPTH_YUV422P        21 +#define WIN_COLOR_DEPTH_YCbCr422R      22 +#define WIN_COLOR_DEPTH_YUV422R        23 +#define WIN_COLOR_DEPTH_YCbCr422RA     24 +#define WIN_COLOR_DEPTH_YUV422RA       25 + +#define DC_WIN_POSITION				0x704 +#define H_POSITION(x) (((x) & 0x1fff) <<  0) +#define V_POSITION(x) (((x) & 0x1fff) << 16) + +#define DC_WIN_SIZE				0x705 +#define H_SIZE(x) (((x) & 0x1fff) <<  0) +#define V_SIZE(x) (((x) & 0x1fff) << 16) + +#define DC_WIN_PRESCALED_SIZE			0x706 +#define H_PRESCALED_SIZE(x) (((x) & 0x7fff) <<  0) +#define V_PRESCALED_SIZE(x) (((x) & 0x1fff) << 16) + +#define DC_WIN_H_INITIAL_DDA			0x707 +#define DC_WIN_V_INITIAL_DDA			0x708 +#define DC_WIN_DDA_INC				0x709 +#define H_DDA_INC(x) (((x) & 0xffff) <<  0) +#define V_DDA_INC(x) (((x) & 0xffff) << 16) + +#define DC_WIN_LINE_STRIDE			0x70a +#define DC_WIN_BUF_STRIDE			0x70b +#define DC_WIN_UV_BUF_STRIDE			0x70c +#define DC_WIN_BUFFER_ADDR_MODE			0x70d +#define DC_WIN_DV_CONTROL			0x70e + +#define DC_WIN_BLEND_NOKEY			0x70f +#define DC_WIN_BLEND_1WIN			0x710 +#define DC_WIN_BLEND_2WIN_X			0x711 +#define DC_WIN_BLEND_2WIN_Y			0x712 +#define DC_WIN_BLEND_3WIN_XY			0x713 + +#define DC_WIN_HP_FETCH_CONTROL			0x714 + +#define DC_WINBUF_START_ADDR			0x800 +#define DC_WINBUF_START_ADDR_NS			0x801 +#define DC_WINBUF_START_ADDR_U			0x802 +#define DC_WINBUF_START_ADDR_U_NS		0x803 +#define DC_WINBUF_START_ADDR_V			0x804 +#define DC_WINBUF_START_ADDR_V_NS		0x805 + +#define DC_WINBUF_ADDR_H_OFFSET			0x806 +#define DC_WINBUF_ADDR_H_OFFSET_NS		0x807 +#define DC_WINBUF_ADDR_V_OFFSET			0x808 +#define DC_WINBUF_ADDR_V_OFFSET_NS		0x809 + +#define DC_WINBUF_UFLOW_STATUS			0x80a + +#define DC_WINBUF_AD_UFLOW_STATUS		0xbca +#define DC_WINBUF_BD_UFLOW_STATUS		0xdca +#define DC_WINBUF_CD_UFLOW_STATUS		0xfca + +/* synchronization points */ +#define SYNCPT_VBLANK0 26 +#define SYNCPT_VBLANK1 27 + +#endif /* TEGRA_DC_H */ diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c new file mode 100644 index 00000000000..2b561c9118c --- /dev/null +++ b/drivers/gpu/host1x/drm/drm.c @@ -0,0 +1,640 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012-2013 NVIDIA CORPORATION.  All rights reserved. + * + * 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/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> + +#include <linux/dma-mapping.h> +#include <asm/dma-iommu.h> + +#include <drm/drm.h> +#include <drm/drmP.h> + +#include "host1x_client.h" +#include "dev.h" +#include "drm.h" +#include "gem.h" +#include "syncpt.h" + +#define DRIVER_NAME "tegra" +#define DRIVER_DESC "NVIDIA Tegra graphics" +#define DRIVER_DATE "20120330" +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct host1x_drm_client { +	struct host1x_client *client; +	struct device_node *np; +	struct list_head list; +}; + +static int host1x_add_drm_client(struct host1x_drm *host1x, +				 struct device_node *np) +{ +	struct host1x_drm_client *client; + +	client = kzalloc(sizeof(*client), GFP_KERNEL); +	if (!client) +		return -ENOMEM; + +	INIT_LIST_HEAD(&client->list); +	client->np = of_node_get(np); + +	list_add_tail(&client->list, &host1x->drm_clients); + +	return 0; +} + +static int host1x_activate_drm_client(struct host1x_drm *host1x, +				      struct host1x_drm_client *drm, +				      struct host1x_client *client) +{ +	mutex_lock(&host1x->drm_clients_lock); +	list_del_init(&drm->list); +	list_add_tail(&drm->list, &host1x->drm_active); +	drm->client = client; +	mutex_unlock(&host1x->drm_clients_lock); + +	return 0; +} + +static int host1x_remove_drm_client(struct host1x_drm *host1x, +				    struct host1x_drm_client *client) +{ +	mutex_lock(&host1x->drm_clients_lock); +	list_del_init(&client->list); +	mutex_unlock(&host1x->drm_clients_lock); + +	of_node_put(client->np); +	kfree(client); + +	return 0; +} + +static int host1x_parse_dt(struct host1x_drm *host1x) +{ +	static const char * const compat[] = { +		"nvidia,tegra20-dc", +		"nvidia,tegra20-hdmi", +		"nvidia,tegra20-gr2d", +		"nvidia,tegra30-dc", +		"nvidia,tegra30-hdmi", +		"nvidia,tegra30-gr2d", +	}; +	unsigned int i; +	int err; + +	for (i = 0; i < ARRAY_SIZE(compat); i++) { +		struct device_node *np; + +		for_each_child_of_node(host1x->dev->of_node, np) { +			if (of_device_is_compatible(np, compat[i]) && +			    of_device_is_available(np)) { +				err = host1x_add_drm_client(host1x, np); +				if (err < 0) +					return err; +			} +		} +	} + +	return 0; +} + +int host1x_drm_alloc(struct platform_device *pdev) +{ +	struct host1x_drm *host1x; +	int err; + +	host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL); +	if (!host1x) +		return -ENOMEM; + +	mutex_init(&host1x->drm_clients_lock); +	INIT_LIST_HEAD(&host1x->drm_clients); +	INIT_LIST_HEAD(&host1x->drm_active); +	mutex_init(&host1x->clients_lock); +	INIT_LIST_HEAD(&host1x->clients); +	host1x->dev = &pdev->dev; + +	err = host1x_parse_dt(host1x); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to parse DT: %d\n", err); +		return err; +	} + +	host1x_set_drm_data(&pdev->dev, host1x); + +	return 0; +} + +int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm) +{ +	struct host1x_client *client; + +	mutex_lock(&host1x->clients_lock); + +	list_for_each_entry(client, &host1x->clients, list) { +		if (client->ops && client->ops->drm_init) { +			int err = client->ops->drm_init(client, drm); +			if (err < 0) { +				dev_err(host1x->dev, +					"DRM setup failed for %s: %d\n", +					dev_name(client->dev), err); +				return err; +			} +		} +	} + +	mutex_unlock(&host1x->clients_lock); + +	return 0; +} + +int host1x_drm_exit(struct host1x_drm *host1x) +{ +	struct platform_device *pdev = to_platform_device(host1x->dev); +	struct host1x_client *client; + +	if (!host1x->drm) +		return 0; + +	mutex_lock(&host1x->clients_lock); + +	list_for_each_entry_reverse(client, &host1x->clients, list) { +		if (client->ops && client->ops->drm_exit) { +			int err = client->ops->drm_exit(client); +			if (err < 0) { +				dev_err(host1x->dev, +					"DRM cleanup failed for %s: %d\n", +					dev_name(client->dev), err); +				return err; +			} +		} +	} + +	mutex_unlock(&host1x->clients_lock); + +	drm_platform_exit(&tegra_drm_driver, pdev); +	host1x->drm = NULL; + +	return 0; +} + +int host1x_register_client(struct host1x_drm *host1x, +			   struct host1x_client *client) +{ +	struct host1x_drm_client *drm, *tmp; +	int err; + +	mutex_lock(&host1x->clients_lock); +	list_add_tail(&client->list, &host1x->clients); +	mutex_unlock(&host1x->clients_lock); + +	list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list) +		if (drm->np == client->dev->of_node) +			host1x_activate_drm_client(host1x, drm, client); + +	if (list_empty(&host1x->drm_clients)) { +		struct platform_device *pdev = to_platform_device(host1x->dev); + +		err = drm_platform_init(&tegra_drm_driver, pdev); +		if (err < 0) { +			dev_err(host1x->dev, "drm_platform_init(): %d\n", err); +			return err; +		} +	} + +	return 0; +} + +int host1x_unregister_client(struct host1x_drm *host1x, +			     struct host1x_client *client) +{ +	struct host1x_drm_client *drm, *tmp; +	int err; + +	list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) { +		if (drm->client == client) { +			err = host1x_drm_exit(host1x); +			if (err < 0) { +				dev_err(host1x->dev, "host1x_drm_exit(): %d\n", +					err); +				return err; +			} + +			host1x_remove_drm_client(host1x, drm); +			break; +		} +	} + +	mutex_lock(&host1x->clients_lock); +	list_del_init(&client->list); +	mutex_unlock(&host1x->clients_lock); + +	return 0; +} + +static int tegra_drm_load(struct drm_device *drm, unsigned long flags) +{ +	struct host1x_drm *host1x; +	int err; + +	host1x = host1x_get_drm_data(drm->dev); +	drm->dev_private = host1x; +	host1x->drm = drm; + +	drm_mode_config_init(drm); + +	err = host1x_drm_init(host1x, drm); +	if (err < 0) +		return err; + +	err = drm_vblank_init(drm, drm->mode_config.num_crtc); +	if (err < 0) +		return err; + +	err = tegra_drm_fb_init(drm); +	if (err < 0) +		return err; + +	drm_kms_helper_poll_init(drm); + +	return 0; +} + +static int tegra_drm_unload(struct drm_device *drm) +{ +	drm_kms_helper_poll_fini(drm); +	tegra_drm_fb_exit(drm); + +	drm_mode_config_cleanup(drm); + +	return 0; +} + +static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp) +{ +	struct host1x_drm_file *fpriv; + +	fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL); +	if (!fpriv) +		return -ENOMEM; + +	INIT_LIST_HEAD(&fpriv->contexts); +	filp->driver_priv = fpriv; + +	return 0; +} + +static void host1x_drm_context_free(struct host1x_drm_context *context) +{ +	context->client->ops->close_channel(context); +	kfree(context); +} + +static void tegra_drm_lastclose(struct drm_device *drm) +{ +	struct host1x_drm *host1x = drm->dev_private; + +	tegra_fbdev_restore_mode(host1x->fbdev); +} + +#ifdef CONFIG_DRM_TEGRA_STAGING +static bool host1x_drm_file_owns_context(struct host1x_drm_file *file, +					 struct host1x_drm_context *context) +{ +	struct host1x_drm_context *ctx; + +	list_for_each_entry(ctx, &file->contexts, list) +		if (ctx == context) +			return true; + +	return false; +} + +static int tegra_gem_create(struct drm_device *drm, void *data, +			    struct drm_file *file) +{ +	struct drm_tegra_gem_create *args = data; +	struct tegra_bo *bo; + +	bo = tegra_bo_create_with_handle(file, drm, args->size, +					 &args->handle); +	if (IS_ERR(bo)) +		return PTR_ERR(bo); + +	return 0; +} + +static int tegra_gem_mmap(struct drm_device *drm, void *data, +			  struct drm_file *file) +{ +	struct drm_tegra_gem_mmap *args = data; +	struct drm_gem_object *gem; +	struct tegra_bo *bo; + +	gem = drm_gem_object_lookup(drm, file, args->handle); +	if (!gem) +		return -EINVAL; + +	bo = to_tegra_bo(gem); + +	args->offset = tegra_bo_get_mmap_offset(bo); + +	drm_gem_object_unreference(gem); + +	return 0; +} + +static int tegra_syncpt_read(struct drm_device *drm, void *data, +			     struct drm_file *file) +{ +	struct drm_tegra_syncpt_read *args = data; +	struct host1x *host = dev_get_drvdata(drm->dev); +	struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id); + +	if (!sp) +		return -EINVAL; + +	args->value = host1x_syncpt_read_min(sp); +	return 0; +} + +static int tegra_syncpt_incr(struct drm_device *drm, void *data, +			     struct drm_file *file) +{ +	struct drm_tegra_syncpt_incr *args = data; +	struct host1x *host = dev_get_drvdata(drm->dev); +	struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id); + +	if (!sp) +		return -EINVAL; + +	host1x_syncpt_incr(sp); +	return 0; +} + +static int tegra_syncpt_wait(struct drm_device *drm, void *data, +			     struct drm_file *file) +{ +	struct drm_tegra_syncpt_wait *args = data; +	struct host1x *host = dev_get_drvdata(drm->dev); +	struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id); + +	if (!sp) +		return -EINVAL; + +	return host1x_syncpt_wait(sp, args->thresh, args->timeout, +				  &args->value); +} + +static int tegra_open_channel(struct drm_device *drm, void *data, +			      struct drm_file *file) +{ +	struct drm_tegra_open_channel *args = data; +	struct host1x_client *client; +	struct host1x_drm_context *context; +	struct host1x_drm_file *fpriv = file->driver_priv; +	struct host1x_drm *host1x = drm->dev_private; +	int err = -ENODEV; + +	context = kzalloc(sizeof(*context), GFP_KERNEL); +	if (!context) +		return -ENOMEM; + +	list_for_each_entry(client, &host1x->clients, list) +		if (client->class == args->client) { +			err = client->ops->open_channel(client, context); +			if (err) +				break; + +			context->client = client; +			list_add(&context->list, &fpriv->contexts); +			args->context = (uintptr_t)context; +			return 0; +		} + +	kfree(context); +	return err; +} + +static int tegra_close_channel(struct drm_device *drm, void *data, +			       struct drm_file *file) +{ +	struct drm_tegra_close_channel *args = data; +	struct host1x_drm_file *fpriv = file->driver_priv; +	struct host1x_drm_context *context = +		(struct host1x_drm_context *)(uintptr_t)args->context; + +	if (!host1x_drm_file_owns_context(fpriv, context)) +		return -EINVAL; + +	list_del(&context->list); +	host1x_drm_context_free(context); + +	return 0; +} + +static int tegra_get_syncpt(struct drm_device *drm, void *data, +			    struct drm_file *file) +{ +	struct drm_tegra_get_syncpt *args = data; +	struct host1x_drm_file *fpriv = file->driver_priv; +	struct host1x_drm_context *context = +		(struct host1x_drm_context *)(uintptr_t)args->context; +	struct host1x_syncpt *syncpt; + +	if (!host1x_drm_file_owns_context(fpriv, context)) +		return -ENODEV; + +	if (args->index >= context->client->num_syncpts) +		return -EINVAL; + +	syncpt = context->client->syncpts[args->index]; +	args->id = host1x_syncpt_id(syncpt); + +	return 0; +} + +static int tegra_submit(struct drm_device *drm, void *data, +			struct drm_file *file) +{ +	struct drm_tegra_submit *args = data; +	struct host1x_drm_file *fpriv = file->driver_priv; +	struct host1x_drm_context *context = +		(struct host1x_drm_context *)(uintptr_t)args->context; + +	if (!host1x_drm_file_owns_context(fpriv, context)) +		return -ENODEV; + +	return context->client->ops->submit(context, args, drm, file); +} +#endif + +static struct drm_ioctl_desc tegra_drm_ioctls[] = { +#ifdef CONFIG_DRM_TEGRA_STAGING +	DRM_IOCTL_DEF_DRV(TEGRA_GEM_CREATE, tegra_gem_create, DRM_UNLOCKED | DRM_AUTH), +	DRM_IOCTL_DEF_DRV(TEGRA_GEM_MMAP, tegra_gem_mmap, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_SYNCPT_READ, tegra_syncpt_read, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_SYNCPT_INCR, tegra_syncpt_incr, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_SYNCPT_WAIT, tegra_syncpt_wait, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_OPEN_CHANNEL, tegra_open_channel, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_CLOSE_CHANNEL, tegra_close_channel, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_GET_SYNCPT, tegra_get_syncpt, DRM_UNLOCKED), +	DRM_IOCTL_DEF_DRV(TEGRA_SUBMIT, tegra_submit, DRM_UNLOCKED), +#endif +}; + +static const struct file_operations tegra_drm_fops = { +	.owner = THIS_MODULE, +	.open = drm_open, +	.release = drm_release, +	.unlocked_ioctl = drm_ioctl, +	.mmap = tegra_drm_mmap, +	.poll = drm_poll, +	.fasync = drm_fasync, +	.read = drm_read, +#ifdef CONFIG_COMPAT +	.compat_ioctl = drm_compat_ioctl, +#endif +	.llseek = noop_llseek, +}; + +static struct drm_crtc *tegra_crtc_from_pipe(struct drm_device *drm, int pipe) +{ +	struct drm_crtc *crtc; + +	list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) { +		struct tegra_dc *dc = to_tegra_dc(crtc); + +		if (dc->pipe == pipe) +			return crtc; +	} + +	return NULL; +} + +static u32 tegra_drm_get_vblank_counter(struct drm_device *dev, int crtc) +{ +	/* TODO: implement real hardware counter using syncpoints */ +	return drm_vblank_count(dev, crtc); +} + +static int tegra_drm_enable_vblank(struct drm_device *drm, int pipe) +{ +	struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); +	struct tegra_dc *dc = to_tegra_dc(crtc); + +	if (!crtc) +		return -ENODEV; + +	tegra_dc_enable_vblank(dc); + +	return 0; +} + +static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe) +{ +	struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); +	struct tegra_dc *dc = to_tegra_dc(crtc); + +	if (crtc) +		tegra_dc_disable_vblank(dc); +} + +static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) +{ +	struct host1x_drm_file *fpriv = file->driver_priv; +	struct host1x_drm_context *context, *tmp; +	struct drm_crtc *crtc; + +	list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) +		tegra_dc_cancel_page_flip(crtc, file); + +	list_for_each_entry_safe(context, tmp, &fpriv->contexts, list) +		host1x_drm_context_free(context); + +	kfree(fpriv); +} + +#ifdef CONFIG_DEBUG_FS +static int tegra_debugfs_framebuffers(struct seq_file *s, void *data) +{ +	struct drm_info_node *node = (struct drm_info_node *)s->private; +	struct drm_device *drm = node->minor->dev; +	struct drm_framebuffer *fb; + +	mutex_lock(&drm->mode_config.fb_lock); + +	list_for_each_entry(fb, &drm->mode_config.fb_list, head) { +		seq_printf(s, "%3d: user size: %d x %d, depth %d, %d bpp, refcount %d\n", +			   fb->base.id, fb->width, fb->height, fb->depth, +			   fb->bits_per_pixel, +			   atomic_read(&fb->refcount.refcount)); +	} + +	mutex_unlock(&drm->mode_config.fb_lock); + +	return 0; +} + +static struct drm_info_list tegra_debugfs_list[] = { +	{ "framebuffers", tegra_debugfs_framebuffers, 0 }, +}; + +static int tegra_debugfs_init(struct drm_minor *minor) +{ +	return drm_debugfs_create_files(tegra_debugfs_list, +					ARRAY_SIZE(tegra_debugfs_list), +					minor->debugfs_root, minor); +} + +static void tegra_debugfs_cleanup(struct drm_minor *minor) +{ +	drm_debugfs_remove_files(tegra_debugfs_list, +				 ARRAY_SIZE(tegra_debugfs_list), minor); +} +#endif + +struct drm_driver tegra_drm_driver = { +	.driver_features = DRIVER_BUS_PLATFORM | DRIVER_MODESET | DRIVER_GEM, +	.load = tegra_drm_load, +	.unload = tegra_drm_unload, +	.open = tegra_drm_open, +	.preclose = tegra_drm_preclose, +	.lastclose = tegra_drm_lastclose, + +	.get_vblank_counter = tegra_drm_get_vblank_counter, +	.enable_vblank = tegra_drm_enable_vblank, +	.disable_vblank = tegra_drm_disable_vblank, + +#if defined(CONFIG_DEBUG_FS) +	.debugfs_init = tegra_debugfs_init, +	.debugfs_cleanup = tegra_debugfs_cleanup, +#endif + +	.gem_free_object = tegra_bo_free_object, +	.gem_vm_ops = &tegra_bo_vm_ops, +	.dumb_create = tegra_bo_dumb_create, +	.dumb_map_offset = tegra_bo_dumb_map_offset, +	.dumb_destroy = tegra_bo_dumb_destroy, + +	.ioctls = tegra_drm_ioctls, +	.num_ioctls = ARRAY_SIZE(tegra_drm_ioctls), +	.fops = &tegra_drm_fops, + +	.name = DRIVER_NAME, +	.desc = DRIVER_DESC, +	.date = DRIVER_DATE, +	.major = DRIVER_MAJOR, +	.minor = DRIVER_MINOR, +	.patchlevel = DRIVER_PATCHLEVEL, +}; diff --git a/drivers/gpu/host1x/drm/drm.h b/drivers/gpu/host1x/drm/drm.h new file mode 100644 index 00000000000..02ce020f257 --- /dev/null +++ b/drivers/gpu/host1x/drm/drm.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012-2013 NVIDIA CORPORATION.  All rights reserved. + * + * 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. + */ + +#ifndef HOST1X_DRM_H +#define HOST1X_DRM_H 1 + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fixed.h> +#include <uapi/drm/tegra_drm.h> + +#include "host1x.h" + +struct tegra_fb { +	struct drm_framebuffer base; +	struct tegra_bo **planes; +	unsigned int num_planes; +}; + +struct tegra_fbdev { +	struct drm_fb_helper base; +	struct tegra_fb *fb; +}; + +struct host1x_drm { +	struct drm_device *drm; +	struct device *dev; +	void __iomem *regs; +	struct clk *clk; +	int syncpt; +	int irq; + +	struct mutex drm_clients_lock; +	struct list_head drm_clients; +	struct list_head drm_active; + +	struct mutex clients_lock; +	struct list_head clients; + +	struct tegra_fbdev *fbdev; +}; + +struct host1x_client; + +struct host1x_drm_context { +	struct host1x_client *client; +	struct host1x_channel *channel; +	struct list_head list; +}; + +struct host1x_client_ops { +	int (*drm_init)(struct host1x_client *client, struct drm_device *drm); +	int (*drm_exit)(struct host1x_client *client); +	int (*open_channel)(struct host1x_client *client, +			    struct host1x_drm_context *context); +	void (*close_channel)(struct host1x_drm_context *context); +	int (*submit)(struct host1x_drm_context *context, +		      struct drm_tegra_submit *args, struct drm_device *drm, +		      struct drm_file *file); +}; + +struct host1x_drm_file { +	struct list_head contexts; +}; + +struct host1x_client { +	struct host1x_drm *host1x; +	struct device *dev; + +	const struct host1x_client_ops *ops; + +	enum host1x_class class; +	struct host1x_channel *channel; + +	struct host1x_syncpt **syncpts; +	unsigned int num_syncpts; + +	struct list_head list; +}; + +extern int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm); +extern int host1x_drm_exit(struct host1x_drm *host1x); + +extern int host1x_register_client(struct host1x_drm *host1x, +				  struct host1x_client *client); +extern int host1x_unregister_client(struct host1x_drm *host1x, +				    struct host1x_client *client); + +struct tegra_output; + +struct tegra_dc { +	struct host1x_client client; +	spinlock_t lock; + +	struct host1x_drm *host1x; +	struct device *dev; + +	struct drm_crtc base; +	int pipe; + +	struct clk *clk; + +	void __iomem *regs; +	int irq; + +	struct tegra_output *rgb; + +	struct list_head list; + +	struct drm_info_list *debugfs_files; +	struct drm_minor *minor; +	struct dentry *debugfs; + +	/* page-flip handling */ +	struct drm_pending_vblank_event *event; +}; + +static inline struct tegra_dc *host1x_client_to_dc(struct host1x_client *client) +{ +	return container_of(client, struct tegra_dc, client); +} + +static inline struct tegra_dc *to_tegra_dc(struct drm_crtc *crtc) +{ +	return container_of(crtc, struct tegra_dc, base); +} + +static inline void tegra_dc_writel(struct tegra_dc *dc, unsigned long value, +				   unsigned long reg) +{ +	writel(value, dc->regs + (reg << 2)); +} + +static inline unsigned long tegra_dc_readl(struct tegra_dc *dc, +					   unsigned long reg) +{ +	return readl(dc->regs + (reg << 2)); +} + +struct tegra_dc_window { +	struct { +		unsigned int x; +		unsigned int y; +		unsigned int w; +		unsigned int h; +	} src; +	struct { +		unsigned int x; +		unsigned int y; +		unsigned int w; +		unsigned int h; +	} dst; +	unsigned int bits_per_pixel; +	unsigned int format; +	unsigned int stride[2]; +	unsigned long base[3]; +}; + +/* from dc.c */ +extern unsigned int tegra_dc_format(uint32_t format); +extern int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, +				 const struct tegra_dc_window *window); +extern void tegra_dc_enable_vblank(struct tegra_dc *dc); +extern void tegra_dc_disable_vblank(struct tegra_dc *dc); +extern void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, +				      struct drm_file *file); + +struct tegra_output_ops { +	int (*enable)(struct tegra_output *output); +	int (*disable)(struct tegra_output *output); +	int (*setup_clock)(struct tegra_output *output, struct clk *clk, +			   unsigned long pclk); +	int (*check_mode)(struct tegra_output *output, +			  struct drm_display_mode *mode, +			  enum drm_mode_status *status); +}; + +enum tegra_output_type { +	TEGRA_OUTPUT_RGB, +	TEGRA_OUTPUT_HDMI, +}; + +struct tegra_output { +	struct device_node *of_node; +	struct device *dev; + +	const struct tegra_output_ops *ops; +	enum tegra_output_type type; + +	struct i2c_adapter *ddc; +	const struct edid *edid; +	unsigned int hpd_irq; +	int hpd_gpio; + +	struct drm_encoder encoder; +	struct drm_connector connector; +}; + +static inline struct tegra_output *encoder_to_output(struct drm_encoder *e) +{ +	return container_of(e, struct tegra_output, encoder); +} + +static inline struct tegra_output *connector_to_output(struct drm_connector *c) +{ +	return container_of(c, struct tegra_output, connector); +} + +static inline int tegra_output_enable(struct tegra_output *output) +{ +	if (output && output->ops && output->ops->enable) +		return output->ops->enable(output); + +	return output ? -ENOSYS : -EINVAL; +} + +static inline int tegra_output_disable(struct tegra_output *output) +{ +	if (output && output->ops && output->ops->disable) +		return output->ops->disable(output); + +	return output ? -ENOSYS : -EINVAL; +} + +static inline int tegra_output_setup_clock(struct tegra_output *output, +					   struct clk *clk, unsigned long pclk) +{ +	if (output && output->ops && output->ops->setup_clock) +		return output->ops->setup_clock(output, clk, pclk); + +	return output ? -ENOSYS : -EINVAL; +} + +static inline int tegra_output_check_mode(struct tegra_output *output, +					  struct drm_display_mode *mode, +					  enum drm_mode_status *status) +{ +	if (output && output->ops && output->ops->check_mode) +		return output->ops->check_mode(output, mode, status); + +	return output ? -ENOSYS : -EINVAL; +} + +/* from rgb.c */ +extern int tegra_dc_rgb_probe(struct tegra_dc *dc); +extern int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc); +extern int tegra_dc_rgb_exit(struct tegra_dc *dc); + +/* from output.c */ +extern int tegra_output_parse_dt(struct tegra_output *output); +extern int tegra_output_init(struct drm_device *drm, struct tegra_output *output); +extern int tegra_output_exit(struct tegra_output *output); + +/* from fb.c */ +struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, +				    unsigned int index); +extern int tegra_drm_fb_init(struct drm_device *drm); +extern void tegra_drm_fb_exit(struct drm_device *drm); +extern void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); + +extern struct drm_driver tegra_drm_driver; + +#endif /* HOST1X_DRM_H */ diff --git a/drivers/gpu/host1x/drm/fb.c b/drivers/gpu/host1x/drm/fb.c new file mode 100644 index 00000000000..979a3e32b78 --- /dev/null +++ b/drivers/gpu/host1x/drm/fb.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2012-2013 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * Based on the KMS/FB CMA helpers + *   Copyright (C) 2012 Analog Device Inc. + * + * 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/module.h> + +#include "drm.h" +#include "gem.h" + +static inline struct tegra_fb *to_tegra_fb(struct drm_framebuffer *fb) +{ +	return container_of(fb, struct tegra_fb, base); +} + +static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) +{ +	return container_of(helper, struct tegra_fbdev, base); +} + +struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, +				    unsigned int index) +{ +	struct tegra_fb *fb = to_tegra_fb(framebuffer); + +	if (index >= drm_format_num_planes(framebuffer->pixel_format)) +		return NULL; + +	return fb->planes[index]; +} + +static void tegra_fb_destroy(struct drm_framebuffer *framebuffer) +{ +	struct tegra_fb *fb = to_tegra_fb(framebuffer); +	unsigned int i; + +	for (i = 0; i < fb->num_planes; i++) { +		struct tegra_bo *bo = fb->planes[i]; + +		if (bo) +			drm_gem_object_unreference_unlocked(&bo->gem); +	} + +	drm_framebuffer_cleanup(framebuffer); +	kfree(fb->planes); +	kfree(fb); +} + +static int tegra_fb_create_handle(struct drm_framebuffer *framebuffer, +				  struct drm_file *file, unsigned int *handle) +{ +	struct tegra_fb *fb = to_tegra_fb(framebuffer); + +	return drm_gem_handle_create(file, &fb->planes[0]->gem, handle); +} + +static struct drm_framebuffer_funcs tegra_fb_funcs = { +	.destroy = tegra_fb_destroy, +	.create_handle = tegra_fb_create_handle, +}; + +static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, +				       struct drm_mode_fb_cmd2 *mode_cmd, +				       struct tegra_bo **planes, +				       unsigned int num_planes) +{ +	struct tegra_fb *fb; +	unsigned int i; +	int err; + +	fb = kzalloc(sizeof(*fb), GFP_KERNEL); +	if (!fb) +		return ERR_PTR(-ENOMEM); + +	fb->planes = kzalloc(num_planes * sizeof(*planes), GFP_KERNEL); +	if (!fb->planes) +		return ERR_PTR(-ENOMEM); + +	fb->num_planes = num_planes; + +	drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd); + +	for (i = 0; i < fb->num_planes; i++) +		fb->planes[i] = planes[i]; + +	err = drm_framebuffer_init(drm, &fb->base, &tegra_fb_funcs); +	if (err < 0) { +		dev_err(drm->dev, "failed to initialize framebuffer: %d\n", +			err); +		kfree(fb->planes); +		kfree(fb); +		return ERR_PTR(err); +	} + +	return fb; +} + +static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, +					       struct drm_file *file, +					       struct drm_mode_fb_cmd2 *cmd) +{ +	unsigned int hsub, vsub, i; +	struct tegra_bo *planes[4]; +	struct drm_gem_object *gem; +	struct tegra_fb *fb; +	int err; + +	hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format); +	vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format); + +	for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) { +		unsigned int width = cmd->width / (i ? hsub : 1); +		unsigned int height = cmd->height / (i ? vsub : 1); +		unsigned int size, bpp; + +		gem = drm_gem_object_lookup(drm, file, cmd->handles[i]); +		if (!gem) { +			err = -ENXIO; +			goto unreference; +		} + +		bpp = drm_format_plane_cpp(cmd->pixel_format, i); + +		size = (height - 1) * cmd->pitches[i] + +		       width * bpp + cmd->offsets[i]; + +		if (gem->size < size) { +			err = -EINVAL; +			goto unreference; +		} + +		planes[i] = to_tegra_bo(gem); +	} + +	fb = tegra_fb_alloc(drm, cmd, planes, i); +	if (IS_ERR(fb)) { +		err = PTR_ERR(fb); +		goto unreference; +	} + +	return &fb->base; + +unreference: +	while (i--) +		drm_gem_object_unreference_unlocked(&planes[i]->gem); + +	return ERR_PTR(err); +} + +static struct fb_ops tegra_fb_ops = { +	.owner = THIS_MODULE, +	.fb_fillrect = sys_fillrect, +	.fb_copyarea = sys_copyarea, +	.fb_imageblit = sys_imageblit, +	.fb_check_var = drm_fb_helper_check_var, +	.fb_set_par = drm_fb_helper_set_par, +	.fb_blank = drm_fb_helper_blank, +	.fb_pan_display = drm_fb_helper_pan_display, +	.fb_setcmap = drm_fb_helper_setcmap, +}; + +static int tegra_fbdev_probe(struct drm_fb_helper *helper, +			     struct drm_fb_helper_surface_size *sizes) +{ +	struct tegra_fbdev *fbdev = to_tegra_fbdev(helper); +	struct drm_device *drm = helper->dev; +	struct drm_mode_fb_cmd2 cmd = { 0 }; +	unsigned int bytes_per_pixel; +	struct drm_framebuffer *fb; +	unsigned long offset; +	struct fb_info *info; +	struct tegra_bo *bo; +	size_t size; +	int err; + +	bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + +	cmd.width = sizes->surface_width; +	cmd.height = sizes->surface_height; +	cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; +	cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, +						     sizes->surface_depth); + +	size = cmd.pitches[0] * cmd.height; + +	bo = tegra_bo_create(drm, size); +	if (IS_ERR(bo)) +		return PTR_ERR(bo); + +	info = framebuffer_alloc(0, drm->dev); +	if (!info) { +		dev_err(drm->dev, "failed to allocate framebuffer info\n"); +		tegra_bo_free_object(&bo->gem); +		return -ENOMEM; +	} + +	fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); +	if (IS_ERR(fbdev->fb)) { +		dev_err(drm->dev, "failed to allocate DRM framebuffer\n"); +		err = PTR_ERR(fbdev->fb); +		goto release; +	} + +	fb = &fbdev->fb->base; +	helper->fb = fb; +	helper->fbdev = info; + +	info->par = helper; +	info->flags = FBINFO_FLAG_DEFAULT; +	info->fbops = &tegra_fb_ops; + +	err = fb_alloc_cmap(&info->cmap, 256, 0); +	if (err < 0) { +		dev_err(drm->dev, "failed to allocate color map: %d\n", err); +		goto destroy; +	} + +	drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); +	drm_fb_helper_fill_var(info, helper, fb->width, fb->height); + +	offset = info->var.xoffset * bytes_per_pixel + +		 info->var.yoffset * fb->pitches[0]; + +	drm->mode_config.fb_base = (resource_size_t)bo->paddr; +	info->screen_base = bo->vaddr + offset; +	info->screen_size = size; +	info->fix.smem_start = (unsigned long)(bo->paddr + offset); +	info->fix.smem_len = size; + +	return 0; + +destroy: +	drm_framebuffer_unregister_private(fb); +	tegra_fb_destroy(fb); +release: +	framebuffer_release(info); +	return err; +} + +static struct drm_fb_helper_funcs tegra_fb_helper_funcs = { +	.fb_probe = tegra_fbdev_probe, +}; + +static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm, +					      unsigned int preferred_bpp, +					      unsigned int num_crtc, +					      unsigned int max_connectors) +{ +	struct drm_fb_helper *helper; +	struct tegra_fbdev *fbdev; +	int err; + +	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); +	if (!fbdev) { +		dev_err(drm->dev, "failed to allocate DRM fbdev\n"); +		return ERR_PTR(-ENOMEM); +	} + +	fbdev->base.funcs = &tegra_fb_helper_funcs; +	helper = &fbdev->base; + +	err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); +	if (err < 0) { +		dev_err(drm->dev, "failed to initialize DRM FB helper\n"); +		goto free; +	} + +	err = drm_fb_helper_single_add_all_connectors(&fbdev->base); +	if (err < 0) { +		dev_err(drm->dev, "failed to add connectors\n"); +		goto fini; +	} + +	drm_helper_disable_unused_functions(drm); + +	err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); +	if (err < 0) { +		dev_err(drm->dev, "failed to set initial configuration\n"); +		goto fini; +	} + +	return fbdev; + +fini: +	drm_fb_helper_fini(&fbdev->base); +free: +	kfree(fbdev); +	return ERR_PTR(err); +} + +static void tegra_fbdev_free(struct tegra_fbdev *fbdev) +{ +	struct fb_info *info = fbdev->base.fbdev; + +	if (info) { +		int err; + +		err = unregister_framebuffer(info); +		if (err < 0) +			DRM_DEBUG_KMS("failed to unregister framebuffer\n"); + +		if (info->cmap.len) +			fb_dealloc_cmap(&info->cmap); + +		framebuffer_release(info); +	} + +	if (fbdev->fb) { +		drm_framebuffer_unregister_private(&fbdev->fb->base); +		tegra_fb_destroy(&fbdev->fb->base); +	} + +	drm_fb_helper_fini(&fbdev->base); +	kfree(fbdev); +} + +static void tegra_fb_output_poll_changed(struct drm_device *drm) +{ +	struct host1x_drm *host1x = drm->dev_private; + +	if (host1x->fbdev) +		drm_fb_helper_hotplug_event(&host1x->fbdev->base); +} + +static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { +	.fb_create = tegra_fb_create, +	.output_poll_changed = tegra_fb_output_poll_changed, +}; + +int tegra_drm_fb_init(struct drm_device *drm) +{ +	struct host1x_drm *host1x = drm->dev_private; +	struct tegra_fbdev *fbdev; + +	drm->mode_config.min_width = 0; +	drm->mode_config.min_height = 0; + +	drm->mode_config.max_width = 4096; +	drm->mode_config.max_height = 4096; + +	drm->mode_config.funcs = &tegra_drm_mode_funcs; + +	fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, +				   drm->mode_config.num_connector); +	if (IS_ERR(fbdev)) +		return PTR_ERR(fbdev); + +	host1x->fbdev = fbdev; + +	return 0; +} + +void tegra_drm_fb_exit(struct drm_device *drm) +{ +	struct host1x_drm *host1x = drm->dev_private; + +	tegra_fbdev_free(host1x->fbdev); +} + +void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) +{ +	if (fbdev) { +		drm_modeset_lock_all(fbdev->base.dev); +		drm_fb_helper_restore_fbdev_mode(&fbdev->base); +		drm_modeset_unlock_all(fbdev->base.dev); +	} +} diff --git a/drivers/gpu/host1x/drm/gem.c b/drivers/gpu/host1x/drm/gem.c new file mode 100644 index 00000000000..c5e9a9b494c --- /dev/null +++ b/drivers/gpu/host1x/drm/gem.c @@ -0,0 +1,270 @@ +/* + * NVIDIA Tegra DRM GEM helper functions + * + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * Copyright (C) 2013 NVIDIA CORPORATION, All rights reserved. + * + * Based on the GEM/CMA helpers + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * 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/mm.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/export.h> +#include <linux/dma-mapping.h> + +#include <drm/drmP.h> +#include <drm/drm.h> + +#include "gem.h" + +static inline struct tegra_bo *host1x_to_drm_bo(struct host1x_bo *bo) +{ +	return container_of(bo, struct tegra_bo, base); +} + +static void tegra_bo_put(struct host1x_bo *bo) +{ +	struct tegra_bo *obj = host1x_to_drm_bo(bo); +	struct drm_device *drm = obj->gem.dev; + +	mutex_lock(&drm->struct_mutex); +	drm_gem_object_unreference(&obj->gem); +	mutex_unlock(&drm->struct_mutex); +} + +static dma_addr_t tegra_bo_pin(struct host1x_bo *bo, struct sg_table **sgt) +{ +	struct tegra_bo *obj = host1x_to_drm_bo(bo); + +	return obj->paddr; +} + +static void tegra_bo_unpin(struct host1x_bo *bo, struct sg_table *sgt) +{ +} + +static void *tegra_bo_mmap(struct host1x_bo *bo) +{ +	struct tegra_bo *obj = host1x_to_drm_bo(bo); + +	return obj->vaddr; +} + +static void tegra_bo_munmap(struct host1x_bo *bo, void *addr) +{ +} + +static void *tegra_bo_kmap(struct host1x_bo *bo, unsigned int page) +{ +	struct tegra_bo *obj = host1x_to_drm_bo(bo); + +	return obj->vaddr + page * PAGE_SIZE; +} + +static void tegra_bo_kunmap(struct host1x_bo *bo, unsigned int page, +			    void *addr) +{ +} + +static struct host1x_bo *tegra_bo_get(struct host1x_bo *bo) +{ +	struct tegra_bo *obj = host1x_to_drm_bo(bo); +	struct drm_device *drm = obj->gem.dev; + +	mutex_lock(&drm->struct_mutex); +	drm_gem_object_reference(&obj->gem); +	mutex_unlock(&drm->struct_mutex); + +	return bo; +} + +const struct host1x_bo_ops tegra_bo_ops = { +	.get = tegra_bo_get, +	.put = tegra_bo_put, +	.pin = tegra_bo_pin, +	.unpin = tegra_bo_unpin, +	.mmap = tegra_bo_mmap, +	.munmap = tegra_bo_munmap, +	.kmap = tegra_bo_kmap, +	.kunmap = tegra_bo_kunmap, +}; + +static void tegra_bo_destroy(struct drm_device *drm, struct tegra_bo *bo) +{ +	dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, bo->paddr); +} + +unsigned int tegra_bo_get_mmap_offset(struct tegra_bo *bo) +{ +	return (unsigned int)bo->gem.map_list.hash.key << PAGE_SHIFT; +} + +struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size) +{ +	struct tegra_bo *bo; +	int err; + +	bo = kzalloc(sizeof(*bo), GFP_KERNEL); +	if (!bo) +		return ERR_PTR(-ENOMEM); + +	host1x_bo_init(&bo->base, &tegra_bo_ops); +	size = round_up(size, PAGE_SIZE); + +	bo->vaddr = dma_alloc_writecombine(drm->dev, size, &bo->paddr, +					   GFP_KERNEL | __GFP_NOWARN); +	if (!bo->vaddr) { +		dev_err(drm->dev, "failed to allocate buffer with size %u\n", +			size); +		err = -ENOMEM; +		goto err_dma; +	} + +	err = drm_gem_object_init(drm, &bo->gem, size); +	if (err) +		goto err_init; + +	err = drm_gem_create_mmap_offset(&bo->gem); +	if (err) +		goto err_mmap; + +	return bo; + +err_mmap: +	drm_gem_object_release(&bo->gem); +err_init: +	tegra_bo_destroy(drm, bo); +err_dma: +	kfree(bo); + +	return ERR_PTR(err); + +} + +struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, +					    struct drm_device *drm, +					    unsigned int size, +					    unsigned int *handle) +{ +	struct tegra_bo *bo; +	int ret; + +	bo = tegra_bo_create(drm, size); +	if (IS_ERR(bo)) +		return bo; + +	ret = drm_gem_handle_create(file, &bo->gem, handle); +	if (ret) +		goto err; + +	drm_gem_object_unreference_unlocked(&bo->gem); + +	return bo; + +err: +	tegra_bo_free_object(&bo->gem); +	return ERR_PTR(ret); +} + +void tegra_bo_free_object(struct drm_gem_object *gem) +{ +	struct tegra_bo *bo = to_tegra_bo(gem); + +	if (gem->map_list.map) +		drm_gem_free_mmap_offset(gem); + +	drm_gem_object_release(gem); +	tegra_bo_destroy(gem->dev, bo); + +	kfree(bo); +} + +int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm, +			 struct drm_mode_create_dumb *args) +{ +	int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); +	struct tegra_bo *bo; + +	if (args->pitch < min_pitch) +		args->pitch = min_pitch; + +	if (args->size < args->pitch * args->height) +		args->size = args->pitch * args->height; + +	bo = tegra_bo_create_with_handle(file, drm, args->size, +					    &args->handle); +	if (IS_ERR(bo)) +		return PTR_ERR(bo); + +	return 0; +} + +int tegra_bo_dumb_map_offset(struct drm_file *file, struct drm_device *drm, +			     uint32_t handle, uint64_t *offset) +{ +	struct drm_gem_object *gem; +	struct tegra_bo *bo; + +	mutex_lock(&drm->struct_mutex); + +	gem = drm_gem_object_lookup(drm, file, handle); +	if (!gem) { +		dev_err(drm->dev, "failed to lookup GEM object\n"); +		mutex_unlock(&drm->struct_mutex); +		return -EINVAL; +	} + +	bo = to_tegra_bo(gem); + +	*offset = tegra_bo_get_mmap_offset(bo); + +	drm_gem_object_unreference(gem); + +	mutex_unlock(&drm->struct_mutex); + +	return 0; +} + +const struct vm_operations_struct tegra_bo_vm_ops = { +	.open = drm_gem_vm_open, +	.close = drm_gem_vm_close, +}; + +int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) +{ +	struct drm_gem_object *gem; +	struct tegra_bo *bo; +	int ret; + +	ret = drm_gem_mmap(file, vma); +	if (ret) +		return ret; + +	gem = vma->vm_private_data; +	bo = to_tegra_bo(gem); + +	ret = remap_pfn_range(vma, vma->vm_start, bo->paddr >> PAGE_SHIFT, +			      vma->vm_end - vma->vm_start, vma->vm_page_prot); +	if (ret) +		drm_gem_vm_close(vma); + +	return ret; +} + +int tegra_bo_dumb_destroy(struct drm_file *file, struct drm_device *drm, +			  unsigned int handle) +{ +	return drm_gem_handle_delete(file, handle); +} diff --git a/drivers/gpu/host1x/drm/gem.h b/drivers/gpu/host1x/drm/gem.h new file mode 100644 index 00000000000..34de2b486eb --- /dev/null +++ b/drivers/gpu/host1x/drm/gem.h @@ -0,0 +1,59 @@ +/* + * Tegra host1x GEM implementation + * + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_GEM_H +#define __HOST1X_GEM_H + +#include <drm/drm.h> +#include <drm/drmP.h> + +#include "host1x_bo.h" + +struct tegra_bo { +	struct drm_gem_object gem; +	struct host1x_bo base; +	dma_addr_t paddr; +	void *vaddr; +}; + +static inline struct tegra_bo *to_tegra_bo(struct drm_gem_object *gem) +{ +	return container_of(gem, struct tegra_bo, gem); +} + +extern const struct host1x_bo_ops tegra_bo_ops; + +struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size); +struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, +					    struct drm_device *drm, +					    unsigned int size, +					    unsigned int *handle); +void tegra_bo_free_object(struct drm_gem_object *gem); +unsigned int tegra_bo_get_mmap_offset(struct tegra_bo *bo); +int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm, +			 struct drm_mode_create_dumb *args); +int tegra_bo_dumb_map_offset(struct drm_file *file, struct drm_device *drm, +			     uint32_t handle, uint64_t *offset); +int tegra_bo_dumb_destroy(struct drm_file *file, struct drm_device *drm, +			  unsigned int handle); + +int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma); + +extern const struct vm_operations_struct tegra_bo_vm_ops; + +#endif diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c new file mode 100644 index 00000000000..6a45ae090ee --- /dev/null +++ b/drivers/gpu/host1x/drm/gr2d.c @@ -0,0 +1,339 @@ +/* + * drivers/video/tegra/host/gr2d/gr2d.c + * + * Tegra Graphics 2D + * + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/export.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> + +#include "channel.h" +#include "drm.h" +#include "gem.h" +#include "job.h" +#include "host1x.h" +#include "host1x_bo.h" +#include "host1x_client.h" +#include "syncpt.h" + +struct gr2d { +	struct host1x_client client; +	struct clk *clk; +	struct host1x_channel *channel; +	unsigned long *addr_regs; +}; + +static inline struct gr2d *to_gr2d(struct host1x_client *client) +{ +	return container_of(client, struct gr2d, client); +} + +static int gr2d_is_addr_reg(struct device *dev, u32 class, u32 reg); + +static int gr2d_client_init(struct host1x_client *client, +			    struct drm_device *drm) +{ +	return 0; +} + +static int gr2d_client_exit(struct host1x_client *client) +{ +	return 0; +} + +static int gr2d_open_channel(struct host1x_client *client, +			     struct host1x_drm_context *context) +{ +	struct gr2d *gr2d = to_gr2d(client); + +	context->channel = host1x_channel_get(gr2d->channel); + +	if (!context->channel) +		return -ENOMEM; + +	return 0; +} + +static void gr2d_close_channel(struct host1x_drm_context *context) +{ +	host1x_channel_put(context->channel); +} + +static struct host1x_bo *host1x_bo_lookup(struct drm_device *drm, +					  struct drm_file *file, +					  u32 handle) +{ +	struct drm_gem_object *gem; +	struct tegra_bo *bo; + +	gem = drm_gem_object_lookup(drm, file, handle); +	if (!gem) +		return 0; + +	mutex_lock(&drm->struct_mutex); +	drm_gem_object_unreference(gem); +	mutex_unlock(&drm->struct_mutex); + +	bo = to_tegra_bo(gem); +	return &bo->base; +} + +static int gr2d_submit(struct host1x_drm_context *context, +		       struct drm_tegra_submit *args, struct drm_device *drm, +		       struct drm_file *file) +{ +	struct host1x_job *job; +	unsigned int num_cmdbufs = args->num_cmdbufs; +	unsigned int num_relocs = args->num_relocs; +	unsigned int num_waitchks = args->num_waitchks; +	struct drm_tegra_cmdbuf __user *cmdbufs = +		(void * __user)(uintptr_t)args->cmdbufs; +	struct drm_tegra_reloc __user *relocs = +		(void * __user)(uintptr_t)args->relocs; +	struct drm_tegra_waitchk __user *waitchks = +		(void * __user)(uintptr_t)args->waitchks; +	struct drm_tegra_syncpt syncpt; +	int err; + +	/* We don't yet support other than one syncpt_incr struct per submit */ +	if (args->num_syncpts != 1) +		return -EINVAL; + +	job = host1x_job_alloc(context->channel, args->num_cmdbufs, +			       args->num_relocs, args->num_waitchks); +	if (!job) +		return -ENOMEM; + +	job->num_relocs = args->num_relocs; +	job->num_waitchk = args->num_waitchks; +	job->client = (u32)args->context; +	job->class = context->client->class; +	job->serialize = true; + +	while (num_cmdbufs) { +		struct drm_tegra_cmdbuf cmdbuf; +		struct host1x_bo *bo; + +		err = copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf)); +		if (err) +			goto fail; + +		bo = host1x_bo_lookup(drm, file, cmdbuf.handle); +		if (!bo) +			goto fail; + +		host1x_job_add_gather(job, bo, cmdbuf.words, cmdbuf.offset); +		num_cmdbufs--; +		cmdbufs++; +	} + +	err = copy_from_user(job->relocarray, relocs, +			     sizeof(*relocs) * num_relocs); +	if (err) +		goto fail; + +	while (num_relocs--) { +		struct host1x_reloc *reloc = &job->relocarray[num_relocs]; +		struct host1x_bo *cmdbuf, *target; + +		cmdbuf = host1x_bo_lookup(drm, file, (u32)reloc->cmdbuf); +		target = host1x_bo_lookup(drm, file, (u32)reloc->target); + +		reloc->cmdbuf = cmdbuf; +		reloc->target = target; + +		if (!reloc->target || !reloc->cmdbuf) +			goto fail; +	} + +	err = copy_from_user(job->waitchk, waitchks, +			     sizeof(*waitchks) * num_waitchks); +	if (err) +		goto fail; + +	err = copy_from_user(&syncpt, (void * __user)(uintptr_t)args->syncpts, +			     sizeof(syncpt)); +	if (err) +		goto fail; + +	job->syncpt_id = syncpt.id; +	job->syncpt_incrs = syncpt.incrs; +	job->timeout = 10000; +	job->is_addr_reg = gr2d_is_addr_reg; + +	if (args->timeout && args->timeout < 10000) +		job->timeout = args->timeout; + +	err = host1x_job_pin(job, context->client->dev); +	if (err) +		goto fail; + +	err = host1x_job_submit(job); +	if (err) +		goto fail_submit; + +	args->fence = job->syncpt_end; + +	host1x_job_put(job); +	return 0; + +fail_submit: +	host1x_job_unpin(job); +fail: +	host1x_job_put(job); +	return err; +} + +static struct host1x_client_ops gr2d_client_ops = { +	.drm_init = gr2d_client_init, +	.drm_exit = gr2d_client_exit, +	.open_channel = gr2d_open_channel, +	.close_channel = gr2d_close_channel, +	.submit = gr2d_submit, +}; + +static void gr2d_init_addr_reg_map(struct device *dev, struct gr2d *gr2d) +{ +	const u32 gr2d_addr_regs[] = {0x1a, 0x1b, 0x26, 0x2b, 0x2c, 0x2d, 0x31, +				      0x32, 0x48, 0x49, 0x4a, 0x4b, 0x4c}; +	unsigned long *bitmap; +	int i; + +	bitmap = devm_kzalloc(dev, DIV_ROUND_UP(256, BITS_PER_BYTE), +			      GFP_KERNEL); + +	for (i = 0; i < ARRAY_SIZE(gr2d_addr_regs); ++i) { +		u32 reg = gr2d_addr_regs[i]; +		bitmap[BIT_WORD(reg)] |= BIT_MASK(reg); +	} + +	gr2d->addr_regs = bitmap; +} + +static int gr2d_is_addr_reg(struct device *dev, u32 class, u32 reg) +{ +	struct gr2d *gr2d = dev_get_drvdata(dev); + +	switch (class) { +	case HOST1X_CLASS_HOST1X: +		return reg == 0x2b; +	case HOST1X_CLASS_GR2D: +	case HOST1X_CLASS_GR2D_SB: +		reg &= 0xff; +		if (gr2d->addr_regs[BIT_WORD(reg)] & BIT_MASK(reg)) +			return 1; +	default: +		return 0; +	} +} + +static const struct of_device_id gr2d_match[] = { +	{ .compatible = "nvidia,tegra30-gr2d" }, +	{ .compatible = "nvidia,tegra20-gr2d" }, +	{ }, +}; + +static int gr2d_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct host1x_drm *host1x = host1x_get_drm_data(dev->parent); +	int err; +	struct gr2d *gr2d = NULL; +	struct host1x_syncpt **syncpts; + +	gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL); +	if (!gr2d) +		return -ENOMEM; + +	syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL); +	if (!syncpts) +		return -ENOMEM; + +	gr2d->clk = devm_clk_get(dev, NULL); +	if (IS_ERR(gr2d->clk)) { +		dev_err(dev, "cannot get clock\n"); +		return PTR_ERR(gr2d->clk); +	} + +	err = clk_prepare_enable(gr2d->clk); +	if (err) { +		dev_err(dev, "cannot turn on clock\n"); +		return err; +	} + +	gr2d->channel = host1x_channel_request(dev); +	if (!gr2d->channel) +		return -ENOMEM; + +	*syncpts = host1x_syncpt_request(dev, 0); +	if (!(*syncpts)) { +		host1x_channel_free(gr2d->channel); +		return -ENOMEM; +	} + +	gr2d->client.ops = &gr2d_client_ops; +	gr2d->client.dev = dev; +	gr2d->client.class = HOST1X_CLASS_GR2D; +	gr2d->client.syncpts = syncpts; +	gr2d->client.num_syncpts = 1; + +	err = host1x_register_client(host1x, &gr2d->client); +	if (err < 0) { +		dev_err(dev, "failed to register host1x client: %d\n", err); +		return err; +	} + +	gr2d_init_addr_reg_map(dev, gr2d); + +	platform_set_drvdata(pdev, gr2d); + +	return 0; +} + +static int __exit gr2d_remove(struct platform_device *pdev) +{ +	struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent); +	struct gr2d *gr2d = platform_get_drvdata(pdev); +	unsigned int i; +	int err; + +	err = host1x_unregister_client(host1x, &gr2d->client); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to unregister client: %d\n", err); +		return err; +	} + +	for (i = 0; i < gr2d->client.num_syncpts; i++) +		host1x_syncpt_free(gr2d->client.syncpts[i]); + +	host1x_channel_free(gr2d->channel); +	clk_disable_unprepare(gr2d->clk); + +	return 0; +} + +struct platform_driver tegra_gr2d_driver = { +	.probe = gr2d_probe, +	.remove = __exit_p(gr2d_remove), +	.driver = { +		.owner = THIS_MODULE, +		.name = "gr2d", +		.of_match_table = gr2d_match, +	} +}; diff --git a/drivers/gpu/host1x/drm/hdmi.c b/drivers/gpu/host1x/drm/hdmi.c new file mode 100644 index 00000000000..01097da09f7 --- /dev/null +++ b/drivers/gpu/host1x/drm/hdmi.c @@ -0,0 +1,1313 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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/clk.h> +#include <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/hdmi.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk/tegra.h> + +#include <drm/drm_edid.h> + +#include "hdmi.h" +#include "drm.h" +#include "dc.h" +#include "host1x_client.h" + +struct tegra_hdmi { +	struct host1x_client client; +	struct tegra_output output; +	struct device *dev; + +	struct regulator *vdd; +	struct regulator *pll; + +	void __iomem *regs; +	unsigned int irq; + +	struct clk *clk_parent; +	struct clk *clk; + +	unsigned int audio_source; +	unsigned int audio_freq; +	bool stereo; +	bool dvi; + +	struct drm_info_list *debugfs_files; +	struct drm_minor *minor; +	struct dentry *debugfs; +}; + +static inline struct tegra_hdmi * +host1x_client_to_hdmi(struct host1x_client *client) +{ +	return container_of(client, struct tegra_hdmi, client); +} + +static inline struct tegra_hdmi *to_hdmi(struct tegra_output *output) +{ +	return container_of(output, struct tegra_hdmi, output); +} + +#define HDMI_AUDIOCLK_FREQ 216000000 +#define HDMI_REKEY_DEFAULT 56 + +enum { +	AUTO = 0, +	SPDIF, +	HDA, +}; + +static inline unsigned long tegra_hdmi_readl(struct tegra_hdmi *hdmi, +					     unsigned long reg) +{ +	return readl(hdmi->regs + (reg << 2)); +} + +static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, unsigned long val, +				     unsigned long reg) +{ +	writel(val, hdmi->regs + (reg << 2)); +} + +struct tegra_hdmi_audio_config { +	unsigned int pclk; +	unsigned int n; +	unsigned int cts; +	unsigned int aval; +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_32k[] = { +	{  25200000, 4096,  25200, 24000 }, +	{  27000000, 4096,  27000, 24000 }, +	{  74250000, 4096,  74250, 24000 }, +	{ 148500000, 4096, 148500, 24000 }, +	{         0,    0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_44_1k[] = { +	{  25200000, 5880,  26250, 25000 }, +	{  27000000, 5880,  28125, 25000 }, +	{  74250000, 4704,  61875, 20000 }, +	{ 148500000, 4704, 123750, 20000 }, +	{         0,    0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_48k[] = { +	{  25200000, 6144,  25200, 24000 }, +	{  27000000, 6144,  27000, 24000 }, +	{  74250000, 6144,  74250, 24000 }, +	{ 148500000, 6144, 148500, 24000 }, +	{         0,    0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_88_2k[] = { +	{  25200000, 11760,  26250, 25000 }, +	{  27000000, 11760,  28125, 25000 }, +	{  74250000,  9408,  61875, 20000 }, +	{ 148500000,  9408, 123750, 20000 }, +	{         0,     0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_96k[] = { +	{  25200000, 12288,  25200, 24000 }, +	{  27000000, 12288,  27000, 24000 }, +	{  74250000, 12288,  74250, 24000 }, +	{ 148500000, 12288, 148500, 24000 }, +	{         0,     0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_176_4k[] = { +	{  25200000, 23520,  26250, 25000 }, +	{  27000000, 23520,  28125, 25000 }, +	{  74250000, 18816,  61875, 20000 }, +	{ 148500000, 18816, 123750, 20000 }, +	{         0,     0,      0,     0 }, +}; + +static const struct tegra_hdmi_audio_config tegra_hdmi_audio_192k[] = { +	{  25200000, 24576,  25200, 24000 }, +	{  27000000, 24576,  27000, 24000 }, +	{  74250000, 24576,  74250, 24000 }, +	{ 148500000, 24576, 148500, 24000 }, +	{         0,     0,      0,     0 }, +}; + +struct tmds_config { +	unsigned int pclk; +	u32 pll0; +	u32 pll1; +	u32 pe_current; +	u32 drive_current; +}; + +static const struct tmds_config tegra2_tmds_config[] = { +	{ /* slow pixel clock modes */ +		.pclk = 27000000, +		.pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | +			SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | +			SOR_PLL_TX_REG_LOAD(3), +		.pll1 = SOR_PLL_TMDS_TERM_ENABLE, +		.pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | +			PE_CURRENT1(PE_CURRENT_0_0_mA) | +			PE_CURRENT2(PE_CURRENT_0_0_mA) | +			PE_CURRENT3(PE_CURRENT_0_0_mA), +		.drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), +	}, +	{ /* high pixel clock modes */ +		.pclk = UINT_MAX, +		.pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | +			SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | +			SOR_PLL_TX_REG_LOAD(3), +		.pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, +		.pe_current = PE_CURRENT0(PE_CURRENT_6_0_mA) | +			PE_CURRENT1(PE_CURRENT_6_0_mA) | +			PE_CURRENT2(PE_CURRENT_6_0_mA) | +			PE_CURRENT3(PE_CURRENT_6_0_mA), +		.drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | +			DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), +	}, +}; + +static const struct tmds_config tegra3_tmds_config[] = { +	{ /* 480p modes */ +		.pclk = 27000000, +		.pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | +			SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | +			SOR_PLL_TX_REG_LOAD(0), +		.pll1 = SOR_PLL_TMDS_TERM_ENABLE, +		.pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | +			PE_CURRENT1(PE_CURRENT_0_0_mA) | +			PE_CURRENT2(PE_CURRENT_0_0_mA) | +			PE_CURRENT3(PE_CURRENT_0_0_mA), +		.drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), +	}, { /* 720p modes */ +		.pclk = 74250000, +		.pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | +			SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | +			SOR_PLL_TX_REG_LOAD(0), +		.pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, +		.pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | +			PE_CURRENT1(PE_CURRENT_5_0_mA) | +			PE_CURRENT2(PE_CURRENT_5_0_mA) | +			PE_CURRENT3(PE_CURRENT_5_0_mA), +		.drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), +	}, { /* 1080p modes */ +		.pclk = UINT_MAX, +		.pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | +			SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(3) | +			SOR_PLL_TX_REG_LOAD(0), +		.pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, +		.pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | +			PE_CURRENT1(PE_CURRENT_5_0_mA) | +			PE_CURRENT2(PE_CURRENT_5_0_mA) | +			PE_CURRENT3(PE_CURRENT_5_0_mA), +		.drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | +			DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), +	}, +}; + +static const struct tegra_hdmi_audio_config * +tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pclk) +{ +	const struct tegra_hdmi_audio_config *table; + +	switch (audio_freq) { +	case 32000: +		table = tegra_hdmi_audio_32k; +		break; + +	case 44100: +		table = tegra_hdmi_audio_44_1k; +		break; + +	case 48000: +		table = tegra_hdmi_audio_48k; +		break; + +	case 88200: +		table = tegra_hdmi_audio_88_2k; +		break; + +	case 96000: +		table = tegra_hdmi_audio_96k; +		break; + +	case 176400: +		table = tegra_hdmi_audio_176_4k; +		break; + +	case 192000: +		table = tegra_hdmi_audio_192k; +		break; + +	default: +		return NULL; +	} + +	while (table->pclk) { +		if (table->pclk == pclk) +			return table; + +		table++; +	} + +	return NULL; +} + +static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) +{ +	const unsigned int freqs[] = { +		32000, 44100, 48000, 88200, 96000, 176400, 192000 +	}; +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(freqs); i++) { +		unsigned int f = freqs[i]; +		unsigned int eight_half; +		unsigned long value; +		unsigned int delta; + +		if (f > 96000) +			delta = 2; +		else if (f > 480000) +			delta = 6; +		else +			delta = 9; + +		eight_half = (8 * HDMI_AUDIOCLK_FREQ) / (f * 128); +		value = AUDIO_FS_LOW(eight_half - delta) | +			AUDIO_FS_HIGH(eight_half + delta); +		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_FS(i)); +	} +} + +static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) +{ +	struct device_node *node = hdmi->dev->of_node; +	const struct tegra_hdmi_audio_config *config; +	unsigned int offset = 0; +	unsigned long value; + +	switch (hdmi->audio_source) { +	case HDA: +		value = AUDIO_CNTRL0_SOURCE_SELECT_HDAL; +		break; + +	case SPDIF: +		value = AUDIO_CNTRL0_SOURCE_SELECT_SPDIF; +		break; + +	default: +		value = AUDIO_CNTRL0_SOURCE_SELECT_AUTO; +		break; +	} + +	if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) { +		value |= AUDIO_CNTRL0_ERROR_TOLERANCE(6) | +			 AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0); +		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); +	} else { +		value |= AUDIO_CNTRL0_INJECT_NULLSMPL; +		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); + +		value = AUDIO_CNTRL0_ERROR_TOLERANCE(6) | +			AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0); +		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); +	} + +	config = tegra_hdmi_get_audio_config(hdmi->audio_freq, pclk); +	if (!config) { +		dev_err(hdmi->dev, "cannot set audio to %u at %u pclk\n", +			hdmi->audio_freq, pclk); +		return -EINVAL; +	} + +	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_HDMI_ACR_CTRL); + +	value = AUDIO_N_RESETF | AUDIO_N_GENERATE_ALTERNATE | +		AUDIO_N_VALUE(config->n - 1); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N); + +	tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE, +			  HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH); + +	value = ACR_SUBPACK_CTS(config->cts); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW); + +	value = SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_SPARE); + +	value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_AUDIO_N); +	value &= ~AUDIO_N_RESETF; +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N); + +	if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) { +		switch (hdmi->audio_freq) { +		case 32000: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320; +			break; + +		case 44100: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441; +			break; + +		case 48000: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480; +			break; + +		case 88200: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882; +			break; + +		case 96000: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960; +			break; + +		case 176400: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764; +			break; + +		case 192000: +			offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920; +			break; +		} + +		tegra_hdmi_writel(hdmi, config->aval, offset); +	} + +	tegra_hdmi_setup_audio_fs_tables(hdmi); + +	return 0; +} + +static inline unsigned long tegra_hdmi_subpack(const u8 *ptr, size_t size) +{ +	unsigned long value = 0; +	size_t i; + +	for (i = size; i > 0; i--) +		value = (value << 8) | ptr[i - 1]; + +	return value; +} + +static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, +				      size_t size) +{ +	const u8 *ptr = data; +	unsigned long offset; +	unsigned long value; +	size_t i, j; + +	switch (ptr[0]) { +	case HDMI_INFOFRAME_TYPE_AVI: +		offset = HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER; +		break; + +	case HDMI_INFOFRAME_TYPE_AUDIO: +		offset = HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER; +		break; + +	case HDMI_INFOFRAME_TYPE_VENDOR: +		offset = HDMI_NV_PDISP_HDMI_GENERIC_HEADER; +		break; + +	default: +		dev_err(hdmi->dev, "unsupported infoframe type: %02x\n", +			ptr[0]); +		return; +	} + +	value = INFOFRAME_HEADER_TYPE(ptr[0]) | +		INFOFRAME_HEADER_VERSION(ptr[1]) | +		INFOFRAME_HEADER_LEN(ptr[2]); +	tegra_hdmi_writel(hdmi, value, offset); +	offset++; + +	/* +	 * Each subpack contains 7 bytes, divided into: +	 * - subpack_low: bytes 0 - 3 +	 * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00) +	 */ +	for (i = 3, j = 0; i < size; i += 7, j += 8) { +		size_t rem = size - i, num = min_t(size_t, rem, 4); + +		value = tegra_hdmi_subpack(&ptr[i], num); +		tegra_hdmi_writel(hdmi, value, offset++); + +		num = min_t(size_t, rem - num, 3); + +		value = tegra_hdmi_subpack(&ptr[i + 4], num); +		tegra_hdmi_writel(hdmi, value, offset++); +	} +} + +static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi, +					   struct drm_display_mode *mode) +{ +	struct hdmi_avi_infoframe frame; +	u8 buffer[17]; +	ssize_t err; + +	if (hdmi->dvi) { +		tegra_hdmi_writel(hdmi, 0, +				  HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); +		return; +	} + +	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to setup AVI infoframe: %zd\n", err); +		return; +	} + +	err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack AVI infoframe: %zd\n", err); +		return; +	} + +	tegra_hdmi_write_infopack(hdmi, buffer, err); + +	tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE, +			  HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); +} + +static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) +{ +	struct hdmi_audio_infoframe frame; +	u8 buffer[14]; +	ssize_t err; + +	if (hdmi->dvi) { +		tegra_hdmi_writel(hdmi, 0, +				  HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); +		return; +	} + +	err = hdmi_audio_infoframe_init(&frame); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to initialize audio infoframe: %d\n", +			err); +		return; +	} + +	frame.channels = 2; + +	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack audio infoframe: %zd\n", +			err); +		return; +	} + +	/* +	 * The audio infoframe has only one set of subpack registers, so the +	 * infoframe needs to be truncated. One set of subpack registers can +	 * contain 7 bytes. Including the 3 byte header only the first 10 +	 * bytes can be programmed. +	 */ +	tegra_hdmi_write_infopack(hdmi, buffer, min(10, err)); + +	tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE, +			  HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); +} + +static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) +{ +	struct hdmi_vendor_infoframe frame; +	unsigned long value; +	u8 buffer[10]; +	ssize_t err; + +	if (!hdmi->stereo) { +		value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +		value &= ~GENERIC_CTRL_ENABLE; +		tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +		return; +	} + +	memset(&frame, 0, sizeof(frame)); + +	frame.type = HDMI_INFOFRAME_TYPE_VENDOR; +	frame.version = 0x01; +	frame.length = 6; + +	frame.data[0] = 0x03; /* regid0 */ +	frame.data[1] = 0x0c; /* regid1 */ +	frame.data[2] = 0x00; /* regid2 */ +	frame.data[3] = 0x02 << 5; /* video format */ + +	/* TODO: 74 MHz limit? */ +	if (1) { +		frame.data[4] = 0x00 << 4; /* 3D structure */ +	} else { +		frame.data[4] = 0x08 << 4; /* 3D structure */ +		frame.data[5] = 0x00 << 4; /* 3D ext. data */ +	} + +	err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack vendor infoframe: %zd\n", +			err); +		return; +	} + +	tegra_hdmi_write_infopack(hdmi, buffer, err); + +	value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +	value |= GENERIC_CTRL_ENABLE; +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +} + +static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi, +				  const struct tmds_config *tmds) +{ +	unsigned long value; + +	tegra_hdmi_writel(hdmi, tmds->pll0, HDMI_NV_PDISP_SOR_PLL0); +	tegra_hdmi_writel(hdmi, tmds->pll1, HDMI_NV_PDISP_SOR_PLL1); +	tegra_hdmi_writel(hdmi, tmds->pe_current, HDMI_NV_PDISP_PE_CURRENT); + +	value = tmds->drive_current | DRIVE_CURRENT_FUSE_OVERRIDE; +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT); +} + +static int tegra_output_hdmi_enable(struct tegra_output *output) +{ +	unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey; +	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); +	struct drm_display_mode *mode = &dc->base.mode; +	struct tegra_hdmi *hdmi = to_hdmi(output); +	struct device_node *node = hdmi->dev->of_node; +	unsigned int pulse_start, div82, pclk; +	const struct tmds_config *tmds; +	unsigned int num_tmds; +	unsigned long value; +	int retries = 1000; +	int err; + +	pclk = mode->clock * 1000; +	h_sync_width = mode->hsync_end - mode->hsync_start; +	h_back_porch = mode->htotal - mode->hsync_end; +	h_front_porch = mode->hsync_start - mode->hdisplay; + +	err = regulator_enable(hdmi->vdd); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); +		return err; +	} + +	err = regulator_enable(hdmi->pll); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err); +		return err; +	} + +	/* +	 * This assumes that the display controller will divide its parent +	 * clock by 2 to generate the pixel clock. +	 */ +	err = tegra_output_setup_clock(output, hdmi->clk, pclk * 2); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to setup clock: %d\n", err); +		return err; +	} + +	err = clk_set_rate(hdmi->clk, pclk); +	if (err < 0) +		return err; + +	err = clk_enable(hdmi->clk); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to enable clock: %d\n", err); +		return err; +	} + +	tegra_periph_reset_assert(hdmi->clk); +	usleep_range(1000, 2000); +	tegra_periph_reset_deassert(hdmi->clk); + +	tegra_dc_writel(dc, VSYNC_H_POSITION(1), +			DC_DISP_DISP_TIMING_OPTIONS); +	tegra_dc_writel(dc, DITHER_CONTROL_DISABLE | BASE_COLOR_SIZE888, +			DC_DISP_DISP_COLOR_CONTROL); + +	/* video_preamble uses h_pulse2 */ +	pulse_start = 1 + h_sync_width + h_back_porch - 10; + +	tegra_dc_writel(dc, H_PULSE_2_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0); + +	value = PULSE_MODE_NORMAL | PULSE_POLARITY_HIGH | PULSE_QUAL_VACTIVE | +		PULSE_LAST_END_A; +	tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_CONTROL); + +	value = PULSE_START(pulse_start) | PULSE_END(pulse_start + 8); +	tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_POSITION_A); + +	value = VSYNC_WINDOW_END(0x210) | VSYNC_WINDOW_START(0x200) | +		VSYNC_WINDOW_ENABLE; +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_VSYNC_WINDOW); + +	if (dc->pipe) +		value = HDMI_SRC_DISPLAYB; +	else +		value = HDMI_SRC_DISPLAYA; + +	if ((mode->hdisplay == 720) && ((mode->vdisplay == 480) || +					(mode->vdisplay == 576))) +		tegra_hdmi_writel(hdmi, +				  value | ARM_VIDEO_RANGE_FULL, +				  HDMI_NV_PDISP_INPUT_CONTROL); +	else +		tegra_hdmi_writel(hdmi, +				  value | ARM_VIDEO_RANGE_LIMITED, +				  HDMI_NV_PDISP_INPUT_CONTROL); + +	div82 = clk_get_rate(hdmi->clk) / 1000000 * 4; +	value = SOR_REFCLK_DIV_INT(div82 >> 2) | SOR_REFCLK_DIV_FRAC(div82); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_REFCLK); + +	if (!hdmi->dvi) { +		err = tegra_hdmi_setup_audio(hdmi, pclk); +		if (err < 0) +			hdmi->dvi = true; +	} + +	if (of_device_is_compatible(node, "nvidia,tegra20-hdmi")) { +		/* +		 * TODO: add ELD support +		 */ +	} + +	rekey = HDMI_REKEY_DEFAULT; +	value = HDMI_CTRL_REKEY(rekey); +	value |= HDMI_CTRL_MAX_AC_PACKET((h_sync_width + h_back_porch + +					  h_front_porch - rekey - 18) / 32); + +	if (!hdmi->dvi) +		value |= HDMI_CTRL_ENABLE; + +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_CTRL); + +	if (hdmi->dvi) +		tegra_hdmi_writel(hdmi, 0x0, +				  HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +	else +		tegra_hdmi_writel(hdmi, GENERIC_CTRL_AUDIO, +				  HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + +	tegra_hdmi_setup_avi_infoframe(hdmi, mode); +	tegra_hdmi_setup_audio_infoframe(hdmi); +	tegra_hdmi_setup_stereo_infoframe(hdmi); + +	/* TMDS CONFIG */ +	if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) { +		num_tmds = ARRAY_SIZE(tegra3_tmds_config); +		tmds = tegra3_tmds_config; +	} else { +		num_tmds = ARRAY_SIZE(tegra2_tmds_config); +		tmds = tegra2_tmds_config; +	} + +	for (i = 0; i < num_tmds; i++) { +		if (pclk <= tmds[i].pclk) { +			tegra_hdmi_setup_tmds(hdmi, &tmds[i]); +			break; +		} +	} + +	tegra_hdmi_writel(hdmi, +			  SOR_SEQ_CTL_PU_PC(0) | +			  SOR_SEQ_PU_PC_ALT(0) | +			  SOR_SEQ_PD_PC(8) | +			  SOR_SEQ_PD_PC_ALT(8), +			  HDMI_NV_PDISP_SOR_SEQ_CTL); + +	value = SOR_SEQ_INST_WAIT_TIME(1) | +		SOR_SEQ_INST_WAIT_UNITS_VSYNC | +		SOR_SEQ_INST_HALT | +		SOR_SEQ_INST_PIN_A_LOW | +		SOR_SEQ_INST_PIN_B_LOW | +		SOR_SEQ_INST_DRIVE_PWM_OUT_LO; + +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_SEQ_INST(0)); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_SEQ_INST(8)); + +	value = 0x1c800; +	value &= ~SOR_CSTM_ROTCLK(~0); +	value |= SOR_CSTM_ROTCLK(2); +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_CSTM); + +	tegra_dc_writel(dc, DISP_CTRL_MODE_STOP, DC_CMD_DISPLAY_COMMAND); +	tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); +	tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + +	/* start SOR */ +	tegra_hdmi_writel(hdmi, +			  SOR_PWR_NORMAL_STATE_PU | +			  SOR_PWR_NORMAL_START_NORMAL | +			  SOR_PWR_SAFE_STATE_PD | +			  SOR_PWR_SETTING_NEW_TRIGGER, +			  HDMI_NV_PDISP_SOR_PWR); +	tegra_hdmi_writel(hdmi, +			  SOR_PWR_NORMAL_STATE_PU | +			  SOR_PWR_NORMAL_START_NORMAL | +			  SOR_PWR_SAFE_STATE_PD | +			  SOR_PWR_SETTING_NEW_DONE, +			  HDMI_NV_PDISP_SOR_PWR); + +	do { +		BUG_ON(--retries < 0); +		value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PWR); +	} while (value & SOR_PWR_SETTING_NEW_PENDING); + +	value = SOR_STATE_ASY_CRCMODE_COMPLETE | +		SOR_STATE_ASY_OWNER_HEAD0 | +		SOR_STATE_ASY_SUBOWNER_BOTH | +		SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A | +		SOR_STATE_ASY_DEPOL_POS; + +	/* setup sync polarities */ +	if (mode->flags & DRM_MODE_FLAG_PHSYNC) +		value |= SOR_STATE_ASY_HSYNCPOL_POS; + +	if (mode->flags & DRM_MODE_FLAG_NHSYNC) +		value |= SOR_STATE_ASY_HSYNCPOL_NEG; + +	if (mode->flags & DRM_MODE_FLAG_PVSYNC) +		value |= SOR_STATE_ASY_VSYNCPOL_POS; + +	if (mode->flags & DRM_MODE_FLAG_NVSYNC) +		value |= SOR_STATE_ASY_VSYNCPOL_NEG; + +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_STATE2); + +	value = SOR_STATE_ASY_HEAD_OPMODE_AWAKE | SOR_STATE_ASY_ORMODE_NORMAL; +	tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_STATE1); + +	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0); +	tegra_hdmi_writel(hdmi, SOR_STATE_UPDATE, HDMI_NV_PDISP_SOR_STATE0); +	tegra_hdmi_writel(hdmi, value | SOR_STATE_ATTACHED, +			  HDMI_NV_PDISP_SOR_STATE1); +	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0); + +	tegra_dc_writel(dc, HDMI_ENABLE, DC_DISP_DISP_WIN_OPTIONS); + +	value = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | +		PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + +	value = DISP_CTRL_MODE_C_DISPLAY; +	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + +	tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); +	tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + +	/* TODO: add HDCP support */ + +	return 0; +} + +static int tegra_output_hdmi_disable(struct tegra_output *output) +{ +	struct tegra_hdmi *hdmi = to_hdmi(output); + +	tegra_periph_reset_assert(hdmi->clk); +	clk_disable(hdmi->clk); +	regulator_disable(hdmi->pll); +	regulator_disable(hdmi->vdd); + +	return 0; +} + +static int tegra_output_hdmi_setup_clock(struct tegra_output *output, +					 struct clk *clk, unsigned long pclk) +{ +	struct tegra_hdmi *hdmi = to_hdmi(output); +	struct clk *base; +	int err; + +	err = clk_set_parent(clk, hdmi->clk_parent); +	if (err < 0) { +		dev_err(output->dev, "failed to set parent: %d\n", err); +		return err; +	} + +	base = clk_get_parent(hdmi->clk_parent); + +	/* +	 * This assumes that the parent clock is pll_d_out0 or pll_d2_out +	 * respectively, each of which divides the base pll_d by 2. +	 */ +	err = clk_set_rate(base, pclk * 2); +	if (err < 0) +		dev_err(output->dev, +			"failed to set base clock rate to %lu Hz\n", +			pclk * 2); + +	return 0; +} + +static int tegra_output_hdmi_check_mode(struct tegra_output *output, +					struct drm_display_mode *mode, +					enum drm_mode_status *status) +{ +	struct tegra_hdmi *hdmi = to_hdmi(output); +	unsigned long pclk = mode->clock * 1000; +	struct clk *parent; +	long err; + +	parent = clk_get_parent(hdmi->clk_parent); + +	err = clk_round_rate(parent, pclk * 4); +	if (err < 0) +		*status = MODE_NOCLOCK; +	else +		*status = MODE_OK; + +	return 0; +} + +static const struct tegra_output_ops hdmi_ops = { +	.enable = tegra_output_hdmi_enable, +	.disable = tegra_output_hdmi_disable, +	.setup_clock = tegra_output_hdmi_setup_clock, +	.check_mode = tegra_output_hdmi_check_mode, +}; + +static int tegra_hdmi_show_regs(struct seq_file *s, void *data) +{ +	struct drm_info_node *node = s->private; +	struct tegra_hdmi *hdmi = node->info_ent->data; + +#define DUMP_REG(name)						\ +	seq_printf(s, "%-56s %#05x %08lx\n", #name, name,	\ +		tegra_hdmi_readl(hdmi, name)) + +	DUMP_REG(HDMI_CTXSW); +	DUMP_REG(HDMI_NV_PDISP_SOR_STATE0); +	DUMP_REG(HDMI_NV_PDISP_SOR_STATE1); +	DUMP_REG(HDMI_NV_PDISP_SOR_STATE2); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AN_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AN_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CN_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CN_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AKSV_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AKSV_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_BKSV_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_BKSV_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CKSV_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CKSV_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_DKSV_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_DKSV_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CTRL); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CMODE); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_RI); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CS_MSB); +	DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CS_LSB); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU0); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU_RDATA0); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU1); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU2); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_STATUS); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_HEADER); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH); +	DUMP_REG(HDMI_NV_PDISP_HDMI_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_VSYNC_KEEPOUT); +	DUMP_REG(HDMI_NV_PDISP_HDMI_VSYNC_WINDOW); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_CTRL); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_STATUS); +	DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_SUBPACK); +	DUMP_REG(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS1); +	DUMP_REG(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS2); +	DUMP_REG(HDMI_NV_PDISP_HDMI_EMU0); +	DUMP_REG(HDMI_NV_PDISP_HDMI_EMU1); +	DUMP_REG(HDMI_NV_PDISP_HDMI_EMU1_RDATA); +	DUMP_REG(HDMI_NV_PDISP_HDMI_SPARE); +	DUMP_REG(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS1); +	DUMP_REG(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS2); +	DUMP_REG(HDMI_NV_PDISP_HDMI_HDCPRIF_ROM_CTRL); +	DUMP_REG(HDMI_NV_PDISP_SOR_CAP); +	DUMP_REG(HDMI_NV_PDISP_SOR_PWR); +	DUMP_REG(HDMI_NV_PDISP_SOR_TEST); +	DUMP_REG(HDMI_NV_PDISP_SOR_PLL0); +	DUMP_REG(HDMI_NV_PDISP_SOR_PLL1); +	DUMP_REG(HDMI_NV_PDISP_SOR_PLL2); +	DUMP_REG(HDMI_NV_PDISP_SOR_CSTM); +	DUMP_REG(HDMI_NV_PDISP_SOR_LVDS); +	DUMP_REG(HDMI_NV_PDISP_SOR_CRCA); +	DUMP_REG(HDMI_NV_PDISP_SOR_CRCB); +	DUMP_REG(HDMI_NV_PDISP_SOR_BLANK); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_CTL); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(0)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(1)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(2)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(3)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(4)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(5)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(6)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(7)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(8)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(9)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(10)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(11)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(12)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(13)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(14)); +	DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(15)); +	DUMP_REG(HDMI_NV_PDISP_SOR_VCRCA0); +	DUMP_REG(HDMI_NV_PDISP_SOR_VCRCA1); +	DUMP_REG(HDMI_NV_PDISP_SOR_CCRCA0); +	DUMP_REG(HDMI_NV_PDISP_SOR_CCRCA1); +	DUMP_REG(HDMI_NV_PDISP_SOR_EDATAA0); +	DUMP_REG(HDMI_NV_PDISP_SOR_EDATAA1); +	DUMP_REG(HDMI_NV_PDISP_SOR_COUNTA0); +	DUMP_REG(HDMI_NV_PDISP_SOR_COUNTA1); +	DUMP_REG(HDMI_NV_PDISP_SOR_DEBUGA0); +	DUMP_REG(HDMI_NV_PDISP_SOR_DEBUGA1); +	DUMP_REG(HDMI_NV_PDISP_SOR_TRIG); +	DUMP_REG(HDMI_NV_PDISP_SOR_MSCHECK); +	DUMP_REG(HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG0); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG1); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG2); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(0)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(1)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(2)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(3)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(4)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(5)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(6)); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_PULSE_WIDTH); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_THRESHOLD); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_CNTRL0); +	DUMP_REG(HDMI_NV_PDISP_AUDIO_N); +	DUMP_REG(HDMI_NV_PDISP_HDCPRIF_ROM_TIMING); +	DUMP_REG(HDMI_NV_PDISP_SOR_REFCLK); +	DUMP_REG(HDMI_NV_PDISP_CRC_CONTROL); +	DUMP_REG(HDMI_NV_PDISP_INPUT_CONTROL); +	DUMP_REG(HDMI_NV_PDISP_SCRATCH); +	DUMP_REG(HDMI_NV_PDISP_PE_CURRENT); +	DUMP_REG(HDMI_NV_PDISP_KEY_CTRL); +	DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG0); +	DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG1); +	DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG2); +	DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_0); +	DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_1); +	DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_2); +	DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_3); +	DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG); +	DUMP_REG(HDMI_NV_PDISP_KEY_SKEY_INDEX); +	DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); +	DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); +	DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); + +#undef DUMP_REG + +	return 0; +} + +static struct drm_info_list debugfs_files[] = { +	{ "regs", tegra_hdmi_show_regs, 0, NULL }, +}; + +static int tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi, +				   struct drm_minor *minor) +{ +	unsigned int i; +	int err; + +	hdmi->debugfs = debugfs_create_dir("hdmi", minor->debugfs_root); +	if (!hdmi->debugfs) +		return -ENOMEM; + +	hdmi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), +				      GFP_KERNEL); +	if (!hdmi->debugfs_files) { +		err = -ENOMEM; +		goto remove; +	} + +	for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) +		hdmi->debugfs_files[i].data = hdmi; + +	err = drm_debugfs_create_files(hdmi->debugfs_files, +				       ARRAY_SIZE(debugfs_files), +				       hdmi->debugfs, minor); +	if (err < 0) +		goto free; + +	hdmi->minor = minor; + +	return 0; + +free: +	kfree(hdmi->debugfs_files); +	hdmi->debugfs_files = NULL; +remove: +	debugfs_remove(hdmi->debugfs); +	hdmi->debugfs = NULL; + +	return err; +} + +static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) +{ +	drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files), +				 hdmi->minor); +	hdmi->minor = NULL; + +	kfree(hdmi->debugfs_files); +	hdmi->debugfs_files = NULL; + +	debugfs_remove(hdmi->debugfs); +	hdmi->debugfs = NULL; + +	return 0; +} + +static int tegra_hdmi_drm_init(struct host1x_client *client, +			       struct drm_device *drm) +{ +	struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); +	int err; + +	hdmi->output.type = TEGRA_OUTPUT_HDMI; +	hdmi->output.dev = client->dev; +	hdmi->output.ops = &hdmi_ops; + +	err = tegra_output_init(drm, &hdmi->output); +	if (err < 0) { +		dev_err(client->dev, "output setup failed: %d\n", err); +		return err; +	} + +	if (IS_ENABLED(CONFIG_DEBUG_FS)) { +		err = tegra_hdmi_debugfs_init(hdmi, drm->primary); +		if (err < 0) +			dev_err(client->dev, "debugfs setup failed: %d\n", err); +	} + +	return 0; +} + +static int tegra_hdmi_drm_exit(struct host1x_client *client) +{ +	struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); +	int err; + +	if (IS_ENABLED(CONFIG_DEBUG_FS)) { +		err = tegra_hdmi_debugfs_exit(hdmi); +		if (err < 0) +			dev_err(client->dev, "debugfs cleanup failed: %d\n", +				err); +	} + +	err = tegra_output_disable(&hdmi->output); +	if (err < 0) { +		dev_err(client->dev, "output failed to disable: %d\n", err); +		return err; +	} + +	err = tegra_output_exit(&hdmi->output); +	if (err < 0) { +		dev_err(client->dev, "output cleanup failed: %d\n", err); +		return err; +	} + +	return 0; +} + +static const struct host1x_client_ops hdmi_client_ops = { +	.drm_init = tegra_hdmi_drm_init, +	.drm_exit = tegra_hdmi_drm_exit, +}; + +static int tegra_hdmi_probe(struct platform_device *pdev) +{ +	struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent); +	struct tegra_hdmi *hdmi; +	struct resource *regs; +	int err; + +	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); +	if (!hdmi) +		return -ENOMEM; + +	hdmi->dev = &pdev->dev; +	hdmi->audio_source = AUTO; +	hdmi->audio_freq = 44100; +	hdmi->stereo = false; +	hdmi->dvi = false; + +	hdmi->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(hdmi->clk)) { +		dev_err(&pdev->dev, "failed to get clock\n"); +		return PTR_ERR(hdmi->clk); +	} + +	err = clk_prepare(hdmi->clk); +	if (err < 0) +		return err; + +	hdmi->clk_parent = devm_clk_get(&pdev->dev, "parent"); +	if (IS_ERR(hdmi->clk_parent)) +		return PTR_ERR(hdmi->clk_parent); + +	err = clk_prepare(hdmi->clk_parent); +	if (err < 0) +		return err; + +	err = clk_set_parent(hdmi->clk, hdmi->clk_parent); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to setup clocks: %d\n", err); +		return err; +	} + +	hdmi->vdd = devm_regulator_get(&pdev->dev, "vdd"); +	if (IS_ERR(hdmi->vdd)) { +		dev_err(&pdev->dev, "failed to get VDD regulator\n"); +		return PTR_ERR(hdmi->vdd); +	} + +	hdmi->pll = devm_regulator_get(&pdev->dev, "pll"); +	if (IS_ERR(hdmi->pll)) { +		dev_err(&pdev->dev, "failed to get PLL regulator\n"); +		return PTR_ERR(hdmi->pll); +	} + +	hdmi->output.dev = &pdev->dev; + +	err = tegra_output_parse_dt(&hdmi->output); +	if (err < 0) +		return err; + +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!regs) +		return -ENXIO; + +	hdmi->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(hdmi->regs)) +		return PTR_ERR(hdmi->regs); + +	err = platform_get_irq(pdev, 0); +	if (err < 0) +		return err; + +	hdmi->irq = err; + +	hdmi->client.ops = &hdmi_client_ops; +	INIT_LIST_HEAD(&hdmi->client.list); +	hdmi->client.dev = &pdev->dev; + +	err = host1x_register_client(host1x, &hdmi->client); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to register host1x client: %d\n", +			err); +		return err; +	} + +	platform_set_drvdata(pdev, hdmi); + +	return 0; +} + +static int tegra_hdmi_remove(struct platform_device *pdev) +{ +	struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent); +	struct tegra_hdmi *hdmi = platform_get_drvdata(pdev); +	int err; + +	err = host1x_unregister_client(host1x, &hdmi->client); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", +			err); +		return err; +	} + +	clk_unprepare(hdmi->clk_parent); +	clk_unprepare(hdmi->clk); + +	return 0; +} + +static struct of_device_id tegra_hdmi_of_match[] = { +	{ .compatible = "nvidia,tegra30-hdmi", }, +	{ .compatible = "nvidia,tegra20-hdmi", }, +	{ }, +}; + +struct platform_driver tegra_hdmi_driver = { +	.driver = { +		.name = "tegra-hdmi", +		.owner = THIS_MODULE, +		.of_match_table = tegra_hdmi_of_match, +	}, +	.probe = tegra_hdmi_probe, +	.remove = tegra_hdmi_remove, +}; diff --git a/drivers/gpu/host1x/drm/hdmi.h b/drivers/gpu/host1x/drm/hdmi.h new file mode 100644 index 00000000000..52ac36e08cc --- /dev/null +++ b/drivers/gpu/host1x/drm/hdmi.h @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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. + */ + +#ifndef TEGRA_HDMI_H +#define TEGRA_HDMI_H 1 + +/* register definitions */ +#define HDMI_CTXSW						0x00 + +#define HDMI_NV_PDISP_SOR_STATE0				0x01 +#define SOR_STATE_UPDATE (1 << 0) + +#define HDMI_NV_PDISP_SOR_STATE1				0x02 +#define SOR_STATE_ASY_HEAD_OPMODE_AWAKE (2 << 0) +#define SOR_STATE_ASY_ORMODE_NORMAL     (1 << 2) +#define SOR_STATE_ATTACHED              (1 << 3) + +#define HDMI_NV_PDISP_SOR_STATE2				0x03 +#define SOR_STATE_ASY_OWNER_NONE         (0 <<  0) +#define SOR_STATE_ASY_OWNER_HEAD0        (1 <<  0) +#define SOR_STATE_ASY_SUBOWNER_NONE      (0 <<  4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD0  (1 <<  4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD1  (2 <<  4) +#define SOR_STATE_ASY_SUBOWNER_BOTH      (3 <<  4) +#define SOR_STATE_ASY_CRCMODE_ACTIVE     (0 <<  6) +#define SOR_STATE_ASY_CRCMODE_COMPLETE   (1 <<  6) +#define SOR_STATE_ASY_CRCMODE_NON_ACTIVE (2 <<  6) +#define SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A (1 << 8) +#define SOR_STATE_ASY_PROTOCOL_CUSTOM        (15 << 8) +#define SOR_STATE_ASY_HSYNCPOL_POS       (0 << 12) +#define SOR_STATE_ASY_HSYNCPOL_NEG       (1 << 12) +#define SOR_STATE_ASY_VSYNCPOL_POS       (0 << 13) +#define SOR_STATE_ASY_VSYNCPOL_NEG       (1 << 13) +#define SOR_STATE_ASY_DEPOL_POS          (0 << 14) +#define SOR_STATE_ASY_DEPOL_NEG          (1 << 14) + +#define HDMI_NV_PDISP_RG_HDCP_AN_MSB				0x04 +#define HDMI_NV_PDISP_RG_HDCP_AN_LSB				0x05 +#define HDMI_NV_PDISP_RG_HDCP_CN_MSB				0x06 +#define HDMI_NV_PDISP_RG_HDCP_CN_LSB				0x07 +#define HDMI_NV_PDISP_RG_HDCP_AKSV_MSB				0x08 +#define HDMI_NV_PDISP_RG_HDCP_AKSV_LSB				0x09 +#define HDMI_NV_PDISP_RG_HDCP_BKSV_MSB				0x0a +#define HDMI_NV_PDISP_RG_HDCP_BKSV_LSB				0x0b +#define HDMI_NV_PDISP_RG_HDCP_CKSV_MSB				0x0c +#define HDMI_NV_PDISP_RG_HDCP_CKSV_LSB				0x0d +#define HDMI_NV_PDISP_RG_HDCP_DKSV_MSB				0x0e +#define HDMI_NV_PDISP_RG_HDCP_DKSV_LSB				0x0f +#define HDMI_NV_PDISP_RG_HDCP_CTRL				0x10 +#define HDMI_NV_PDISP_RG_HDCP_CMODE				0x11 +#define HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB			0x12 +#define HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB			0x13 +#define HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB			0x14 +#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2			0x15 +#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1			0x16 +#define HDMI_NV_PDISP_RG_HDCP_RI				0x17 +#define HDMI_NV_PDISP_RG_HDCP_CS_MSB				0x18 +#define HDMI_NV_PDISP_RG_HDCP_CS_LSB				0x19 +#define HDMI_NV_PDISP_HDMI_AUDIO_EMU0				0x1a +#define HDMI_NV_PDISP_HDMI_AUDIO_EMU_RDATA0			0x1b +#define HDMI_NV_PDISP_HDMI_AUDIO_EMU1				0x1c +#define HDMI_NV_PDISP_HDMI_AUDIO_EMU2				0x1d + +#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL			0x1e +#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS		0x1f +#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER		0x20 +#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW		0x21 +#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH	0x22 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL			0x23 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS			0x24 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER			0x25 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW		0x26 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH		0x27 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW		0x28 +#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH		0x29 + +#define INFOFRAME_CTRL_ENABLE (1 << 0) + +#define INFOFRAME_HEADER_TYPE(x)    (((x) & 0xff) <<  0) +#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) <<  8) +#define INFOFRAME_HEADER_LEN(x)     (((x) & 0x0f) << 16) + +#define HDMI_NV_PDISP_HDMI_GENERIC_CTRL				0x2a +#define GENERIC_CTRL_ENABLE (1 <<  0) +#define GENERIC_CTRL_OTHER  (1 <<  4) +#define GENERIC_CTRL_SINGLE (1 <<  8) +#define GENERIC_CTRL_HBLANK (1 << 12) +#define GENERIC_CTRL_AUDIO  (1 << 16) + +#define HDMI_NV_PDISP_HDMI_GENERIC_STATUS			0x2b +#define HDMI_NV_PDISP_HDMI_GENERIC_HEADER			0x2c +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW			0x2d +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH		0x2e +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW			0x2f +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH		0x30 +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW			0x31 +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH		0x32 +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW			0x33 +#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH		0x34 + +#define HDMI_NV_PDISP_HDMI_ACR_CTRL				0x35 +#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW			0x36 +#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH		0x37 +#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW			0x38 +#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH		0x39 +#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW			0x3a +#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH		0x3b +#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW			0x3c +#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH		0x3d +#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW			0x3e +#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH		0x3f +#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW			0x40 +#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH		0x41 +#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW			0x42 +#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH		0x43 + +#define ACR_SUBPACK_CTS(x) (((x) & 0xffffff) << 8) +#define ACR_SUBPACK_N(x)   (((x) & 0xffffff) << 0) +#define ACR_ENABLE         (1 << 31) + +#define HDMI_NV_PDISP_HDMI_CTRL					0x44 +#define HDMI_CTRL_REKEY(x)         (((x) & 0x7f) <<  0) +#define HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) +#define HDMI_CTRL_ENABLE           (1 << 30) + +#define HDMI_NV_PDISP_HDMI_VSYNC_KEEPOUT			0x45 +#define HDMI_NV_PDISP_HDMI_VSYNC_WINDOW				0x46 +#define VSYNC_WINDOW_END(x)   (((x) & 0x3ff) <<  0) +#define VSYNC_WINDOW_START(x) (((x) & 0x3ff) << 16) +#define VSYNC_WINDOW_ENABLE   (1 << 31) + +#define HDMI_NV_PDISP_HDMI_GCP_CTRL				0x47 +#define HDMI_NV_PDISP_HDMI_GCP_STATUS				0x48 +#define HDMI_NV_PDISP_HDMI_GCP_SUBPACK				0x49 +#define HDMI_NV_PDISP_HDMI_CHANNEL_STATUS1			0x4a +#define HDMI_NV_PDISP_HDMI_CHANNEL_STATUS2			0x4b +#define HDMI_NV_PDISP_HDMI_EMU0					0x4c +#define HDMI_NV_PDISP_HDMI_EMU1					0x4d +#define HDMI_NV_PDISP_HDMI_EMU1_RDATA				0x4e + +#define HDMI_NV_PDISP_HDMI_SPARE				0x4f +#define SPARE_HW_CTS           (1 << 0) +#define SPARE_FORCE_SW_CTS     (1 << 1) +#define SPARE_CTS_RESET_VAL(x) (((x) & 0x7) << 16) + +#define HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS1			0x50 +#define HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS2			0x51 +#define HDMI_NV_PDISP_HDMI_HDCPRIF_ROM_CTRL			0x53 +#define HDMI_NV_PDISP_SOR_CAP					0x54 +#define HDMI_NV_PDISP_SOR_PWR					0x55 +#define SOR_PWR_NORMAL_STATE_PD     (0 <<  0) +#define SOR_PWR_NORMAL_STATE_PU     (1 <<  0) +#define SOR_PWR_NORMAL_START_NORMAL (0 <<  1) +#define SOR_PWR_NORMAL_START_ALT    (1 <<  1) +#define SOR_PWR_SAFE_STATE_PD       (0 << 16) +#define SOR_PWR_SAFE_STATE_PU       (1 << 16) +#define SOR_PWR_SETTING_NEW_DONE    (0 << 31) +#define SOR_PWR_SETTING_NEW_PENDING (1 << 31) +#define SOR_PWR_SETTING_NEW_TRIGGER (1 << 31) + +#define HDMI_NV_PDISP_SOR_TEST					0x56 +#define HDMI_NV_PDISP_SOR_PLL0					0x57 +#define SOR_PLL_PWR            (1 << 0) +#define SOR_PLL_PDBG           (1 << 1) +#define SOR_PLL_VCAPD          (1 << 2) +#define SOR_PLL_PDPORT         (1 << 3) +#define SOR_PLL_RESISTORSEL    (1 << 4) +#define SOR_PLL_PULLDOWN       (1 << 5) +#define SOR_PLL_VCOCAP(x)      (((x) & 0xf) <<  8) +#define SOR_PLL_BG_V17_S(x)    (((x) & 0xf) << 12) +#define SOR_PLL_FILTER(x)      (((x) & 0xf) << 16) +#define SOR_PLL_ICHPMP(x)      (((x) & 0xf) << 24) +#define SOR_PLL_TX_REG_LOAD(x) (((x) & 0xf) << 28) + +#define HDMI_NV_PDISP_SOR_PLL1					0x58 +#define SOR_PLL_TMDS_TERM_ENABLE (1 << 8) +#define SOR_PLL_TMDS_TERMADJ(x)  (((x) & 0xf) <<  9) +#define SOR_PLL_LOADADJ(x)       (((x) & 0xf) << 20) +#define SOR_PLL_PE_EN            (1 << 28) +#define SOR_PLL_HALF_FULL_PE     (1 << 29) +#define SOR_PLL_S_D_PIN_PE       (1 << 30) + +#define HDMI_NV_PDISP_SOR_PLL2					0x59 + +#define HDMI_NV_PDISP_SOR_CSTM					0x5a +#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24) + +#define HDMI_NV_PDISP_SOR_LVDS					0x5b +#define HDMI_NV_PDISP_SOR_CRCA					0x5c +#define HDMI_NV_PDISP_SOR_CRCB					0x5d +#define HDMI_NV_PDISP_SOR_BLANK					0x5e +#define HDMI_NV_PDISP_SOR_SEQ_CTL				0x5f +#define SOR_SEQ_CTL_PU_PC(x) (((x) & 0xf) <<  0) +#define SOR_SEQ_PU_PC_ALT(x) (((x) & 0xf) <<  4) +#define SOR_SEQ_PD_PC(x)     (((x) & 0xf) <<  8) +#define SOR_SEQ_PD_PC_ALT(x) (((x) & 0xf) << 12) +#define SOR_SEQ_PC(x)        (((x) & 0xf) << 16) +#define SOR_SEQ_STATUS       (1 << 28) +#define SOR_SEQ_SWITCH       (1 << 30) + +#define HDMI_NV_PDISP_SOR_SEQ_INST(x)				(0x60 + (x)) + +#define SOR_SEQ_INST_WAIT_TIME(x)     (((x) & 0x3ff) << 0) +#define SOR_SEQ_INST_WAIT_UNITS_VSYNC (2 << 12) +#define SOR_SEQ_INST_HALT             (1 << 15) +#define SOR_SEQ_INST_PIN_A_LOW        (0 << 21) +#define SOR_SEQ_INST_PIN_A_HIGH       (1 << 21) +#define SOR_SEQ_INST_PIN_B_LOW        (0 << 22) +#define SOR_SEQ_INST_PIN_B_HIGH       (1 << 22) +#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23) + +#define HDMI_NV_PDISP_SOR_VCRCA0				0x72 +#define HDMI_NV_PDISP_SOR_VCRCA1				0x73 +#define HDMI_NV_PDISP_SOR_CCRCA0				0x74 +#define HDMI_NV_PDISP_SOR_CCRCA1				0x75 +#define HDMI_NV_PDISP_SOR_EDATAA0				0x76 +#define HDMI_NV_PDISP_SOR_EDATAA1				0x77 +#define HDMI_NV_PDISP_SOR_COUNTA0				0x78 +#define HDMI_NV_PDISP_SOR_COUNTA1				0x79 +#define HDMI_NV_PDISP_SOR_DEBUGA0				0x7a +#define HDMI_NV_PDISP_SOR_DEBUGA1				0x7b +#define HDMI_NV_PDISP_SOR_TRIG					0x7c +#define HDMI_NV_PDISP_SOR_MSCHECK				0x7d + +#define HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT			0x7e +#define DRIVE_CURRENT_LANE0(x)      (((x) & 0x3f) <<  0) +#define DRIVE_CURRENT_LANE1(x)      (((x) & 0x3f) <<  8) +#define DRIVE_CURRENT_LANE2(x)      (((x) & 0x3f) << 16) +#define DRIVE_CURRENT_LANE3(x)      (((x) & 0x3f) << 24) +#define DRIVE_CURRENT_FUSE_OVERRIDE (1 << 31) + +#define DRIVE_CURRENT_1_500_mA  0x00 +#define DRIVE_CURRENT_1_875_mA  0x01 +#define DRIVE_CURRENT_2_250_mA  0x02 +#define DRIVE_CURRENT_2_625_mA  0x03 +#define DRIVE_CURRENT_3_000_mA  0x04 +#define DRIVE_CURRENT_3_375_mA  0x05 +#define DRIVE_CURRENT_3_750_mA  0x06 +#define DRIVE_CURRENT_4_125_mA  0x07 +#define DRIVE_CURRENT_4_500_mA  0x08 +#define DRIVE_CURRENT_4_875_mA  0x09 +#define DRIVE_CURRENT_5_250_mA  0x0a +#define DRIVE_CURRENT_5_625_mA  0x0b +#define DRIVE_CURRENT_6_000_mA  0x0c +#define DRIVE_CURRENT_6_375_mA  0x0d +#define DRIVE_CURRENT_6_750_mA  0x0e +#define DRIVE_CURRENT_7_125_mA  0x0f +#define DRIVE_CURRENT_7_500_mA  0x10 +#define DRIVE_CURRENT_7_875_mA  0x11 +#define DRIVE_CURRENT_8_250_mA  0x12 +#define DRIVE_CURRENT_8_625_mA  0x13 +#define DRIVE_CURRENT_9_000_mA  0x14 +#define DRIVE_CURRENT_9_375_mA  0x15 +#define DRIVE_CURRENT_9_750_mA  0x16 +#define DRIVE_CURRENT_10_125_mA 0x17 +#define DRIVE_CURRENT_10_500_mA 0x18 +#define DRIVE_CURRENT_10_875_mA 0x19 +#define DRIVE_CURRENT_11_250_mA 0x1a +#define DRIVE_CURRENT_11_625_mA 0x1b +#define DRIVE_CURRENT_12_000_mA 0x1c +#define DRIVE_CURRENT_12_375_mA 0x1d +#define DRIVE_CURRENT_12_750_mA 0x1e +#define DRIVE_CURRENT_13_125_mA 0x1f +#define DRIVE_CURRENT_13_500_mA 0x20 +#define DRIVE_CURRENT_13_875_mA 0x21 +#define DRIVE_CURRENT_14_250_mA 0x22 +#define DRIVE_CURRENT_14_625_mA 0x23 +#define DRIVE_CURRENT_15_000_mA 0x24 +#define DRIVE_CURRENT_15_375_mA 0x25 +#define DRIVE_CURRENT_15_750_mA 0x26 +#define DRIVE_CURRENT_16_125_mA 0x27 +#define DRIVE_CURRENT_16_500_mA 0x28 +#define DRIVE_CURRENT_16_875_mA 0x29 +#define DRIVE_CURRENT_17_250_mA 0x2a +#define DRIVE_CURRENT_17_625_mA 0x2b +#define DRIVE_CURRENT_18_000_mA 0x2c +#define DRIVE_CURRENT_18_375_mA 0x2d +#define DRIVE_CURRENT_18_750_mA 0x2e +#define DRIVE_CURRENT_19_125_mA 0x2f +#define DRIVE_CURRENT_19_500_mA 0x30 +#define DRIVE_CURRENT_19_875_mA 0x31 +#define DRIVE_CURRENT_20_250_mA 0x32 +#define DRIVE_CURRENT_20_625_mA 0x33 +#define DRIVE_CURRENT_21_000_mA 0x34 +#define DRIVE_CURRENT_21_375_mA 0x35 +#define DRIVE_CURRENT_21_750_mA 0x36 +#define DRIVE_CURRENT_22_125_mA 0x37 +#define DRIVE_CURRENT_22_500_mA 0x38 +#define DRIVE_CURRENT_22_875_mA 0x39 +#define DRIVE_CURRENT_23_250_mA 0x3a +#define DRIVE_CURRENT_23_625_mA 0x3b +#define DRIVE_CURRENT_24_000_mA 0x3c +#define DRIVE_CURRENT_24_375_mA 0x3d +#define DRIVE_CURRENT_24_750_mA 0x3e + +#define HDMI_NV_PDISP_AUDIO_DEBUG0				0x7f +#define HDMI_NV_PDISP_AUDIO_DEBUG1				0x80 +#define HDMI_NV_PDISP_AUDIO_DEBUG2				0x81 + +#define HDMI_NV_PDISP_AUDIO_FS(x)				(0x82 + (x)) +#define AUDIO_FS_LOW(x)  (((x) & 0xfff) <<  0) +#define AUDIO_FS_HIGH(x) (((x) & 0xfff) << 16) + +#define HDMI_NV_PDISP_AUDIO_PULSE_WIDTH				0x89 +#define HDMI_NV_PDISP_AUDIO_THRESHOLD				0x8a +#define HDMI_NV_PDISP_AUDIO_CNTRL0				0x8b +#define AUDIO_CNTRL0_ERROR_TOLERANCE(x)  (((x) & 0xff) << 0) +#define AUDIO_CNTRL0_SOURCE_SELECT_AUTO  (0 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_HDAL  (2 << 20) +#define AUDIO_CNTRL0_FRAMES_PER_BLOCK(x) (((x) & 0xff) << 24) + +#define HDMI_NV_PDISP_AUDIO_N					0x8c +#define AUDIO_N_VALUE(x)           (((x) & 0xfffff) << 0) +#define AUDIO_N_RESETF             (1 << 20) +#define AUDIO_N_GENERATE_NORMAL    (0 << 24) +#define AUDIO_N_GENERATE_ALTERNATE (1 << 24) + +#define HDMI_NV_PDISP_HDCPRIF_ROM_TIMING			0x94 +#define HDMI_NV_PDISP_SOR_REFCLK				0x95 +#define SOR_REFCLK_DIV_INT(x)  (((x) & 0xff) << 8) +#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x03) << 6) + +#define HDMI_NV_PDISP_CRC_CONTROL				0x96 +#define HDMI_NV_PDISP_INPUT_CONTROL				0x97 +#define HDMI_SRC_DISPLAYA       (0 << 0) +#define HDMI_SRC_DISPLAYB       (1 << 0) +#define ARM_VIDEO_RANGE_FULL    (0 << 1) +#define ARM_VIDEO_RANGE_LIMITED (1 << 1) + +#define HDMI_NV_PDISP_SCRATCH					0x98 +#define HDMI_NV_PDISP_PE_CURRENT				0x99 +#define PE_CURRENT0(x) (((x) & 0xf) << 0) +#define PE_CURRENT1(x) (((x) & 0xf) << 8) +#define PE_CURRENT2(x) (((x) & 0xf) << 16) +#define PE_CURRENT3(x) (((x) & 0xf) << 24) + +#define PE_CURRENT_0_0_mA 0x0 +#define PE_CURRENT_0_5_mA 0x1 +#define PE_CURRENT_1_0_mA 0x2 +#define PE_CURRENT_1_5_mA 0x3 +#define PE_CURRENT_2_0_mA 0x4 +#define PE_CURRENT_2_5_mA 0x5 +#define PE_CURRENT_3_0_mA 0x6 +#define PE_CURRENT_3_5_mA 0x7 +#define PE_CURRENT_4_0_mA 0x8 +#define PE_CURRENT_4_5_mA 0x9 +#define PE_CURRENT_5_0_mA 0xa +#define PE_CURRENT_5_5_mA 0xb +#define PE_CURRENT_6_0_mA 0xc +#define PE_CURRENT_6_5_mA 0xd +#define PE_CURRENT_7_0_mA 0xe +#define PE_CURRENT_7_5_mA 0xf + +#define HDMI_NV_PDISP_KEY_CTRL					0x9a +#define HDMI_NV_PDISP_KEY_DEBUG0				0x9b +#define HDMI_NV_PDISP_KEY_DEBUG1				0x9c +#define HDMI_NV_PDISP_KEY_DEBUG2				0x9d +#define HDMI_NV_PDISP_KEY_HDCP_KEY_0				0x9e +#define HDMI_NV_PDISP_KEY_HDCP_KEY_1				0x9f +#define HDMI_NV_PDISP_KEY_HDCP_KEY_2				0xa0 +#define HDMI_NV_PDISP_KEY_HDCP_KEY_3				0xa1 +#define HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG				0xa2 +#define HDMI_NV_PDISP_KEY_SKEY_INDEX				0xa3 + +#define HDMI_NV_PDISP_SOR_AUDIO_CNTRL0				0xac +#define AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29) +#define HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR			0xbc +#define HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE			0xbd + +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320    0xbf +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441    0xc0 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882    0xc1 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764    0xc2 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480    0xc3 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960    0xc4 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920    0xc5 +#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_DEFAULT 0xc5 + +#endif /* TEGRA_HDMI_H */ diff --git a/drivers/gpu/host1x/drm/output.c b/drivers/gpu/host1x/drm/output.c new file mode 100644 index 00000000000..8140fc6c34d --- /dev/null +++ b/drivers/gpu/host1x/drm/output.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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/module.h> +#include <linux/of_gpio.h> +#include <linux/of_i2c.h> + +#include "drm.h" + +static int tegra_connector_get_modes(struct drm_connector *connector) +{ +	struct tegra_output *output = connector_to_output(connector); +	struct edid *edid = NULL; +	int err = 0; + +	if (output->edid) +		edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL); +	else if (output->ddc) +		edid = drm_get_edid(connector, output->ddc); + +	drm_mode_connector_update_edid_property(connector, edid); + +	if (edid) { +		err = drm_add_edid_modes(connector, edid); +		kfree(edid); +	} + +	return err; +} + +static int tegra_connector_mode_valid(struct drm_connector *connector, +				      struct drm_display_mode *mode) +{ +	struct tegra_output *output = connector_to_output(connector); +	enum drm_mode_status status = MODE_OK; +	int err; + +	err = tegra_output_check_mode(output, mode, &status); +	if (err < 0) +		return MODE_ERROR; + +	return status; +} + +static struct drm_encoder * +tegra_connector_best_encoder(struct drm_connector *connector) +{ +	struct tegra_output *output = connector_to_output(connector); + +	return &output->encoder; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { +	.get_modes = tegra_connector_get_modes, +	.mode_valid = tegra_connector_mode_valid, +	.best_encoder = tegra_connector_best_encoder, +}; + +static enum drm_connector_status +tegra_connector_detect(struct drm_connector *connector, bool force) +{ +	struct tegra_output *output = connector_to_output(connector); +	enum drm_connector_status status = connector_status_unknown; + +	if (gpio_is_valid(output->hpd_gpio)) { +		if (gpio_get_value(output->hpd_gpio) == 0) +			status = connector_status_disconnected; +		else +			status = connector_status_connected; +	} else { +		if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) +			status = connector_status_connected; +	} + +	return status; +} + +static void tegra_connector_destroy(struct drm_connector *connector) +{ +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.detect = tegra_connector_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.destroy = tegra_connector_destroy, +}; + +static void tegra_encoder_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs encoder_funcs = { +	.destroy = tegra_encoder_destroy, +}; + +static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder, +				     const struct drm_display_mode *mode, +				     struct drm_display_mode *adjusted) +{ +	return true; +} + +static void tegra_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void tegra_encoder_commit(struct drm_encoder *encoder) +{ +} + +static void tegra_encoder_mode_set(struct drm_encoder *encoder, +				   struct drm_display_mode *mode, +				   struct drm_display_mode *adjusted) +{ +	struct tegra_output *output = encoder_to_output(encoder); +	int err; + +	err = tegra_output_enable(output); +	if (err < 0) +		dev_err(encoder->dev->dev, "tegra_output_enable(): %d\n", err); +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { +	.dpms = tegra_encoder_dpms, +	.mode_fixup = tegra_encoder_mode_fixup, +	.prepare = tegra_encoder_prepare, +	.commit = tegra_encoder_commit, +	.mode_set = tegra_encoder_mode_set, +}; + +static irqreturn_t hpd_irq(int irq, void *data) +{ +	struct tegra_output *output = data; + +	drm_helper_hpd_irq_event(output->connector.dev); + +	return IRQ_HANDLED; +} + +int tegra_output_parse_dt(struct tegra_output *output) +{ +	enum of_gpio_flags flags; +	struct device_node *ddc; +	size_t size; +	int err; + +	if (!output->of_node) +		output->of_node = output->dev->of_node; + +	output->edid = of_get_property(output->of_node, "nvidia,edid", &size); + +	ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); +	if (ddc) { +		output->ddc = of_find_i2c_adapter_by_node(ddc); +		if (!output->ddc) { +			err = -EPROBE_DEFER; +			of_node_put(ddc); +			return err; +		} + +		of_node_put(ddc); +	} + +	if (!output->edid && !output->ddc) +		return -ENODEV; + +	output->hpd_gpio = of_get_named_gpio_flags(output->of_node, +						   "nvidia,hpd-gpio", 0, +						   &flags); + +	return 0; +} + +int tegra_output_init(struct drm_device *drm, struct tegra_output *output) +{ +	int connector, encoder, err; + +	if (gpio_is_valid(output->hpd_gpio)) { +		unsigned long flags; + +		err = gpio_request_one(output->hpd_gpio, GPIOF_DIR_IN, +				       "HDMI hotplug detect"); +		if (err < 0) { +			dev_err(output->dev, "gpio_request_one(): %d\n", err); +			return err; +		} + +		err = gpio_to_irq(output->hpd_gpio); +		if (err < 0) { +			dev_err(output->dev, "gpio_to_irq(): %d\n", err); +			goto free_hpd; +		} + +		output->hpd_irq = err; + +		flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | +			IRQF_ONESHOT; + +		err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq, +					   flags, "hpd", output); +		if (err < 0) { +			dev_err(output->dev, "failed to request IRQ#%u: %d\n", +				output->hpd_irq, err); +			goto free_hpd; +		} + +		output->connector.polled = DRM_CONNECTOR_POLL_HPD; +	} + +	switch (output->type) { +	case TEGRA_OUTPUT_RGB: +		connector = DRM_MODE_CONNECTOR_LVDS; +		encoder = DRM_MODE_ENCODER_LVDS; +		break; + +	case TEGRA_OUTPUT_HDMI: +		connector = DRM_MODE_CONNECTOR_HDMIA; +		encoder = DRM_MODE_ENCODER_TMDS; +		break; + +	default: +		connector = DRM_MODE_CONNECTOR_Unknown; +		encoder = DRM_MODE_ENCODER_NONE; +		break; +	} + +	drm_connector_init(drm, &output->connector, &connector_funcs, +			   connector); +	drm_connector_helper_add(&output->connector, &connector_helper_funcs); + +	drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder); +	drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs); + +	drm_mode_connector_attach_encoder(&output->connector, &output->encoder); +	drm_sysfs_connector_add(&output->connector); + +	output->encoder.possible_crtcs = 0x3; + +	return 0; + +free_hpd: +	gpio_free(output->hpd_gpio); + +	return err; +} + +int tegra_output_exit(struct tegra_output *output) +{ +	if (gpio_is_valid(output->hpd_gpio)) { +		free_irq(output->hpd_irq, output); +		gpio_free(output->hpd_gpio); +	} + +	if (output->ddc) +		put_device(&output->ddc->dev); + +	return 0; +} diff --git a/drivers/gpu/host1x/drm/rgb.c b/drivers/gpu/host1x/drm/rgb.c new file mode 100644 index 00000000000..ed4416f2026 --- /dev/null +++ b/drivers/gpu/host1x/drm/rgb.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "drm.h" +#include "dc.h" + +struct tegra_rgb { +	struct tegra_output output; +	struct clk *clk_parent; +	struct clk *clk; +}; + +static inline struct tegra_rgb *to_rgb(struct tegra_output *output) +{ +	return container_of(output, struct tegra_rgb, output); +} + +struct reg_entry { +	unsigned long offset; +	unsigned long value; +}; + +static const struct reg_entry rgb_enable[] = { +	{ DC_COM_PIN_OUTPUT_ENABLE(0),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(1),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(2),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(3),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(1), 0x01000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_DATA(0),     0x00000000 }, +	{ DC_COM_PIN_OUTPUT_DATA(1),     0x00000000 }, +	{ DC_COM_PIN_OUTPUT_DATA(2),     0x00000000 }, +	{ DC_COM_PIN_OUTPUT_DATA(3),     0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(0),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(1),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(2),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(3),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(4),   0x00210222 }, +	{ DC_COM_PIN_OUTPUT_SELECT(5),   0x00002200 }, +	{ DC_COM_PIN_OUTPUT_SELECT(6),   0x00020000 }, +}; + +static const struct reg_entry rgb_disable[] = { +	{ DC_COM_PIN_OUTPUT_SELECT(6),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(5),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(4),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(3),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(2),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(1),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_SELECT(0),   0x00000000 }, +	{ DC_COM_PIN_OUTPUT_DATA(3),     0xaaaaaaaa }, +	{ DC_COM_PIN_OUTPUT_DATA(2),     0xaaaaaaaa }, +	{ DC_COM_PIN_OUTPUT_DATA(1),     0xaaaaaaaa }, +	{ DC_COM_PIN_OUTPUT_DATA(0),     0xaaaaaaaa }, +	{ DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(1), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(3),   0x55555555 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(2),   0x55555555 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(1),   0x55150005 }, +	{ DC_COM_PIN_OUTPUT_ENABLE(0),   0x55555555 }, +}; + +static void tegra_dc_write_regs(struct tegra_dc *dc, +				const struct reg_entry *table, +				unsigned int num) +{ +	unsigned int i; + +	for (i = 0; i < num; i++) +		tegra_dc_writel(dc, table[i].value, table[i].offset); +} + +static int tegra_output_rgb_enable(struct tegra_output *output) +{ +	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + +	tegra_dc_write_regs(dc, rgb_enable, ARRAY_SIZE(rgb_enable)); + +	return 0; +} + +static int tegra_output_rgb_disable(struct tegra_output *output) +{ +	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + +	tegra_dc_write_regs(dc, rgb_disable, ARRAY_SIZE(rgb_disable)); + +	return 0; +} + +static int tegra_output_rgb_setup_clock(struct tegra_output *output, +					struct clk *clk, unsigned long pclk) +{ +	struct tegra_rgb *rgb = to_rgb(output); + +	return clk_set_parent(clk, rgb->clk_parent); +} + +static int tegra_output_rgb_check_mode(struct tegra_output *output, +				       struct drm_display_mode *mode, +				       enum drm_mode_status *status) +{ +	/* +	 * FIXME: For now, always assume that the mode is okay. There are +	 * unresolved issues with clk_round_rate(), which doesn't always +	 * reliably report whether a frequency can be set or not. +	 */ + +	*status = MODE_OK; + +	return 0; +} + +static const struct tegra_output_ops rgb_ops = { +	.enable = tegra_output_rgb_enable, +	.disable = tegra_output_rgb_disable, +	.setup_clock = tegra_output_rgb_setup_clock, +	.check_mode = tegra_output_rgb_check_mode, +}; + +int tegra_dc_rgb_probe(struct tegra_dc *dc) +{ +	struct device_node *np; +	struct tegra_rgb *rgb; +	int err; + +	np = of_get_child_by_name(dc->dev->of_node, "rgb"); +	if (!np || !of_device_is_available(np)) +		return -ENODEV; + +	rgb = devm_kzalloc(dc->dev, sizeof(*rgb), GFP_KERNEL); +	if (!rgb) +		return -ENOMEM; + +	rgb->clk = devm_clk_get(dc->dev, NULL); +	if (IS_ERR(rgb->clk)) { +		dev_err(dc->dev, "failed to get clock\n"); +		return PTR_ERR(rgb->clk); +	} + +	rgb->clk_parent = devm_clk_get(dc->dev, "parent"); +	if (IS_ERR(rgb->clk_parent)) { +		dev_err(dc->dev, "failed to get parent clock\n"); +		return PTR_ERR(rgb->clk_parent); +	} + +	err = clk_set_parent(rgb->clk, rgb->clk_parent); +	if (err < 0) { +		dev_err(dc->dev, "failed to set parent clock: %d\n", err); +		return err; +	} + +	rgb->output.dev = dc->dev; +	rgb->output.of_node = np; + +	err = tegra_output_parse_dt(&rgb->output); +	if (err < 0) +		return err; + +	dc->rgb = &rgb->output; + +	return 0; +} + +int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) +{ +	struct tegra_rgb *rgb = to_rgb(dc->rgb); +	int err; + +	if (!dc->rgb) +		return -ENODEV; + +	rgb->output.type = TEGRA_OUTPUT_RGB; +	rgb->output.ops = &rgb_ops; + +	err = tegra_output_init(dc->base.dev, &rgb->output); +	if (err < 0) { +		dev_err(dc->dev, "output setup failed: %d\n", err); +		return err; +	} + +	/* +	 * By default, outputs can be associated with each display controller. +	 * RGB outputs are an exception, so we make sure they can be attached +	 * to only their parent display controller. +	 */ +	rgb->output.encoder.possible_crtcs = 1 << dc->pipe; + +	return 0; +} + +int tegra_dc_rgb_exit(struct tegra_dc *dc) +{ +	if (dc->rgb) { +		int err; + +		err = tegra_output_disable(dc->rgb); +		if (err < 0) { +			dev_err(dc->dev, "output failed to disable: %d\n", err); +			return err; +		} + +		err = tegra_output_exit(dc->rgb); +		if (err < 0) { +			dev_err(dc->dev, "output cleanup failed: %d\n", err); +			return err; +		} + +		dc->rgb = NULL; +	} + +	return 0; +} diff --git a/drivers/gpu/host1x/host1x.h b/drivers/gpu/host1x/host1x.h new file mode 100644 index 00000000000..a2bc1e65e97 --- /dev/null +++ b/drivers/gpu/host1x/host1x.h @@ -0,0 +1,30 @@ +/* + * Tegra host1x driver + * + * Copyright (c) 2009-2013, NVIDIA Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + */ + +#ifndef __LINUX_HOST1X_H +#define __LINUX_HOST1X_H + +enum host1x_class { +	HOST1X_CLASS_HOST1X	= 0x1, +	HOST1X_CLASS_GR2D	= 0x51, +	HOST1X_CLASS_GR2D_SB    = 0x52 +}; + +#endif diff --git a/drivers/gpu/host1x/host1x_bo.h b/drivers/gpu/host1x/host1x_bo.h new file mode 100644 index 00000000000..4c1f10bd773 --- /dev/null +++ b/drivers/gpu/host1x/host1x_bo.h @@ -0,0 +1,87 @@ +/* + * Tegra host1x Memory Management Abstraction header + * + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _HOST1X_BO_H +#define _HOST1X_BO_H + +struct host1x_bo; + +struct host1x_bo_ops { +	struct host1x_bo *(*get)(struct host1x_bo *bo); +	void (*put)(struct host1x_bo *bo); +	dma_addr_t (*pin)(struct host1x_bo *bo, struct sg_table **sgt); +	void (*unpin)(struct host1x_bo *bo, struct sg_table *sgt); +	void *(*mmap)(struct host1x_bo *bo); +	void (*munmap)(struct host1x_bo *bo, void *addr); +	void *(*kmap)(struct host1x_bo *bo, unsigned int pagenum); +	void (*kunmap)(struct host1x_bo *bo, unsigned int pagenum, void *addr); +}; + +struct host1x_bo { +	const struct host1x_bo_ops *ops; +}; + +static inline void host1x_bo_init(struct host1x_bo *bo, +				  const struct host1x_bo_ops *ops) +{ +	bo->ops = ops; +} + +static inline struct host1x_bo *host1x_bo_get(struct host1x_bo *bo) +{ +	return bo->ops->get(bo); +} + +static inline void host1x_bo_put(struct host1x_bo *bo) +{ +	bo->ops->put(bo); +} + +static inline dma_addr_t host1x_bo_pin(struct host1x_bo *bo, +				       struct sg_table **sgt) +{ +	return bo->ops->pin(bo, sgt); +} + +static inline void host1x_bo_unpin(struct host1x_bo *bo, struct sg_table *sgt) +{ +	bo->ops->unpin(bo, sgt); +} + +static inline void *host1x_bo_mmap(struct host1x_bo *bo) +{ +	return bo->ops->mmap(bo); +} + +static inline void host1x_bo_munmap(struct host1x_bo *bo, void *addr) +{ +	bo->ops->munmap(bo, addr); +} + +static inline void *host1x_bo_kmap(struct host1x_bo *bo, unsigned int pagenum) +{ +	return bo->ops->kmap(bo, pagenum); +} + +static inline void host1x_bo_kunmap(struct host1x_bo *bo, +				    unsigned int pagenum, void *addr) +{ +	bo->ops->kunmap(bo, pagenum, addr); +} + +#endif diff --git a/drivers/gpu/host1x/host1x_client.h b/drivers/gpu/host1x/host1x_client.h new file mode 100644 index 00000000000..9b85f10f4a4 --- /dev/null +++ b/drivers/gpu/host1x/host1x_client.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 HOST1X_CLIENT_H +#define HOST1X_CLIENT_H + +struct device; +struct platform_device; + +#ifdef CONFIG_DRM_TEGRA +int host1x_drm_alloc(struct platform_device *pdev); +#else +static inline int host1x_drm_alloc(struct platform_device *pdev) +{ +	return 0; +} +#endif + +void host1x_set_drm_data(struct device *dev, void *data); +void *host1x_get_drm_data(struct device *dev); + +#endif diff --git a/drivers/gpu/host1x/hw/Makefile b/drivers/gpu/host1x/hw/Makefile new file mode 100644 index 00000000000..9b50863a223 --- /dev/null +++ b/drivers/gpu/host1x/hw/Makefile @@ -0,0 +1,6 @@ +ccflags-y = -Idrivers/gpu/host1x + +host1x-hw-objs  = \ +	host1x01.o + +obj-$(CONFIG_TEGRA_HOST1X) += host1x-hw.o diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c new file mode 100644 index 00000000000..590b69d91da --- /dev/null +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -0,0 +1,326 @@ +/* + * Tegra host1x Command DMA + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/slab.h> +#include <linux/scatterlist.h> +#include <linux/dma-mapping.h> + +#include "cdma.h" +#include "channel.h" +#include "dev.h" +#include "debug.h" + +/* + * Put the restart at the end of pushbuffer memor + */ +static void push_buffer_init(struct push_buffer *pb) +{ +	*(pb->mapped + (pb->size_bytes >> 2)) = host1x_opcode_restart(0); +} + +/* + * Increment timedout buffer's syncpt via CPU. + */ +static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr, +				u32 syncpt_incrs, u32 syncval, u32 nr_slots) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); +	struct push_buffer *pb = &cdma->push_buffer; +	u32 i; + +	for (i = 0; i < syncpt_incrs; i++) +		host1x_syncpt_cpu_incr(cdma->timeout.syncpt); + +	/* after CPU incr, ensure shadow is up to date */ +	host1x_syncpt_load(cdma->timeout.syncpt); + +	/* NOP all the PB slots */ +	while (nr_slots--) { +		u32 *p = (u32 *)((u32)pb->mapped + getptr); +		*(p++) = HOST1X_OPCODE_NOP; +		*(p++) = HOST1X_OPCODE_NOP; +		dev_dbg(host1x->dev, "%s: NOP at 0x%x\n", __func__, +			pb->phys + getptr); +		getptr = (getptr + 8) & (pb->size_bytes - 1); +	} +	wmb(); +} + +/* + * Start channel DMA + */ +static void cdma_start(struct host1x_cdma *cdma) +{ +	struct host1x_channel *ch = cdma_to_channel(cdma); + +	if (cdma->running) +		return; + +	cdma->last_pos = cdma->push_buffer.pos; + +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, +			 HOST1X_CHANNEL_DMACTRL); + +	/* set base, put and end pointer */ +	host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART); +	host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT); +	host1x_ch_writel(ch, cdma->push_buffer.phys + +			 cdma->push_buffer.size_bytes + 4, +			 HOST1X_CHANNEL_DMAEND); + +	/* reset GET */ +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP | +			 HOST1X_CHANNEL_DMACTRL_DMAGETRST | +			 HOST1X_CHANNEL_DMACTRL_DMAINITGET, +			 HOST1X_CHANNEL_DMACTRL); + +	/* start the command DMA */ +	host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL); + +	cdma->running = true; +} + +/* + * Similar to cdma_start(), but rather than starting from an idle + * state (where DMA GET is set to DMA PUT), on a timeout we restore + * DMA GET from an explicit value (so DMA may again be pending). + */ +static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); +	struct host1x_channel *ch = cdma_to_channel(cdma); + +	if (cdma->running) +		return; + +	cdma->last_pos = cdma->push_buffer.pos; + +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, +			 HOST1X_CHANNEL_DMACTRL); + +	/* set base, end pointer (all of memory) */ +	host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART); +	host1x_ch_writel(ch, cdma->push_buffer.phys + +			 cdma->push_buffer.size_bytes, +			 HOST1X_CHANNEL_DMAEND); + +	/* set GET, by loading the value in PUT (then reset GET) */ +	host1x_ch_writel(ch, getptr, HOST1X_CHANNEL_DMAPUT); +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP | +			 HOST1X_CHANNEL_DMACTRL_DMAGETRST | +			 HOST1X_CHANNEL_DMACTRL_DMAINITGET, +			 HOST1X_CHANNEL_DMACTRL); + +	dev_dbg(host1x->dev, +		"%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n", __func__, +		host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET), +		host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT), +		cdma->last_pos); + +	/* deassert GET reset and set PUT */ +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, +			 HOST1X_CHANNEL_DMACTRL); +	host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT); + +	/* start the command DMA */ +	host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL); + +	cdma->running = true; +} + +/* + * Kick channel DMA into action by writing its PUT offset (if it has changed) + */ +static void cdma_flush(struct host1x_cdma *cdma) +{ +	struct host1x_channel *ch = cdma_to_channel(cdma); + +	if (cdma->push_buffer.pos != cdma->last_pos) { +		host1x_ch_writel(ch, cdma->push_buffer.pos, +				 HOST1X_CHANNEL_DMAPUT); +		cdma->last_pos = cdma->push_buffer.pos; +	} +} + +static void cdma_stop(struct host1x_cdma *cdma) +{ +	struct host1x_channel *ch = cdma_to_channel(cdma); + +	mutex_lock(&cdma->lock); +	if (cdma->running) { +		host1x_cdma_wait_locked(cdma, CDMA_EVENT_SYNC_QUEUE_EMPTY); +		host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, +				 HOST1X_CHANNEL_DMACTRL); +		cdma->running = false; +	} +	mutex_unlock(&cdma->lock); +} + +/* + * Stops both channel's command processor and CDMA immediately. + * Also, tears down the channel and resets corresponding module. + */ +static void cdma_freeze(struct host1x_cdma *cdma) +{ +	struct host1x *host = cdma_to_host1x(cdma); +	struct host1x_channel *ch = cdma_to_channel(cdma); +	u32 cmdproc_stop; + +	if (cdma->torndown && !cdma->running) { +		dev_warn(host->dev, "Already torn down\n"); +		return; +	} + +	dev_dbg(host->dev, "freezing channel (id %d)\n", ch->id); + +	cmdproc_stop = host1x_sync_readl(host, HOST1X_SYNC_CMDPROC_STOP); +	cmdproc_stop |= BIT(ch->id); +	host1x_sync_writel(host, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP); + +	dev_dbg(host->dev, "%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n", +		__func__, host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET), +		host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT), +		cdma->last_pos); + +	host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, +			 HOST1X_CHANNEL_DMACTRL); + +	host1x_sync_writel(host, BIT(ch->id), HOST1X_SYNC_CH_TEARDOWN); + +	cdma->running = false; +	cdma->torndown = true; +} + +static void cdma_resume(struct host1x_cdma *cdma, u32 getptr) +{ +	struct host1x *host1x = cdma_to_host1x(cdma); +	struct host1x_channel *ch = cdma_to_channel(cdma); +	u32 cmdproc_stop; + +	dev_dbg(host1x->dev, +		"resuming channel (id %d, DMAGET restart = 0x%x)\n", +		ch->id, getptr); + +	cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP); +	cmdproc_stop &= ~(BIT(ch->id)); +	host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP); + +	cdma->torndown = false; +	cdma_timeout_restart(cdma, getptr); +} + +/* + * If this timeout fires, it indicates the current sync_queue entry has + * exceeded its TTL and the userctx should be timed out and remaining + * submits already issued cleaned up (future submits return an error). + */ +static void cdma_timeout_handler(struct work_struct *work) +{ +	struct host1x_cdma *cdma; +	struct host1x *host1x; +	struct host1x_channel *ch; + +	u32 syncpt_val; + +	u32 prev_cmdproc, cmdproc_stop; + +	cdma = container_of(to_delayed_work(work), struct host1x_cdma, +			    timeout.wq); +	host1x = cdma_to_host1x(cdma); +	ch = cdma_to_channel(cdma); + +	host1x_debug_dump(cdma_to_host1x(cdma)); + +	mutex_lock(&cdma->lock); + +	if (!cdma->timeout.client) { +		dev_dbg(host1x->dev, +			"cdma_timeout: expired, but has no clientid\n"); +		mutex_unlock(&cdma->lock); +		return; +	} + +	/* stop processing to get a clean snapshot */ +	prev_cmdproc = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP); +	cmdproc_stop = prev_cmdproc | BIT(ch->id); +	host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP); + +	dev_dbg(host1x->dev, "cdma_timeout: cmdproc was 0x%x is 0x%x\n", +		prev_cmdproc, cmdproc_stop); + +	syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt); + +	/* has buffer actually completed? */ +	if ((s32)(syncpt_val - cdma->timeout.syncpt_val) >= 0) { +		dev_dbg(host1x->dev, +			"cdma_timeout: expired, but buffer had completed\n"); +		/* restore */ +		cmdproc_stop = prev_cmdproc & ~(BIT(ch->id)); +		host1x_sync_writel(host1x, cmdproc_stop, +				   HOST1X_SYNC_CMDPROC_STOP); +		mutex_unlock(&cdma->lock); +		return; +	} + +	dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n", +		__func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name, +		syncpt_val, cdma->timeout.syncpt_val); + +	/* stop HW, resetting channel/module */ +	host1x_hw_cdma_freeze(host1x, cdma); + +	host1x_cdma_update_sync_queue(cdma, ch->dev); +	mutex_unlock(&cdma->lock); +} + +/* + * Init timeout resources + */ +static int cdma_timeout_init(struct host1x_cdma *cdma, u32 syncpt_id) +{ +	INIT_DELAYED_WORK(&cdma->timeout.wq, cdma_timeout_handler); +	cdma->timeout.initialized = true; + +	return 0; +} + +/* + * Clean up timeout resources + */ +static void cdma_timeout_destroy(struct host1x_cdma *cdma) +{ +	if (cdma->timeout.initialized) +		cancel_delayed_work(&cdma->timeout.wq); +	cdma->timeout.initialized = false; +} + +static const struct host1x_cdma_ops host1x_cdma_ops = { +	.start = cdma_start, +	.stop = cdma_stop, +	.flush = cdma_flush, + +	.timeout_init = cdma_timeout_init, +	.timeout_destroy = cdma_timeout_destroy, +	.freeze = cdma_freeze, +	.resume = cdma_resume, +	.timeout_cpu_incr = cdma_timeout_cpu_incr, +}; + +static const struct host1x_pushbuffer_ops host1x_pushbuffer_ops = { +	.init = push_buffer_init, +}; diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c new file mode 100644 index 00000000000..ee199623e36 --- /dev/null +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -0,0 +1,168 @@ +/* + * Tegra host1x Channel + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/slab.h> +#include <trace/events/host1x.h> + +#include "host1x.h" +#include "host1x_bo.h" +#include "channel.h" +#include "dev.h" +#include "intr.h" +#include "job.h" + +#define HOST1X_CHANNEL_SIZE 16384 +#define TRACE_MAX_LENGTH 128U + +static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, +			       u32 offset, u32 words) +{ +	void *mem = NULL; + +	if (host1x_debug_trace_cmdbuf) +		mem = host1x_bo_mmap(bo); + +	if (mem) { +		u32 i; +		/* +		 * Write in batches of 128 as there seems to be a limit +		 * of how much you can output to ftrace at once. +		 */ +		for (i = 0; i < words; i += TRACE_MAX_LENGTH) { +			trace_host1x_cdma_push_gather( +				dev_name(cdma_to_channel(cdma)->dev), +				(u32)bo, min(words - i, TRACE_MAX_LENGTH), +				offset + i * sizeof(u32), mem); +		} +		host1x_bo_munmap(bo, mem); +	} +} + +static void submit_gathers(struct host1x_job *job) +{ +	struct host1x_cdma *cdma = &job->channel->cdma; +	unsigned int i; + +	for (i = 0; i < job->num_gathers; i++) { +		struct host1x_job_gather *g = &job->gathers[i]; +		u32 op1 = host1x_opcode_gather(g->words); +		u32 op2 = g->base + g->offset; +		trace_write_gather(cdma, g->bo, g->offset, op1 & 0xffff); +		host1x_cdma_push(cdma, op1, op2); +	} +} + +static int channel_submit(struct host1x_job *job) +{ +	struct host1x_channel *ch = job->channel; +	struct host1x_syncpt *sp; +	u32 user_syncpt_incrs = job->syncpt_incrs; +	u32 prev_max = 0; +	u32 syncval; +	int err; +	struct host1x_waitlist *completed_waiter = NULL; +	struct host1x *host = dev_get_drvdata(ch->dev->parent); + +	sp = host->syncpt + job->syncpt_id; +	trace_host1x_channel_submit(dev_name(ch->dev), +				    job->num_gathers, job->num_relocs, +				    job->num_waitchk, job->syncpt_id, +				    job->syncpt_incrs); + +	/* before error checks, return current max */ +	prev_max = job->syncpt_end = host1x_syncpt_read_max(sp); + +	/* get submit lock */ +	err = mutex_lock_interruptible(&ch->submitlock); +	if (err) +		goto error; + +	completed_waiter = kzalloc(sizeof(*completed_waiter), GFP_KERNEL); +	if (!completed_waiter) { +		mutex_unlock(&ch->submitlock); +		err = -ENOMEM; +		goto error; +	} + +	/* begin a CDMA submit */ +	err = host1x_cdma_begin(&ch->cdma, job); +	if (err) { +		mutex_unlock(&ch->submitlock); +		goto error; +	} + +	if (job->serialize) { +		/* +		 * Force serialization by inserting a host wait for the +		 * previous job to finish before this one can commence. +		 */ +		host1x_cdma_push(&ch->cdma, +				 host1x_opcode_setclass(HOST1X_CLASS_HOST1X, +					host1x_uclass_wait_syncpt_r(), 1), +				 host1x_class_host_wait_syncpt(job->syncpt_id, +					host1x_syncpt_read_max(sp))); +	} + +	syncval = host1x_syncpt_incr_max(sp, user_syncpt_incrs); + +	job->syncpt_end = syncval; + +	/* add a setclass for modules that require it */ +	if (job->class) +		host1x_cdma_push(&ch->cdma, +				 host1x_opcode_setclass(job->class, 0, 0), +				 HOST1X_OPCODE_NOP); + +	submit_gathers(job); + +	/* end CDMA submit & stash pinned hMems into sync queue */ +	host1x_cdma_end(&ch->cdma, job); + +	trace_host1x_channel_submitted(dev_name(ch->dev), prev_max, syncval); + +	/* schedule a submit complete interrupt */ +	err = host1x_intr_add_action(host, job->syncpt_id, syncval, +				     HOST1X_INTR_ACTION_SUBMIT_COMPLETE, ch, +				     completed_waiter, NULL); +	completed_waiter = NULL; +	WARN(err, "Failed to set submit complete interrupt"); + +	mutex_unlock(&ch->submitlock); + +	return 0; + +error: +	kfree(completed_waiter); +	return err; +} + +static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev, +			       unsigned int index) +{ +	ch->id = index; +	mutex_init(&ch->reflock); +	mutex_init(&ch->submitlock); + +	ch->regs = dev->regs + index * HOST1X_CHANNEL_SIZE; +	return 0; +} + +static const struct host1x_channel_ops host1x_channel_ops = { +	.init = host1x_channel_init, +	.submit = channel_submit, +}; diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c new file mode 100644 index 00000000000..334c038052f --- /dev/null +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Author: Erik Gilling <konkers@android.com> + * + * Copyright (C) 2011-2013 NVIDIA Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mm.h> +#include <linux/scatterlist.h> + +#include <linux/io.h> + +#include "dev.h" +#include "debug.h" +#include "cdma.h" +#include "channel.h" +#include "host1x_bo.h" + +#define HOST1X_DEBUG_MAX_PAGE_OFFSET 102400 + +enum { +	HOST1X_OPCODE_SETCLASS	= 0x00, +	HOST1X_OPCODE_INCR	= 0x01, +	HOST1X_OPCODE_NONINCR	= 0x02, +	HOST1X_OPCODE_MASK	= 0x03, +	HOST1X_OPCODE_IMM	= 0x04, +	HOST1X_OPCODE_RESTART	= 0x05, +	HOST1X_OPCODE_GATHER	= 0x06, +	HOST1X_OPCODE_EXTEND	= 0x0e, +}; + +enum { +	HOST1X_OPCODE_EXTEND_ACQUIRE_MLOCK	= 0x00, +	HOST1X_OPCODE_EXTEND_RELEASE_MLOCK	= 0x01, +}; + +static unsigned int show_channel_command(struct output *o, u32 val) +{ +	unsigned mask; +	unsigned subop; + +	switch (val >> 28) { +	case HOST1X_OPCODE_SETCLASS: +		mask = val & 0x3f; +		if (mask) { +			host1x_debug_output(o, "SETCL(class=%03x, offset=%03x, mask=%02x, [", +					    val >> 6 & 0x3ff, +					    val >> 16 & 0xfff, mask); +			return hweight8(mask); +		} else { +			host1x_debug_output(o, "SETCL(class=%03x)\n", +					    val >> 6 & 0x3ff); +			return 0; +		} + +	case HOST1X_OPCODE_INCR: +		host1x_debug_output(o, "INCR(offset=%03x, [", +				    val >> 16 & 0xfff); +		return val & 0xffff; + +	case HOST1X_OPCODE_NONINCR: +		host1x_debug_output(o, "NONINCR(offset=%03x, [", +				    val >> 16 & 0xfff); +		return val & 0xffff; + +	case HOST1X_OPCODE_MASK: +		mask = val & 0xffff; +		host1x_debug_output(o, "MASK(offset=%03x, mask=%03x, [", +				    val >> 16 & 0xfff, mask); +		return hweight16(mask); + +	case HOST1X_OPCODE_IMM: +		host1x_debug_output(o, "IMM(offset=%03x, data=%03x)\n", +				    val >> 16 & 0xfff, val & 0xffff); +		return 0; + +	case HOST1X_OPCODE_RESTART: +		host1x_debug_output(o, "RESTART(offset=%08x)\n", val << 4); +		return 0; + +	case HOST1X_OPCODE_GATHER: +		host1x_debug_output(o, "GATHER(offset=%03x, insert=%d, type=%d, count=%04x, addr=[", +				    val >> 16 & 0xfff, val >> 15 & 0x1, +				    val >> 14 & 0x1, val & 0x3fff); +		return 1; + +	case HOST1X_OPCODE_EXTEND: +		subop = val >> 24 & 0xf; +		if (subop == HOST1X_OPCODE_EXTEND_ACQUIRE_MLOCK) +			host1x_debug_output(o, "ACQUIRE_MLOCK(index=%d)\n", +					    val & 0xff); +		else if (subop == HOST1X_OPCODE_EXTEND_RELEASE_MLOCK) +			host1x_debug_output(o, "RELEASE_MLOCK(index=%d)\n", +					    val & 0xff); +		else +			host1x_debug_output(o, "EXTEND_UNKNOWN(%08x)\n", val); +		return 0; + +	default: +		return 0; +	} +} + +static void show_gather(struct output *o, phys_addr_t phys_addr, +			unsigned int words, struct host1x_cdma *cdma, +			phys_addr_t pin_addr, u32 *map_addr) +{ +	/* Map dmaget cursor to corresponding mem handle */ +	u32 offset = phys_addr - pin_addr; +	unsigned int data_count = 0, i; + +	/* +	 * Sometimes we're given different hardware address to the same +	 * page - in these cases the offset will get an invalid number and +	 * we just have to bail out. +	 */ +	if (offset > HOST1X_DEBUG_MAX_PAGE_OFFSET) { +		host1x_debug_output(o, "[address mismatch]\n"); +		return; +	} + +	for (i = 0; i < words; i++) { +		u32 addr = phys_addr + i * 4; +		u32 val = *(map_addr + offset / 4 + i); + +		if (!data_count) { +			host1x_debug_output(o, "%08x: %08x:", addr, val); +			data_count = show_channel_command(o, val); +		} else { +			host1x_debug_output(o, "%08x%s", val, +					    data_count > 0 ? ", " : "])\n"); +			data_count--; +		} +	} +} + +static void show_channel_gathers(struct output *o, struct host1x_cdma *cdma) +{ +	struct host1x_job *job; + +	list_for_each_entry(job, &cdma->sync_queue, list) { +		int i; +		host1x_debug_output(o, "\n%p: JOB, syncpt_id=%d, syncpt_val=%d, first_get=%08x, timeout=%d num_slots=%d, num_handles=%d\n", +				    job, job->syncpt_id, job->syncpt_end, +				    job->first_get, job->timeout, +				    job->num_slots, job->num_unpins); + +		for (i = 0; i < job->num_gathers; i++) { +			struct host1x_job_gather *g = &job->gathers[i]; +			u32 *mapped; + +			if (job->gather_copy_mapped) +				mapped = (u32 *)job->gather_copy_mapped; +			else +				mapped = host1x_bo_mmap(g->bo); + +			if (!mapped) { +				host1x_debug_output(o, "[could not mmap]\n"); +				continue; +			} + +			host1x_debug_output(o, "    GATHER at %08x+%04x, %d words\n", +					    g->base, g->offset, g->words); + +			show_gather(o, g->base + g->offset, g->words, cdma, +				    g->base, mapped); + +			if (!job->gather_copy_mapped) +				host1x_bo_munmap(g->bo, mapped); +		} +	} +} + +static void host1x_debug_show_channel_cdma(struct host1x *host, +					   struct host1x_channel *ch, +					   struct output *o) +{ +	struct host1x_cdma *cdma = &ch->cdma; +	u32 dmaput, dmaget, dmactrl; +	u32 cbstat, cbread; +	u32 val, base, baseval; + +	dmaput = host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT); +	dmaget = host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET); +	dmactrl = host1x_ch_readl(ch, HOST1X_CHANNEL_DMACTRL); +	cbread = host1x_sync_readl(host, HOST1X_SYNC_CBREAD(ch->id)); +	cbstat = host1x_sync_readl(host, HOST1X_SYNC_CBSTAT(ch->id)); + +	host1x_debug_output(o, "%d-%s: ", ch->id, dev_name(ch->dev)); + +	if (HOST1X_CHANNEL_DMACTRL_DMASTOP_V(dmactrl) || +	    !ch->cdma.push_buffer.mapped) { +		host1x_debug_output(o, "inactive\n\n"); +		return; +	} + +	if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == HOST1X_CLASS_HOST1X && +	    HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == +	    HOST1X_UCLASS_WAIT_SYNCPT) +		host1x_debug_output(o, "waiting on syncpt %d val %d\n", +				    cbread >> 24, cbread & 0xffffff); +	else if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == +	   HOST1X_CLASS_HOST1X && +	   HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == +	   HOST1X_UCLASS_WAIT_SYNCPT_BASE) { + +		base = (cbread >> 16) & 0xff; +		baseval = +			host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(base)); +		val = cbread & 0xffff; +		host1x_debug_output(o, "waiting on syncpt %d val %d (base %d = %d; offset = %d)\n", +				    cbread >> 24, baseval + val, base, +				    baseval, val); +	} else +		host1x_debug_output(o, "active class %02x, offset %04x, val %08x\n", +				    HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat), +				    HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat), +				    cbread); + +	host1x_debug_output(o, "DMAPUT %08x, DMAGET %08x, DMACTL %08x\n", +			    dmaput, dmaget, dmactrl); +	host1x_debug_output(o, "CBREAD %08x, CBSTAT %08x\n", cbread, cbstat); + +	show_channel_gathers(o, cdma); +	host1x_debug_output(o, "\n"); +} + +static void host1x_debug_show_channel_fifo(struct host1x *host, +					   struct host1x_channel *ch, +					   struct output *o) +{ +	u32 val, rd_ptr, wr_ptr, start, end; +	unsigned int data_count = 0; + +	host1x_debug_output(o, "%d: fifo:\n", ch->id); + +	val = host1x_ch_readl(ch, HOST1X_CHANNEL_FIFOSTAT); +	host1x_debug_output(o, "FIFOSTAT %08x\n", val); +	if (HOST1X_CHANNEL_FIFOSTAT_CFEMPTY_V(val)) { +		host1x_debug_output(o, "[empty]\n"); +		return; +	} + +	host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL); +	host1x_sync_writel(host, HOST1X_SYNC_CFPEEK_CTRL_ENA_F(1) | +			   HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(ch->id), +			   HOST1X_SYNC_CFPEEK_CTRL); + +	val = host1x_sync_readl(host, HOST1X_SYNC_CFPEEK_PTRS); +	rd_ptr = HOST1X_SYNC_CFPEEK_PTRS_CF_RD_PTR_V(val); +	wr_ptr = HOST1X_SYNC_CFPEEK_PTRS_CF_WR_PTR_V(val); + +	val = host1x_sync_readl(host, HOST1X_SYNC_CF_SETUP(ch->id)); +	start = HOST1X_SYNC_CF_SETUP_BASE_V(val); +	end = HOST1X_SYNC_CF_SETUP_LIMIT_V(val); + +	do { +		host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL); +		host1x_sync_writel(host, HOST1X_SYNC_CFPEEK_CTRL_ENA_F(1) | +				   HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(ch->id) | +				   HOST1X_SYNC_CFPEEK_CTRL_ADDR_F(rd_ptr), +				   HOST1X_SYNC_CFPEEK_CTRL); +		val = host1x_sync_readl(host, HOST1X_SYNC_CFPEEK_READ); + +		if (!data_count) { +			host1x_debug_output(o, "%08x:", val); +			data_count = show_channel_command(o, val); +		} else { +			host1x_debug_output(o, "%08x%s", val, +					    data_count > 0 ? ", " : "])\n"); +			data_count--; +		} + +		if (rd_ptr == end) +			rd_ptr = start; +		else +			rd_ptr++; +	} while (rd_ptr != wr_ptr); + +	if (data_count) +		host1x_debug_output(o, ", ...])\n"); +	host1x_debug_output(o, "\n"); + +	host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL); +} + +static void host1x_debug_show_mlocks(struct host1x *host, struct output *o) +{ +	int i; + +	host1x_debug_output(o, "---- mlocks ----\n"); +	for (i = 0; i < host1x_syncpt_nb_mlocks(host); i++) { +		u32 owner = +			host1x_sync_readl(host, HOST1X_SYNC_MLOCK_OWNER(i)); +		if (HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(owner)) +			host1x_debug_output(o, "%d: locked by channel %d\n", +				i, HOST1X_SYNC_MLOCK_OWNER_CHID_F(owner)); +		else if (HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(owner)) +			host1x_debug_output(o, "%d: locked by cpu\n", i); +		else +			host1x_debug_output(o, "%d: unlocked\n", i); +	} +	host1x_debug_output(o, "\n"); +} + +static const struct host1x_debug_ops host1x_debug_ops = { +	.show_channel_cdma = host1x_debug_show_channel_cdma, +	.show_channel_fifo = host1x_debug_show_channel_fifo, +	.show_mlocks = host1x_debug_show_mlocks, +}; diff --git a/drivers/gpu/host1x/hw/host1x01.c b/drivers/gpu/host1x/hw/host1x01.c new file mode 100644 index 00000000000..a14e91cd1e5 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x01.c @@ -0,0 +1,42 @@ +/* + * Host1x init for T20 and T30 Architecture Chips + * + * Copyright (c) 2011-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 hw specification */ +#include "hw/host1x01.h" +#include "hw/host1x01_hardware.h" + +/* include code */ +#include "hw/cdma_hw.c" +#include "hw/channel_hw.c" +#include "hw/debug_hw.c" +#include "hw/intr_hw.c" +#include "hw/syncpt_hw.c" + +#include "dev.h" + +int host1x01_init(struct host1x *host) +{ +	host->channel_op = &host1x_channel_ops; +	host->cdma_op = &host1x_cdma_ops; +	host->cdma_pb_op = &host1x_pushbuffer_ops; +	host->syncpt_op = &host1x_syncpt_ops; +	host->intr_op = &host1x_intr_ops; +	host->debug_op = &host1x_debug_ops; + +	return 0; +} diff --git a/drivers/gpu/host1x/hw/host1x01.h b/drivers/gpu/host1x/hw/host1x01.h new file mode 100644 index 00000000000..2706b674325 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x01.h @@ -0,0 +1,25 @@ +/* + * Host1x init for T20 and T30 Architecture Chips + * + * Copyright (c) 2011-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 HOST1X_HOST1X01_H +#define HOST1X_HOST1X01_H + +struct host1x; + +int host1x01_init(struct host1x *host); + +#endif /* HOST1X_HOST1X01_H_ */ diff --git a/drivers/gpu/host1x/hw/host1x01_hardware.h b/drivers/gpu/host1x/hw/host1x01_hardware.h new file mode 100644 index 00000000000..5f0fb866efa --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x01_hardware.h @@ -0,0 +1,143 @@ +/* + * Tegra host1x Register Offsets for Tegra20 and Tegra30 + * + * Copyright (c) 2010-2013 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_HOST1X01_HARDWARE_H +#define __HOST1X_HOST1X01_HARDWARE_H + +#include <linux/types.h> +#include <linux/bitops.h> + +#include "hw_host1x01_channel.h" +#include "hw_host1x01_sync.h" +#include "hw_host1x01_uclass.h" + +static inline u32 host1x_class_host_wait_syncpt( +	unsigned indx, unsigned threshold) +{ +	return host1x_uclass_wait_syncpt_indx_f(indx) +		| host1x_uclass_wait_syncpt_thresh_f(threshold); +} + +static inline u32 host1x_class_host_load_syncpt_base( +	unsigned indx, unsigned threshold) +{ +	return host1x_uclass_load_syncpt_base_base_indx_f(indx) +		| host1x_uclass_load_syncpt_base_value_f(threshold); +} + +static inline u32 host1x_class_host_wait_syncpt_base( +	unsigned indx, unsigned base_indx, unsigned offset) +{ +	return host1x_uclass_wait_syncpt_base_indx_f(indx) +		| host1x_uclass_wait_syncpt_base_base_indx_f(base_indx) +		| host1x_uclass_wait_syncpt_base_offset_f(offset); +} + +static inline u32 host1x_class_host_incr_syncpt_base( +	unsigned base_indx, unsigned offset) +{ +	return host1x_uclass_incr_syncpt_base_base_indx_f(base_indx) +		| host1x_uclass_incr_syncpt_base_offset_f(offset); +} + +static inline u32 host1x_class_host_incr_syncpt( +	unsigned cond, unsigned indx) +{ +	return host1x_uclass_incr_syncpt_cond_f(cond) +		| host1x_uclass_incr_syncpt_indx_f(indx); +} + +static inline u32 host1x_class_host_indoff_reg_write( +	unsigned mod_id, unsigned offset, bool auto_inc) +{ +	u32 v = host1x_uclass_indoff_indbe_f(0xf) +		| host1x_uclass_indoff_indmodid_f(mod_id) +		| host1x_uclass_indoff_indroffset_f(offset); +	if (auto_inc) +		v |= host1x_uclass_indoff_autoinc_f(1); +	return v; +} + +static inline u32 host1x_class_host_indoff_reg_read( +	unsigned mod_id, unsigned offset, bool auto_inc) +{ +	u32 v = host1x_uclass_indoff_indmodid_f(mod_id) +		| host1x_uclass_indoff_indroffset_f(offset) +		| host1x_uclass_indoff_rwn_read_v(); +	if (auto_inc) +		v |= host1x_uclass_indoff_autoinc_f(1); +	return v; +} + + +/* cdma opcodes */ +static inline u32 host1x_opcode_setclass( +	unsigned class_id, unsigned offset, unsigned mask) +{ +	return (0 << 28) | (offset << 16) | (class_id << 6) | mask; +} + +static inline u32 host1x_opcode_incr(unsigned offset, unsigned count) +{ +	return (1 << 28) | (offset << 16) | count; +} + +static inline u32 host1x_opcode_nonincr(unsigned offset, unsigned count) +{ +	return (2 << 28) | (offset << 16) | count; +} + +static inline u32 host1x_opcode_mask(unsigned offset, unsigned mask) +{ +	return (3 << 28) | (offset << 16) | mask; +} + +static inline u32 host1x_opcode_imm(unsigned offset, unsigned value) +{ +	return (4 << 28) | (offset << 16) | value; +} + +static inline u32 host1x_opcode_imm_incr_syncpt(unsigned cond, unsigned indx) +{ +	return host1x_opcode_imm(host1x_uclass_incr_syncpt_r(), +		host1x_class_host_incr_syncpt(cond, indx)); +} + +static inline u32 host1x_opcode_restart(unsigned address) +{ +	return (5 << 28) | (address >> 4); +} + +static inline u32 host1x_opcode_gather(unsigned count) +{ +	return (6 << 28) | count; +} + +static inline u32 host1x_opcode_gather_nonincr(unsigned offset,	unsigned count) +{ +	return (6 << 28) | (offset << 16) | BIT(15) | count; +} + +static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count) +{ +	return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count; +} + +#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0) + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x01_channel.h b/drivers/gpu/host1x/hw/hw_host1x01_channel.h new file mode 100644 index 00000000000..b4bc7ca4e05 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x01_channel.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/>. + * + */ + + /* +  * Function naming determines intended use: +  * +  *     <x>_r(void) : Returns the offset for register <x>. +  * +  *     <x>_w(void) : Returns the word offset for word (4 byte) element <x>. +  * +  *     <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits. +  * +  *     <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted +  *         and masked to place it at field <y> of register <x>.  This value +  *         can be |'d with others to produce a full register value for +  *         register <x>. +  * +  *     <x>_<y>_m(void) : Returns a mask for field <y> of register <x>.  This +  *         value can be ~'d and then &'d to clear the value of field <y> for +  *         register <x>. +  * +  *     <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted +  *         to place it at field <y> of register <x>.  This value can be |'d +  *         with others to produce a full register value for <x>. +  * +  *     <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register +  *         <x> value 'r' after being shifted to place its LSB at bit 0. +  *         This value is suitable for direct comparison with other unshifted +  *         values appropriate for use in field <y> of register <x>. +  * +  *     <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for +  *         field <y> of register <x>.  This value is suitable for direct +  *         comparison with unshifted values appropriate for use in field <y> +  *         of register <x>. +  */ + +#ifndef __hw_host1x_channel_host1x_h__ +#define __hw_host1x_channel_host1x_h__ + +static inline u32 host1x_channel_fifostat_r(void) +{ +	return 0x0; +} +#define HOST1X_CHANNEL_FIFOSTAT \ +	host1x_channel_fifostat_r() +static inline u32 host1x_channel_fifostat_cfempty_v(u32 r) +{ +	return (r >> 10) & 0x1; +} +#define HOST1X_CHANNEL_FIFOSTAT_CFEMPTY_V(r) \ +	host1x_channel_fifostat_cfempty_v(r) +static inline u32 host1x_channel_dmastart_r(void) +{ +	return 0x14; +} +#define HOST1X_CHANNEL_DMASTART \ +	host1x_channel_dmastart_r() +static inline u32 host1x_channel_dmaput_r(void) +{ +	return 0x18; +} +#define HOST1X_CHANNEL_DMAPUT \ +	host1x_channel_dmaput_r() +static inline u32 host1x_channel_dmaget_r(void) +{ +	return 0x1c; +} +#define HOST1X_CHANNEL_DMAGET \ +	host1x_channel_dmaget_r() +static inline u32 host1x_channel_dmaend_r(void) +{ +	return 0x20; +} +#define HOST1X_CHANNEL_DMAEND \ +	host1x_channel_dmaend_r() +static inline u32 host1x_channel_dmactrl_r(void) +{ +	return 0x24; +} +#define HOST1X_CHANNEL_DMACTRL \ +	host1x_channel_dmactrl_r() +static inline u32 host1x_channel_dmactrl_dmastop(void) +{ +	return 1 << 0; +} +#define HOST1X_CHANNEL_DMACTRL_DMASTOP \ +	host1x_channel_dmactrl_dmastop() +static inline u32 host1x_channel_dmactrl_dmastop_v(u32 r) +{ +	return (r >> 0) & 0x1; +} +#define HOST1X_CHANNEL_DMACTRL_DMASTOP_V(r) \ +	host1x_channel_dmactrl_dmastop_v(r) +static inline u32 host1x_channel_dmactrl_dmagetrst(void) +{ +	return 1 << 1; +} +#define HOST1X_CHANNEL_DMACTRL_DMAGETRST \ +	host1x_channel_dmactrl_dmagetrst() +static inline u32 host1x_channel_dmactrl_dmainitget(void) +{ +	return 1 << 2; +} +#define HOST1X_CHANNEL_DMACTRL_DMAINITGET \ +	host1x_channel_dmactrl_dmainitget() +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x01_sync.h b/drivers/gpu/host1x/hw/hw_host1x01_sync.h new file mode 100644 index 00000000000..ac704e57997 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x01_sync.h @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/>. + * + */ + + /* +  * Function naming determines intended use: +  * +  *     <x>_r(void) : Returns the offset for register <x>. +  * +  *     <x>_w(void) : Returns the word offset for word (4 byte) element <x>. +  * +  *     <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits. +  * +  *     <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted +  *         and masked to place it at field <y> of register <x>.  This value +  *         can be |'d with others to produce a full register value for +  *         register <x>. +  * +  *     <x>_<y>_m(void) : Returns a mask for field <y> of register <x>.  This +  *         value can be ~'d and then &'d to clear the value of field <y> for +  *         register <x>. +  * +  *     <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted +  *         to place it at field <y> of register <x>.  This value can be |'d +  *         with others to produce a full register value for <x>. +  * +  *     <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register +  *         <x> value 'r' after being shifted to place its LSB at bit 0. +  *         This value is suitable for direct comparison with other unshifted +  *         values appropriate for use in field <y> of register <x>. +  * +  *     <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for +  *         field <y> of register <x>.  This value is suitable for direct +  *         comparison with unshifted values appropriate for use in field <y> +  *         of register <x>. +  */ + +#ifndef __hw_host1x01_sync_h__ +#define __hw_host1x01_sync_h__ + +#define REGISTER_STRIDE	4 + +static inline u32 host1x_sync_syncpt_r(unsigned int id) +{ +	return 0x400 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT(id) \ +	host1x_sync_syncpt_r(id) +static inline u32 host1x_sync_syncpt_thresh_cpu0_int_status_r(unsigned int id) +{ +	return 0x40 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(id) \ +	host1x_sync_syncpt_thresh_cpu0_int_status_r(id) +static inline u32 host1x_sync_syncpt_thresh_int_disable_r(unsigned int id) +{ +	return 0x60 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(id) \ +	host1x_sync_syncpt_thresh_int_disable_r(id) +static inline u32 host1x_sync_syncpt_thresh_int_enable_cpu0_r(unsigned int id) +{ +	return 0x68 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(id) \ +	host1x_sync_syncpt_thresh_int_enable_cpu0_r(id) +static inline u32 host1x_sync_cf_setup_r(unsigned int channel) +{ +	return 0x80 + channel * REGISTER_STRIDE; +} +#define HOST1X_SYNC_CF_SETUP(channel) \ +	host1x_sync_cf_setup_r(channel) +static inline u32 host1x_sync_cf_setup_base_v(u32 r) +{ +	return (r >> 0) & 0x1ff; +} +#define HOST1X_SYNC_CF_SETUP_BASE_V(r) \ +	host1x_sync_cf_setup_base_v(r) +static inline u32 host1x_sync_cf_setup_limit_v(u32 r) +{ +	return (r >> 16) & 0x1ff; +} +#define HOST1X_SYNC_CF_SETUP_LIMIT_V(r) \ +	host1x_sync_cf_setup_limit_v(r) +static inline u32 host1x_sync_cmdproc_stop_r(void) +{ +	return 0xac; +} +#define HOST1X_SYNC_CMDPROC_STOP \ +	host1x_sync_cmdproc_stop_r() +static inline u32 host1x_sync_ch_teardown_r(void) +{ +	return 0xb0; +} +#define HOST1X_SYNC_CH_TEARDOWN \ +	host1x_sync_ch_teardown_r() +static inline u32 host1x_sync_usec_clk_r(void) +{ +	return 0x1a4; +} +#define HOST1X_SYNC_USEC_CLK \ +	host1x_sync_usec_clk_r() +static inline u32 host1x_sync_ctxsw_timeout_cfg_r(void) +{ +	return 0x1a8; +} +#define HOST1X_SYNC_CTXSW_TIMEOUT_CFG \ +	host1x_sync_ctxsw_timeout_cfg_r() +static inline u32 host1x_sync_ip_busy_timeout_r(void) +{ +	return 0x1bc; +} +#define HOST1X_SYNC_IP_BUSY_TIMEOUT \ +	host1x_sync_ip_busy_timeout_r() +static inline u32 host1x_sync_mlock_owner_r(unsigned int id) +{ +	return 0x340 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_MLOCK_OWNER(id) \ +	host1x_sync_mlock_owner_r(id) +static inline u32 host1x_sync_mlock_owner_chid_f(u32 v) +{ +	return (v & 0xf) << 8; +} +#define HOST1X_SYNC_MLOCK_OWNER_CHID_F(v) \ +	host1x_sync_mlock_owner_chid_f(v) +static inline u32 host1x_sync_mlock_owner_cpu_owns_v(u32 r) +{ +	return (r >> 1) & 0x1; +} +#define HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(r) \ +	host1x_sync_mlock_owner_cpu_owns_v(r) +static inline u32 host1x_sync_mlock_owner_ch_owns_v(u32 r) +{ +	return (r >> 0) & 0x1; +} +#define HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(r) \ +	host1x_sync_mlock_owner_ch_owns_v(r) +static inline u32 host1x_sync_syncpt_int_thresh_r(unsigned int id) +{ +	return 0x500 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_INT_THRESH(id) \ +	host1x_sync_syncpt_int_thresh_r(id) +static inline u32 host1x_sync_syncpt_base_r(unsigned int id) +{ +	return 0x600 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_BASE(id) \ +	host1x_sync_syncpt_base_r(id) +static inline u32 host1x_sync_syncpt_cpu_incr_r(unsigned int id) +{ +	return 0x700 + id * REGISTER_STRIDE; +} +#define HOST1X_SYNC_SYNCPT_CPU_INCR(id) \ +	host1x_sync_syncpt_cpu_incr_r(id) +static inline u32 host1x_sync_cbread_r(unsigned int channel) +{ +	return 0x720 + channel * REGISTER_STRIDE; +} +#define HOST1X_SYNC_CBREAD(channel) \ +	host1x_sync_cbread_r(channel) +static inline u32 host1x_sync_cfpeek_ctrl_r(void) +{ +	return 0x74c; +} +#define HOST1X_SYNC_CFPEEK_CTRL \ +	host1x_sync_cfpeek_ctrl_r() +static inline u32 host1x_sync_cfpeek_ctrl_addr_f(u32 v) +{ +	return (v & 0x1ff) << 0; +} +#define HOST1X_SYNC_CFPEEK_CTRL_ADDR_F(v) \ +	host1x_sync_cfpeek_ctrl_addr_f(v) +static inline u32 host1x_sync_cfpeek_ctrl_channr_f(u32 v) +{ +	return (v & 0x7) << 16; +} +#define HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(v) \ +	host1x_sync_cfpeek_ctrl_channr_f(v) +static inline u32 host1x_sync_cfpeek_ctrl_ena_f(u32 v) +{ +	return (v & 0x1) << 31; +} +#define HOST1X_SYNC_CFPEEK_CTRL_ENA_F(v) \ +	host1x_sync_cfpeek_ctrl_ena_f(v) +static inline u32 host1x_sync_cfpeek_read_r(void) +{ +	return 0x750; +} +#define HOST1X_SYNC_CFPEEK_READ \ +	host1x_sync_cfpeek_read_r() +static inline u32 host1x_sync_cfpeek_ptrs_r(void) +{ +	return 0x754; +} +#define HOST1X_SYNC_CFPEEK_PTRS \ +	host1x_sync_cfpeek_ptrs_r() +static inline u32 host1x_sync_cfpeek_ptrs_cf_rd_ptr_v(u32 r) +{ +	return (r >> 0) & 0x1ff; +} +#define HOST1X_SYNC_CFPEEK_PTRS_CF_RD_PTR_V(r) \ +	host1x_sync_cfpeek_ptrs_cf_rd_ptr_v(r) +static inline u32 host1x_sync_cfpeek_ptrs_cf_wr_ptr_v(u32 r) +{ +	return (r >> 16) & 0x1ff; +} +#define HOST1X_SYNC_CFPEEK_PTRS_CF_WR_PTR_V(r) \ +	host1x_sync_cfpeek_ptrs_cf_wr_ptr_v(r) +static inline u32 host1x_sync_cbstat_r(unsigned int channel) +{ +	return 0x758 + channel * REGISTER_STRIDE; +} +#define HOST1X_SYNC_CBSTAT(channel) \ +	host1x_sync_cbstat_r(channel) +static inline u32 host1x_sync_cbstat_cboffset_v(u32 r) +{ +	return (r >> 0) & 0xffff; +} +#define HOST1X_SYNC_CBSTAT_CBOFFSET_V(r) \ +	host1x_sync_cbstat_cboffset_v(r) +static inline u32 host1x_sync_cbstat_cbclass_v(u32 r) +{ +	return (r >> 16) & 0x3ff; +} +#define HOST1X_SYNC_CBSTAT_CBCLASS_V(r) \ +	host1x_sync_cbstat_cbclass_v(r) + +#endif /* __hw_host1x01_sync_h__ */ diff --git a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h new file mode 100644 index 00000000000..42f3ce19ca3 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2012-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/>. + * + */ + + /* +  * Function naming determines intended use: +  * +  *     <x>_r(void) : Returns the offset for register <x>. +  * +  *     <x>_w(void) : Returns the word offset for word (4 byte) element <x>. +  * +  *     <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits. +  * +  *     <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted +  *         and masked to place it at field <y> of register <x>.  This value +  *         can be |'d with others to produce a full register value for +  *         register <x>. +  * +  *     <x>_<y>_m(void) : Returns a mask for field <y> of register <x>.  This +  *         value can be ~'d and then &'d to clear the value of field <y> for +  *         register <x>. +  * +  *     <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted +  *         to place it at field <y> of register <x>.  This value can be |'d +  *         with others to produce a full register value for <x>. +  * +  *     <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register +  *         <x> value 'r' after being shifted to place its LSB at bit 0. +  *         This value is suitable for direct comparison with other unshifted +  *         values appropriate for use in field <y> of register <x>. +  * +  *     <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for +  *         field <y> of register <x>.  This value is suitable for direct +  *         comparison with unshifted values appropriate for use in field <y> +  *         of register <x>. +  */ + +#ifndef __hw_host1x_uclass_host1x_h__ +#define __hw_host1x_uclass_host1x_h__ + +static inline u32 host1x_uclass_incr_syncpt_r(void) +{ +	return 0x0; +} +#define HOST1X_UCLASS_INCR_SYNCPT \ +	host1x_uclass_incr_syncpt_r() +static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v) +{ +	return (v & 0xff) << 8; +} +#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \ +	host1x_uclass_incr_syncpt_cond_f(v) +static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v) +{ +	return (v & 0xff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \ +	host1x_uclass_incr_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_r(void) +{ +	return 0x8; +} +#define HOST1X_UCLASS_WAIT_SYNCPT \ +	host1x_uclass_wait_syncpt_r() +static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v) +{ +	return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \ +	host1x_uclass_wait_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v) +{ +	return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \ +	host1x_uclass_wait_syncpt_thresh_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_r(void) +{ +	return 0x9; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE \ +	host1x_uclass_wait_syncpt_base_r() +static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v) +{ +	return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \ +	host1x_uclass_wait_syncpt_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v) +{ +	return (v & 0xff) << 16; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \ +	host1x_uclass_wait_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v) +{ +	return (v & 0xffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \ +	host1x_uclass_wait_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v) +{ +	return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \ +	host1x_uclass_load_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v) +{ +	return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \ +	host1x_uclass_load_syncpt_base_value_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v) +{ +	return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \ +	host1x_uclass_incr_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v) +{ +	return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \ +	host1x_uclass_incr_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_indoff_r(void) +{ +	return 0x2d; +} +#define HOST1X_UCLASS_INDOFF \ +	host1x_uclass_indoff_r() +static inline u32 host1x_uclass_indoff_indbe_f(u32 v) +{ +	return (v & 0xf) << 28; +} +#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \ +	host1x_uclass_indoff_indbe_f(v) +static inline u32 host1x_uclass_indoff_autoinc_f(u32 v) +{ +	return (v & 0x1) << 27; +} +#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \ +	host1x_uclass_indoff_autoinc_f(v) +static inline u32 host1x_uclass_indoff_indmodid_f(u32 v) +{ +	return (v & 0xff) << 18; +} +#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \ +	host1x_uclass_indoff_indmodid_f(v) +static inline u32 host1x_uclass_indoff_indroffset_f(u32 v) +{ +	return (v & 0xffff) << 2; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ +	host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_indoff_rwn_read_v(void) +{ +	return 1; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ +	host1x_uclass_indoff_indroffset_f(v) +#endif diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c new file mode 100644 index 00000000000..b592eef1efc --- /dev/null +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -0,0 +1,143 @@ +/* + * Tegra host1x Interrupt Management + * + * Copyright (C) 2010 Google, Inc. + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <asm/mach/irq.h> + +#include "intr.h" +#include "dev.h" + +/* + * Sync point threshold interrupt service function + * Handles sync point threshold triggers, in interrupt context + */ +static void host1x_intr_syncpt_handle(struct host1x_syncpt *syncpt) +{ +	unsigned int id = syncpt->id; +	struct host1x *host = syncpt->host; + +	host1x_sync_writel(host, BIT_MASK(id), +		HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id))); +	host1x_sync_writel(host, BIT_MASK(id), +		HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id))); + +	queue_work(host->intr_wq, &syncpt->intr.work); +} + +static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id) +{ +	struct host1x *host = dev_id; +	unsigned long reg; +	int i, id; + +	for (i = 0; i <= BIT_WORD(host->info->nb_pts); i++) { +		reg = host1x_sync_readl(host, +			HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(i)); +		for_each_set_bit(id, ®, BITS_PER_LONG) { +			struct host1x_syncpt *syncpt = +				host->syncpt + (i * BITS_PER_LONG + id); +			host1x_intr_syncpt_handle(syncpt); +		} +	} + +	return IRQ_HANDLED; +} + +static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host) +{ +	u32 i; + +	for (i = 0; i <= BIT_WORD(host->info->nb_pts); ++i) { +		host1x_sync_writel(host, 0xffffffffu, +			HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(i)); +		host1x_sync_writel(host, 0xffffffffu, +			HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(i)); +	} +} + +static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm, +	void (*syncpt_thresh_work)(struct work_struct *)) +{ +	int i, err; + +	host1x_hw_intr_disable_all_syncpt_intrs(host); + +	for (i = 0; i < host->info->nb_pts; i++) +		INIT_WORK(&host->syncpt[i].intr.work, syncpt_thresh_work); + +	err = devm_request_irq(host->dev, host->intr_syncpt_irq, +			       syncpt_thresh_isr, IRQF_SHARED, +			       "host1x_syncpt", host); +	if (IS_ERR_VALUE(err)) { +		WARN_ON(1); +		return err; +	} + +	/* disable the ip_busy_timeout. this prevents write drops */ +	host1x_sync_writel(host, 0, HOST1X_SYNC_IP_BUSY_TIMEOUT); + +	/* +	 * increase the auto-ack timout to the maximum value. 2d will hang +	 * otherwise on Tegra2. +	 */ +	host1x_sync_writel(host, 0xff, HOST1X_SYNC_CTXSW_TIMEOUT_CFG); + +	/* update host clocks per usec */ +	host1x_sync_writel(host, cpm, HOST1X_SYNC_USEC_CLK); + +	return 0; +} + +static void _host1x_intr_set_syncpt_threshold(struct host1x *host, +	u32 id, u32 thresh) +{ +	host1x_sync_writel(host, thresh, HOST1X_SYNC_SYNCPT_INT_THRESH(id)); +} + +static void _host1x_intr_enable_syncpt_intr(struct host1x *host, u32 id) +{ +	host1x_sync_writel(host, BIT_MASK(id), +		HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(BIT_WORD(id))); +} + +static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id) +{ +	host1x_sync_writel(host, BIT_MASK(id), +		HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id))); +	host1x_sync_writel(host, BIT_MASK(id), +		HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id))); +} + +static int _host1x_free_syncpt_irq(struct host1x *host) +{ +	devm_free_irq(host->dev, host->intr_syncpt_irq, host); +	flush_workqueue(host->intr_wq); +	return 0; +} + +static const struct host1x_intr_ops host1x_intr_ops = { +	.init_host_sync = _host1x_intr_init_host_sync, +	.set_syncpt_threshold = _host1x_intr_set_syncpt_threshold, +	.enable_syncpt_intr = _host1x_intr_enable_syncpt_intr, +	.disable_syncpt_intr = _host1x_intr_disable_syncpt_intr, +	.disable_all_syncpt_intrs = _host1x_intr_disable_all_syncpt_intrs, +	.free_syncpt_irq = _host1x_free_syncpt_irq, +}; diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c new file mode 100644 index 00000000000..61174990102 --- /dev/null +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -0,0 +1,114 @@ +/* + * Tegra host1x Syncpoints + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/io.h> + +#include "dev.h" +#include "syncpt.h" + +/* + * Write the current syncpoint value back to hw. + */ +static void syncpt_restore(struct host1x_syncpt *sp) +{ +	struct host1x *host = sp->host; +	int min = host1x_syncpt_read_min(sp); +	host1x_sync_writel(host, min, HOST1X_SYNC_SYNCPT(sp->id)); +} + +/* + * Write the current waitbase value back to hw. + */ +static void syncpt_restore_wait_base(struct host1x_syncpt *sp) +{ +	struct host1x *host = sp->host; +	host1x_sync_writel(host, sp->base_val, +			   HOST1X_SYNC_SYNCPT_BASE(sp->id)); +} + +/* + * Read waitbase value from hw. + */ +static void syncpt_read_wait_base(struct host1x_syncpt *sp) +{ +	struct host1x *host = sp->host; +	sp->base_val = +		host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id)); +} + +/* + * Updates the last value read from hardware. + */ +static u32 syncpt_load(struct host1x_syncpt *sp) +{ +	struct host1x *host = sp->host; +	u32 old, live; + +	/* Loop in case there's a race writing to min_val */ +	do { +		old = host1x_syncpt_read_min(sp); +		live = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT(sp->id)); +	} while ((u32)atomic_cmpxchg(&sp->min_val, old, live) != old); + +	if (!host1x_syncpt_check_max(sp, live)) +		dev_err(host->dev, "%s failed: id=%u, min=%d, max=%d\n", +			__func__, sp->id, host1x_syncpt_read_min(sp), +			host1x_syncpt_read_max(sp)); + +	return live; +} + +/* + * Write a cpu syncpoint increment to the hardware, without touching + * the cache. + */ +static void syncpt_cpu_incr(struct host1x_syncpt *sp) +{ +	struct host1x *host = sp->host; +	u32 reg_offset = sp->id / 32; + +	if (!host1x_syncpt_client_managed(sp) && +	    host1x_syncpt_idle(sp)) { +		dev_err(host->dev, "Trying to increment syncpoint id %d beyond max\n", +			sp->id); +		host1x_debug_dump(sp->host); +		return; +	} +	host1x_sync_writel(host, BIT_MASK(sp->id), +			   HOST1X_SYNC_SYNCPT_CPU_INCR(reg_offset)); +	wmb(); +} + +/* remove a wait pointed to by patch_addr */ +static int syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr) +{ +	u32 override = host1x_class_host_wait_syncpt( +		HOST1X_SYNCPT_RESERVED, 0); + +	*((u32 *)patch_addr) = override; +	return 0; +} + +static const struct host1x_syncpt_ops host1x_syncpt_ops = { +	.restore = syncpt_restore, +	.restore_wait_base = syncpt_restore_wait_base, +	.load_wait_base = syncpt_read_wait_base, +	.load = syncpt_load, +	.cpu_incr = syncpt_cpu_incr, +	.patch_wait = syncpt_patch_wait, +}; diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c new file mode 100644 index 00000000000..2491bf82e30 --- /dev/null +++ b/drivers/gpu/host1x/intr.c @@ -0,0 +1,354 @@ +/* + * Tegra host1x Interrupt Management + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/clk.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <trace/events/host1x.h> +#include "channel.h" +#include "dev.h" +#include "intr.h" + +/* Wait list management */ + +enum waitlist_state { +	WLS_PENDING, +	WLS_REMOVED, +	WLS_CANCELLED, +	WLS_HANDLED +}; + +static void waiter_release(struct kref *kref) +{ +	kfree(container_of(kref, struct host1x_waitlist, refcount)); +} + +/* + * add a waiter to a waiter queue, sorted by threshold + * returns true if it was added at the head of the queue + */ +static bool add_waiter_to_queue(struct host1x_waitlist *waiter, +				struct list_head *queue) +{ +	struct host1x_waitlist *pos; +	u32 thresh = waiter->thresh; + +	list_for_each_entry_reverse(pos, queue, list) +		if ((s32)(pos->thresh - thresh) <= 0) { +			list_add(&waiter->list, &pos->list); +			return false; +		} + +	list_add(&waiter->list, queue); +	return true; +} + +/* + * run through a waiter queue for a single sync point ID + * and gather all completed waiters into lists by actions + */ +static void remove_completed_waiters(struct list_head *head, u32 sync, +			struct list_head completed[HOST1X_INTR_ACTION_COUNT]) +{ +	struct list_head *dest; +	struct host1x_waitlist *waiter, *next, *prev; + +	list_for_each_entry_safe(waiter, next, head, list) { +		if ((s32)(waiter->thresh - sync) > 0) +			break; + +		dest = completed + waiter->action; + +		/* consolidate submit cleanups */ +		if (waiter->action == HOST1X_INTR_ACTION_SUBMIT_COMPLETE && +		    !list_empty(dest)) { +			prev = list_entry(dest->prev, +					  struct host1x_waitlist, list); +			if (prev->data == waiter->data) { +				prev->count++; +				dest = NULL; +			} +		} + +		/* PENDING->REMOVED or CANCELLED->HANDLED */ +		if (atomic_inc_return(&waiter->state) == WLS_HANDLED || !dest) { +			list_del(&waiter->list); +			kref_put(&waiter->refcount, waiter_release); +		} else +			list_move_tail(&waiter->list, dest); +	} +} + +static void reset_threshold_interrupt(struct host1x *host, +				      struct list_head *head, +				      unsigned int id) +{ +	u32 thresh = +		list_first_entry(head, struct host1x_waitlist, list)->thresh; + +	host1x_hw_intr_set_syncpt_threshold(host, id, thresh); +	host1x_hw_intr_enable_syncpt_intr(host, id); +} + +static void action_submit_complete(struct host1x_waitlist *waiter) +{ +	struct host1x_channel *channel = waiter->data; + +	host1x_cdma_update(&channel->cdma); + +	/*  Add nr_completed to trace */ +	trace_host1x_channel_submit_complete(dev_name(channel->dev), +					     waiter->count, waiter->thresh); + +} + +static void action_wakeup(struct host1x_waitlist *waiter) +{ +	wait_queue_head_t *wq = waiter->data; +	wake_up(wq); +} + +static void action_wakeup_interruptible(struct host1x_waitlist *waiter) +{ +	wait_queue_head_t *wq = waiter->data; +	wake_up_interruptible(wq); +} + +typedef void (*action_handler)(struct host1x_waitlist *waiter); + +static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = { +	action_submit_complete, +	action_wakeup, +	action_wakeup_interruptible, +}; + +static void run_handlers(struct list_head completed[HOST1X_INTR_ACTION_COUNT]) +{ +	struct list_head *head = completed; +	int i; + +	for (i = 0; i < HOST1X_INTR_ACTION_COUNT; ++i, ++head) { +		action_handler handler = action_handlers[i]; +		struct host1x_waitlist *waiter, *next; + +		list_for_each_entry_safe(waiter, next, head, list) { +			list_del(&waiter->list); +			handler(waiter); +			WARN_ON(atomic_xchg(&waiter->state, WLS_HANDLED) != +				WLS_REMOVED); +			kref_put(&waiter->refcount, waiter_release); +		} +	} +} + +/* + * Remove & handle all waiters that have completed for the given syncpt + */ +static int process_wait_list(struct host1x *host, +			     struct host1x_syncpt *syncpt, +			     u32 threshold) +{ +	struct list_head completed[HOST1X_INTR_ACTION_COUNT]; +	unsigned int i; +	int empty; + +	for (i = 0; i < HOST1X_INTR_ACTION_COUNT; ++i) +		INIT_LIST_HEAD(completed + i); + +	spin_lock(&syncpt->intr.lock); + +	remove_completed_waiters(&syncpt->intr.wait_head, threshold, +				 completed); + +	empty = list_empty(&syncpt->intr.wait_head); +	if (empty) +		host1x_hw_intr_disable_syncpt_intr(host, syncpt->id); +	else +		reset_threshold_interrupt(host, &syncpt->intr.wait_head, +					  syncpt->id); + +	spin_unlock(&syncpt->intr.lock); + +	run_handlers(completed); + +	return empty; +} + +/* + * Sync point threshold interrupt service thread function + * Handles sync point threshold triggers, in thread context + */ + +static void syncpt_thresh_work(struct work_struct *work) +{ +	struct host1x_syncpt_intr *syncpt_intr = +		container_of(work, struct host1x_syncpt_intr, work); +	struct host1x_syncpt *syncpt = +		container_of(syncpt_intr, struct host1x_syncpt, intr); +	unsigned int id = syncpt->id; +	struct host1x *host = syncpt->host; + +	(void)process_wait_list(host, syncpt, +				host1x_syncpt_load(host->syncpt + id)); +} + +int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, +			   enum host1x_intr_action action, void *data, +			   struct host1x_waitlist *waiter, void **ref) +{ +	struct host1x_syncpt *syncpt; +	int queue_was_empty; + +	if (waiter == NULL) { +		pr_warn("%s: NULL waiter\n", __func__); +		return -EINVAL; +	} + +	/* initialize a new waiter */ +	INIT_LIST_HEAD(&waiter->list); +	kref_init(&waiter->refcount); +	if (ref) +		kref_get(&waiter->refcount); +	waiter->thresh = thresh; +	waiter->action = action; +	atomic_set(&waiter->state, WLS_PENDING); +	waiter->data = data; +	waiter->count = 1; + +	syncpt = host->syncpt + id; + +	spin_lock(&syncpt->intr.lock); + +	queue_was_empty = list_empty(&syncpt->intr.wait_head); + +	if (add_waiter_to_queue(waiter, &syncpt->intr.wait_head)) { +		/* added at head of list - new threshold value */ +		host1x_hw_intr_set_syncpt_threshold(host, id, thresh); + +		/* added as first waiter - enable interrupt */ +		if (queue_was_empty) +			host1x_hw_intr_enable_syncpt_intr(host, id); +	} + +	spin_unlock(&syncpt->intr.lock); + +	if (ref) +		*ref = waiter; +	return 0; +} + +void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref) +{ +	struct host1x_waitlist *waiter = ref; +	struct host1x_syncpt *syncpt; + +	while (atomic_cmpxchg(&waiter->state, WLS_PENDING, WLS_CANCELLED) == +	       WLS_REMOVED) +		schedule(); + +	syncpt = host->syncpt + id; +	(void)process_wait_list(host, syncpt, +				host1x_syncpt_load(host->syncpt + id)); + +	kref_put(&waiter->refcount, waiter_release); +} + +int host1x_intr_init(struct host1x *host, unsigned int irq_sync) +{ +	unsigned int id; +	u32 nb_pts = host1x_syncpt_nb_pts(host); + +	mutex_init(&host->intr_mutex); +	host->intr_syncpt_irq = irq_sync; +	host->intr_wq = create_workqueue("host_syncpt"); +	if (!host->intr_wq) +		return -ENOMEM; + +	for (id = 0; id < nb_pts; ++id) { +		struct host1x_syncpt *syncpt = host->syncpt + id; + +		spin_lock_init(&syncpt->intr.lock); +		INIT_LIST_HEAD(&syncpt->intr.wait_head); +		snprintf(syncpt->intr.thresh_irq_name, +			 sizeof(syncpt->intr.thresh_irq_name), +			 "host1x_sp_%02d", id); +	} + +	host1x_intr_start(host); + +	return 0; +} + +void host1x_intr_deinit(struct host1x *host) +{ +	host1x_intr_stop(host); +	destroy_workqueue(host->intr_wq); +} + +void host1x_intr_start(struct host1x *host) +{ +	u32 hz = clk_get_rate(host->clk); +	int err; + +	mutex_lock(&host->intr_mutex); +	err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000), +					    syncpt_thresh_work); +	if (err) { +		mutex_unlock(&host->intr_mutex); +		return; +	} +	mutex_unlock(&host->intr_mutex); +} + +void host1x_intr_stop(struct host1x *host) +{ +	unsigned int id; +	struct host1x_syncpt *syncpt = host->syncpt; +	u32 nb_pts = host1x_syncpt_nb_pts(host); + +	mutex_lock(&host->intr_mutex); + +	host1x_hw_intr_disable_all_syncpt_intrs(host); + +	for (id = 0; id < nb_pts; ++id) { +		struct host1x_waitlist *waiter, *next; + +		list_for_each_entry_safe(waiter, next, +			&syncpt[id].intr.wait_head, list) { +			if (atomic_cmpxchg(&waiter->state, +			    WLS_CANCELLED, WLS_HANDLED) == WLS_CANCELLED) { +				list_del(&waiter->list); +				kref_put(&waiter->refcount, waiter_release); +			} +		} + +		if (!list_empty(&syncpt[id].intr.wait_head)) { +			/* output diagnostics */ +			mutex_unlock(&host->intr_mutex); +			pr_warn("%s cannot stop syncpt intr id=%d\n", +				__func__, id); +			return; +		} +	} + +	host1x_hw_intr_free_syncpt_irq(host); + +	mutex_unlock(&host->intr_mutex); +} diff --git a/drivers/gpu/host1x/intr.h b/drivers/gpu/host1x/intr.h new file mode 100644 index 00000000000..2b8adf016a0 --- /dev/null +++ b/drivers/gpu/host1x/intr.h @@ -0,0 +1,102 @@ +/* + * Tegra host1x Interrupt Management + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_INTR_H +#define __HOST1X_INTR_H + +#include <linux/interrupt.h> +#include <linux/workqueue.h> + +struct host1x; + +enum host1x_intr_action { +	/* +	 * Perform cleanup after a submit has completed. +	 * 'data' points to a channel +	 */ +	HOST1X_INTR_ACTION_SUBMIT_COMPLETE = 0, + +	/* +	 * Wake up a  task. +	 * 'data' points to a wait_queue_head_t +	 */ +	HOST1X_INTR_ACTION_WAKEUP, + +	/* +	 * Wake up a interruptible task. +	 * 'data' points to a wait_queue_head_t +	 */ +	HOST1X_INTR_ACTION_WAKEUP_INTERRUPTIBLE, + +	HOST1X_INTR_ACTION_COUNT +}; + +struct host1x_syncpt_intr { +	spinlock_t lock; +	struct list_head wait_head; +	char thresh_irq_name[12]; +	struct work_struct work; +}; + +struct host1x_waitlist { +	struct list_head list; +	struct kref refcount; +	u32 thresh; +	enum host1x_intr_action action; +	atomic_t state; +	void *data; +	int count; +}; + +/* + * Schedule an action to be taken when a sync point reaches the given threshold. + * + * @id the sync point + * @thresh the threshold + * @action the action to take + * @data a pointer to extra data depending on action, see above + * @waiter waiter structure - assumes ownership + * @ref must be passed if cancellation is possible, else NULL + * + * This is a non-blocking api. + */ +int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, +	enum host1x_intr_action action, void *data, +	struct host1x_waitlist *waiter, void **ref); + +/* + * Unreference an action submitted to host1x_intr_add_action(). + * You must call this if you passed non-NULL as ref. + * @ref the ref returned from host1x_intr_add_action() + */ +void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref); + +/* Initialize host1x sync point interrupt */ +int host1x_intr_init(struct host1x *host, unsigned int irq_sync); + +/* Deinitialize host1x sync point interrupt */ +void host1x_intr_deinit(struct host1x *host); + +/* Enable host1x sync point interrupt */ +void host1x_intr_start(struct host1x *host); + +/* Disable host1x sync point interrupt */ +void host1x_intr_stop(struct host1x *host); + +irqreturn_t host1x_syncpt_thresh_fn(void *dev_id); +#endif diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c new file mode 100644 index 00000000000..f665d679031 --- /dev/null +++ b/drivers/gpu/host1x/job.c @@ -0,0 +1,603 @@ +/* + * Tegra host1x Job + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/dma-mapping.h> +#include <linux/err.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <trace/events/host1x.h> + +#include "channel.h" +#include "dev.h" +#include "host1x_bo.h" +#include "job.h" +#include "syncpt.h" + +struct host1x_job *host1x_job_alloc(struct host1x_channel *ch, +				    u32 num_cmdbufs, u32 num_relocs, +				    u32 num_waitchks) +{ +	struct host1x_job *job = NULL; +	unsigned int num_unpins = num_cmdbufs + num_relocs; +	u64 total; +	void *mem; + +	/* Check that we're not going to overflow */ +	total = sizeof(struct host1x_job) + +		num_relocs * sizeof(struct host1x_reloc) + +		num_unpins * sizeof(struct host1x_job_unpin_data) + +		num_waitchks * sizeof(struct host1x_waitchk) + +		num_cmdbufs * sizeof(struct host1x_job_gather) + +		num_unpins * sizeof(dma_addr_t) + +		num_unpins * sizeof(u32 *); +	if (total > ULONG_MAX) +		return NULL; + +	mem = job = kzalloc(total, GFP_KERNEL); +	if (!job) +		return NULL; + +	kref_init(&job->ref); +	job->channel = ch; + +	/* Redistribute memory to the structs  */ +	mem += sizeof(struct host1x_job); +	job->relocarray = num_relocs ? mem : NULL; +	mem += num_relocs * sizeof(struct host1x_reloc); +	job->unpins = num_unpins ? mem : NULL; +	mem += num_unpins * sizeof(struct host1x_job_unpin_data); +	job->waitchk = num_waitchks ? mem : NULL; +	mem += num_waitchks * sizeof(struct host1x_waitchk); +	job->gathers = num_cmdbufs ? mem : NULL; +	mem += num_cmdbufs * sizeof(struct host1x_job_gather); +	job->addr_phys = num_unpins ? mem : NULL; + +	job->reloc_addr_phys = job->addr_phys; +	job->gather_addr_phys = &job->addr_phys[num_relocs]; + +	return job; +} + +struct host1x_job *host1x_job_get(struct host1x_job *job) +{ +	kref_get(&job->ref); +	return job; +} + +static void job_free(struct kref *ref) +{ +	struct host1x_job *job = container_of(ref, struct host1x_job, ref); + +	kfree(job); +} + +void host1x_job_put(struct host1x_job *job) +{ +	kref_put(&job->ref, job_free); +} + +void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *bo, +			   u32 words, u32 offset) +{ +	struct host1x_job_gather *cur_gather = &job->gathers[job->num_gathers]; + +	cur_gather->words = words; +	cur_gather->bo = bo; +	cur_gather->offset = offset; +	job->num_gathers++; +} + +/* + * NULL an already satisfied WAIT_SYNCPT host method, by patching its + * args in the command stream. The method data is changed to reference + * a reserved (never given out or incr) HOST1X_SYNCPT_RESERVED syncpt + * with a matching threshold value of 0, so is guaranteed to be popped + * by the host HW. + */ +static void host1x_syncpt_patch_offset(struct host1x_syncpt *sp, +				       struct host1x_bo *h, u32 offset) +{ +	void *patch_addr = NULL; + +	/* patch the wait */ +	patch_addr = host1x_bo_kmap(h, offset >> PAGE_SHIFT); +	if (patch_addr) { +		host1x_syncpt_patch_wait(sp, +					 patch_addr + (offset & ~PAGE_MASK)); +		host1x_bo_kunmap(h, offset >> PAGE_SHIFT, patch_addr); +	} else +		pr_err("Could not map cmdbuf for wait check\n"); +} + +/* + * Check driver supplied waitchk structs for syncpt thresholds + * that have already been satisfied and NULL the comparison (to + * avoid a wrap condition in the HW). + */ +static int do_waitchks(struct host1x_job *job, struct host1x *host, +		       struct host1x_bo *patch) +{ +	int i; + +	/* compare syncpt vs wait threshold */ +	for (i = 0; i < job->num_waitchk; i++) { +		struct host1x_waitchk *wait = &job->waitchk[i]; +		struct host1x_syncpt *sp = +			host1x_syncpt_get(host, wait->syncpt_id); + +		/* validate syncpt id */ +		if (wait->syncpt_id > host1x_syncpt_nb_pts(host)) +			continue; + +		/* skip all other gathers */ +		if (patch != wait->bo) +			continue; + +		trace_host1x_syncpt_wait_check(wait->bo, wait->offset, +					       wait->syncpt_id, wait->thresh, +					       host1x_syncpt_read_min(sp)); + +		if (host1x_syncpt_is_expired(sp, wait->thresh)) { +			dev_dbg(host->dev, +				"drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n", +				wait->syncpt_id, sp->name, wait->thresh, +				host1x_syncpt_read_min(sp)); + +			host1x_syncpt_patch_offset(sp, patch, wait->offset); +		} + +		wait->bo = NULL; +	} + +	return 0; +} + +static unsigned int pin_job(struct host1x_job *job) +{ +	unsigned int i; + +	job->num_unpins = 0; + +	for (i = 0; i < job->num_relocs; i++) { +		struct host1x_reloc *reloc = &job->relocarray[i]; +		struct sg_table *sgt; +		dma_addr_t phys_addr; + +		reloc->target = host1x_bo_get(reloc->target); +		if (!reloc->target) +			goto unpin; + +		phys_addr = host1x_bo_pin(reloc->target, &sgt); +		if (!phys_addr) +			goto unpin; + +		job->addr_phys[job->num_unpins] = phys_addr; +		job->unpins[job->num_unpins].bo = reloc->target; +		job->unpins[job->num_unpins].sgt = sgt; +		job->num_unpins++; +	} + +	for (i = 0; i < job->num_gathers; i++) { +		struct host1x_job_gather *g = &job->gathers[i]; +		struct sg_table *sgt; +		dma_addr_t phys_addr; + +		g->bo = host1x_bo_get(g->bo); +		if (!g->bo) +			goto unpin; + +		phys_addr = host1x_bo_pin(g->bo, &sgt); +		if (!phys_addr) +			goto unpin; + +		job->addr_phys[job->num_unpins] = phys_addr; +		job->unpins[job->num_unpins].bo = g->bo; +		job->unpins[job->num_unpins].sgt = sgt; +		job->num_unpins++; +	} + +	return job->num_unpins; + +unpin: +	host1x_job_unpin(job); +	return 0; +} + +static unsigned int do_relocs(struct host1x_job *job, struct host1x_bo *cmdbuf) +{ +	int i = 0; +	u32 last_page = ~0; +	void *cmdbuf_page_addr = NULL; + +	/* pin & patch the relocs for one gather */ +	while (i < job->num_relocs) { +		struct host1x_reloc *reloc = &job->relocarray[i]; +		u32 reloc_addr = (job->reloc_addr_phys[i] + +			reloc->target_offset) >> reloc->shift; +		u32 *target; + +		/* skip all other gathers */ +		if (!(reloc->cmdbuf && cmdbuf == reloc->cmdbuf)) { +			i++; +			continue; +		} + +		if (last_page != reloc->cmdbuf_offset >> PAGE_SHIFT) { +			if (cmdbuf_page_addr) +				host1x_bo_kunmap(cmdbuf, last_page, +						 cmdbuf_page_addr); + +			cmdbuf_page_addr = host1x_bo_kmap(cmdbuf, +					reloc->cmdbuf_offset >> PAGE_SHIFT); +			last_page = reloc->cmdbuf_offset >> PAGE_SHIFT; + +			if (unlikely(!cmdbuf_page_addr)) { +				pr_err("Could not map cmdbuf for relocation\n"); +				return -ENOMEM; +			} +		} + +		target = cmdbuf_page_addr + (reloc->cmdbuf_offset & ~PAGE_MASK); +		*target = reloc_addr; + +		/* mark this gather as handled */ +		reloc->cmdbuf = 0; +	} + +	if (cmdbuf_page_addr) +		host1x_bo_kunmap(cmdbuf, last_page, cmdbuf_page_addr); + +	return 0; +} + +static int check_reloc(struct host1x_reloc *reloc, struct host1x_bo *cmdbuf, +		       unsigned int offset) +{ +	offset *= sizeof(u32); + +	if (reloc->cmdbuf != cmdbuf || reloc->cmdbuf_offset != offset) +		return -EINVAL; + +	return 0; +} + +struct host1x_firewall { +	struct host1x_job *job; +	struct device *dev; + +	unsigned int num_relocs; +	struct host1x_reloc *reloc; + +	struct host1x_bo *cmdbuf_id; +	unsigned int offset; + +	u32 words; +	u32 class; +	u32 reg; +	u32 mask; +	u32 count; +}; + +static int check_mask(struct host1x_firewall *fw) +{ +	u32 mask = fw->mask; +	u32 reg = fw->reg; + +	while (mask) { +		if (fw->words == 0) +			return -EINVAL; + +		if (mask & 1) { +			if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) { +				bool bad_reloc = check_reloc(fw->reloc, +							     fw->cmdbuf_id, +							     fw->offset); +				if (!fw->num_relocs || bad_reloc) +					return -EINVAL; +				fw->reloc++; +				fw->num_relocs--; +			} +			fw->words--; +			fw->offset++; +		} +		mask >>= 1; +		reg++; +	} + +	return 0; +} + +static int check_incr(struct host1x_firewall *fw) +{ +	u32 count = fw->count; +	u32 reg = fw->reg; + +	while (fw) { +		if (fw->words == 0) +			return -EINVAL; + +		if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) { +			bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id, +						     fw->offset); +			if (!fw->num_relocs || bad_reloc) +				return -EINVAL; +			fw->reloc++; +			fw->num_relocs--; +		} +		reg++; +		fw->words--; +		fw->offset++; +		count--; +	} + +	return 0; +} + +static int check_nonincr(struct host1x_firewall *fw) +{ +	int is_addr_reg = fw->job->is_addr_reg(fw->dev, fw->class, fw->reg); +	u32 count = fw->count; + +	while (count) { +		if (fw->words == 0) +			return -EINVAL; + +		if (is_addr_reg) { +			bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id, +						     fw->offset); +			if (!fw->num_relocs || bad_reloc) +				return -EINVAL; +			fw->reloc++; +			fw->num_relocs--; +		} +		fw->words--; +		fw->offset++; +		count--; +	} + +	return 0; +} + +static int validate(struct host1x_job *job, struct device *dev, +		    struct host1x_job_gather *g) +{ +	u32 *cmdbuf_base; +	int err = 0; +	struct host1x_firewall fw; + +	fw.job = job; +	fw.dev = dev; +	fw.reloc = job->relocarray; +	fw.num_relocs = job->num_relocs; +	fw.cmdbuf_id = g->bo; + +	fw.offset = 0; +	fw.class = 0; + +	if (!job->is_addr_reg) +		return 0; + +	cmdbuf_base = host1x_bo_mmap(g->bo); +	if (!cmdbuf_base) +		return -ENOMEM; + +	fw.words = g->words; +	while (fw.words && !err) { +		u32 word = cmdbuf_base[fw.offset]; +		u32 opcode = (word & 0xf0000000) >> 28; + +		fw.mask = 0; +		fw.reg = 0; +		fw.count = 0; +		fw.words--; +		fw.offset++; + +		switch (opcode) { +		case 0: +			fw.class = word >> 6 & 0x3ff; +			fw.mask = word & 0x3f; +			fw.reg = word >> 16 & 0xfff; +			err = check_mask(&fw); +			if (err) +				goto out; +			break; +		case 1: +			fw.reg = word >> 16 & 0xfff; +			fw.count = word & 0xffff; +			err = check_incr(&fw); +			if (err) +				goto out; +			break; + +		case 2: +			fw.reg = word >> 16 & 0xfff; +			fw.count = word & 0xffff; +			err = check_nonincr(&fw); +			if (err) +				goto out; +			break; + +		case 3: +			fw.mask = word & 0xffff; +			fw.reg = word >> 16 & 0xfff; +			err = check_mask(&fw); +			if (err) +				goto out; +			break; +		case 4: +		case 5: +		case 14: +			break; +		default: +			err = -EINVAL; +			break; +		} +	} + +	/* No relocs should remain at this point */ +	if (fw.num_relocs) +		err = -EINVAL; + +out: +	host1x_bo_munmap(g->bo, cmdbuf_base); + +	return err; +} + +static inline int copy_gathers(struct host1x_job *job, struct device *dev) +{ +	size_t size = 0; +	size_t offset = 0; +	int i; + +	for (i = 0; i < job->num_gathers; i++) { +		struct host1x_job_gather *g = &job->gathers[i]; +		size += g->words * sizeof(u32); +	} + +	job->gather_copy_mapped = dma_alloc_writecombine(dev, size, +							 &job->gather_copy, +							 GFP_KERNEL); +	if (!job->gather_copy_mapped) { +		int err = PTR_ERR(job->gather_copy_mapped); +		job->gather_copy_mapped = NULL; +		return err; +	} + +	job->gather_copy_size = size; + +	for (i = 0; i < job->num_gathers; i++) { +		struct host1x_job_gather *g = &job->gathers[i]; +		void *gather; + +		gather = host1x_bo_mmap(g->bo); +		memcpy(job->gather_copy_mapped + offset, gather + g->offset, +		       g->words * sizeof(u32)); +		host1x_bo_munmap(g->bo, gather); + +		g->base = job->gather_copy; +		g->offset = offset; +		g->bo = NULL; + +		offset += g->words * sizeof(u32); +	} + +	return 0; +} + +int host1x_job_pin(struct host1x_job *job, struct device *dev) +{ +	int err; +	unsigned int i, j; +	struct host1x *host = dev_get_drvdata(dev->parent); +	DECLARE_BITMAP(waitchk_mask, host1x_syncpt_nb_pts(host)); + +	bitmap_zero(waitchk_mask, host1x_syncpt_nb_pts(host)); +	for (i = 0; i < job->num_waitchk; i++) { +		u32 syncpt_id = job->waitchk[i].syncpt_id; +		if (syncpt_id < host1x_syncpt_nb_pts(host)) +			set_bit(syncpt_id, waitchk_mask); +	} + +	/* get current syncpt values for waitchk */ +	for_each_set_bit(i, waitchk_mask, host1x_syncpt_nb_pts(host)) +		host1x_syncpt_load(host->syncpt + i); + +	/* pin memory */ +	err = pin_job(job); +	if (!err) +		goto out; + +	/* patch gathers */ +	for (i = 0; i < job->num_gathers; i++) { +		struct host1x_job_gather *g = &job->gathers[i]; + +		/* process each gather mem only once */ +		if (g->handled) +			continue; + +		g->base = job->gather_addr_phys[i]; + +		for (j = 0; j < job->num_gathers; j++) +			if (job->gathers[j].bo == g->bo) +				job->gathers[j].handled = true; + +		err = 0; + +		if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL)) +			err = validate(job, dev, g); + +		if (err) +			dev_err(dev, "Job invalid (err=%d)\n", err); + +		if (!err) +			err = do_relocs(job, g->bo); + +		if (!err) +			err = do_waitchks(job, host, g->bo); + +		if (err) +			break; +	} + +	if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) && !err) { +		err = copy_gathers(job, dev); +		if (err) { +			host1x_job_unpin(job); +			return err; +		} +	} + +out: +	wmb(); + +	return err; +} + +void host1x_job_unpin(struct host1x_job *job) +{ +	unsigned int i; + +	for (i = 0; i < job->num_unpins; i++) { +		struct host1x_job_unpin_data *unpin = &job->unpins[i]; +		host1x_bo_unpin(unpin->bo, unpin->sgt); +		host1x_bo_put(unpin->bo); +	} +	job->num_unpins = 0; + +	if (job->gather_copy_size) +		dma_free_writecombine(job->channel->dev, job->gather_copy_size, +				      job->gather_copy_mapped, +				      job->gather_copy); +} + +/* + * Debug routine used to dump job entries + */ +void host1x_job_dump(struct device *dev, struct host1x_job *job) +{ +	dev_dbg(dev, "    SYNCPT_ID   %d\n", job->syncpt_id); +	dev_dbg(dev, "    SYNCPT_VAL  %d\n", job->syncpt_end); +	dev_dbg(dev, "    FIRST_GET   0x%x\n", job->first_get); +	dev_dbg(dev, "    TIMEOUT     %d\n", job->timeout); +	dev_dbg(dev, "    NUM_SLOTS   %d\n", job->num_slots); +	dev_dbg(dev, "    NUM_HANDLES %d\n", job->num_unpins); +} diff --git a/drivers/gpu/host1x/job.h b/drivers/gpu/host1x/job.h new file mode 100644 index 00000000000..fba45f20458 --- /dev/null +++ b/drivers/gpu/host1x/job.h @@ -0,0 +1,162 @@ +/* + * Tegra host1x Job + * + * Copyright (c) 2011-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_JOB_H +#define __HOST1X_JOB_H + +struct host1x_job_gather { +	u32 words; +	dma_addr_t base; +	struct host1x_bo *bo; +	int offset; +	bool handled; +}; + +struct host1x_cmdbuf { +	u32 handle; +	u32 offset; +	u32 words; +	u32 pad; +}; + +struct host1x_reloc { +	struct host1x_bo *cmdbuf; +	u32 cmdbuf_offset; +	struct host1x_bo *target; +	u32 target_offset; +	u32 shift; +	u32 pad; +}; + +struct host1x_waitchk { +	struct host1x_bo *bo; +	u32 offset; +	u32 syncpt_id; +	u32 thresh; +}; + +struct host1x_job_unpin_data { +	struct host1x_bo *bo; +	struct sg_table *sgt; +}; + +/* + * Each submit is tracked as a host1x_job. + */ +struct host1x_job { +	/* When refcount goes to zero, job can be freed */ +	struct kref ref; + +	/* List entry */ +	struct list_head list; + +	/* Channel where job is submitted to */ +	struct host1x_channel *channel; + +	u32 client; + +	/* Gathers and their memory */ +	struct host1x_job_gather *gathers; +	unsigned int num_gathers; + +	/* Wait checks to be processed at submit time */ +	struct host1x_waitchk *waitchk; +	unsigned int num_waitchk; +	u32 waitchk_mask; + +	/* Array of handles to be pinned & unpinned */ +	struct host1x_reloc *relocarray; +	unsigned int num_relocs; +	struct host1x_job_unpin_data *unpins; +	unsigned int num_unpins; + +	dma_addr_t *addr_phys; +	dma_addr_t *gather_addr_phys; +	dma_addr_t *reloc_addr_phys; + +	/* Sync point id, number of increments and end related to the submit */ +	u32 syncpt_id; +	u32 syncpt_incrs; +	u32 syncpt_end; + +	/* Maximum time to wait for this job */ +	unsigned int timeout; + +	/* Index and number of slots used in the push buffer */ +	unsigned int first_get; +	unsigned int num_slots; + +	/* Copy of gathers */ +	size_t gather_copy_size; +	dma_addr_t gather_copy; +	u8 *gather_copy_mapped; + +	/* Check if register is marked as an address reg */ +	int (*is_addr_reg)(struct device *dev, u32 reg, u32 class); + +	/* Request a SETCLASS to this class */ +	u32 class; + +	/* Add a channel wait for previous ops to complete */ +	bool serialize; +}; +/* + * Allocate memory for a job. Just enough memory will be allocated to + * accomodate the submit. + */ +struct host1x_job *host1x_job_alloc(struct host1x_channel *ch, +				    u32 num_cmdbufs, u32 num_relocs, +				    u32 num_waitchks); + +/* + * Add a gather to a job. + */ +void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *mem_id, +			   u32 words, u32 offset); + +/* + * Increment reference going to host1x_job. + */ +struct host1x_job *host1x_job_get(struct host1x_job *job); + +/* + * Decrement reference job, free if goes to zero. + */ +void host1x_job_put(struct host1x_job *job); + +/* + * Pin memory related to job. This handles relocation of addresses to the + * host1x address space. Handles both the gather memory and any other memory + * referred to from the gather buffers. + * + * Handles also patching out host waits that would wait for an expired sync + * point value. + */ +int host1x_job_pin(struct host1x_job *job, struct device *dev); + +/* + * Unpin memory related to job. + */ +void host1x_job_unpin(struct host1x_job *job); + +/* + * Dump contents of job to debug output. + */ +void host1x_job_dump(struct device *dev, struct host1x_job *job); + +#endif diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c new file mode 100644 index 00000000000..4b493453e80 --- /dev/null +++ b/drivers/gpu/host1x/syncpt.c @@ -0,0 +1,387 @@ +/* + * Tegra host1x Syncpoints + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <trace/events/host1x.h> + +#include "syncpt.h" +#include "dev.h" +#include "intr.h" +#include "debug.h" + +#define SYNCPT_CHECK_PERIOD (2 * HZ) +#define MAX_STUCK_CHECK_COUNT 15 + +static struct host1x_syncpt *_host1x_syncpt_alloc(struct host1x *host, +						  struct device *dev, +						  int client_managed) +{ +	int i; +	struct host1x_syncpt *sp = host->syncpt; +	char *name; + +	for (i = 0; i < host->info->nb_pts && sp->name; i++, sp++) +		; +	if (sp->dev) +		return NULL; + +	name = kasprintf(GFP_KERNEL, "%02d-%s", sp->id, +			dev ? dev_name(dev) : NULL); +	if (!name) +		return NULL; + +	sp->dev = dev; +	sp->name = name; +	sp->client_managed = client_managed; + +	return sp; +} + +u32 host1x_syncpt_id(struct host1x_syncpt *sp) +{ +	return sp->id; +} + +/* + * Updates the value sent to hardware. + */ +u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs) +{ +	return (u32)atomic_add_return(incrs, &sp->max_val); +} + + /* + * Write cached syncpoint and waitbase values to hardware. + */ +void host1x_syncpt_restore(struct host1x *host) +{ +	struct host1x_syncpt *sp_base = host->syncpt; +	u32 i; + +	for (i = 0; i < host1x_syncpt_nb_pts(host); i++) +		host1x_hw_syncpt_restore(host, sp_base + i); +	for (i = 0; i < host1x_syncpt_nb_bases(host); i++) +		host1x_hw_syncpt_restore_wait_base(host, sp_base + i); +	wmb(); +} + +/* + * Update the cached syncpoint and waitbase values by reading them + * from the registers. +  */ +void host1x_syncpt_save(struct host1x *host) +{ +	struct host1x_syncpt *sp_base = host->syncpt; +	u32 i; + +	for (i = 0; i < host1x_syncpt_nb_pts(host); i++) { +		if (host1x_syncpt_client_managed(sp_base + i)) +			host1x_hw_syncpt_load(host, sp_base + i); +		else +			WARN_ON(!host1x_syncpt_idle(sp_base + i)); +	} + +	for (i = 0; i < host1x_syncpt_nb_bases(host); i++) +		host1x_hw_syncpt_load_wait_base(host, sp_base + i); +} + +/* + * Updates the cached syncpoint value by reading a new value from the hardware + * register + */ +u32 host1x_syncpt_load(struct host1x_syncpt *sp) +{ +	u32 val; +	val = host1x_hw_syncpt_load(sp->host, sp); +	trace_host1x_syncpt_load_min(sp->id, val); + +	return val; +} + +/* + * Get the current syncpoint base + */ +u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp) +{ +	u32 val; +	host1x_hw_syncpt_load_wait_base(sp->host, sp); +	val = sp->base_val; +	return val; +} + +/* + * Write a cpu syncpoint increment to the hardware, without touching + * the cache. Caller is responsible for host being powered. + */ +void host1x_syncpt_cpu_incr(struct host1x_syncpt *sp) +{ +	host1x_hw_syncpt_cpu_incr(sp->host, sp); +} + +/* + * Increment syncpoint value from cpu, updating cache + */ +void host1x_syncpt_incr(struct host1x_syncpt *sp) +{ +	if (host1x_syncpt_client_managed(sp)) +		host1x_syncpt_incr_max(sp, 1); +	host1x_syncpt_cpu_incr(sp); +} + +/* + * Updated sync point form hardware, and returns true if syncpoint is expired, + * false if we may need to wait + */ +static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh) +{ +	host1x_hw_syncpt_load(sp->host, sp); +	return host1x_syncpt_is_expired(sp, thresh); +} + +/* + * Main entrypoint for syncpoint value waits. + */ +int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, +			u32 *value) +{ +	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); +	void *ref; +	struct host1x_waitlist *waiter; +	int err = 0, check_count = 0; +	u32 val; + +	if (value) +		*value = 0; + +	/* first check cache */ +	if (host1x_syncpt_is_expired(sp, thresh)) { +		if (value) +			*value = host1x_syncpt_load(sp); +		return 0; +	} + +	/* try to read from register */ +	val = host1x_hw_syncpt_load(sp->host, sp); +	if (host1x_syncpt_is_expired(sp, thresh)) { +		if (value) +			*value = val; +		goto done; +	} + +	if (!timeout) { +		err = -EAGAIN; +		goto done; +	} + +	/* allocate a waiter */ +	waiter = kzalloc(sizeof(*waiter), GFP_KERNEL); +	if (!waiter) { +		err = -ENOMEM; +		goto done; +	} + +	/* schedule a wakeup when the syncpoint value is reached */ +	err = host1x_intr_add_action(sp->host, sp->id, thresh, +				     HOST1X_INTR_ACTION_WAKEUP_INTERRUPTIBLE, +				     &wq, waiter, &ref); +	if (err) +		goto done; + +	err = -EAGAIN; +	/* Caller-specified timeout may be impractically low */ +	if (timeout < 0) +		timeout = LONG_MAX; + +	/* wait for the syncpoint, or timeout, or signal */ +	while (timeout) { +		long check = min_t(long, SYNCPT_CHECK_PERIOD, timeout); +		int remain = wait_event_interruptible_timeout(wq, +				syncpt_load_min_is_expired(sp, thresh), +				check); +		if (remain > 0 || host1x_syncpt_is_expired(sp, thresh)) { +			if (value) +				*value = host1x_syncpt_load(sp); +			err = 0; +			break; +		} +		if (remain < 0) { +			err = remain; +			break; +		} +		timeout -= check; +		if (timeout && check_count <= MAX_STUCK_CHECK_COUNT) { +			dev_warn(sp->host->dev, +				"%s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld\n", +				 current->comm, sp->id, sp->name, +				 thresh, timeout); + +			host1x_debug_dump_syncpts(sp->host); +			if (check_count == MAX_STUCK_CHECK_COUNT) +				host1x_debug_dump(sp->host); +			check_count++; +		} +	} +	host1x_intr_put_ref(sp->host, sp->id, ref); + +done: +	return err; +} +EXPORT_SYMBOL(host1x_syncpt_wait); + +/* + * Returns true if syncpoint is expired, false if we may need to wait + */ +bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh) +{ +	u32 current_val; +	u32 future_val; +	smp_rmb(); +	current_val = (u32)atomic_read(&sp->min_val); +	future_val = (u32)atomic_read(&sp->max_val); + +	/* Note the use of unsigned arithmetic here (mod 1<<32). +	 * +	 * c = current_val = min_val	= the current value of the syncpoint. +	 * t = thresh			= the value we are checking +	 * f = future_val  = max_val	= the value c will reach when all +	 *				  outstanding increments have completed. +	 * +	 * Note that c always chases f until it reaches f. +	 * +	 * Dtf = (f - t) +	 * Dtc = (c - t) +	 * +	 *  Consider all cases: +	 * +	 *	A) .....c..t..f.....	Dtf < Dtc	need to wait +	 *	B) .....c.....f..t..	Dtf > Dtc	expired +	 *	C) ..t..c.....f.....	Dtf > Dtc	expired	   (Dct very large) +	 * +	 *  Any case where f==c: always expired (for any t).	Dtf == Dcf +	 *  Any case where t==c: always expired (for any f).	Dtf >= Dtc (because Dtc==0) +	 *  Any case where t==f!=c: always wait.		Dtf <  Dtc (because Dtf==0, +	 *							Dtc!=0) +	 * +	 *  Other cases: +	 * +	 *	A) .....t..f..c.....	Dtf < Dtc	need to wait +	 *	A) .....f..c..t.....	Dtf < Dtc	need to wait +	 *	A) .....f..t..c.....	Dtf > Dtc	expired +	 * +	 *   So: +	 *	   Dtf >= Dtc implies EXPIRED	(return true) +	 *	   Dtf <  Dtc implies WAIT	(return false) +	 * +	 * Note: If t is expired then we *cannot* wait on it. We would wait +	 * forever (hang the system). +	 * +	 * Note: do NOT get clever and remove the -thresh from both sides. It +	 * is NOT the same. +	 * +	 * If future valueis zero, we have a client managed sync point. In that +	 * case we do a direct comparison. +	 */ +	if (!host1x_syncpt_client_managed(sp)) +		return future_val - thresh >= current_val - thresh; +	else +		return (s32)(current_val - thresh) >= 0; +} + +/* remove a wait pointed to by patch_addr */ +int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr) +{ +	return host1x_hw_syncpt_patch_wait(sp->host, sp, patch_addr); +} + +int host1x_syncpt_init(struct host1x *host) +{ +	struct host1x_syncpt *syncpt; +	int i; + +	syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts, +		GFP_KERNEL); +	if (!syncpt) +		return -ENOMEM; + +	for (i = 0; i < host->info->nb_pts; ++i) { +		syncpt[i].id = i; +		syncpt[i].host = host; +	} + +	host->syncpt = syncpt; + +	host1x_syncpt_restore(host); + +	/* Allocate sync point to use for clearing waits for expired fences */ +	host->nop_sp = _host1x_syncpt_alloc(host, NULL, 0); +	if (!host->nop_sp) +		return -ENOMEM; + +	return 0; +} + +struct host1x_syncpt *host1x_syncpt_request(struct device *dev, +					    int client_managed) +{ +	struct host1x *host = dev_get_drvdata(dev->parent); +	return _host1x_syncpt_alloc(host, dev, client_managed); +} + +void host1x_syncpt_free(struct host1x_syncpt *sp) +{ +	if (!sp) +		return; + +	kfree(sp->name); +	sp->dev = NULL; +	sp->name = NULL; +	sp->client_managed = 0; +} + +void host1x_syncpt_deinit(struct host1x *host) +{ +	int i; +	struct host1x_syncpt *sp = host->syncpt; +	for (i = 0; i < host->info->nb_pts; i++, sp++) +		kfree(sp->name); +} + +int host1x_syncpt_nb_pts(struct host1x *host) +{ +	return host->info->nb_pts; +} + +int host1x_syncpt_nb_bases(struct host1x *host) +{ +	return host->info->nb_bases; +} + +int host1x_syncpt_nb_mlocks(struct host1x *host) +{ +	return host->info->nb_mlocks; +} + +struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id) +{ +	if (host->info->nb_pts < id) +		return NULL; +	return host->syncpt + id; +} diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h new file mode 100644 index 00000000000..c99806130f2 --- /dev/null +++ b/drivers/gpu/host1x/syncpt.h @@ -0,0 +1,165 @@ +/* + * Tegra host1x Syncpoints + * + * Copyright (c) 2010-2013, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __HOST1X_SYNCPT_H +#define __HOST1X_SYNCPT_H + +#include <linux/atomic.h> +#include <linux/kernel.h> +#include <linux/sched.h> + +#include "intr.h" + +struct host1x; + +/* Reserved for replacing an expired wait with a NOP */ +#define HOST1X_SYNCPT_RESERVED			0 + +struct host1x_syncpt { +	int id; +	atomic_t min_val; +	atomic_t max_val; +	u32 base_val; +	const char *name; +	int client_managed; +	struct host1x *host; +	struct device *dev; + +	/* interrupt data */ +	struct host1x_syncpt_intr intr; +}; + +/* Initialize sync point array  */ +int host1x_syncpt_init(struct host1x *host); + +/*  Free sync point array */ +void host1x_syncpt_deinit(struct host1x *host); + +/* + * Read max. It indicates how many operations there are in queue, either in + * channel or in a software thread. + * */ +static inline u32 host1x_syncpt_read_max(struct host1x_syncpt *sp) +{ +	smp_rmb(); +	return (u32)atomic_read(&sp->max_val); +} + +/* + * Read min, which is a shadow of the current sync point value in hardware. + */ +static inline u32 host1x_syncpt_read_min(struct host1x_syncpt *sp) +{ +	smp_rmb(); +	return (u32)atomic_read(&sp->min_val); +} + +/* Return number of sync point supported. */ +int host1x_syncpt_nb_pts(struct host1x *host); + +/* Return number of wait bases supported. */ +int host1x_syncpt_nb_bases(struct host1x *host); + +/* Return number of mlocks supported. */ +int host1x_syncpt_nb_mlocks(struct host1x *host); + +/* + * Check sync point sanity. If max is larger than min, there have too many + * sync point increments. + * + * Client managed sync point are not tracked. + * */ +static inline bool host1x_syncpt_check_max(struct host1x_syncpt *sp, u32 real) +{ +	u32 max; +	if (sp->client_managed) +		return true; +	max = host1x_syncpt_read_max(sp); +	return (s32)(max - real) >= 0; +} + +/* Return true if sync point is client managed. */ +static inline int host1x_syncpt_client_managed(struct host1x_syncpt *sp) +{ +	return sp->client_managed; +} + +/* + * Returns true if syncpoint min == max, which means that there are no + * outstanding operations. + */ +static inline bool host1x_syncpt_idle(struct host1x_syncpt *sp) +{ +	int min, max; +	smp_rmb(); +	min = atomic_read(&sp->min_val); +	max = atomic_read(&sp->max_val); +	return (min == max); +} + +/* Return pointer to struct denoting sync point id. */ +struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id); + +/* Request incrementing a sync point. */ +void host1x_syncpt_cpu_incr(struct host1x_syncpt *sp); + +/* Load current value from hardware to the shadow register. */ +u32 host1x_syncpt_load(struct host1x_syncpt *sp); + +/* Check if the given syncpoint value has already passed */ +bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh); + +/* Save host1x sync point state into shadow registers. */ +void host1x_syncpt_save(struct host1x *host); + +/* Reset host1x sync point state from shadow registers. */ +void host1x_syncpt_restore(struct host1x *host); + +/* Read current wait base value into shadow register and return it. */ +u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp); + +/* Increment sync point and its max. */ +void host1x_syncpt_incr(struct host1x_syncpt *sp); + +/* Indicate future operations by incrementing the sync point max. */ +u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs); + +/* Wait until sync point reaches a threshold value, or a timeout. */ +int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, +			long timeout, u32 *value); + +/* Check if sync point id is valid. */ +static inline int host1x_syncpt_is_valid(struct host1x_syncpt *sp) +{ +	return sp->id < host1x_syncpt_nb_pts(sp->host); +} + +/* Patch a wait by replacing it with a wait for syncpt 0 value 0 */ +int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr); + +/* Return id of the sync point */ +u32 host1x_syncpt_id(struct host1x_syncpt *sp); + +/* Allocate a sync point for a device. */ +struct host1x_syncpt *host1x_syncpt_request(struct device *dev, +		int client_managed); + +/* Free a sync point. */ +void host1x_syncpt_free(struct host1x_syncpt *sp); + +#endif  |