Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
GCOV_PROFILE := y
ccflags-y += -Idrivers/video/tegra/host
obj-y += dev.o
obj-y += util.o
obj-y += cursor.o
obj-y += events.o
obj-y += control.o

View File

@@ -0,0 +1,297 @@
/*
* drivers/video/tegra/dc/ext/control.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.com>
*
* 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/device.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include "tegra_dc_ext_priv.h"
#include "../dc_priv_defs.h"
static struct tegra_dc_ext_control g_control;
int tegra_dc_ext_process_hotplug(int output)
{
return tegra_dc_ext_queue_hotplug(&g_control, output);
}
static int
get_output_properties(struct tegra_dc_ext_control_output_properties *properties)
{
struct tegra_dc *dc;
/* TODO: this should be more dynamic */
if (properties->handle > 2)
return -EINVAL;
properties->associated_head = properties->handle;
properties->head_mask = (1 << properties->associated_head);
dc = tegra_dc_get_dc(properties->associated_head);
switch (tegra_dc_get_out(dc)) {
case TEGRA_DC_OUT_DSI:
properties->type = TEGRA_DC_EXT_DSI;
break;
case TEGRA_DC_OUT_RGB:
properties->type = TEGRA_DC_EXT_LVDS;
break;
case TEGRA_DC_OUT_HDMI:
properties->type = TEGRA_DC_EXT_HDMI;
break;
case TEGRA_DC_OUT_LVDS:
properties->type = TEGRA_DC_EXT_LVDS;
break;
case TEGRA_DC_OUT_DP:
properties->type = TEGRA_DC_EXT_DP;
break;
default:
return -EINVAL;
}
properties->connected = tegra_dc_get_connected(dc);
return 0;
}
static int get_output_edid(struct tegra_dc_ext_control_output_edid *edid)
{
struct tegra_dc *dc;
size_t user_size = edid->size;
struct tegra_dc_edid *dc_edid = NULL;
int ret = 0;
/* TODO: this should be more dynamic */
if (edid->handle > 2)
return -EINVAL;
dc = tegra_dc_get_dc(edid->handle);
dc_edid = tegra_dc_get_edid(dc);
if (IS_ERR(dc_edid))
return PTR_ERR(dc_edid);
if (!dc_edid) {
edid->size = 0;
} else {
edid->size = dc_edid->len;
if (user_size < edid->size) {
ret = -EFBIG;
goto done;
}
if (copy_to_user(edid->data, dc_edid->buf, edid->size)) {
ret = -EFAULT;
goto done;
}
}
done:
if (dc_edid)
tegra_dc_put_edid(dc_edid);
return ret;
}
static int set_event_mask(struct tegra_dc_ext_control_user *user, u32 mask)
{
struct list_head *list, *tmp;
if (mask & ~TEGRA_DC_EXT_EVENT_MASK_ALL)
return -EINVAL;
mutex_lock(&user->lock);
user->event_mask = mask;
list_for_each_safe(list, tmp, &user->event_list) {
struct tegra_dc_ext_event_list *ev_list;
ev_list = list_entry(list, struct tegra_dc_ext_event_list,
list);
if (!(mask & ev_list->event.type)) {
list_del(list);
kfree(ev_list);
}
}
mutex_unlock(&user->lock);
return 0;
}
static int get_capabilities(struct tegra_dc_ext_control_capabilities *caps)
{
caps->caps = TEGRA_DC_EXT_CAPABILITIES_CURSOR_MODE |
TEGRA_DC_EXT_CAPABILITIES_BLOCKLINEAR |
TEGRA_DC_EXT_CAPABILITIES_CURSOR_TWO_COLOR;
caps->caps |= TEGRA_DC_EXT_CAPABILITIES_CURSOR_RGBA_NON_PREMULT_ALPHA;
if (is_tegra124())
caps->caps |=
TEGRA_DC_EXT_CAPABILITIES_CURSOR_RGBA_PREMULT_ALPHA;
return 0;
}
static long tegra_dc_ext_control_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
void __user *user_arg = (void __user *)arg;
struct tegra_dc_ext_control_user *user = filp->private_data;
switch (cmd) {
case TEGRA_DC_EXT_CONTROL_GET_NUM_OUTPUTS:
{
u32 num = tegra_dc_ext_get_num_outputs();
if (copy_to_user(user_arg, &num, sizeof(num)))
return -EFAULT;
return 0;
}
case TEGRA_DC_EXT_CONTROL_GET_OUTPUT_PROPERTIES:
{
struct tegra_dc_ext_control_output_properties args;
int ret;
if (copy_from_user(&args, user_arg, sizeof(args)))
return -EFAULT;
ret = get_output_properties(&args);
if (copy_to_user(user_arg, &args, sizeof(args)))
return -EFAULT;
return ret;
}
case TEGRA_DC_EXT_CONTROL_GET_OUTPUT_EDID:
{
struct tegra_dc_ext_control_output_edid args;
int ret;
if (copy_from_user(&args, user_arg, sizeof(args)))
return -EFAULT;
ret = get_output_edid(&args);
if (copy_to_user(user_arg, &args, sizeof(args)))
return -EFAULT;
return ret;
}
case TEGRA_DC_EXT_CONTROL_SET_EVENT_MASK:
return set_event_mask(user, (u32) arg);
case TEGRA_DC_EXT_CONTROL_GET_CAPABILITIES:
{
struct tegra_dc_ext_control_capabilities args;
int ret;
ret = get_capabilities(&args);
if (copy_to_user(user_arg, &args, sizeof(args)))
return -EFAULT;
return ret;
}
default:
return -EINVAL;
}
}
static int tegra_dc_ext_control_open(struct inode *inode, struct file *filp)
{
struct tegra_dc_ext_control_user *user;
struct tegra_dc_ext_control *control;
user = kzalloc(sizeof(*user), GFP_KERNEL);
if (!user)
return -ENOMEM;
control = container_of(inode->i_cdev, struct tegra_dc_ext_control,
cdev);
user->control = control;
INIT_LIST_HEAD(&user->event_list);
mutex_init(&user->lock);
filp->private_data = user;
mutex_lock(&control->lock);
list_add(&user->list, &control->users);
mutex_unlock(&control->lock);
return 0;
}
static int tegra_dc_ext_control_release(struct inode *inode, struct file *filp)
{
struct tegra_dc_ext_control_user *user = filp->private_data;
struct tegra_dc_ext_control *control = user->control;
/* This will free any pending events for this user */
set_event_mask(user, 0);
mutex_lock(&control->lock);
list_del(&user->list);
mutex_unlock(&control->lock);
kfree(user);
return 0;
}
static const struct file_operations tegra_dc_ext_event_devops = {
.owner = THIS_MODULE,
.open = tegra_dc_ext_control_open,
.release = tegra_dc_ext_control_release,
.read = tegra_dc_ext_event_read,
.poll = tegra_dc_ext_event_poll,
.unlocked_ioctl = tegra_dc_ext_control_ioctl,
};
int tegra_dc_ext_control_init(void)
{
struct tegra_dc_ext_control *control = &g_control;
int ret;
cdev_init(&control->cdev, &tegra_dc_ext_event_devops);
control->cdev.owner = THIS_MODULE;
ret = cdev_add(&control->cdev, tegra_dc_ext_devno, 1);
if (ret)
return ret;
control->dev = device_create(tegra_dc_ext_class,
NULL, tegra_dc_ext_devno, NULL, "tegra_dc_ctrl");
if (IS_ERR(control->dev)) {
ret = PTR_ERR(control->dev);
cdev_del(&control->cdev);
}
mutex_init(&control->lock);
INIT_LIST_HEAD(&control->users);
return ret;
}
int tegra_dc_ext_process_bandwidth_renegotiate(int output)
{
return tegra_dc_ext_queue_bandwidth_renegotiate(&g_control, output);
}

View File

@@ -0,0 +1,531 @@
/*
* drivers/video/tegra/dc/ext/cursor.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.com>
*
* 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 <video/tegra_dc_ext.h>
#include "tegra_dc_ext_priv.h"
/* ugh */
#include "../dc_priv.h"
#include "../dc_reg.h"
#include "../dc_config.h"
int tegra_dc_ext_get_cursor(struct tegra_dc_ext_user *user)
{
struct tegra_dc_ext *ext = user->ext;
int ret = 0;
mutex_lock(&ext->cursor.lock);
if (!ext->cursor.user)
ext->cursor.user = user;
else if (ext->cursor.user != user)
ret = -EBUSY;
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_put_cursor(struct tegra_dc_ext_user *user)
{
struct tegra_dc_ext *ext = user->ext;
int ret = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user == user)
ext->cursor.user = 0;
else
ret = -EACCES;
mutex_unlock(&ext->cursor.lock);
return ret;
}
static unsigned int set_cursor_start_addr(struct tegra_dc *dc,
u32 size, dma_addr_t phys_addr)
{
unsigned long val;
int clip_win;
BUG_ON(phys_addr & ~CURSOR_START_ADDR_MASK);
switch (size) {
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_64x64:
val = CURSOR_SIZE_64;
break;
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_128x128:
val = CURSOR_SIZE_128;
break;
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_256x256:
val = CURSOR_SIZE_256;
break;
default:
val = 0;
}
/* Get the cursor clip window number */
clip_win = CURSOR_CLIP_GET_WINDOW(tegra_dc_readl(dc,
DC_DISP_CURSOR_START_ADDR));
val |= CURSOR_CLIP_SHIFT_BITS(clip_win);
tegra_dc_writel(dc, val | CURSOR_START_ADDR_LOW(phys_addr),
DC_DISP_CURSOR_START_ADDR);
if (tegra_dc_feature_has_cursor_hi_regs(dc)) {
/* TO DO: check calculation with HW */
tegra_dc_writel(dc, CURSOR_START_ADDR_HI(phys_addr),
DC_DISP_CURSOR_START_ADDR_HI);
}
if (is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
return 0;
}
return 1;
}
static int set_cursor_position(struct tegra_dc *dc, s16 x, s16 y)
{
tegra_dc_writel(dc, CURSOR_POSITION(x, y), DC_DISP_CURSOR_POSITION);
if (is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
return 0;
}
return 1;
}
static int set_cursor_activation_control(struct tegra_dc *dc)
{
if (is_tegra124()) {
u32 reg = tegra_dc_readl(dc, DC_CMD_REG_ACT_CONTROL);
if ((reg & (1 << CURSOR_ACT_CNTR_SEL)) ==
(CURSOR_ACT_CNTR_SEL_V << CURSOR_ACT_CNTR_SEL)) {
reg &= ~(1 << CURSOR_ACT_CNTR_SEL);
reg |= (CURSOR_ACT_CNTR_SEL_V << CURSOR_ACT_CNTR_SEL);
tegra_dc_writel(dc, reg, DC_CMD_REG_ACT_CONTROL);
return 1;
}
}
return 0;
}
static int set_cursor_enable(struct tegra_dc *dc, bool enable)
{
u32 val = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
if (!!(val & CURSOR_ENABLE) != enable) {
val &= ~CURSOR_ENABLE;
if (enable)
val |= CURSOR_ENABLE;
tegra_dc_writel(dc, val, DC_DISP_DISP_WIN_OPTIONS);
return 1;
}
return 0;
}
static int set_cursor_blend(struct tegra_dc *dc, u32 format)
{
u32 val = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL);
u32 newval = WINH_CURS_SELECT(0);
switch (format) {
case TEGRA_DC_EXT_CURSOR_FORMAT_2BIT_LEGACY:
newval |= CURSOR_MODE_SELECT(0);
break;
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_NON_PREMULT_ALPHA:
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA:
newval |= CURSOR_MODE_SELECT(1);
if (is_tegra124()) {
newval |= CURSOR_ALPHA |
CURSOR_DST_BLEND_FACTOR_SELECT(2);
if (format ==
TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA)
newval |= CURSOR_SRC_BLEND_FACTOR_SELECT(0);
else
newval |= CURSOR_SRC_BLEND_FACTOR_SELECT(1);
}
break;
}
if (val != newval) {
tegra_dc_writel(dc, newval, DC_DISP_BLEND_CURSOR_CONTROL);
return 1;
}
return 0;
}
static int set_cursor_fg_bg(struct tegra_dc *dc,
struct tegra_dc_ext_cursor_image *args)
{
int general_update_needed = 0;
u32 fg = CURSOR_COLOR(args->foreground.r,
args->foreground.g,
args->foreground.b);
u32 bg = CURSOR_COLOR(args->background.r,
args->background.g,
args->background.b);
if (fg != tegra_dc_readl(dc, DC_DISP_CURSOR_FOREGROUND)) {
tegra_dc_writel(dc, fg, DC_DISP_CURSOR_FOREGROUND);
general_update_needed |= 1;
}
if (bg != tegra_dc_readl(dc, DC_DISP_CURSOR_BACKGROUND)) {
tegra_dc_writel(dc, bg, DC_DISP_CURSOR_BACKGROUND);
general_update_needed |= 1;
}
return general_update_needed;
}
static bool check_cursor_size(struct tegra_dc *dc, u32 size)
{
if (size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_32x32 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_64x64 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_128x128 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_256x256)
return false;
return true;
}
int tegra_dc_ext_set_cursor_image(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
struct tegra_dc_dmabuf *handle, *old_handle;
dma_addr_t phys_addr;
u32 size;
int ret;
int need_general_update = 0;
u32 format = TEGRA_DC_EXT_CURSOR_FORMAT_FLAGS(args->flags);
if (!user->nvmap)
return -EFAULT;
size = TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE(args->flags);
if (!check_cursor_size(dc, size))
return -EINVAL;
switch (format) {
case TEGRA_DC_EXT_CURSOR_FORMAT_2BIT_LEGACY:
break;
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_NON_PREMULT_ALPHA:
break;
#if defined(CONFIG_ARCH_TEGRA_124_SOC)
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA:
break;
#endif
default:
return -EINVAL;
}
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
old_handle = ext->cursor.cur_handle;
ret = tegra_dc_ext_pin_window(user, args->buff_id, &handle, &phys_addr);
if (ret)
goto unlock;
ext->cursor.cur_handle = handle;
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_start_addr(dc, size, phys_addr);
need_general_update |= set_cursor_fg_bg(dc, args);
need_general_update |= set_cursor_blend(dc, format);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
/* XXX sync here? */
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
if (old_handle) {
dma_buf_unmap_attachment(old_handle->attach,
old_handle->sgt, DMA_TO_DEVICE);
dma_buf_detach(old_handle->buf, old_handle->attach);
dma_buf_put(old_handle->buf);
kfree(old_handle);
}
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
bool enable;
int ret;
int need_general_update = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
enable = !!(args->flags & TEGRA_DC_EXT_CURSOR_FLAGS_VISIBLE);
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_enable(dc, enable);
need_general_update |= set_cursor_position(dc, args->x, args->y);
need_general_update |= set_cursor_activation_control(dc);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
/* TODO: need to sync here? hopefully can avoid this, but need to
* figure out interaction w/ rest of GENERAL_ACT_REQ */
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_cursor_clip(struct tegra_dc_ext_user *user,
int *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
int ret;
unsigned long reg_val;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
mutex_lock(&dc->lock);
tegra_dc_get(dc);
reg_val = tegra_dc_readl(dc, DC_DISP_CURSOR_START_ADDR);
reg_val &= ~CURSOR_CLIP_SHIFT_BITS(3); /* Clear out the old value */
tegra_dc_writel(dc, reg_val | CURSOR_CLIP_SHIFT_BITS(*args),
DC_DISP_CURSOR_START_ADDR);
if(is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
}
else {
tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor_image_low_latency(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
int ret;
int need_general_update = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_fg_bg(dc, args);
need_general_update |= set_cursor_blend(dc, !!args->mode);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor_low_latency(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
u32 size;
int ret;
struct tegra_dc_dmabuf *handle, *old_handle;
dma_addr_t phys_addr;
bool enable = !!(args->vis & TEGRA_DC_EXT_CURSOR_FLAGS_VISIBLE);
int need_general_update = 0;
if (!user->nvmap)
return -EFAULT;
size = TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE(args->flags);
if (!check_cursor_size(dc, size))
return -EINVAL;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
old_handle = ext->cursor.cur_handle;
ret = tegra_dc_ext_pin_window(user, args->buff_id, &handle, &phys_addr);
if (ret)
goto unlock;
ext->cursor.cur_handle = handle;
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_start_addr(dc, size, phys_addr);
need_general_update |= set_cursor_position(dc, args->x, args->y);
need_general_update |= set_cursor_activation_control(dc);
need_general_update |= set_cursor_enable(dc, enable);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
if (old_handle) {
dma_buf_unmap_attachment(old_handle->attach,
old_handle->sgt, DMA_TO_DEVICE);
dma_buf_detach(old_handle->buf, handle->attach);
dma_buf_put(old_handle->buf);
kfree(old_handle);
}
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
/*
* drivers/video/tegra/dc/ext/events.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.com>
*
* 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/err.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "tegra_dc_ext_priv.h"
static DECLARE_WAIT_QUEUE_HEAD(event_wait);
unsigned int tegra_dc_ext_event_poll(struct file *filp, poll_table *wait)
{
struct tegra_dc_ext_control_user *user = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &event_wait, wait);
if (atomic_read(&user->num_events))
mask |= POLLIN;
return mask;
}
static int get_next_event(struct tegra_dc_ext_control_user *user,
struct tegra_dc_ext_event_list *event,
bool block)
{
struct list_head *list = &user->event_list;
struct tegra_dc_ext_event_list *next_event;
int ret;
if (block) {
ret = wait_event_interruptible(event_wait,
atomic_read(&user->num_events));
if (unlikely(ret)) {
if (ret == -ERESTARTSYS)
return -EAGAIN;
return ret;
}
} else {
if (!atomic_read(&user->num_events))
return 0;
}
mutex_lock(&user->lock);
BUG_ON(list_empty(list));
next_event = list_first_entry(list, struct tegra_dc_ext_event_list,
list);
*event = *next_event;
list_del(&next_event->list);
kfree(next_event);
atomic_dec(&user->num_events);
mutex_unlock(&user->lock);
return 1;
}
ssize_t tegra_dc_ext_event_read(struct file *filp, char __user *buf,
size_t size, loff_t *ppos)
{
struct tegra_dc_ext_control_user *user = filp->private_data;
struct tegra_dc_ext_event_list event_elem;
struct tegra_dc_ext_event *event = &event_elem.event;
ssize_t retval = 0, to_copy, event_size, pending;
loff_t previously_copied = 0;
char *to_copy_ptr;
if (size == 0)
return 0;
if (user->partial_copy) {
/*
* We didn't transfer the entire event last time, need to
* finish it up
*/
event_elem = user->event_to_copy;
previously_copied = user->partial_copy;
} else {
/* Get the next event, if any */
pending = get_next_event(user, &event_elem,
!(filp->f_flags & O_NONBLOCK));
if (pending <= 0)
return pending;
}
/* Write the event to the user */
event_size = sizeof(*event) + event->data_size;
BUG_ON(event_size <= previously_copied);
event_size -= previously_copied;
to_copy_ptr = (char *)event + previously_copied;
to_copy = min_t(ssize_t, size, event_size);
if (copy_to_user(buf, to_copy_ptr, to_copy)) {
retval = -EFAULT;
to_copy = 0;
}
/* Note that we currently only deliver one event at a time */
if (event_size > to_copy) {
/*
* We were only able to copy part of this event. Stash it for
* next time.
*/
user->event_to_copy = event_elem;
user->partial_copy = previously_copied + to_copy;
} else {
user->partial_copy = 0;
}
return to_copy ? to_copy : retval;
}
static int tegra_dc_ext_queue_event(struct tegra_dc_ext_control *control,
struct tegra_dc_ext_event *event)
{
struct list_head *cur;
int retval = 0;
mutex_lock(&control->lock);
list_for_each(cur, &control->users) {
struct tegra_dc_ext_control_user *user;
struct tegra_dc_ext_event_list *ev_list;
user = container_of(cur, struct tegra_dc_ext_control_user,
list);
mutex_lock(&user->lock);
if (!(user->event_mask & event->type)) {
mutex_unlock(&user->lock);
continue;
}
ev_list = kmalloc(sizeof(*ev_list), GFP_KERNEL);
if (!ev_list) {
retval = -ENOMEM;
mutex_unlock(&user->lock);
continue;
}
memcpy(&ev_list->event, event,
sizeof(*event) + event->data_size);
list_add_tail(&ev_list->list, &user->event_list);
atomic_inc(&user->num_events);
mutex_unlock(&user->lock);
}
mutex_unlock(&control->lock);
/* Is it worth it to track waiters with more granularity? */
wake_up(&event_wait);
return retval;
}
int tegra_dc_ext_queue_hotplug(struct tegra_dc_ext_control *control, int output)
{
struct {
struct tegra_dc_ext_event event;
struct tegra_dc_ext_control_event_hotplug hotplug;
} __packed pack;
pack.event.type = TEGRA_DC_EXT_EVENT_HOTPLUG;
pack.event.data_size = sizeof(pack.hotplug);
pack.hotplug.handle = output;
tegra_dc_ext_queue_event(control, &pack.event);
return 0;
}
int tegra_dc_ext_queue_bandwidth_renegotiate(
struct tegra_dc_ext_control *control, int output)
{
struct {
struct tegra_dc_ext_event event;
struct tegra_dc_ext_control_event_bandwidth bandwidth;
} __packed pack;
pack.event.type = TEGRA_DC_EXT_EVENT_BANDWIDTH;
pack.event.data_size = sizeof(pack.bandwidth);
pack.bandwidth.handle = output;
tegra_dc_ext_queue_event(control, &pack.event);
return 0;
}

View File

@@ -0,0 +1,161 @@
/*
* drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.com>
*
* 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.
*/
#ifndef __TEGRA_DC_EXT_PRIV_H
#define __TEGRA_DC_EXT_PRIV_H
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/nvmap.h>
#include <video/tegra_dc_ext.h>
struct tegra_dc_ext;
struct tegra_dc_ext_user {
struct tegra_dc_ext *ext;
struct nvmap_client *nvmap;
};
struct tegra_dc_dmabuf {
struct dma_buf *buf;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
};
enum {
TEGRA_DC_Y,
TEGRA_DC_U,
TEGRA_DC_V,
TEGRA_DC_NUM_PLANES,
};
struct tegra_dc_ext_win {
struct tegra_dc_ext *ext;
int idx;
struct tegra_dc_ext_user *user;
struct mutex lock;
/* Current nvmap handle (if any) for Y, U, V planes */
struct tegra_dc_dmabuf *cur_handle[TEGRA_DC_NUM_PLANES];
struct workqueue_struct *flip_wq;
atomic_t nr_pending_flips;
struct mutex queue_lock;
struct list_head timestamp_queue;
};
struct tegra_dc_ext {
struct tegra_dc *dc;
struct cdev cdev;
struct device *dev;
struct tegra_dc_ext_win *win;
struct {
struct tegra_dc_ext_user *user;
struct tegra_dc_dmabuf *cur_handle;
struct mutex lock;
} cursor;
bool enabled;
};
#define TEGRA_DC_EXT_EVENT_MASK_ALL \
TEGRA_DC_EXT_EVENT_HOTPLUG
#define TEGRA_DC_EXT_EVENT_MAX_SZ 8
struct tegra_dc_ext_event_list {
struct tegra_dc_ext_event event;
/* The data field _must_ follow the event field. */
char data[TEGRA_DC_EXT_EVENT_MAX_SZ];
struct list_head list;
};
struct tegra_dc_ext_control_user {
struct tegra_dc_ext_control *control;
struct list_head event_list;
atomic_t num_events;
u32 event_mask;
struct tegra_dc_ext_event_list event_to_copy;
loff_t partial_copy;
struct mutex lock;
struct list_head list;
};
struct tegra_dc_ext_control {
struct cdev cdev;
struct device *dev;
struct list_head users;
struct mutex lock;
};
extern int tegra_dc_ext_devno;
extern struct class *tegra_dc_ext_class;
extern int tegra_dc_ext_pin_window(struct tegra_dc_ext_user *user, u32 id,
struct tegra_dc_dmabuf **handle,
dma_addr_t *phys_addr);
extern int tegra_dc_ext_get_cursor(struct tegra_dc_ext_user *user);
extern int tegra_dc_ext_put_cursor(struct tegra_dc_ext_user *user);
extern int tegra_dc_ext_set_cursor_image(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *);
extern int tegra_dc_ext_set_cursor(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor *);
extern int tegra_dc_ext_cursor_clip(struct tegra_dc_ext_user *user,
int *args);
extern int tegra_dc_ext_set_cursor_image_low_latency(
struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *);
extern int tegra_dc_ext_set_cursor_low_latency(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *);
extern int tegra_dc_ext_control_init(void);
extern int tegra_dc_ext_queue_hotplug(struct tegra_dc_ext_control *,
int output);
extern int tegra_dc_ext_queue_bandwidth_renegotiate(
struct tegra_dc_ext_control *, int output);
extern ssize_t tegra_dc_ext_event_read(struct file *filp, char __user *buf,
size_t size, loff_t *ppos);
extern unsigned int tegra_dc_ext_event_poll(struct file *, poll_table *);
extern int tegra_dc_ext_get_num_outputs(void);
#endif /* __TEGRA_DC_EXT_PRIV_H */

View File

@@ -0,0 +1,71 @@
/*
* drivers/video/tegra/dc/ext/util.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.com>
*
* 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/err.h>
#include <linux/types.h>
#include <linux/dma-buf.h>
#include <linux/nvmap.h>
#include <linux/slab.h>
#include "tegra_dc_ext_priv.h"
int tegra_dc_ext_pin_window(struct tegra_dc_ext_user *user, u32 id,
struct tegra_dc_dmabuf **dc_buf,
dma_addr_t *phys_addr)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc_dmabuf *dc_dmabuf;
*dc_buf = NULL;
*phys_addr = -1;
if (!id)
return 0;
dc_dmabuf = kzalloc(sizeof(*dc_dmabuf), GFP_KERNEL);
if (!dc_dmabuf)
return -ENOMEM;
/*
* Take a reference to the buffer using the user's nvmap context, to
* make sure they have permissions to access it.
*/
dc_dmabuf->buf = nvmap_dmabuf_export(user->nvmap, id);
if (IS_ERR_OR_NULL(dc_dmabuf->buf))
goto buf_fail;
dc_dmabuf->attach = dma_buf_attach(dc_dmabuf->buf, ext->dev->parent);
if (IS_ERR_OR_NULL(dc_dmabuf->attach))
goto attach_fail;
dc_dmabuf->sgt = dma_buf_map_attachment(dc_dmabuf->attach,
DMA_TO_DEVICE);
if (IS_ERR_OR_NULL(dc_dmabuf->sgt))
goto sgt_fail;
*phys_addr = sg_dma_address(dc_dmabuf->sgt->sgl);
*dc_buf = dc_dmabuf;
return 0;
sgt_fail:
dma_buf_detach(dc_dmabuf->buf, dc_dmabuf->attach);
attach_fail:
dma_buf_put(dc_dmabuf->buf);
buf_fail:
kfree(dc_dmabuf);
return -ENOMEM;
}