1252 lines
33 KiB
C
1252 lines
33 KiB
C
/*
|
|
* drivers/video/tegra/dc/dc_common.c
|
|
*
|
|
* Copyright (c) 2017-2020, NVIDIA CORPORATION, All rights reserved.
|
|
* Author: Arun Swain <arswain@nvidia.com>
|
|
*
|
|
* 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/pm_runtime.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/delay.h>
|
|
#include "dc.h"
|
|
#include "dc_reg.h"
|
|
#include "dc_priv.h"
|
|
#include <linux/nvhost.h>
|
|
#include "dc_common.h"
|
|
#include "nvhost_channel.h"
|
|
#include "nvhost_job.h"
|
|
#include "host1x/host1x03_hardware.h"
|
|
#include <trace/events/display.h>
|
|
|
|
#define CMDBUF_SIZE 128
|
|
#define NV_DISPLAY_CLASS_ID 0x70
|
|
#define NVDISP_HEAD_WORD_OFFSET 0x4000
|
|
#define T210_HEAD_WORD_OFFSET 0x10000
|
|
#define HEAD_WORD_OFFSET head_offset
|
|
|
|
#define WAIT_TYPE_PROGRAM_REG (1<<0)
|
|
#define WAIT_TYPE_CHECK_GEN_ACT_PROMOTION (1<<1)
|
|
#define WRITE_READ_MUX_ACTIVE (0x05)
|
|
#define WRITE_MUX_ACTIVE_READ_MUX_ASSEMBLY (0x01)
|
|
/**
|
|
* __nvhost_opcode_nonincr_write_reg - Fill the command buffer with
|
|
* host1x opcode and value.
|
|
* @buff: the command buffer to filled by dc_common
|
|
* @len: length to keep track of the no. of words written to @buff
|
|
* @offset: register offset host1x to write to.
|
|
* @value: value to be written.
|
|
*
|
|
* __nvhost_opcode_nonincr_write_reg prepares the command buffer.
|
|
*/
|
|
#define __nvhost_opcode_nonincr_write_reg(buff, len, offset, value) \
|
|
({ \
|
|
buff[len++] = (9 << 28) | 1; \
|
|
buff[len++] = (11 << 28) | offset; \
|
|
buff[len++] = value; \
|
|
})
|
|
|
|
/**
|
|
* __received_bit_set - Sets a particular bit
|
|
* @nr: bit number to be set
|
|
* @addr: address of destination variable
|
|
*/
|
|
#define __received_bit_set(nr, addr) __set_bit(nr, addr)
|
|
|
|
/**
|
|
* __received_bit_clear - Clears a particular bit
|
|
* @nr: bit number to be cleared
|
|
* @addr: address of destination variable
|
|
*/
|
|
#define __received_bit_clear(nr, addr) __clear_bit(nr, addr)
|
|
|
|
#define __all_req_rcvd(req_rcvd, valid_heads) \
|
|
(req_rcvd == valid_heads)
|
|
/**
|
|
* __get_word_offset - gives the word offset for a register
|
|
* @offset: word offset from @HEAD_WORD_OFFSET
|
|
* @head_id: head no.
|
|
*/
|
|
#define __get_word_offset(offset, head_id) \
|
|
(offset + head_id * HEAD_WORD_OFFSET)
|
|
|
|
/**
|
|
* __get_byte_offset - gives the byte offset for a register
|
|
* @offset: word offset from @HEAD_WORD_OFFSET
|
|
* @head_id: head no.
|
|
*/
|
|
#define __get_byte_offset(offset, head_id) \
|
|
(__get_word_offset(offset, head_id) * 4)
|
|
|
|
/**
|
|
* __wait_timed_out - Checks if the process woke up because of timeout.
|
|
* @req_rcvd: the bit-mapped data for received requests.
|
|
* @valid_heads: the bit mapped data for heads ids involved in framelock.
|
|
* @addr: address of @dc_common->flags
|
|
* @bit: bit to be checked in the flags. Can be either
|
|
* DC_COMMON_JOB_SUBMITTED or DC_COMMON_ERROR_CHECK_DONE.
|
|
*/
|
|
#define __wait_timed_out(req_rcvd, valid_heads, addr, bit) \
|
|
(!__all_req_rcvd(req_rcvd, valid_heads) && \
|
|
!(test_bit(bit, addr)))
|
|
|
|
/**
|
|
* __valid_request - Checks if the request received from a head is valid.
|
|
* @valid_heads: the bit mapped data for heads ids involved in framelock.
|
|
* @head_id: head id of the caller head.
|
|
*/
|
|
#define __valid_request(valid_heads, head_id) \
|
|
(valid_heads & (0x01 << head_id))
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct of_device_id tegra_display_common_of_match[] = {
|
|
{.compatible = "nvidia,tegra_dc_common", },
|
|
{ },
|
|
};
|
|
#endif
|
|
|
|
static int max_heads;
|
|
static int head_offset;
|
|
static struct tegra_dc_common *dc_common;
|
|
static bool probe_success;
|
|
|
|
/**
|
|
* tegra_dc_common_probe_status - Returns status
|
|
* of dc_common module probe.
|
|
*/
|
|
bool tegra_dc_common_probe_status(void)
|
|
{
|
|
return probe_success;
|
|
}
|
|
|
|
/**
|
|
* tegra_dc_common_get_imp_table - Returns a pointer
|
|
* to global imp table if provided in device tree.
|
|
*/
|
|
struct nvdisp_imp_table *tegra_dc_common_get_imp_table(void)
|
|
{
|
|
if (!dc_common)
|
|
return NULL;
|
|
return dc_common->imp_table;
|
|
}
|
|
/**
|
|
* dc_common_channel_submit_gather - prepares/submits the hsot1x job and
|
|
* waits for completion too.
|
|
* @dsp_cmd_reg: Word offset of DC_CMD_STATE_CONTROL from @HEAD_WORD_OFFSET.
|
|
* @dsp_cmd_state_access_reg: Word offset of DC_CMD_STATE_ACCESS from
|
|
* @HEAD_WORD_OFFSET.
|
|
* @lock_type: Could be frame_lock or flip_lock
|
|
*
|
|
* Fills the command buffer, gathers the address, submits the job, and
|
|
* waits for the job completion. It programs the immediate syncpt condition
|
|
* and asks host1x to submit the job immediately for this channel.
|
|
*
|
|
* Return: 0 if no errors else corresponding error value.
|
|
*/
|
|
static inline int _dc_common_channel_submit_gather(ulong dsp_cmd_reg,
|
|
ulong dsp_cmd_state_access_reg, int lock_type)
|
|
{
|
|
int i;
|
|
int ret;
|
|
int ofst;
|
|
int head_id = 0;
|
|
u32 buf_length = 0;
|
|
struct nvhost_job *job;
|
|
struct nvhost_device_data *pdata;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
pdata = platform_get_drvdata(dc_common->pdev);
|
|
|
|
if (lock_type == LOCK_TYPE_FLIP) {
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
head_id = i;
|
|
ofst = __get_word_offset(dsp_cmd_reg, i);
|
|
__nvhost_opcode_nonincr_write_reg(dc_common->cpuvaddr,
|
|
buf_length, ofst, dc_common->upd_val[i]);
|
|
}
|
|
} else {
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
head_id = i;
|
|
ofst = __get_word_offset(dsp_cmd_state_access_reg, i);
|
|
__nvhost_opcode_nonincr_write_reg(dc_common->cpuvaddr,
|
|
buf_length, ofst, WRITE_READ_MUX_ACTIVE);
|
|
|
|
ofst = __get_word_offset(dsp_cmd_reg, i);
|
|
__nvhost_opcode_nonincr_write_reg(dc_common->cpuvaddr,
|
|
buf_length, ofst,
|
|
dc_common->dsp_cmd_reg_val[i]);
|
|
|
|
ofst = __get_word_offset(dsp_cmd_state_access_reg, i);
|
|
__nvhost_opcode_nonincr_write_reg(dc_common->cpuvaddr,
|
|
buf_length, ofst,
|
|
WRITE_MUX_ACTIVE_READ_MUX_ASSEMBLY);
|
|
|
|
}
|
|
}
|
|
|
|
ofst = __get_word_offset(DC_CMD_GENERAL_INCR_SYNCPT, head_id);
|
|
__nvhost_opcode_nonincr_write_reg(dc_common->cpuvaddr, buf_length, ofst,
|
|
IMMEDIATE_COND | dc_common->syncpt_id);
|
|
|
|
job = nvhost_job_alloc(dc_common->channel, 1, 0, 0, 1);
|
|
if (!job) {
|
|
dev_err(&dc_common->pdev->dev, "failed to allocate job\n");
|
|
ret = -ENOMEM;
|
|
goto err_handle;
|
|
}
|
|
|
|
trace_host1x_job_allocated(dc_common, &dc_common->head_data, -1);
|
|
|
|
job->sp->id = dc_common->syncpt_id;
|
|
job->sp->incrs = 1;
|
|
job->num_syncpts = 1;
|
|
|
|
nvhost_job_add_client_gather_address(job, buf_length,
|
|
pdata->class, dc_common->dma_handle);
|
|
|
|
ret = nvhost_channel_submit(job);
|
|
if (ret)
|
|
goto err_handle_free_job;
|
|
|
|
trace_host1x_job_submitted(dc_common, &dc_common->head_data, -1);
|
|
|
|
nvhost_syncpt_wait_timeout_ext(dc_common->pdev, dc_common->syncpt_id,
|
|
job->sp->fence, (u32)MAX_SCHEDULE_TIMEOUT,
|
|
NULL, NULL);
|
|
|
|
trace_host1x_job_executed(dc_common, &dc_common->head_data, -1);
|
|
return 0;
|
|
|
|
err_handle_free_job:
|
|
nvhost_job_put(job);
|
|
job = NULL;
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev,
|
|
"%s: gather and job submit failed\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* _dc_common_wait - Puts the process into wait queue based on a conditon
|
|
* and after the process wakes up checks if it's a
|
|
* gracefull wake-up or because of timeout.
|
|
* @head_id: Head id no.
|
|
*
|
|
* Puts the requesting process in the corresponding wait_queue as
|
|
* TASK_UNINTERRUPTIBLE when it waits. The process waits as long as dc_common
|
|
* hasn't received requests from all the heads or if time-out happens. In case
|
|
* of the latter, it lets the caller know after clearing the corresponding
|
|
* head's bit from @fl_lck_req_rcvd.
|
|
*
|
|
* Static inline to reduce overheads. Might slightly increase the code
|
|
* memory footprint. But even readablity inreases a bit.
|
|
*
|
|
* Return: -ETIME if timeout happens else 0. -EINVAL if @wait_type is invalid.
|
|
*/
|
|
static inline int _dc_common_wait(int head_id)
|
|
{
|
|
int ret = 0;
|
|
|
|
___wait_event(dc_common->prgrm_reg_reqs_wq,
|
|
___wait_cond_timeout(__all_req_rcvd(
|
|
dc_common->head_data.fl_lck_req_rcvd,
|
|
dc_common->valid_heads)), TASK_UNINTERRUPTIBLE,
|
|
0, msecs_to_jiffies(6000),
|
|
mutex_unlock(&dc_common->lock);
|
|
__ret = schedule_timeout(__ret);
|
|
mutex_lock(&dc_common->lock));
|
|
|
|
if (__wait_timed_out(dc_common->head_data.fl_lck_req_rcvd,
|
|
dc_common->valid_heads, &dc_common->flags,
|
|
DC_COMMON_JOB_SUBMITTED)) {
|
|
/* the body of "if" starts here. */
|
|
ret = -ETIME;
|
|
__received_bit_clear(
|
|
head_id, &dc_common->head_data.fl_lck_req_rcvd);
|
|
} /* End of if(__wait_timed_out)*/
|
|
|
|
return ret;
|
|
}
|
|
/**
|
|
* _tegra_dc_common_sync_frames - Internal function to synchronize frames of
|
|
* multiple heads participating in frame lock.
|
|
* @dc: Pointer to struct tegra_dc.
|
|
* @dsp_cmd_reg: Word offset of DC_CMD_STATE_CONTROL from @HEAD_WORD_OFFSET.
|
|
* @dsp_cmd_state_access_reg: Word offset of DC_CMD_STATE_ACCESS from
|
|
* @HEAD_WORD_OFFSET.
|
|
* @dsp_cmd_reg_val: Value to be written to DC_CMD_STATE_CONTROL.
|
|
* @cmd_st_accss_reg_val: Value to be written to DC_CMD_STATE_ACCESS.
|
|
*
|
|
* On T186 it uses host1x pushbuffers to start the heads at the same time.
|
|
*/
|
|
static int _tegra_dc_common_sync_frames(struct tegra_dc *dc, ulong dsp_cmd_reg,
|
|
ulong dsp_cmd_state_access_reg, ulong dsp_cmd_reg_val)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
|
|
__received_bit_set(dc->ctrl_num, &dc_common->head_data.fr_lck_req_rcvd);
|
|
|
|
trace_received_fr_lock_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
|
|
dc_common->dsp_cmd_reg_val[dc->ctrl_num] = dsp_cmd_reg_val;
|
|
|
|
if (tegra_dc_is_t19x()) {
|
|
ret = nvdisp_t19x_program_raster_lock_seq(dc, dsp_cmd_reg_val);
|
|
if (ret)
|
|
goto err_handle;
|
|
}
|
|
|
|
if (!__all_req_rcvd(dc_common->head_data.fr_lck_req_rcvd,
|
|
dc_common->valid_heads))
|
|
return 0;
|
|
|
|
if (tegra_dc_is_t19x())
|
|
nvdisp_t19x_enable_raster_lock(dc, dc_common->valid_heads);
|
|
else
|
|
ret = _dc_common_channel_submit_gather(dsp_cmd_reg,
|
|
dsp_cmd_state_access_reg, LOCK_TYPE_FRAME);
|
|
if (ret)
|
|
goto err_handle;
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads)
|
|
__received_bit_clear(i, &dc_common->head_data.fr_lck_req_rcvd);
|
|
|
|
dc_common->heads_locked = true;
|
|
|
|
#if 0
|
|
/* For Debug*/
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
int value;
|
|
|
|
dc_head = tegra_dc_get_dc(i);
|
|
value = tegra_dc_get_v_count(dc_head);
|
|
}
|
|
#endif
|
|
trace_completed_fr_lock_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev, "%s: failed to sync flips\n", __func__);
|
|
return ret;
|
|
}
|
|
/**
|
|
* tegra_dc_common_sync_frames - Fucntion exposed to dc heads participating in
|
|
* frame lock to lock their rasters.
|
|
* @dc: Pointer to struct tegra_dc.
|
|
* @dsp_cmd_reg: Word offset of DC_CMD_STATE_CONTROL from @HEAD_WORD_OFFSET.
|
|
* @dsp_cmd_state_access_reg: Word offset of DC_CMD_STATE_ACCESS from
|
|
* @HEAD_WORD_OFFSET.
|
|
* @dsp_cmd_reg_val: Value to be written to DC_CMD_STATE_CONTROL.
|
|
*
|
|
* On T186 it uses host1x pushbuffers to start the heads at the same time.
|
|
*
|
|
*/
|
|
|
|
int tegra_dc_common_sync_frames(struct tegra_dc *dc, ulong dsp_cmd_reg,
|
|
ulong dsp_cmd_state_access_reg, ulong dsp_cmd_reg_val)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dc_common || !dc)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
|
|
if (!__valid_request(dc_common->valid_heads, dc->ctrl_num)) {
|
|
mutex_unlock(&dc_common->lock);
|
|
ret = -EINVAL;
|
|
goto err_handle;
|
|
}
|
|
ret = _tegra_dc_common_sync_frames(dc, dsp_cmd_reg,
|
|
dsp_cmd_state_access_reg, dsp_cmd_reg_val);
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
if (ret)
|
|
goto err_handle;
|
|
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev, "%s: failed to sync frames for tegradc:%d\n",
|
|
__func__, dc->ctrl_num);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_common_sync_frames);
|
|
|
|
/**
|
|
* _tegra_dc_common_sync_flips- Internal function to process requests for flip
|
|
* lock. This is acheived through programming
|
|
* DC_CMD_STATE_CONTROL through host1x for
|
|
* "almost" atomic register writes.
|
|
* @dc: Pointer to the caller head's tegra_dc struct.
|
|
* @upd_val: Value to be written to the head's DC_CMD_STATE_CONTROL reg.
|
|
* @reg: Word offset of DC_CMD_STATE_CONTROL from @HEAD_WORD_OFFSET.
|
|
*
|
|
* Blocks the requesting processes until all the heads have requested. Submits
|
|
* the host1x job of wrting to the DC_CMD_STATE_CONTROL of mulitple heads
|
|
* through the last process that calls @tegra_dc_common_sync_flips().Currently
|
|
* also handles the case of just 1 head participating in frame-lock for debug
|
|
* purpose.
|
|
*
|
|
* Return: 0 if successful else the corresponding error value.
|
|
*/
|
|
static int _tegra_dc_common_sync_flips(struct tegra_dc *dc, ulong upd_val,
|
|
ulong reg)
|
|
{
|
|
int ret;
|
|
|
|
__received_bit_set(dc->ctrl_num, &dc_common->head_data.fl_lck_req_rcvd);
|
|
dc_common->upd_val[dc->ctrl_num] = upd_val;
|
|
|
|
trace_received_fl_lock_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
|
|
ret = _dc_common_wait(dc->ctrl_num);
|
|
if (ret)
|
|
goto err_handle;
|
|
|
|
if (!dc_common->head_data.fl_lck_req_completed) {
|
|
dc_common->head_data.fl_lck_req_completed =
|
|
dc_common->valid_heads;
|
|
wake_up(&dc_common->prgrm_reg_reqs_wq);
|
|
}
|
|
|
|
__received_bit_clear(dc->ctrl_num,
|
|
&dc_common->head_data.fl_lck_req_completed);
|
|
|
|
if (test_bit(DC_COMMON_JOB_SUBMITTED, &dc_common->flags)) {
|
|
if (!dc_common->head_data.fl_lck_req_completed) {
|
|
__clear_bit(DC_COMMON_JOB_SUBMITTED, &dc_common->flags);
|
|
dc_common->head_data.fl_lck_req_rcvd = 0UL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ret = _dc_common_channel_submit_gather(reg, 0UL, LOCK_TYPE_FLIP);
|
|
if (ret)
|
|
goto err_handle;
|
|
|
|
if (dc_common->head_data.fl_lck_req_completed) {
|
|
__set_bit(DC_COMMON_JOB_SUBMITTED, &dc_common->flags);
|
|
__set_bit(DC_COMMON_ERROR_CHECK_REQ, &dc_common->flags);
|
|
} else {
|
|
dev_info(&dc_common->pdev->dev,
|
|
"%s: Need not have frame/flip lock with just one head\n",
|
|
__func__);
|
|
dc_common->head_data.fl_lck_req_rcvd = 0UL;
|
|
}
|
|
|
|
trace_completed_fl_lock_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
|
|
#if 0
|
|
/* For Debugf*/
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
struct tegra_dc *dc_head;
|
|
int value;
|
|
|
|
dc_head = tegra_dc_get_dc(i);
|
|
value = tegra_dc_get_v_count(dc_head);
|
|
}
|
|
#endif
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev, "%s: failed to program gen_act for tegradc:%d\n",
|
|
__func__, dc->ctrl_num);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tegra_dc_common_sync_flips- Function exposed to dc heads participating in
|
|
* flip lock to sync their flips. This is acheived
|
|
* through programming DC_CMD_STATE_CONTROL
|
|
* through host1x for "almost" atomic register
|
|
* writes.
|
|
* @dc: Pointer to the caller head's tegra_dc struct.
|
|
* @upd_val: Value to be written to the head's DC_CMD_STATE_CONTROL reg.
|
|
* @reg: Word offset of DC_CMD_STATE_CONTROL from @HEAD_WORD_OFFSET.
|
|
*
|
|
* Please refer to _tegra_dc_common_sync_flips a bit detail description of the
|
|
* algorithm used.
|
|
*
|
|
* Return: 0 if successful else the corresponding error value.
|
|
*/
|
|
|
|
int tegra_dc_common_sync_flips(struct tegra_dc *dc, ulong val, ulong reg)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dc_common || !dc)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
|
|
if (!__valid_request(dc_common->valid_heads, dc->ctrl_num)) {
|
|
mutex_unlock(&dc_common->lock);
|
|
ret = -EINVAL;
|
|
goto err_handle;
|
|
}
|
|
|
|
if (test_bit(DC_COMMON_JOB_SUBMITTED, &dc_common->flags)) {
|
|
do {
|
|
mutex_unlock(&dc_common->lock);
|
|
udelay(10);
|
|
mutex_lock(&dc_common->lock);
|
|
} while (test_bit(DC_COMMON_JOB_SUBMITTED, &dc_common->flags));
|
|
}
|
|
|
|
ret = _tegra_dc_common_sync_flips(dc, val, reg);
|
|
if (ret) {
|
|
mutex_unlock(&dc_common->lock);
|
|
goto err_handle;
|
|
}
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev,
|
|
"%s: failed to sync flips for tegradc:%d\n", __func__,
|
|
dc->ctrl_num);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_common_sync_flips);
|
|
|
|
/**
|
|
* dc_common_get_gen_act_status - Reads the DC_CMD_STATE_CONTROL register
|
|
* for all the heads that are frame locked
|
|
* and figures out if any of the GEN_ACT_REQ
|
|
* is not promoted.
|
|
* @dc_common: Pointer to struct tegra_dc_common.
|
|
* @reg: The register value to be read. Mostly DC_CMD_STATE_CONTROL.
|
|
*
|
|
* If any of the gen_act_req is not promoted then it returns error.
|
|
*
|
|
* Return: 0 if successful else -1.
|
|
*/
|
|
static int dc_common_get_gen_act_status(struct tegra_dc_common *dc_common,
|
|
unsigned long reg)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
int val = 0;
|
|
unsigned long res = 0x00;
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
int offset = __get_byte_offset(reg, i);
|
|
val = readl(dc_common->base + offset);
|
|
if (!(val & GENERAL_ACT_REQ))
|
|
__set_bit(i, &res);
|
|
}
|
|
dc_common->head_data.gen_act_read_result = res;
|
|
|
|
if (res == dc_common->valid_heads)
|
|
ret = 0;
|
|
else
|
|
ret = -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tegra_dc_common_handle_flip_lock_error - Checks for error in flip lock.
|
|
* @dc: Pointer to the caller head's tegra_dc struct.
|
|
*
|
|
* tegra_dc_common_handle_flip_lock_error figures if writing to multiple
|
|
* head's DC_CMD_STATE_CONTROL registers still straddled across vsync. If
|
|
* so then it stalls the heads to make sure that all the heads are flip-locked
|
|
* from the next frame onwards. It does the error check only if
|
|
* @DC_COMMON_ERROR_CHECK_REQ flag is set. @DC_COMMON_ERROR_CHECK_REQ is set
|
|
* after submitting a host1x job for syncing flips successfully.
|
|
*
|
|
* TODO : Inform usersapce to drop frames to avoid bubbles in the graphics
|
|
* pipeline.
|
|
*
|
|
* Return: -ENODEV/-EINVAL/-EAGAIN from this function. Other error codes
|
|
* depending on the function called from here.
|
|
*/
|
|
int tegra_dc_common_handle_flip_lock_error(struct tegra_dc *dc)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
struct tegra_dc *dc_head = NULL;
|
|
|
|
if (!dc_common || !dc)
|
|
return -ENODEV;
|
|
|
|
trace_received_err_check_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
mutex_lock(&dc_common->lock);
|
|
|
|
if (!__valid_request(dc_common->valid_heads, dc->ctrl_num) ||
|
|
!dc_common->heads_locked) {
|
|
mutex_unlock(&dc_common->lock);
|
|
ret = -EINVAL;
|
|
goto err_handle;
|
|
}
|
|
|
|
if (!test_bit(DC_COMMON_ERROR_CHECK_REQ, &dc_common->flags)) {
|
|
mutex_unlock(&dc_common->lock);
|
|
return ret;
|
|
}
|
|
|
|
if (dc_common_get_gen_act_status(dc_common, DC_CMD_STATE_CONTROL)) {
|
|
ret = -EAGAIN;
|
|
mutex_unlock(&dc_common->lock);
|
|
return ret;
|
|
}
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
dc_head = tegra_dc_get_dc(i);
|
|
if (!dc_head) {
|
|
mutex_unlock(&dc_common->lock);
|
|
ret = -ENODEV;
|
|
goto err_handle;
|
|
}
|
|
tegra_dc_request_trigger_wins(dc_head);
|
|
}
|
|
|
|
__clear_bit(DC_COMMON_ERROR_CHECK_REQ, &dc_common->flags);
|
|
|
|
trace_completed_err_check_request(dc_common, &dc_common->head_data,
|
|
dc->ctrl_num);
|
|
mutex_unlock(&dc_common->lock);
|
|
return 0;
|
|
|
|
err_handle:
|
|
if (ret)
|
|
dev_err(&dc_common->pdev->dev,
|
|
"%s: failed to sync frames\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_common_handle_flip_lock_error);
|
|
|
|
/**
|
|
* _tegra_dc_common_enable_frame_lock - Enables framelock in all the
|
|
* participating heads.
|
|
* @dc_common: Pointer to struct tegra_dc_common.
|
|
*
|
|
* Expects the locking/unlocking of mutex to be handled outside this
|
|
* function.
|
|
*
|
|
* Return: -EINVAL if the any of the heads are unreachable.
|
|
*/
|
|
static int _tegra_dc_common_enable_frame_lock(struct tegra_dc_common *dc_common)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
unsigned long frame_lock = 0;
|
|
struct tegra_dc *dc = NULL;
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
dc = tegra_dc_get_dc(i);
|
|
if (!dc)
|
|
break;
|
|
tegra_dc_enable_disable_frame_lock(dc, true);
|
|
__set_bit(i, &frame_lock);
|
|
}
|
|
|
|
if (frame_lock != dc_common->valid_heads) {
|
|
for_each_set_bit(i, &frame_lock, max_heads) {
|
|
dc = tegra_dc_get_dc(i);
|
|
if (dc)
|
|
tegra_dc_enable_disable_frame_lock(dc, false);
|
|
}
|
|
ret = -EINVAL;
|
|
goto err_handle;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev,
|
|
"%s: failed to enable frame lock\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* _tegra_dc_common_disable_frame_lock - Disables frame/flip lock in all the
|
|
* participating heads and in dc_common.
|
|
* @dc_common: Pointer to struct tegra_dc_common.
|
|
*
|
|
* Expects the locking/unlocking of mutex to be handled outside this
|
|
* function. Also wakes up any pending processes in the wait queues.
|
|
*
|
|
* Return: -EINVAL if the any of the heads are unreachable.
|
|
*/
|
|
static int _tegra_dc_common_disable_frame_lock(
|
|
struct tegra_dc_common *dc_common)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
unsigned long frame_lock = 0;
|
|
struct tegra_dc *dc = NULL;
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
dc = tegra_dc_get_dc(i);
|
|
if (!dc)
|
|
break;
|
|
tegra_dc_enable_disable_frame_lock(dc, false);
|
|
__set_bit(i, &frame_lock);
|
|
}
|
|
|
|
if (frame_lock != dc_common->valid_heads) {
|
|
for_each_set_bit(i, &frame_lock, max_heads) {
|
|
dc = tegra_dc_get_dc(i);
|
|
if (dc)
|
|
tegra_dc_enable_disable_frame_lock(dc, true);
|
|
}
|
|
ret = -EINVAL;
|
|
goto err_handle;
|
|
}
|
|
|
|
dc_common->heads_locked = false;
|
|
dc_common->head_data.fr_lck_req_rcvd = 0UL;
|
|
if (dc_common->head_data.fl_lck_req_rcvd)
|
|
wake_up(&dc_common->prgrm_reg_reqs_wq);
|
|
|
|
return 0;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev,
|
|
"%s: failed to disable frame lock with ret val :%d \n",
|
|
__func__, ret);
|
|
return ret;
|
|
|
|
}
|
|
|
|
/**
|
|
* _is_frame_lock_enable - Checks if current state of frame/flip lock.
|
|
* @dc_common: Pointer to struct tegra_dc_common.
|
|
*
|
|
* Expects the locking/unlocking of mutex to be handled outside this
|
|
* function.
|
|
*
|
|
* Return: True if frame/flip lock is enabled else False otherwise.
|
|
*/
|
|
static bool _is_frame_lock_enable(struct tegra_dc_common *dc_common)
|
|
{
|
|
int i;
|
|
bool ret = false;
|
|
unsigned long frame_lock = 0x00;
|
|
|
|
for_each_set_bit(i, &dc_common->valid_heads, max_heads) {
|
|
struct tegra_dc *dc = tegra_dc_get_dc(i);
|
|
if (!dc)
|
|
break;
|
|
if (dc->frm_lck_info.frame_lock_enable)
|
|
__set_bit(i, &frame_lock);
|
|
}
|
|
|
|
if (frame_lock == dc_common->valid_heads)
|
|
ret = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline unsigned long _tegra_dc_common_get_head_mask(
|
|
struct tegra_dc_common *dc_common)
|
|
{
|
|
return dc_common->valid_heads;
|
|
}
|
|
|
|
/**
|
|
* _tegra_dc_common_upd_valid_heads_mask - Updates the @valid_heads mask
|
|
* in dc_common.
|
|
* @dc_common: Pointer to struct tegra_dc_common.
|
|
* @new_mask: The new valid_heads mask value.
|
|
*
|
|
* Expects the locking/unlocking of mutex to be handled outside this
|
|
* function. And also the user space needs to blank/unblank when the
|
|
* valid_heads value is being updated.
|
|
*
|
|
* Return: 0 if succesfull else error value from
|
|
* @_tegra_dc_common_enable_frame_lock.
|
|
*/
|
|
static int _tegra_dc_common_upd_valid_heads_mask(
|
|
struct tegra_dc_common *dc_common, unsigned long new_mask)
|
|
{
|
|
if (dc_common->valid_heads == new_mask)
|
|
return 0;
|
|
|
|
dc_common->valid_heads = new_mask;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/**
|
|
* tegra_dc_common_get_frm_lock_params- Gets the current valid_heads and
|
|
* frame_lock status for userspace
|
|
* @params: Pointer to struct tegra_dc_ext_control_frm_lck_params.
|
|
*
|
|
* Return: 0 if succesfull else -ENODEV is dc_common isn't present.
|
|
*/
|
|
|
|
int tegra_dc_common_get_frm_lock_params(
|
|
struct tegra_dc_ext_control_frm_lck_params *params)
|
|
{
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
|
|
params->frame_lock_status = _is_frame_lock_enable(dc_common);
|
|
params->valid_heads = _tegra_dc_common_get_head_mask(dc_common);
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_common_get_frm_lock_params);
|
|
|
|
/**
|
|
* tegra_dc_common_set_frm_lock_params- Used from control.c to update
|
|
* frame_lock related parameters from
|
|
* user sapce.
|
|
* @params: Pointer to struct tegra_dc_ext_control_frm_lck_params.
|
|
*
|
|
* Expects the user space to blank/unblank when it sets new values.
|
|
*
|
|
* Return: 0 if succesfull else the corresponding error value.
|
|
*/
|
|
|
|
int tegra_dc_common_set_frm_lock_params(
|
|
struct tegra_dc_ext_control_frm_lck_params *params)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
|
|
if (params->frame_lock_status != _is_frame_lock_enable(dc_common)) {
|
|
if (params->frame_lock_status)
|
|
ret = _tegra_dc_common_enable_frame_lock(dc_common);
|
|
else
|
|
ret = _tegra_dc_common_disable_frame_lock(dc_common);
|
|
}
|
|
if (ret) {
|
|
mutex_unlock(&dc_common->lock);
|
|
return ret;
|
|
}
|
|
|
|
if (hweight_long(params->valid_heads) > max_heads) {
|
|
mutex_unlock(&dc_common->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = _tegra_dc_common_upd_valid_heads_mask(dc_common,
|
|
params->valid_heads);
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_common_set_frm_lock_params);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int dbg_dc_common_frame_lock_enable_show(struct seq_file *m,
|
|
void *unused)
|
|
{
|
|
struct tegra_dc_common *dc_common = m->private;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
if (mutex_lock_killable(&dc_common->lock))
|
|
return -EINVAL;
|
|
|
|
if (_is_frame_lock_enable(dc_common))
|
|
seq_printf(m, "1\n");
|
|
else
|
|
seq_printf(m, "0\n");
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbg_dc_common_frame_lock_enable_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
return single_open(file, dbg_dc_common_frame_lock_enable_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static ssize_t dbg_dc_common_frame_lock_enable_write(struct file *file,
|
|
const char __user *addr, size_t len, loff_t *pos)
|
|
{
|
|
int ret;
|
|
long enable_val;
|
|
struct seq_file *m = file->private_data;
|
|
struct tegra_dc_common *dc_common = m->private;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
ret = kstrtol_from_user(addr, len, 10, &enable_val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
if (enable_val)
|
|
ret = _tegra_dc_common_enable_frame_lock(dc_common);
|
|
else
|
|
ret = _tegra_dc_common_disable_frame_lock(dc_common);
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
return len;
|
|
}
|
|
|
|
static const struct file_operations frame_lock_enable_fops = {
|
|
.open = dbg_dc_common_frame_lock_enable_open,
|
|
.read = seq_read,
|
|
.write = dbg_dc_common_frame_lock_enable_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int dbg_dc_common_frame_lock_head_mask_show(struct seq_file *m,
|
|
void *unused)
|
|
{
|
|
u8 head_mask;
|
|
struct tegra_dc_common *dc_common = m->private;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
if (mutex_lock_killable(&dc_common->lock))
|
|
return -EINVAL;
|
|
|
|
head_mask = _tegra_dc_common_get_head_mask(dc_common);
|
|
|
|
seq_printf(m, "Head Mask : %d\n", head_mask);
|
|
|
|
mutex_unlock(&dc_common->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbg_dc_common_frame_lock_head_mask_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
return single_open(file, dbg_dc_common_frame_lock_head_mask_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static ssize_t dbg_dc_common_frame_lock_head_mask_write(struct file *file,
|
|
const char __user *addr, size_t len, loff_t *pos)
|
|
{
|
|
int ret;
|
|
long new_head_mask;
|
|
struct seq_file *m = file->private_data;
|
|
struct tegra_dc_common *dc_common = m->private;
|
|
|
|
if (!dc_common)
|
|
return -ENODEV;
|
|
|
|
ret = kstrtol_from_user(addr, len, 10, &new_head_mask);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (hweight_long(new_head_mask) > max_heads)
|
|
return -EINVAL;
|
|
|
|
if (new_head_mask) {
|
|
mutex_lock(&dc_common->lock);
|
|
ret = _tegra_dc_common_upd_valid_heads_mask(dc_common,
|
|
new_head_mask);
|
|
mutex_unlock(&dc_common->lock);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static const struct file_operations frame_lock_head_mask_fops = {
|
|
.open = dbg_dc_common_frame_lock_head_mask_open,
|
|
.read = seq_read,
|
|
.write = dbg_dc_common_frame_lock_head_mask_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void tegra_dc_common_remove_debugfs(struct tegra_dc_common *dc_common)
|
|
{
|
|
if (dc_common->debugdir)
|
|
debugfs_remove(dc_common->debugdir);
|
|
|
|
dc_common->debugdir = NULL;
|
|
}
|
|
|
|
static void tegra_dc_common_create_debugfs(struct tegra_dc_common *dc_common)
|
|
{
|
|
struct dentry *retval;
|
|
|
|
dc_common->debugdir = debugfs_create_dir("dc_common", NULL);
|
|
if (!dc_common->debugdir)
|
|
goto err_handle;
|
|
|
|
retval = debugfs_create_file("frame_lock_enable", 0444,
|
|
dc_common->debugdir, dc_common, &frame_lock_enable_fops);
|
|
if (!retval)
|
|
goto err_handle;
|
|
|
|
retval = debugfs_create_file("frame_lock_head_mask", 0444,
|
|
dc_common->debugdir, dc_common, &frame_lock_head_mask_fops);
|
|
if (!retval)
|
|
goto err_handle;
|
|
|
|
return;
|
|
|
|
err_handle:
|
|
dev_err(&dc_common->pdev->dev, "could not create debugfs\n");
|
|
tegra_dc_common_remove_debugfs(dc_common);
|
|
}
|
|
#else /* !CONFIG_DEBUGFS */
|
|
static inline void tegra_dc_common_create_debugfs(
|
|
struct tegra_dc_common *dc_common) { };
|
|
static inline void tegra_dc_common_remove_debugfs(
|
|
struct tegra_dc_common *dc_common) { };
|
|
#endif /* CONFIG_DEBUGFS */
|
|
|
|
static int tegra_dc_common_assign_head_offset(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (tegra_dc_is_t21x())
|
|
head_offset = T210_HEAD_WORD_OFFSET;
|
|
else if (tegra_dc_is_nvdisplay())
|
|
head_offset = NVDISP_HEAD_WORD_OFFSET;
|
|
else
|
|
ret = -ENOENT;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_dc_common_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
void __iomem *base;
|
|
struct resource dt_res;
|
|
struct resource *res;
|
|
struct nvhost_device_data *pdata = NULL;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct tegra_dc_common_platform_data *dt_pdata = NULL;
|
|
|
|
if (!np) {
|
|
dev_err(&pdev->dev, "no platform data\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
max_heads = tegra_dc_get_numof_dispheads();
|
|
if (max_heads <= 0) {
|
|
dev_err(&pdev->dev, "max no. of heads isn't configured\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = tegra_dc_common_assign_head_offset();
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "can't assign head_offset in tegra_dc_common\n");
|
|
return ret;
|
|
}
|
|
|
|
dc_common = devm_kzalloc(&pdev->dev, sizeof(*dc_common), GFP_KERNEL);
|
|
if (!dc_common) {
|
|
dev_err(&pdev->dev, "can't allocate memory for tegra_dc_common\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!dc_common) {
|
|
dev_err(&pdev->dev, "can't allocate memory for nvhost_device_data\n");
|
|
goto err_free_dc_common;
|
|
}
|
|
|
|
ret = of_address_to_resource(np, 0, &dt_res);
|
|
if (ret)
|
|
goto err_free;
|
|
res = &dt_res;
|
|
base = ioremap(res->start, resource_size(res));
|
|
if (!base) {
|
|
dev_err(&pdev->dev, "registers can't be mapped\n");
|
|
ret = -EBUSY;
|
|
goto err_free;
|
|
}
|
|
dc_common->base = base;
|
|
|
|
dt_pdata = of_dc_common_parse_platform_data(pdev);
|
|
if (IS_ERR_OR_NULL(dt_pdata)) {
|
|
if (dt_pdata)
|
|
ret = PTR_ERR(dt_pdata);
|
|
goto err_iounmap_reg;
|
|
}
|
|
|
|
dc_common->pdev = pdev;
|
|
pdata->pdev = pdev;
|
|
platform_set_drvdata(pdev, pdata);
|
|
pdata->private_data = dc_common;
|
|
pdata->class = NV_DISPLAY_CLASS_ID;
|
|
|
|
ret = nvhost_channel_map(pdata, &dc_common->channel, pdata);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Nvhost Channel map failed\n");
|
|
goto err_iounmap_reg;
|
|
}
|
|
dev_info(&pdev->dev, "host1x channel mapped\n");
|
|
|
|
dc_common->syncpt_id = nvhost_get_syncpt_host_managed(pdev, 0,
|
|
pdev->name);
|
|
if (!dc_common->syncpt_id) {
|
|
dev_err(&pdev->dev, "failed to get syncpoint\n");
|
|
ret = -ENOMEM;
|
|
goto err_drop_channel;
|
|
}
|
|
dev_info(&pdev->dev, "dc_common syncpt # %d allocated\n",
|
|
dc_common->syncpt_id);
|
|
|
|
dc_common->cpuvaddr = dma_alloc_coherent(
|
|
dc_common->pdev->dev.parent, CMDBUF_SIZE,
|
|
&dc_common->dma_handle, GFP_KERNEL);
|
|
if (!dc_common->cpuvaddr) {
|
|
dev_err(&pdev->dev, "failed to allocate command buffer\n");
|
|
ret = -ENOMEM;
|
|
goto err_syncpt_drop;
|
|
}
|
|
dev_info(&dc_common->pdev->dev, "dma mapping done\n");
|
|
|
|
mutex_init(&dc_common->lock);
|
|
init_waitqueue_head(&dc_common->prgrm_reg_reqs_wq);
|
|
|
|
dc_common->valid_heads = dt_pdata->valid_heads;
|
|
dc_common->imp_table = dt_pdata->imp_table;
|
|
|
|
|
|
dc_common->upd_val = devm_kzalloc(&pdev->dev,
|
|
sizeof(*dc_common->upd_val) * max_heads, GFP_KERNEL);
|
|
if (!dc_common->upd_val) {
|
|
dev_err(&pdev->dev, "can't allocate memory for upd_val\n");
|
|
ret = -ENOMEM;
|
|
goto err_syncpt_drop;
|
|
}
|
|
|
|
dc_common->dsp_cmd_reg_val = devm_kzalloc(&pdev->dev,
|
|
sizeof(*dc_common->dsp_cmd_reg_val) * max_heads, GFP_KERNEL);
|
|
if (!dc_common->dsp_cmd_reg_val) {
|
|
dev_err(&pdev->dev, "can't allocate memory for dsp_cmd_reg_val\n");
|
|
ret = -ENOMEM;
|
|
goto err_free_upd_val;
|
|
}
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
tegra_dc_common_create_debugfs(dc_common);
|
|
|
|
probe_success = true;
|
|
|
|
return 0;
|
|
|
|
err_free_upd_val:
|
|
kfree(dc_common->upd_val);
|
|
err_syncpt_drop:
|
|
nvhost_syncpt_put_ref_ext(pdev, dc_common->syncpt_id);
|
|
err_drop_channel:
|
|
nvhost_putchannel(dc_common->channel, 1);
|
|
err_iounmap_reg:
|
|
iounmap(dc_common->base);
|
|
err_free:
|
|
kfree(pdata);
|
|
err_free_dc_common:
|
|
kfree(dc_common);
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_dc_common_remove(struct platform_device *pdev)
|
|
{
|
|
struct nvhost_device_data *pdata = platform_get_drvdata(pdev);
|
|
struct tegra_dc_common *dc_common = pdata->private_data;
|
|
|
|
mutex_lock(&dc_common->lock);
|
|
_tegra_dc_common_disable_frame_lock(dc_common);
|
|
mutex_unlock(&dc_common->lock);
|
|
dma_free_coherent(pdev->dev.parent, CMDBUF_SIZE,
|
|
dc_common->cpuvaddr, dc_common->dma_handle);
|
|
nvhost_syncpt_put_ref_ext(pdev, dc_common->syncpt_id);
|
|
nvhost_putchannel(dc_common->channel, 1);
|
|
|
|
probe_success = false;
|
|
|
|
iounmap(dc_common->base);
|
|
kfree(dc_common->dsp_cmd_reg_val);
|
|
kfree(dc_common->upd_val);
|
|
kfree(dc_common);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver tegra_dc_common_driver = {
|
|
.driver = {
|
|
.name = "tegradccommon",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table =
|
|
of_match_ptr(tegra_display_common_of_match),
|
|
#endif
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
.probe = tegra_dc_common_probe,
|
|
.remove = tegra_dc_common_remove,
|
|
};
|
|
|
|
|
|
static int __init tegra_dc_common_module_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = tegra_dc_hw_init();
|
|
if (ret) {
|
|
printk(KERN_ERR "tegradccommon module_init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return platform_driver_register(&tegra_dc_common_driver);
|
|
}
|
|
|
|
static void __exit tegra_dc_common_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&tegra_dc_common_driver);
|
|
}
|
|
|
|
module_init(tegra_dc_common_module_init);
|
|
module_exit(tegra_dc_common_module_exit);
|
|
|
|
MODULE_DESCRIPTION("Tegra DC Common for framelock/flipllock support using Host1x Interface");
|
|
MODULE_AUTHOR("NVIDIA Corporation");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("tegra_dc_common");
|