/* * dev.c: Device interface for tegradc ext. * * Copyright (c) 2011-2019, NVIDIA CORPORATION, All rights reserved. * * Author: Robert Morell * Some code based on fbdev extensions written by: * Erik Gilling * * 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 #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #include #include #endif #include #include /* XXX ew */ #include "../dc.h" #include "../dc_priv.h" #include "../dc_priv_defs.h" #include "../dc_config.h" #include /* XXX ew 3 */ #include "tegra_dc_ext_priv.h" /* XXX ew 4 */ #ifdef CONFIG_TEGRA_GRHOST_SYNC #include "../drivers/staging/android/sync.h" #endif #include "../edid.h" #define TEGRA_DC_TS_MAX_DELAY_US 1000000 #define TEGRA_DC_TS_SLACK_US 2000 /* Compatibility for kthread refactoring */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) #define kthread_init_work init_kthread_work #define kthread_init_worker init_kthread_worker #define kthread_queue_work queue_kthread_work #define kthread_flush_worker flush_kthread_worker #endif #ifdef CONFIG_COMPAT /* compat versions that happen to be the same size as the uapi version. */ struct tegra_dc_ext_lut32 { __u32 win_index; /* window index to set lut for */ __u32 flags; /* Flag bitmask, see TEGRA_DC_EXT_LUT_FLAGS_* */ __u32 start; /* start index to update lut from */ __u32 len; /* number of valid lut entries */ __u32 r; /* array of 16-bit red values, 0 to reset */ __u32 g; /* array of 16-bit green values, 0 to reset */ __u32 b; /* array of 16-bit blue values, 0 to reset */ }; #define TEGRA_DC_EXT_SET_LUT32 \ _IOW('D', 0x0A, struct tegra_dc_ext_lut32) struct tegra_dc_ext_flip_2_32 { __u32 __user win; /* struct tegra_dc_ext_flip_windowattr */ __u8 win_num; __u8 reserved1; /* unused - must be 0 */ __u16 reserved2; /* unused - must be 0 */ __u32 post_syncpt_id; __u32 post_syncpt_val; __u16 dirty_rect[4]; /* x,y,w,h for partial screen update. 0 ignores */ }; #define TEGRA_DC_EXT_SET_PROPOSED_BW32 \ _IOR('D', 0x13, struct tegra_dc_ext_flip_2_32) #endif dev_t tegra_dc_ext_devno; struct class *tegra_dc_ext_class; static int head_count; static atomic_t dc_open_count; struct tegra_dc_ext_flip_win { struct tegra_dc_ext_flip_windowattr attr; struct tegra_dc_dmabuf *handle[TEGRA_DC_NUM_PLANES]; dma_addr_t phys_addr; dma_addr_t phys_addr_u; dma_addr_t phys_addr_v; dma_addr_t phys_addr_cde; /* field 2 */ dma_addr_t phys_addr2; dma_addr_t phys_addr_u2; dma_addr_t phys_addr_v2; u32 syncpt_max; #ifdef CONFIG_TEGRA_GRHOST_SYNC struct sync_fence *pre_syncpt_fence; #endif bool user_nvdisp_win_csc; struct tegra_dc_ext_nvdisp_win_csc nvdisp_win_csc; }; struct tegra_dc_ext_flip_data { struct tegra_dc_ext *ext; struct kthread_work work; struct tegra_dc_ext_flip_win win[DC_N_WINDOWS]; struct list_head timestamp_node; int act_window_num; u16 dirty_rect[4]; bool dirty_rect_valid; u8 flags; struct tegra_dc_hdr hdr_data; struct tegra_dc_ext_avi avi_info; bool hdr_cache_dirty; bool avi_cache_dirty; bool imp_dirty; u64 imp_session_id; bool cmu_update_needed; /* this is needed for ENABLE or DISABLE */ bool new_cmu_values; /* this is to be used only when ENABLE */ struct tegra_dc_ext_nvdisp_cmu user_nvdisp_cmu; bool output_colorspace_update_needed; bool output_range_update_needed; u32 output_colorspace; u8 limited_range_enable; struct tegra_dc_flip_buf_ele *flip_buf_ele; bool background_color_update_needed; u32 background_color; }; struct tegra_dc_ext_scanline_data { struct tegra_dc_ext *ext; struct kthread_work scanline_work; int triggered_line; int max_val; }; static int tegra_dc_ext_set_vblank(struct tegra_dc_ext *ext, bool enable); static void tegra_dc_ext_unpin_window(struct tegra_dc_ext_win *win); static void tegra_dc_flip_trace(struct tegra_dc_ext_flip_data *data, display_syncpt_notifier trace_fn); static inline s64 tegra_timespec_to_ns(const struct tegra_timespec *ts) { return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec; } static inline int test_bit_win_colorfmt(int bitnum, const u32 *data, int entries) { int i; /* Checking for SW defined formats */ if (bitnum >= TEGRA_WIN_SW_FORMAT_MIN) { if (1UL & (data[entries - 1] >> ((bitnum - TEGRA_WIN_SW_FORMAT_MIN)))) return 1; return 0; } for (i = 0; i < entries - 1; i++) { if (bitnum < 32) { if (1UL & (data[bitnum / 32] >> (bitnum & 31))) return 1; } else { bitnum -= 32; data++; } } return 0; } int tegra_dc_ext_get_num_outputs(void) { /* TODO: decouple output count from head count */ return head_count; } static int tegra_dc_ext_get_window(struct tegra_dc_ext_user *user, unsigned int n) { struct tegra_dc_ext *ext = user->ext; struct tegra_dc_ext_win *win; int ret = 0; if ((n >= tegra_dc_get_numof_dispwindows()) || !(ext->dc->valid_windows & BIT(n))) return -EINVAL; n = array_index_nospec(n, tegra_dc_get_numof_dispwindows()); win = &ext->win[n]; mutex_lock(&win->lock); if (!win->user) { win->user = user; win->enabled = false; } else { if (win->user != user) ret = -EBUSY; } mutex_unlock(&win->lock); return ret; } static int tegra_dc_ext_put_window(struct tegra_dc_ext_user *user, unsigned int n) { struct tegra_dc_ext *ext = user->ext; struct tegra_dc_ext_win *win; int ret = 0; if ((n >= tegra_dc_get_numof_dispwindows()) || !(ext->dc->valid_windows & BIT(n))) return -EINVAL; n = array_index_nospec(n, tegra_dc_get_numof_dispwindows()); win = &ext->win[n]; mutex_lock(&win->lock); if (win->user == user) { kthread_flush_worker(&win->flip_worker); win->user = NULL; win->enabled = false; } else { ret = -EACCES; } mutex_unlock(&win->lock); return ret; } static unsigned long tegra_dc_ext_get_winmask(struct tegra_dc_ext_user *user) { return user->ext->dc->valid_windows; } static int tegra_dc_ext_set_winmask(struct tegra_dc_ext_user *user, unsigned long winmask) { if (!user || !user->ext || !user->ext->dc) return -EINVAL; return tegra_dc_update_winmask(user->ext->dc, winmask); } int tegra_dc_ext_restore(struct tegra_dc_ext *ext) { int nwins = tegra_dc_get_numof_dispwindows(); struct tegra_dc_win *wins[nwins]; int i, nr_win = 0; for_each_set_bit(i, &ext->dc->valid_windows, tegra_dc_get_numof_dispwindows()) if (ext->win[i].enabled) { wins[nr_win] = tegra_dc_get_window(ext->dc, i); wins[nr_win++]->flags |= TEGRA_WIN_FLAG_ENABLED; } if (nr_win) { tegra_dc_update_windows(&wins[0], nr_win, NULL, true, false); tegra_dc_sync_windows(&wins[0], nr_win); tegra_dc_program_bandwidth(ext->dc, true); } return nr_win; } static void set_enable(struct tegra_dc_ext *ext, bool en) { int i; /* * Take all locks to make sure any flip requests or cursor moves are * out of their critical sections */ for (i = 0; i < ext->dc->n_windows; i++) mutex_lock_nested(&ext->win[i].lock, i); mutex_lock(&ext->cursor.lock); ext->enabled = en; mutex_unlock(&ext->cursor.lock); for (i = ext->dc->n_windows - 1; i >= 0 ; i--) mutex_unlock(&ext->win[i].lock); } void tegra_dc_ext_enable(struct tegra_dc_ext *ext) { set_enable(ext, true); } int tegra_dc_ext_disable(struct tegra_dc_ext *ext) { int i; unsigned long int windows = 0; set_enable(ext, false); /* Flush any scanline work */ kthread_flush_worker(&ext->scanline_worker); /* * Disable vblank requests */ tegra_dc_ext_set_vblank(ext, false); /* * Flush the flip queue -- note that this must be called with dc->lock * unlocked or else it will hang. */ for (i = 0; i < ext->dc->n_windows; i++) { struct tegra_dc_ext_win *win = &ext->win[i]; kthread_flush_worker(&win->flip_worker); } tegra_dc_en_dis_latency_msrmnt_mode(ext->dc, false); /* * Blank all windows owned by dcext driver, unpin buffers that were * removed from screen, and advance syncpt. */ if (ext->dc->enabled) { for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { if (ext->win[i].user) windows |= BIT(i); } tegra_dc_blank_wins(ext->dc, windows); if (!tegra_fb_is_console_enabled(ext->dc->pdata)) { for_each_set_bit(i, &windows, tegra_dc_get_numof_dispwindows()) { tegra_dc_ext_unpin_window(&ext->win[i]); } } } return windows; } static int tegra_dc_ext_check_windowattr(struct tegra_dc_ext *ext, struct tegra_dc_win *win) { u32 *p_data; struct tegra_dc *dc = ext->dc; p_data = tegra_dc_parse_feature(dc, win->idx, GET_WIN_FORMATS); /* Check if the window exists */ if (!p_data) { dev_err(&dc->ndev->dev, "Window %d is not found.\n", win->idx); goto fail; } /* Check the window format */ if (!test_bit_win_colorfmt(win->fmt, p_data, WIN_FEATURE_ENTRY_SIZE)) { dev_err(&dc->ndev->dev, "Color format of window %d is invalid: %u.\n", win->idx, win->fmt); goto fail; } /* Check window size */ p_data = tegra_dc_parse_feature(dc, win->idx, GET_WIN_SIZE); if (CHECK_SIZE(win->out_w, p_data[MIN_WIDTH], p_data[MAX_WIDTH]) || CHECK_SIZE(win->out_h, p_data[MIN_HEIGHT], p_data[MAX_HEIGHT])) { dev_err(&dc->ndev->dev, "Size of window %d is invalid with %d wide %d high.\n", win->idx, win->out_w, win->out_h); goto fail; } if (win->flags & TEGRA_DC_EXT_FLIP_FLAG_BLOCKLINEAR) { if (win->flags & TEGRA_DC_EXT_FLIP_FLAG_TILED) { dev_err(&dc->ndev->dev, "Layout cannot be both " "blocklinear and tile for window %d.\n", win->idx); goto fail; } /* TODO: also check current window blocklinear support */ } if ((win->flags & TEGRA_DC_EXT_FLIP_FLAG_SCAN_COLUMN) && !tegra_dc_feature_has_scan_column(dc, win->idx)) { dev_err(&dc->ndev->dev, "Rotation not supported for window %d.\n", win->idx); goto fail; } /* our interface doesn't support both compression and interlace, * even if the HW can do it. */ if ((win->flags & TEGRA_DC_EXT_FLIP_FLAG_COMPRESSED) && (win->flags & TEGRA_DC_EXT_FLIP_FLAG_INTERLACE)) { dev_err(&dc->ndev->dev, "Compression and interlace can't both be on win %d.\n", win->idx); goto fail; } return 0; fail: return -EINVAL; } static void tegra_dc_ext_set_windowattr_basic(struct tegra_dc_win *win, const struct tegra_dc_ext_flip_windowattr *flip_win) { win->flags = TEGRA_WIN_FLAG_ENABLED; if (flip_win->blend == TEGRA_DC_EXT_BLEND_PREMULT) win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT; else if (flip_win->blend == TEGRA_DC_EXT_BLEND_COVERAGE) win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE; else if (flip_win->blend == TEGRA_DC_EXT_BLEND_ADD) win->flags |= TEGRA_WIN_FLAG_BLEND_ADD; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_TILED) win->flags |= TEGRA_WIN_FLAG_TILED; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_H) win->flags |= TEGRA_WIN_FLAG_INVERT_H; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_V) win->flags |= TEGRA_WIN_FLAG_INVERT_V; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_GLOBAL_ALPHA) win->global_alpha = flip_win->global_alpha; else win->global_alpha = 255; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_SCAN_COLUMN) win->flags |= TEGRA_WIN_FLAG_SCAN_COLUMN; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_BLOCKLINEAR) { win->flags |= TEGRA_WIN_FLAG_BLOCKLINEAR; win->block_height_log2 = flip_win->block_height_log2; } if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_INTERLACE) win->flags |= TEGRA_WIN_FLAG_INTERLACE; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_COMPRESSED) { win->cde.zbc_color = flip_win->cde.zbc_color; win->cde.offset_x = flip_win->cde.offset_x; win->cde.offset_y = flip_win->cde.offset_y; win->cde.ctb_entry = 0x02; } if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_INPUT_RANGE_LIMITED) win->flags |= TEGRA_WIN_FLAG_INPUT_RANGE_LIMITED; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_INPUT_RANGE_BYPASS) win->flags |= TEGRA_WIN_FLAG_INPUT_RANGE_BYPASS; else win->flags |= TEGRA_WIN_FLAG_INPUT_RANGE_FULL; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_CS_REC601) win->flags |= TEGRA_WIN_FLAG_CS_REC601; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_CS_REC709) win->flags |= TEGRA_WIN_FLAG_CS_REC709; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_CS_REC2020) win->flags |= TEGRA_WIN_FLAG_CS_REC2020; else win->flags |= TEGRA_WIN_FLAG_CS_NONE; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_DEGAMMA_NONE) win->flags |= TEGRA_WIN_FLAG_DEGAMMA_NONE; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_DEGAMMA_SRGB) win->flags |= TEGRA_WIN_FLAG_DEGAMMA_SRGB; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_DEGAMMA_YUV_8_10) win->flags |= TEGRA_WIN_FLAG_DEGAMMA_YUV_8_10; else if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_DEGAMMA_YUV_12) win->flags |= TEGRA_WIN_FLAG_DEGAMMA_YUV_12; if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_COLOR_EXPAND_UPDATE) { if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_COLOR_EXPAND_DISABLE) win->color_expand_enable = false; else win->color_expand_enable = true; } if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_CLAMP_BEFORE_BLEND_DISABLE) win->clamp_before_blend = false; else win->clamp_before_blend = true; win->fmt = flip_win->pixformat; win->x.full = flip_win->x; win->y.full = flip_win->y; win->w.full = flip_win->w; win->h.full = flip_win->h; /* XXX verify that this doesn't go outside display's active region */ win->out_x = flip_win->out_x; win->out_y = flip_win->out_y; win->out_w = flip_win->out_w; win->out_h = flip_win->out_h; win->z = flip_win->z; win->stride = flip_win->stride; win->stride_uv = flip_win->stride_uv; } static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext, struct tegra_dc_win *win, const struct tegra_dc_ext_flip_win *flip_win) { int err = 0; struct tegra_dc_ext_win *ext_win = &ext->win[win->idx]; s64 timestamp_ns; struct tegra_vrr *vrr = ext->dc->out->vrr; if (flip_win->handle[TEGRA_DC_Y] == NULL) { win->flags = 0; memset(ext_win->cur_handle, 0, sizeof(ext_win->cur_handle)); return 0; } tegra_dc_ext_set_windowattr_basic(win, &flip_win->attr); memcpy(ext_win->cur_handle, flip_win->handle, sizeof(ext_win->cur_handle)); /* XXX verify that this won't read outside of the surface */ win->phys_addr = flip_win->phys_addr + flip_win->attr.offset; win->phys_addr_u = flip_win->handle[TEGRA_DC_U] ? flip_win->phys_addr_u : flip_win->phys_addr; win->phys_addr_u += flip_win->attr.offset_u; win->phys_addr_v = flip_win->handle[TEGRA_DC_V] ? flip_win->phys_addr_v : flip_win->phys_addr; win->phys_addr_v += flip_win->attr.offset_v; if (ext->dc->mode.vmode == FB_VMODE_INTERLACED) { if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_INTERLACE) { win->phys_addr2 = flip_win->phys_addr + flip_win->attr.offset2; win->phys_addr_u2 = flip_win->handle[TEGRA_DC_U] ? flip_win->phys_addr_u : flip_win->phys_addr; win->phys_addr_u2 += flip_win->attr.offset_u2; win->phys_addr_v2 = flip_win->handle[TEGRA_DC_V] ? flip_win->phys_addr_v : flip_win->phys_addr; win->phys_addr_v2 += flip_win->attr.offset_v2; } else { win->phys_addr2 = flip_win->phys_addr; win->phys_addr_u2 = flip_win->handle[TEGRA_DC_U] ? flip_win->phys_addr_u : flip_win->phys_addr; win->phys_addr_v2 = flip_win->handle[TEGRA_DC_V] ? flip_win->phys_addr_v : flip_win->phys_addr; } } if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_COMPRESSED) win->cde.cde_addr = flip_win->phys_addr_cde + flip_win->attr.cde.offset; else win->cde.cde_addr = 0; err = tegra_dc_ext_check_windowattr(ext, win); if (err < 0) dev_err(&ext->dc->ndev->dev, "Window atrributes are invalid.\n"); #ifdef CONFIG_TEGRA_GRHOST_SYNC if (flip_win->pre_syncpt_fence) { sync_fence_wait(flip_win->pre_syncpt_fence, 5000); sync_fence_put(flip_win->pre_syncpt_fence); } else #endif if ((s32)flip_win->attr.pre_syncpt_id >= 0) { nvhost_syncpt_wait_timeout_ext(ext->dc->ndev, flip_win->attr.pre_syncpt_id, flip_win->attr.pre_syncpt_val, msecs_to_jiffies(5000), NULL, NULL); } if (err < 0) return err; if (tegra_platform_is_silicon()) { timestamp_ns = tegra_timespec_to_ns(&flip_win->attr.timestamp); if (timestamp_ns) { /* XXX: Should timestamping be overridden by "no_vsync" * flag */ if (vrr && vrr->enable) { struct timespec tm; s64 now_ns = 0; s64 sleep_us = 0; ktime_get_ts(&tm); now_ns = timespec_to_ns(&tm); sleep_us = div_s64(timestamp_ns - now_ns, 1000ll); if (sleep_us > TEGRA_DC_TS_MAX_DELAY_US) sleep_us = TEGRA_DC_TS_MAX_DELAY_US; if (sleep_us > 0) { usleep_range((s32)sleep_us, ((s32)sleep_us) + TEGRA_DC_TS_SLACK_US); } } else { tegra_dc_config_frame_end_intr(win->dc, true); err = wait_event_interruptible( win->dc->timestamp_wq, tegra_dc_is_within_n_vsync(win->dc, timestamp_ns)); tegra_dc_config_frame_end_intr(win->dc, false); } } } return err; } static int tegra_dc_ext_should_show_background( struct tegra_dc_ext_flip_data *data, int win_num) { struct tegra_dc *dc = data->ext->dc; int i; if (!dc->yuv_bypass || !tegra_dc_is_yuv420_10bpc(&dc->mode)) return false; /* Bypass for YUV420 10-bit 4k mode expects one or three scaling capable * windows, depending on chip capabilities. The windows always span * whole active display area of 2400x2160. dc->mode is already adjusted * to this dimension. Hence it is only necessary to show a special * background pattern when display is blank, i.e. all valid windows are * inactive. */ for (i = 0; i < win_num; i++) { struct tegra_dc_ext_flip_win *flip_win = &data->win[i]; int index = flip_win->attr.index; if (index < 0 || !test_bit(index, &dc->valid_windows)) continue; if (flip_win->handle[TEGRA_DC_Y] != NULL) return false; } return true; } static int tegra_dc_ext_get_background(struct tegra_dc_ext *ext, struct tegra_dc_win *win) { struct tegra_dc *dc = ext->dc; u32 active_width = dc->mode.h_active; u32 active_height = dc->mode.v_active; *win = *tegra_fb_get_blank_win(dc->fb); win->flags |= TEGRA_WIN_FLAG_ENABLED; win->fmt = TEGRA_DC_EXT_FMT_T_A8R8G8B8; win->x.full = dfixed_const(0); win->y.full = dfixed_const(0); win->h.full = dfixed_const(1); win->w.full = dfixed_const(active_width); win->out_x = 0; win->out_y = 0; win->out_w = active_width; win->out_h = active_height; win->z = 0xff; return 0; } static int tegra_dc_ext_set_vblank(struct tegra_dc_ext *ext, bool enable) { struct tegra_dc *dc; int ret = 0; if (ext->vblank_enabled == enable) return 0; dc = ext->dc; if (enable) ret = tegra_dc_vsync_enable(dc); else if (ext->vblank_enabled) tegra_dc_vsync_disable(dc); if (!ret) { ext->vblank_enabled = enable; return 0; } return 1; } static void tegra_dc_ext_setup_vpulse3(struct tegra_dc_ext *ext, unsigned int scanline_num) { struct tegra_dc *dc = ext->dc; u32 old_state; tegra_dc_get(dc); mutex_lock(&dc->lock); old_state = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* write active version; this updates assembly as well */ tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, scanline_num, DC_DISP_V_PULSE3_POSITION_A); tegra_dc_writel(dc, old_state, DC_CMD_STATE_ACCESS); tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* flush */ mutex_unlock(&dc->lock); tegra_dc_put(dc); } static void tegra_dc_ext_incr_vpulse3(struct tegra_dc_ext *ext) { struct tegra_dc *dc = ext->dc; unsigned int vpulse3_sync_id = dc->vpulse3_syncpt; /* Request vpulse3 syncpt increment */ tegra_dc_writel(dc, VPULSE3_COND | vpulse3_sync_id, DC_CMD_GENERAL_INCR_SYNCPT); } static void tegra_dc_ext_scanline_worker(struct kthread_work *work) { struct tegra_dc_ext_scanline_data *data = container_of(work, struct tegra_dc_ext_scanline_data, scanline_work); struct tegra_dc_ext *ext = data->ext; struct tegra_dc *dc = ext->dc; unsigned int vpulse3_sync_id = dc->vpulse3_syncpt; int min_val; /* Wait for frame end before programming new request */ _tegra_dc_wait_for_frame_end(dc, div_s64(dc->frametime_ns, 1000000ll) * 2); if (data->triggered_line >= 0) { if (ext->scanline_trigger != data->triggered_line) { dev_dbg(&dc->ndev->dev, "vp3 sl#: %d\n", data->triggered_line); /* Setup vpulse3 trigger line */ tegra_dc_ext_setup_vpulse3(ext, data->triggered_line); /* Save new scanline trigger state */ ext->scanline_trigger = data->triggered_line; } /* Request vpulse3 increment */ tegra_dc_ext_incr_vpulse3(ext); } else { /* Clear scanline trigger state */ ext->scanline_trigger = -1; /* Read current min_val */ min_val = nvhost_syncpt_read_minval(dc->ndev, vpulse3_sync_id); /* Udate min_val to expected max only if they don't match */ if (min_val != data->max_val) { dev_dbg(&dc->ndev->dev, "vp3 mismatch; %d vs %d", min_val, data->max_val); nvhost_syncpt_set_minval(dc->ndev, vpulse3_sync_id, data->max_val); } } /* Free arg allocated in the ioctl call */ kfree(data); } int tegra_dc_ext_vpulse3(struct tegra_dc_ext *ext, struct tegra_dc_ext_scanline_info *args) { int ret = -EFAULT; struct tegra_dc *dc = ext->dc; struct tegra_dc_ext_scanline_data *data; unsigned int vpulse3_sync_id = dc->vpulse3_syncpt; unsigned int vpulse3_sync_max_val = 0; /* If display has been disconnected OR * Vpulse3 syncpt is invalid OR * scanline workqueue is not setup, return error */ if (!dc->enabled || !dc->connected || vpulse3_sync_id == NVSYNCPT_INVALID) { dev_err(&dc->ndev->dev, "DC not setup\n"); return -EACCES; } /* TODO: Support id != 0 */ if (args->id) { dev_err(&dc->ndev->dev, "Invalid scanline id\n"); return -EINVAL; } /* TODO: Support frame != 0 and raw syncpt */ if ((args->flags & TEGRA_DC_EXT_SCANLINE_FLAG_ENABLE) && (args->frame || args->flags & TEGRA_DC_EXT_SCANLINE_FLAG_RAW_SYNCPT)) { dev_err(&dc->ndev->dev, "Invalid args\n"); return -EINVAL; } dev_dbg(&dc->ndev->dev, "vp3 id : %d\n", vpulse3_sync_id); /* Allocate arg for workqueue */ data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; if (args->flags & TEGRA_DC_EXT_SCANLINE_FLAG_ENABLE) { /* Increment max_val by 1 */ vpulse3_sync_max_val = nvhost_syncpt_incr_max_ext(dc->ndev, vpulse3_sync_id, 1); dev_dbg(&dc->ndev->dev, "vp3 max: %d\n", vpulse3_sync_max_val); /* Create a fencefd */ ret = nvhost_syncpt_create_fence_single_ext(dc->ndev, vpulse3_sync_id, vpulse3_sync_max_val, "vpulse3-fence", &args->syncfd); if (ret) { dev_err(&dc->ndev->dev, "Failed creating vpulse3 fence err: %d\n", ret); kfree(data); return ret; } /* Pass trigger line value */ data->triggered_line = args->triggered_line; } else { /* Clear trigger line value */ data->triggered_line = -1; /* Pass current max_val */ data->max_val = nvhost_syncpt_read_maxval(dc->ndev, vpulse3_sync_id); } /* Queue work to setup/increment/remove scanline trigger */ data->ext = ext; kthread_init_work(&data->scanline_work, tegra_dc_ext_scanline_worker); kthread_queue_work(&ext->scanline_worker, &data->scanline_work); return 0; } int tegra_dc_ext_get_scanline(struct tegra_dc_ext *ext) { return ext->scanline_trigger; } static void tegra_dc_ext_unpin_handles(struct tegra_dc_dmabuf *unpin_handles[], int nr_unpin) { int i; for (i = 0; i < nr_unpin; i++) { dma_buf_unmap_attachment(unpin_handles[i]->attach, unpin_handles[i]->sgt, DMA_TO_DEVICE); dma_buf_detach(unpin_handles[i]->buf, unpin_handles[i]->attach); dma_buf_put(unpin_handles[i]->buf); kfree(unpin_handles[i]); } } static void tegra_dc_flip_trace(struct tegra_dc_ext_flip_data *data, display_syncpt_notifier trace_fn) { struct tegra_dc *dc = data->ext->dc; struct tegra_dc_ext_flip_win *win; struct tegra_dc_ext_flip_windowattr *attr; int win_num; u64 timestamp; int i; timestamp = tegra_dc_get_tsc_time(); for (i = 0; i < data->act_window_num; i++) { win = &data->win[i]; attr = &win->attr; win_num = attr->index; if (win_num < 0 || !test_bit(win_num, &dc->valid_windows)) continue; (*trace_fn)(dc->ctrl_num, win_num, win->syncpt_max, attr->buff_id, timestamp); } } static void tegra_dc_update_background_color(struct tegra_dc *dc, struct tegra_dc_ext_flip_data *data) { if (tegra_dc_is_nvdisplay()) tegra_nvdisp_set_background_color(dc, data->background_color); else tegra_dc_set_background_color(dc, data->background_color); data->background_color_update_needed = false; } static void tegra_dc_update_postcomp(struct tegra_dc *dc, struct tegra_dc_ext_flip_data *data) { if (!tegra_dc_is_nvdisplay()) return; /* If we're transitioning between a bypass and * a non-bypass mode, update the output CSC * and chroma LPF during this flip. */ if (dc->yuv_bypass_dirty) { tegra_nvdisp_set_ocsc(dc, &dc->mode); tegra_nvdisp_set_chroma_lpf(dc); } if (data->cmu_update_needed) tegra_nvdisp_set_output_lut(dc, &data->user_nvdisp_cmu, data->new_cmu_values); if (data->output_colorspace_update_needed) tegra_nvdisp_set_output_colorspace(dc, data->output_colorspace); if (data->output_range_update_needed) tegra_nvdisp_set_output_range(dc, data->limited_range_enable); if (data->output_range_update_needed || data->output_colorspace_update_needed) tegra_nvdisp_set_csc2(dc); /* * Note: as per current use, this function is called only from * flip_worker before tegra_dc_update_windows. * Since general channel is activated by default in * update_windows, we don't need another activation here */ } /* * tegra_dc_ext_store_latency_msrmnt_info() - stores relevant info needed * for latency instrumentation * @dc : pointer to struct tegra_dc of the current head. * @flip_data: pointer the current tegra_dc_ext_flip_data that has all * the relevant info regarding the windows used in the cuurent flip. * * Currently supports nvdisplay only. Gets the first enabled window and * stores the corresponding buff handle and offset. If there are more than * one window enabled, returns and doesn't store the buff handle. * * Return : void */ static void tegra_dc_ext_store_latency_msrmnt_info(struct tegra_dc *dc, struct tegra_dc_ext_flip_data *flip_data) { int i; int nr_windows_enabled = 0; int nr_wins = flip_data->act_window_num; struct tegra_dc_ext_flip_win *flip_win = flip_data->win; mutex_lock(&dc->msrmnt_info.lock); if (!dc->msrmnt_info.enabled) { mutex_unlock(&dc->msrmnt_info.lock); return; } for (i = 0; i < nr_wins; i++) { struct tegra_dc_ext_flip_windowattr *attr = &flip_win[i].attr; struct tegra_dc_ext_win *ext_win = &dc->ext->win[attr->index]; if (ext_win->enabled) { dc->msrmnt_info.buf_handle = ext_win->cur_handle[TEGRA_DC_Y]->buf; dc->msrmnt_info.offset = attr->offset; nr_windows_enabled++; } if (nr_windows_enabled > 1) { dev_dbg(&dc->ndev->dev, "More than 1 window enabled. Can't collect msrmnt info\n"); dc->msrmnt_info.buf_handle = NULL; break; } } mutex_unlock(&dc->msrmnt_info.lock); } static void tegra_dc_ext_flip_worker(struct kthread_work *work) { struct tegra_dc_ext_flip_data *data = container_of(work, struct tegra_dc_ext_flip_data, work); int win_num = data->act_window_num; struct tegra_dc_ext *ext = data->ext; struct tegra_dc_win *wins[DC_N_WINDOWS]; struct tegra_dc_win *blank_win; struct tegra_dc_dmabuf *unpin_handles[DC_N_WINDOWS * TEGRA_DC_NUM_PLANES]; struct tegra_dc_dmabuf *old_handle; struct tegra_dc *dc = ext->dc; int i, nr_unpin = 0, nr_win = 0; bool skip_flip = true; bool wait_for_vblank = false; bool lock_flip = false; bool show_background = tegra_dc_ext_should_show_background(data, win_num); struct tegra_dc_flip_buf_ele *flip_ele = data->flip_buf_ele; if (flip_ele) flip_ele->state = TEGRA_DC_FLIP_STATE_DEQUEUED; blank_win = kzalloc(sizeof(*blank_win), GFP_KERNEL); if (!blank_win) dev_err(&ext->dc->ndev->dev, "Failed to allocate blank_win.\n"); tegra_dc_scrncapt_disp_pause_lock(dc); BUG_ON(win_num > tegra_dc_get_numof_dispwindows()); for (i = 0; i < win_num; i++) { struct tegra_dc_ext_flip_win *flip_win = &data->win[i]; int index = flip_win->attr.index; struct tegra_dc_win *win; struct tegra_dc_ext_win *ext_win; struct tegra_dc_ext_flip_data *temp = NULL; s64 head_timestamp = -1; int j = 0; u32 reg_val = 0; bool win_skip_flip = false; if (index < 0 || !test_bit(index, &dc->valid_windows)) continue; win = tegra_dc_get_window(dc, index); if (!win) continue; ext_win = &ext->win[index]; if (!(atomic_dec_and_test(&ext_win->nr_pending_flips)) && (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR)) win_skip_flip = true; mutex_lock(&ext_win->queue_lock); list_for_each_entry(temp, &ext_win->timestamp_queue, timestamp_node) { if (!tegra_platform_is_silicon()) continue; if (j == 0) { if (unlikely(temp != data)) { /* Frame doesn't contain timestamp in list */ break; } else head_timestamp = tegra_timespec_to_ns( &flip_win->attr.timestamp); } else { s64 timestamp = tegra_timespec_to_ns( &temp->win[i].attr.timestamp); win_skip_flip = !tegra_dc_does_vsync_separate( dc, timestamp, head_timestamp); /* Look ahead only one flip */ break; } j++; } if (head_timestamp >= 0) list_del(&data->timestamp_node); mutex_unlock(&ext_win->queue_lock); skip_flip = skip_flip && win_skip_flip; if (win_skip_flip) { if (flip_ele) flip_ele->state = TEGRA_DC_FLIP_STATE_SKIPPED; old_handle = flip_win->handle[TEGRA_DC_Y]; } else { old_handle = ext_win->cur_handle[TEGRA_DC_Y]; } if (old_handle) { int j; for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) { if (win_skip_flip) old_handle = flip_win->handle[j]; else old_handle = ext_win->cur_handle[j]; if (!old_handle) continue; unpin_handles[nr_unpin++] = old_handle; } } if (tegra_dc_is_t21x()) { if ((data->win[i].attr.flags & TEGRA_DC_EXT_FLIP_FLAG_UPDATE_CSC) && !dc->yuv_bypass) { win->win_csc.yof = data->win[i].attr.csc.yof; win->win_csc.kyrgb = data->win[i].attr.csc.kyrgb; win->win_csc.kur = data->win[i].attr.csc.kur; win->win_csc.kug = data->win[i].attr.csc.kug; win->win_csc.kub = data->win[i].attr.csc.kub; win->win_csc.kvr = data->win[i].attr.csc.kvr; win->win_csc.kvg = data->win[i].attr.csc.kvg; win->win_csc.kvb = data->win[i].attr.csc.kvb; win->csc_dirty = true; } } else if (tegra_dc_is_nvdisplay()) { if (data->win[i].user_nvdisp_win_csc && !ext->dc->yuv_bypass && !win->force_user_csc) { win->nvdisp_win_csc.csc_enable = data->win[i].nvdisp_win_csc.csc_enable; win->nvdisp_win_csc.r2r = data->win[i].nvdisp_win_csc.r2r; win->nvdisp_win_csc.g2r = data->win[i].nvdisp_win_csc.g2r; win->nvdisp_win_csc.b2r = data->win[i].nvdisp_win_csc.b2r; win->nvdisp_win_csc.const2r = data->win[i].nvdisp_win_csc.const2r; win->nvdisp_win_csc.r2g = data->win[i].nvdisp_win_csc.r2g; win->nvdisp_win_csc.g2g = data->win[i].nvdisp_win_csc.g2g; win->nvdisp_win_csc.b2g = data->win[i].nvdisp_win_csc.b2g; win->nvdisp_win_csc.const2g = data->win[i].nvdisp_win_csc.const2g; win->nvdisp_win_csc.r2b = data->win[i].nvdisp_win_csc.r2b; win->nvdisp_win_csc.g2b = data->win[i].nvdisp_win_csc.g2b; win->nvdisp_win_csc.b2b = data->win[i].nvdisp_win_csc.b2b; win->nvdisp_win_csc.const2b = data->win[i].nvdisp_win_csc.const2b; win->csc_dirty = true; } } if (!win_skip_flip) tegra_dc_ext_set_windowattr(ext, win, &data->win[i]); if (dc->yuv_bypass) { reg_val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); reg_val &= ~CMU_ENABLE; tegra_dc_writel(dc, reg_val, DC_DISP_DISP_COLOR_CONTROL); } if (flip_win->attr.swap_interval && !no_vsync) wait_for_vblank = true; ext_win->enabled = !!(win->flags & TEGRA_WIN_FLAG_ENABLED); /* Hijack first disabled, scaling capable window to host * the background pattern. */ if (blank_win && !ext_win->enabled && show_background && tegra_dc_feature_has_scaling(ext->dc, win->idx)) { tegra_dc_ext_get_background(ext, blank_win); blank_win->idx = win->idx; wins[nr_win++] = blank_win; show_background = false; } else { wins[nr_win++] = win; } } /* Window with blank background pattern is shown only if all windows * are inactive, thus there must be free window which can host * background pattern. */ BUG_ON(show_background); if (trace_sync_wt_ovr_syncpt_upd_enabled()) tegra_dc_flip_trace(data, trace_sync_wt_ovr_syncpt_upd); if (dc->enabled && !skip_flip) { tegra_dc_set_hdr(dc, &data->hdr_data, data->hdr_cache_dirty); dc->blanked = false; if (dc->out_ops && dc->out_ops->vrr_enable) dc->out_ops->vrr_enable(dc, data->flags & TEGRA_DC_EXT_FLIP_HEAD_FLAG_VRR_MODE); if (data->imp_dirty) { trace_display_imp_flip_started(dc->ctrl_num, data->imp_session_id); dc->imp_dirty = true; tegra_dc_adjust_imp(dc, true); } if (dc->frm_lck_info.frame_lock_enable && ((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP) || (dc->out->type == TEGRA_DC_OUT_FAKE_DP))) lock_flip = true; if (data->background_color_update_needed) tegra_dc_update_background_color(dc, data); /* Perform per flip postcomp updates here */ tegra_dc_update_postcomp(dc, data); if (dc->yuv_bypass_dirty || dc->yuv_bypass) dc->yuv_bypass_dirty = false; tegra_dc_update_windows(wins, nr_win, data->dirty_rect_valid ? data->dirty_rect : NULL, wait_for_vblank, lock_flip); if (data->avi_cache_dirty) if (dc->out_ops && dc->out_ops->set_avi) dc->out_ops->set_avi(dc, &data->avi_info); /* TODO: implement swapinterval here */ tegra_dc_sync_windows(wins, nr_win); if (flip_ele) flip_ele->state = TEGRA_DC_FLIP_STATE_FLIPPED; if (trace_scanout_syncpt_upd_enabled()) tegra_dc_flip_trace(data, trace_scanout_syncpt_upd); tegra_dc_ext_store_latency_msrmnt_info(dc, data); if (dc->out->vrr) trace_scanout_vrr_stats((data->win[win_num-1]).syncpt_max , dc->out->vrr->dcb); tegra_dc_program_bandwidth(dc, true); if (!tegra_dc_has_multiple_dc()) tegra_dc_call_flip_callback(); if (data->imp_dirty) { tegra_dc_adjust_imp(dc, false); trace_display_imp_flip_completed(dc->ctrl_num, data->imp_session_id); } } else { trace_dc_flip_dropped(dc->enabled, skip_flip); } if (data->imp_dirty) tegra_dc_release_common_channel(dc); tegra_dc_scrncapt_disp_pause_unlock(dc); if (!skip_flip) { for (i = 0; i < win_num; i++) { struct tegra_dc_ext_flip_win *flip_win = &data->win[i]; int index = flip_win->attr.index; if (index < 0 || !test_bit(index, &dc->valid_windows)) continue; tegra_dc_incr_syncpt_min(dc, index, flip_win->syncpt_max); } atomic64_inc(&dc->flip_stats.flips_cmpltd); } else { atomic64_inc(&dc->flip_stats.flips_skipped); } /* unpin and deref previous front buffers */ tegra_dc_ext_unpin_handles(unpin_handles, nr_unpin); #ifdef CONFIG_ANDROID /* now DC has submitted buffer for display, try to release fbmem */ tegra_fb_release_fbmem(ext->dc->fb); #endif kfree(data); kfree(blank_win); } static int lock_windows_for_flip(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *win_attr, int win_num) { struct tegra_dc_ext *ext = user->ext; u8 idx_mask = 0; int i; BUG_ON(win_num > tegra_dc_get_numof_dispwindows()); for (i = 0; i < win_num; i++) { int index = win_attr[i].index; if (index < 0 || !test_bit(index, &ext->dc->valid_windows)) continue; idx_mask |= BIT(index); } for (i = 0; i < win_num; i++) { struct tegra_dc_ext_win *win; if (!(idx_mask & BIT(i))) continue; win = &ext->win[i]; mutex_lock_nested(&win->lock, i); if (win->user != user) { goto fail_unlock; } } return 0; fail_unlock: do { if (!(idx_mask & BIT(i))) continue; mutex_unlock(&ext->win[i].lock); } while (i--); return -EACCES; } static void unlock_windows_for_flip(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *win, int win_num) { struct tegra_dc_ext *ext = user->ext; u8 idx_mask = 0; int i; BUG_ON(win_num > tegra_dc_get_numof_dispwindows()); for (i = 0; i < win_num; i++) { int index = win[i].index; if (index < 0 || !test_bit(index, &ext->dc->valid_windows)) continue; idx_mask |= BIT(index); } for (i = win_num - 1; i >= 0; i--) { if (!(idx_mask & BIT(i))) continue; mutex_unlock(&ext->win[i].lock); } } static int sanitize_flip_args(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *win_list, int win_num, __u16 **dirty_rect) { int i, used_windows = 0; struct tegra_dc *dc = user->ext->dc; if (win_num > tegra_dc_get_numof_dispwindows()) return -EINVAL; for (i = 0; i < win_num; i++) { struct tegra_dc_ext_flip_windowattr *win = &win_list[i]; int index = win->index; fixed20_12 input_w, input_h; u32 w, h; if (index < 0) continue; if (index >= tegra_dc_get_numof_dispwindows() || !test_bit(index, &dc->valid_windows)) return -EINVAL; if (used_windows & BIT(index)) return -EINVAL; used_windows |= BIT(index); /* * Check that the window dimensions are non-zero. The input * width and height are specified as 20.12 fixed-point numbers. */ input_w.full = win->w; input_h.full = win->h; w = dfixed_trunc(input_w); h = dfixed_trunc(input_h); if (win->buff_id != 0 && (w == 0 || h == 0 || win->out_w == 0 || win->out_h == 0)) { dev_err(&dc->ndev->dev, "%s: WIN %d invalid size:w=%u,h=%u,out_w=%u,out_h=%u\n", __func__, index, w, h, win->out_w, win->out_h); return -EINVAL; } /* * Window output geometry including width/height + offset * should not exceed hActive/vActive of current mode */ if ((win->out_w + win->out_x) > dc->mode.h_active || (win->out_h + win->out_y) > dc->mode.v_active) { dev_err(&dc->ndev->dev, "Invalid out_w + out_x (%u) > hActive (%u)\n OR/AND out_h + out_y (%u) > vActive (%u)\n for WIN %d\n", win->out_w + win->out_x, dc->mode.h_active, win->out_h + win->out_y, dc->mode.v_active, i); return -EINVAL; } if (tegra_dc_is_nvdisplay()) { if (tegra_nvdisp_verify_win_properties(dc, win)) { /* Error in window properties */ return -EINVAL; } } } if (!used_windows) return -EINVAL; if (*dirty_rect) { unsigned int xoff = (*dirty_rect)[0]; unsigned int yoff = (*dirty_rect)[1]; unsigned int width = (*dirty_rect)[2]; unsigned int height = (*dirty_rect)[3]; if ((!width && !height) || dc->mode.vmode == FB_VMODE_INTERLACED || !dc->out_ops || !dc->out_ops->partial_update || (!xoff && !yoff && (width == dc->mode.h_active) && (height == dc->mode.v_active))) { /* Partial update undesired, unsupported, * or dirty_rect covers entire frame. */ *dirty_rect = NULL; } else { if (!width || !height || (xoff + width) > dc->mode.h_active || (yoff + height) > dc->mode.v_active) return -EINVAL; /* Constraint 7: H/V_DISP_ACTIVE >= 16. * Make sure the minimal size of dirty region is 16*16. * If not, extend the dirty region. */ if (width < 16) { width = (*dirty_rect)[2] = 16; if (xoff + width > dc->mode.h_active) (*dirty_rect)[0] = dc->mode.h_active - width; } if (height < 16) { height = (*dirty_rect)[3] = 16; if (yoff + height > dc->mode.v_active) (*dirty_rect)[1] = dc->mode.v_active - height; } } } return 0; } static int tegra_dc_ext_pin_windows(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *wins, int win_num, struct tegra_dc_ext_flip_win *flip_wins, bool *has_timestamp, bool syncpt_fd) { int i, ret; struct tegra_dc *dc = user->ext->dc; for (i = 0; i < win_num; i++) { struct tegra_dc_ext_flip_win *flip_win = &flip_wins[i]; int index = wins[i].index; memcpy(&flip_win->attr, &wins[i], sizeof(flip_win->attr)); if (has_timestamp && tegra_timespec_to_ns( &flip_win->attr.timestamp)) { /* Set first frame timestamp to 0 after device boot-up to prevent wait on 1st flip request */ if (!dc->frame_end_timestamp) memset(&flip_win->attr.timestamp, 0, sizeof(flip_win->attr.timestamp)); *has_timestamp = true; } if (index < 0 || !test_bit(index, &dc->valid_windows)) continue; ret = tegra_dc_ext_pin_window(user, flip_win->attr.buff_id, &flip_win->handle[TEGRA_DC_Y], &flip_win->phys_addr); if (ret) return ret; if (flip_win->attr.buff_id_u) { ret = tegra_dc_ext_pin_window(user, flip_win->attr.buff_id_u, &flip_win->handle[TEGRA_DC_U], &flip_win->phys_addr_u); if (ret) return ret; } else { flip_win->handle[TEGRA_DC_U] = NULL; flip_win->phys_addr_u = 0; } if (flip_win->attr.buff_id_v) { ret = tegra_dc_ext_pin_window(user, flip_win->attr.buff_id_v, &flip_win->handle[TEGRA_DC_V], &flip_win->phys_addr_v); if (ret) return ret; } else { flip_win->handle[TEGRA_DC_V] = NULL; flip_win->phys_addr_v = 0; } if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_COMPRESSED) { /* use buff_id of the main surface when cde is 0 */ __u32 cde_buff_id = flip_win->attr.cde.buff_id; if (!cde_buff_id) cde_buff_id = flip_win->attr.buff_id; ret = tegra_dc_ext_pin_window(user, cde_buff_id, &flip_win->handle[TEGRA_DC_CDE], &flip_win->phys_addr_cde); if (ret) return ret; } else { flip_win->handle[TEGRA_DC_CDE] = NULL; flip_win->phys_addr_cde = 0; } if (syncpt_fd) { if (flip_win->attr.pre_syncpt_fd >= 0) { #ifdef CONFIG_TEGRA_GRHOST_SYNC flip_win->pre_syncpt_fence = sync_fence_fdget( flip_win->attr.pre_syncpt_fd); #else BUG(); #endif } else { flip_win->attr.pre_syncpt_id = NVSYNCPT_INVALID; } } } return 0; } static void tegra_dc_ext_unpin_window(struct tegra_dc_ext_win *win) { struct tegra_dc_dmabuf *unpin_handles[TEGRA_DC_NUM_PLANES]; int nr_unpin = 0; mutex_lock(&win->lock); if (win->cur_handle[TEGRA_DC_Y]) { int j; for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) { if (win->cur_handle[j]) unpin_handles[nr_unpin++] = win->cur_handle[j]; } memset(win->cur_handle, 0, sizeof(win->cur_handle)); } mutex_unlock(&win->lock); tegra_dc_ext_unpin_handles(unpin_handles, nr_unpin); } static int tegra_dc_ext_configure_nvdisp_cmu_user_data( struct tegra_dc_ext_flip_data *flip_kdata, struct tegra_dc_ext_flip_user_data *flip_udata) { int ret = 0; size_t size; struct tegra_dc *dc; struct tegra_dc_ext_nvdisp_cmu *knvdisp_cmu; struct tegra_dc_ext_udata_nvdisp_cmu *udata_nvdisp_cmu; struct tegra_dc_ext_nvdisp_cmu *unvdisp_cmu; if (!flip_kdata || !flip_udata) return -EINVAL; dc = flip_kdata->ext->dc; udata_nvdisp_cmu = &flip_udata->nvdisp_cmu; unvdisp_cmu = (struct tegra_dc_ext_nvdisp_cmu *) udata_nvdisp_cmu->nvdisp_cmu; knvdisp_cmu = &flip_kdata->user_nvdisp_cmu; size = sizeof(knvdisp_cmu->cmu_enable); /* copying only cmu_enable now */ if (copy_from_user(knvdisp_cmu, (void __user *) (uintptr_t)unvdisp_cmu, size)) { return -EFAULT; } /* copying rest of the nvdisp_cmu struct after checking for * update flag */ if (knvdisp_cmu->cmu_enable) { if (flip_udata->flags & TEGRA_DC_EXT_FLIP_FLAG_UPDATE_NVDISP_CMU) { size = sizeof(knvdisp_cmu) - size; if (copy_from_user(&knvdisp_cmu->lut_size, (void __user *) (uintptr_t)&unvdisp_cmu->lut_size, size)) { return -EFAULT; } flip_kdata->new_cmu_values = true; } } flip_kdata->cmu_update_needed = true; return ret; } static int tegra_dc_ext_configure_output_csc_user_data( struct tegra_dc_ext_flip_data *flip_kdata, struct tegra_dc_ext_flip_user_data *flip_udata) { struct tegra_dc_ext_udata_output_csc *user_csc = &flip_udata->output_csc; if (flip_udata->flags & TEGRA_DC_EXT_FLIP_FLAG_UPDATE_OCSC_CS) { switch (user_csc->output_colorspace) { case TEGRA_DC_EXT_FLIP_FLAG_CS_NONE: case TEGRA_DC_EXT_FLIP_FLAG_CS_REC601: case TEGRA_DC_EXT_FLIP_FLAG_CS_REC709: case TEGRA_DC_EXT_FLIP_FLAG_CS_REC2020: flip_kdata->output_colorspace_update_needed = true; flip_kdata->output_colorspace = user_csc->output_colorspace; break; default: dev_err(&flip_kdata->ext->dc->ndev->dev, "Invalid colorspace value\n"); return -EINVAL; } } if (flip_udata->flags & TEGRA_DC_EXT_FLIP_FLAG_UPDATE_OCSC_RANGE) { flip_kdata->output_range_update_needed = true; flip_kdata->limited_range_enable = user_csc->limited_range_enable; } return 0; } static int tegra_dc_ext_read_nvdisp_win_csc_user_data( struct tegra_dc_ext_flip_data *flip_kdata, struct tegra_dc_ext_flip_user_data *flip_udata) { int ret = 0; int i, j; struct tegra_dc_ext_nvdisp_win_csc *entry; struct tegra_dc_ext_udata_nvdisp_win_csc *unvdisp_win_csc; if (!flip_kdata || !flip_udata) return -EINVAL; unvdisp_win_csc = &flip_udata->nvdisp_win_csc; if (unvdisp_win_csc->nr_elements <= 0) { dev_err(&flip_kdata->ext->dc->ndev->dev, "Invalid TEGRA_DC_EXT_FLIP_USER_DATA_NVDISP_WIN_CSC config\n"); return -EINVAL; } entry = kcalloc((size_t)unvdisp_win_csc->nr_elements, (size_t)sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; if (copy_from_user(entry, (void __user *) (uintptr_t)unvdisp_win_csc->array, sizeof(*entry) * unvdisp_win_csc->nr_elements)) { ret = -EFAULT; goto end; } for (i = 0; i < unvdisp_win_csc->nr_elements; i++) { for (j = 0; j < flip_kdata->act_window_num; j++) { if (entry[i].win_index == flip_kdata->win[j].attr.index) { break; } } if (j >= flip_kdata->act_window_num) { dev_err(&flip_kdata->ext->dc->ndev->dev, "win%d for nvdisp_win_csc not in flip wins\n", entry[i].win_index); ret = -EINVAL; goto end; } /* copy into local tegra_dc_ext_flip_win */ flip_kdata->win[j].nvdisp_win_csc = entry[i]; flip_kdata->win[j].user_nvdisp_win_csc = true; } end: kfree(entry); return ret; } static int tegra_dc_ext_configure_background_color_user_data( struct tegra_dc_ext_flip_data *flip_kdata, struct tegra_dc_ext_flip_user_data *flip_udata) { struct tegra_dc_ext_udata_background_color *background_color = &flip_udata->background_color; flip_kdata->background_color_update_needed = true; flip_kdata->background_color = background_color->background_color; return 0; } static int tegra_dc_ext_read_user_data(struct tegra_dc_ext_flip_data *data, struct tegra_dc_ext_flip_user_data *flip_user_data, int nr_user_data) { int i = 0, ret = 0; bool hdr_present = false; bool imp_tag_present = false; bool nvdisp_win_csc_present = false; for (i = 0; i < nr_user_data; i++) { switch (flip_user_data[i].data_type) { case TEGRA_DC_EXT_FLIP_USER_DATA_HDR_DATA: { struct tegra_dc_hdr *hdr; struct tegra_dc_ext_hdr *info; if (hdr_present) { dev_err(&data->ext->dc->ndev->dev, "only one hdr_data/flip is allowed\n"); return -EINVAL; } hdr_present = true; hdr = &data->hdr_data; info = &flip_user_data[i].hdr_info; if (flip_user_data[i].flags & TEGRA_DC_EXT_FLIP_FLAG_HDR_ENABLE) hdr->enabled = true; if (flip_user_data[i].flags & TEGRA_DC_EXT_FLIP_FLAG_HDR_DATA_UPDATED) { data->hdr_cache_dirty = true; hdr->eotf = info->eotf; hdr->static_metadata_id = info->static_metadata_id; memcpy(hdr->static_metadata, info->static_metadata, sizeof(hdr->static_metadata)); } break; } case TEGRA_DC_EXT_FLIP_USER_DATA_AVI_DATA: { struct tegra_dc_ext_avi *kdata = &data->avi_info; struct tegra_dc_ext_avi *udata = &flip_user_data[i].avi_info; kdata->avi_colorimetry = udata->avi_colorimetry; data->avi_cache_dirty = true; break; } case TEGRA_DC_EXT_FLIP_USER_DATA_IMP_TAG: if (!tegra_dc_is_nvdisplay()) return -EINVAL; if (imp_tag_present) { dev_err(&data->ext->dc->ndev->dev, "only one imp_tag/flip is allowed\n"); return -EINVAL; } imp_tag_present = true; data->imp_session_id = flip_user_data[i].imp_tag.session_id; data->imp_dirty = true; break; /* Handled in the TEGRA_DC_EXT_FLIP4 ioctl context */ case TEGRA_DC_EXT_FLIP_USER_DATA_POST_SYNCPT: case TEGRA_DC_EXT_FLIP_USER_DATA_GET_FLIP_INFO: break; case TEGRA_DC_EXT_FLIP_USER_DATA_NVDISP_WIN_CSC: if (nvdisp_win_csc_present) { dev_err(&data->ext->dc->ndev->dev, "only one nvdisp_win_csc/flip is allowed\n"); return -EINVAL; } nvdisp_win_csc_present = true; ret = tegra_dc_ext_read_nvdisp_win_csc_user_data(data, &flip_user_data[i]); if (ret) return ret; break; case TEGRA_DC_EXT_FLIP_USER_DATA_NVDISP_CMU: ret = tegra_dc_ext_configure_nvdisp_cmu_user_data(data, &flip_user_data[i]); if (ret) return ret; break; case TEGRA_DC_EXT_FLIP_USER_DATA_OUTPUT_CSC: ret = tegra_dc_ext_configure_output_csc_user_data(data, &flip_user_data[i]); if (ret) return ret; break; case TEGRA_DC_EXT_FLIP_USER_DATA_BACKGROUND_COLOR: ret = tegra_dc_ext_configure_background_color_user_data( data, &flip_user_data[i]); if (ret) return ret; break; default: dev_err(&data->ext->dc->ndev->dev, "Invalid FLIP_USER_DATA_TYPE\n"); return -EINVAL; } } return ret; } static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *win, int win_num, __u32 *syncpt_id, __u32 *syncpt_val, int *syncpt_fd, __u16 *dirty_rect, u8 flip_flags, struct tegra_dc_ext_flip_user_data *flip_user_data, int nr_user_data, u64 *flip_id) { struct tegra_dc_ext *ext = user->ext; struct tegra_dc_ext_flip_data *data; int work_index = -1; __u32 post_sync_val = 0, post_sync_id = NVSYNCPT_INVALID; int i, ret = 0; bool has_timestamp = false; u64 flip_id_local; /* If display has been disconnected return with error. */ if (!ext->dc->connected) return -1; ret = sanitize_flip_args(user, win, win_num, &dirty_rect); if (ret) return ret; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; kthread_init_work(&data->work, &tegra_dc_ext_flip_worker); data->ext = ext; data->act_window_num = win_num; if (dirty_rect) { memcpy(data->dirty_rect, dirty_rect, sizeof(data->dirty_rect)); data->dirty_rect_valid = true; } BUG_ON(win_num > tegra_dc_get_numof_dispwindows()); ret = tegra_dc_ext_pin_windows(user, win, win_num, data->win, &has_timestamp, syncpt_fd != NULL); if (ret) goto fail_pin; ret = tegra_dc_ext_read_user_data(data, flip_user_data, nr_user_data); if (ret) goto fail_pin; /* * If this flip needs to update the current IMP settings, reserve * exclusive access to the COMMON channel. This call can potentially * block. */ if (data->imp_dirty) { ret = tegra_dc_reserve_common_channel(ext->dc); if (ret) { dev_err(&ext->dc->ndev->dev, "%s: DC %d flip failed to reserve the COMMON channel\n", __func__, ext->dc->ctrl_num); goto fail_pin; } ret = tegra_dc_validate_imp_queue(ext->dc, data->imp_session_id); if (ret) { dev_err(&ext->dc->ndev->dev, "Couldn't find corresponding PROPOSE\n"); goto fail_pin; } trace_display_imp_flip_queued(ext->dc->ctrl_num, data->imp_session_id); } ret = lock_windows_for_flip(user, win, win_num); if (ret) goto fail_pin; if (!ext->enabled) { ret = -ENXIO; goto unlock; } BUG_ON(win_num > tegra_dc_get_numof_dispwindows()); for (i = 0; i < win_num; i++) { u32 syncpt_max; int index = win[i].index; struct tegra_dc_ext_win *ext_win; if (index < 0 || !test_bit(index, &ext->dc->valid_windows)) continue; ext_win = &ext->win[index]; syncpt_max = tegra_dc_incr_syncpt_max(ext->dc, index); data->win[i].syncpt_max = syncpt_max; /* * Any of these windows' syncpoints should be equivalent for * the client, so we just send back an arbitrary one of them */ post_sync_val = syncpt_max; post_sync_id = tegra_dc_get_syncpt_id(ext->dc, index); work_index = index; atomic_inc(&ext->win[work_index].nr_pending_flips); } if (work_index < 0) { ret = -EINVAL; goto unlock; } #ifdef CONFIG_ANDROID work_index = ffs(ext->dc->valid_windows); if (!work_index) { dev_err(&ext->dc->ndev->dev, "no valid window\n"); ret = -EINVAL; goto unlock; } work_index -= 1; /* window index starts from 0 */ #endif if (syncpt_fd) { if (post_sync_id != NVSYNCPT_INVALID) { ret = nvhost_syncpt_create_fence_single_ext( ext->dc->ndev, post_sync_id, post_sync_val + 1, "flip-fence", syncpt_fd); if (ret) { dev_err(&ext->dc->ndev->dev, "Failed creating fence err:%d\n", ret); goto unlock; } } } else { *syncpt_val = post_sync_val; *syncpt_id = post_sync_id; } if (trace_flip_rcvd_syncpt_upd_enabled()) tegra_dc_flip_trace(data, trace_flip_rcvd_syncpt_upd); /* Avoid queueing timestamps on Android, to disable skipping flips */ #ifndef CONFIG_ANDROID if (has_timestamp) { mutex_lock(&ext->win[work_index].queue_lock); list_add_tail(&data->timestamp_node, &ext->win[work_index].timestamp_queue); mutex_unlock(&ext->win[work_index].queue_lock); } #endif data->flags = flip_flags; flip_id_local = atomic64_inc_return (&user->ext->dc->flip_stats.flips_queued); if (flip_id) *flip_id = flip_id_local; /* Insert the flip in the flip queue if CRC is enabled */ if (atomic_read(&ext->dc->crc_ref_cnt.global)) { struct tegra_dc_flip_buf_ele flip_buf_ele; struct tegra_dc_flip_buf_ele *in_q_ptr = NULL; flip_buf_ele.id = flip_id_local; flip_buf_ele.state = TEGRA_DC_FLIP_STATE_QUEUED; mutex_lock(&ext->dc->flip_buf.lock); tegra_dc_ring_buf_add(&ext->dc->flip_buf, &flip_buf_ele, (char **)&in_q_ptr); mutex_unlock(&ext->dc->flip_buf.lock); data->flip_buf_ele = in_q_ptr; } kthread_queue_work(&ext->win[work_index].flip_worker, &data->work); unlock_windows_for_flip(user, win, win_num); return 0; unlock: unlock_windows_for_flip(user, win, win_num); fail_pin: for (i = 0; i < win_num; i++) { int j; for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) { if (!data->win[i].handle[j]) continue; dma_buf_unmap_attachment(data->win[i].handle[j]->attach, data->win[i].handle[j]->sgt, DMA_TO_DEVICE); dma_buf_detach(data->win[i].handle[j]->buf, data->win[i].handle[j]->attach); dma_buf_put(data->win[i].handle[j]->buf); kfree(data->win[i].handle[j]); } } /* Release the COMMON channel in case of failure. */ if (data->imp_dirty) tegra_dc_release_common_channel(ext->dc); kfree(data); return ret; } static int tegra_dc_ext_set_win_csc(struct tegra_dc_ext_user *user, struct tegra_dc_ext_csc *new_csc) { unsigned int index = new_csc->win_index; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_ext_win *ext_win; struct tegra_dc_win_csc *win_csc; struct tegra_dc_win *win; if (index >= tegra_dc_get_numof_dispwindows()) return -EINVAL; index = array_index_nospec(index, tegra_dc_get_numof_dispwindows()); win = tegra_dc_get_window(dc, index); if (!win) return -EINVAL; ext_win = &user->ext->win[index]; win_csc = &win->win_csc; mutex_lock(&ext_win->lock); if (ext_win->user != user) { mutex_unlock(&ext_win->lock); return -EACCES; } win_csc->yof = new_csc->yof; win_csc->kyrgb = new_csc->kyrgb; win_csc->kur = new_csc->kur; win_csc->kvr = new_csc->kvr; win_csc->kug = new_csc->kug; win_csc->kvg = new_csc->kvg; win_csc->kub = new_csc->kub; win_csc->kvb = new_csc->kvb; tegra_dc_update_win_csc(dc, index); mutex_unlock(&ext_win->lock); return 0; } static int tegra_dc_ext_set_nvdisp_win_csc(struct tegra_dc_ext_user *user, struct tegra_dc_ext_nvdisp_win_csc *new_csc) { unsigned int index = new_csc->win_index; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_ext_win *ext_win; struct tegra_dc_nvdisp_win_csc *nvdisp_win_csc; struct tegra_dc_win *win; if (index >= tegra_dc_get_numof_dispwindows()) return -EINVAL; index = array_index_nospec(index, tegra_dc_get_numof_dispwindows()); win = tegra_dc_get_window(dc, index); if (!win) return -EINVAL; ext_win = &user->ext->win[index]; nvdisp_win_csc = &win->nvdisp_win_csc; mutex_lock(&ext_win->lock); if (ext_win->user != user) { mutex_unlock(&ext_win->lock); return -EACCES; } if (!win->force_user_csc) { nvdisp_win_csc->csc_enable = new_csc->csc_enable; nvdisp_win_csc->r2r = new_csc->r2r; nvdisp_win_csc->g2r = new_csc->g2r; nvdisp_win_csc->b2r = new_csc->b2r; nvdisp_win_csc->const2r = new_csc->const2r; nvdisp_win_csc->r2g = new_csc->r2g; nvdisp_win_csc->g2g = new_csc->g2g; nvdisp_win_csc->b2g = new_csc->b2g; nvdisp_win_csc->const2g = new_csc->const2g; nvdisp_win_csc->r2b = new_csc->r2b; nvdisp_win_csc->g2b = new_csc->g2b; nvdisp_win_csc->b2b = new_csc->b2b; nvdisp_win_csc->const2b = new_csc->const2b; tegra_nvdisp_update_win_csc(dc, index); } mutex_unlock(&ext_win->lock); return 0; } static int set_lut_channel(u16 __user *channel_from_user, u8 *channel_to, u32 start, u32 len) { int i; u16 lut16bpp[256]; if (channel_from_user) { if (copy_from_user(lut16bpp, channel_from_user, len<<1)) return 1; for (i = 0; i < len; i++) channel_to[start+i] = lut16bpp[i]>>8; } else { for (i = 0; i < len; i++) channel_to[start+i] = start+i; } return 0; } static int set_nvdisp_lut_channel(struct tegra_dc_ext_lut *new_lut, u64 *channel_to) { int i, j; u16 lut16bpp[256]; u64 inlut = 0; u16 __user *channel_from_user; u32 start = new_lut->start; u32 len = new_lut->len; for (j = 0; j < 3; j++) { if (j == 0) channel_from_user = new_lut->r; else if (j == 1) channel_from_user = new_lut->g; else if (j == 2) channel_from_user = new_lut->b; if (channel_from_user) { if (copy_from_user(lut16bpp, channel_from_user, len<<1)) return 1; for (i = 0; i < len; i++) { inlut = (u64)lut16bpp[i]; /*16bit data in MSB format*/ channel_to[start+i] |= ((inlut & 0xFF00) << (j * 16)); } } else { for (i = 0; i < len; i++) { inlut = (u64)(start+i); channel_to[start+i] |= (inlut << (j * 16)); } } } return 0; } static int tegra_dc_ext_set_lut(struct tegra_dc_ext_user *user, struct tegra_dc_ext_lut *new_lut) { int err = 0; unsigned int index = new_lut->win_index; u32 start = new_lut->start; u32 len = new_lut->len; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_ext_win *ext_win; struct tegra_dc_lut *lut; struct tegra_dc_nvdisp_lut *nvdisp_lut; struct tegra_dc_win *win; if (index >= tegra_dc_get_numof_dispwindows()) return -EINVAL; index = array_index_nospec(index, tegra_dc_get_numof_dispwindows()); win = tegra_dc_get_window(dc, index); if (!win) return -EINVAL; if ((start >= 256) || (len > 256) || ((start + len) > 256)) return -EINVAL; ext_win = &user->ext->win[index]; mutex_lock(&ext_win->lock); if (ext_win->user != user) { mutex_unlock(&ext_win->lock); return -EACCES; } tegra_dc_scrncapt_disp_pause_lock(dc); if (tegra_dc_is_t21x()) { lut = &win->lut; err = set_lut_channel(new_lut->r, lut->r, start, len) | set_lut_channel(new_lut->g, lut->g, start, len) | set_lut_channel(new_lut->b, lut->b, start, len); } else if (tegra_dc_is_nvdisplay()) { nvdisp_lut = &win->nvdisp_lut; memset(nvdisp_lut->rgb, 0, sizeof(u64) * len); err = set_nvdisp_lut_channel(new_lut, nvdisp_lut->rgb); } if (err) { tegra_dc_scrncapt_disp_pause_unlock(dc); mutex_unlock(&ext_win->lock); return -EFAULT; } if (tegra_dc_is_t21x()) tegra_dc_update_lut(dc, index, new_lut->flags & TEGRA_DC_EXT_LUT_FLAGS_FBOVERRIDE); else if (tegra_dc_is_nvdisplay()) tegra_dc_update_nvdisp_lut(dc, index, new_lut->flags & TEGRA_DC_EXT_LUT_FLAGS_FBOVERRIDE); tegra_dc_scrncapt_disp_pause_unlock(dc); mutex_unlock(&ext_win->lock); return 0; } static int tegra_dc_ext_get_nvdisp_cmu(struct tegra_dc_ext_user *user, struct tegra_dc_ext_nvdisp_cmu *args, bool custom_value) { int i; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_nvdisp_cmu *nvdisp_cmu = dc->pdata->nvdisp_cmu; struct tegra_dc_nvdisp_lut *cmu_nvdisp_lut = &dc->nvdisp_postcomp_lut; if (custom_value && dc->pdata->nvdisp_cmu) { nvdisp_cmu = dc->pdata->nvdisp_cmu; for (i = 0; i < TEGRA_DC_EXT_LUT_SIZE_1025; i++) args->rgb[i] = nvdisp_cmu->rgb[i]; } else if (custom_value && !dc->pdata->nvdisp_cmu) return -EACCES; else { cmu_nvdisp_lut = &dc->nvdisp_postcomp_lut; for (i = 0; i < TEGRA_DC_EXT_LUT_SIZE_1025; i++) args->rgb[i] = cmu_nvdisp_lut->rgb[i]; } args->cmu_enable = dc->pdata->cmu_enable; return 0; } static int tegra_dc_ext_set_nvdisp_cmu(struct tegra_dc_ext_user *user, struct tegra_dc_ext_nvdisp_cmu *args) { int i, lut_size; struct tegra_dc_nvdisp_lut *nvdisp_cmu; struct tegra_dc *dc = user->ext->dc; /* Directly copying the values to DC * output lut whose address is already * programmed to HW register. */ nvdisp_cmu = &dc->nvdisp_postcomp_lut; if (!nvdisp_cmu) return -ENOMEM; tegra_dc_scrncapt_disp_pause_lock(dc); dc->pdata->cmu_enable = args->cmu_enable; lut_size = args->lut_size; for (i = 0; i < lut_size; i++) nvdisp_cmu->rgb[i] = args->rgb[i]; tegra_nvdisp_update_cmu(dc, nvdisp_cmu); tegra_dc_scrncapt_disp_pause_unlock(dc); return 0; } static int tegra_dc_ext_get_cmu(struct tegra_dc_ext_user *user, struct tegra_dc_ext_cmu *args, bool custom_value) { int i; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_cmu *cmu; if (custom_value && dc->pdata->cmu) cmu = dc->pdata->cmu; else if (custom_value && !dc->pdata->cmu) return -EACCES; else cmu = &dc->cmu; args->cmu_enable = dc->pdata->cmu_enable; for (i = 0; i < 256; i++) args->lut1[i] = cmu->lut1[i]; args->csc[0] = cmu->csc.krr; args->csc[1] = cmu->csc.kgr; args->csc[2] = cmu->csc.kbr; args->csc[3] = cmu->csc.krg; args->csc[4] = cmu->csc.kgg; args->csc[5] = cmu->csc.kbg; args->csc[6] = cmu->csc.krb; args->csc[7] = cmu->csc.kgb; args->csc[8] = cmu->csc.kbb; for (i = 0; i < 960; i++) args->lut2[i] = cmu->lut2[i]; return 0; } static int tegra_dc_ext_set_cmu(struct tegra_dc_ext_user *user, struct tegra_dc_ext_cmu *args) { int i; struct tegra_dc_cmu *cmu; struct tegra_dc *dc = user->ext->dc; cmu = kzalloc(sizeof(*cmu), GFP_KERNEL); if (!cmu) return -ENOMEM; dc->pdata->cmu_enable = args->cmu_enable; for (i = 0; i < 256; i++) cmu->lut1[i] = args->lut1[i]; cmu->csc.krr = args->csc[0]; cmu->csc.kgr = args->csc[1]; cmu->csc.kbr = args->csc[2]; cmu->csc.krg = args->csc[3]; cmu->csc.kgg = args->csc[4]; cmu->csc.kbg = args->csc[5]; cmu->csc.krb = args->csc[6]; cmu->csc.kgb = args->csc[7]; cmu->csc.kbb = args->csc[8]; for (i = 0; i < 960; i++) cmu->lut2[i] = args->lut2[i]; tegra_dc_update_cmu(dc, cmu); kfree(cmu); return 0; } static int tegra_dc_ext_set_cmu_aligned(struct tegra_dc_ext_user *user, struct tegra_dc_ext_cmu *args) { int i; struct tegra_dc_cmu *cmu; struct tegra_dc *dc = user->ext->dc; cmu = kzalloc(sizeof(*cmu), GFP_KERNEL); if (!cmu) return -ENOMEM; for (i = 0; i < 256; i++) cmu->lut1[i] = args->lut1[i]; cmu->csc.krr = args->csc[0]; cmu->csc.kgr = args->csc[1]; cmu->csc.kbr = args->csc[2]; cmu->csc.krg = args->csc[3]; cmu->csc.kgg = args->csc[4]; cmu->csc.kbg = args->csc[5]; cmu->csc.krb = args->csc[6]; cmu->csc.kgb = args->csc[7]; cmu->csc.kbb = args->csc[8]; for (i = 0; i < 960; i++) cmu->lut2[i] = args->lut2[i]; tegra_dc_update_cmu_aligned(dc, cmu); kfree(cmu); return 0; } static int tegra_dc_ext_get_cmu_adbRGB(struct tegra_dc_ext_user *user, struct tegra_dc_ext_cmu *args) { int i; struct tegra_dc *dc = user->ext->dc; struct tegra_dc_cmu *cmu; if (dc->pdata->cmu_adbRGB) cmu = dc->pdata->cmu_adbRGB; else return -EACCES; args->cmu_enable = dc->pdata->cmu_enable; for (i = 0; i < 256; i++) args->lut1[i] = cmu->lut1[i]; args->csc[0] = cmu->csc.krr; args->csc[1] = cmu->csc.kgr; args->csc[2] = cmu->csc.kbr; args->csc[3] = cmu->csc.krg; args->csc[4] = cmu->csc.kgg; args->csc[5] = cmu->csc.kbg; args->csc[6] = cmu->csc.krb; args->csc[7] = cmu->csc.kgb; args->csc[8] = cmu->csc.kbb; for (i = 0; i < 960; i++) args->lut2[i] = cmu->lut2[i]; return 0; } #ifdef CONFIG_TEGRA_ISOMGR static int tegra_dc_ext_negotiate_bw(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_windowattr *wins, int win_num) { int i; int ret; struct tegra_dc_win *dc_wins[DC_N_WINDOWS]; struct tegra_dc *dc = user->ext->dc; /* If display has been disconnected return with error. */ if (!dc->connected) return -1; for (i = 0; i < win_num; i++) { if (wins[i].index >= tegra_dc_get_numof_dispwindows()) return -EINVAL; wins[i].index = array_index_nospec(wins[i].index, tegra_dc_get_numof_dispwindows()); } for (i = 0; i < win_num; i++) { int idx = wins[i].index; if (wins[i].buff_id > 0) { tegra_dc_ext_set_windowattr_basic(&dc->tmp_wins[idx], &wins[i]); } else { dc->tmp_wins[idx].flags = 0; dc->tmp_wins[idx].new_bandwidth = 0; } dc_wins[i] = &dc->tmp_wins[idx]; } ret = tegra_dc_bandwidth_negotiate_bw(dc, dc_wins, win_num); return ret; } #endif static u32 tegra_dc_ext_get_vblank_syncpt(struct tegra_dc_ext_user *user) { struct tegra_dc *dc = user->ext->dc; return dc->vblank_syncpt; } static int tegra_dc_ext_get_status(struct tegra_dc_ext_user *user, struct tegra_dc_ext_status *status) { struct tegra_dc *dc = user->ext->dc; memset(status, 0, sizeof(*status)); if (dc->enabled) status->flags |= TEGRA_DC_EXT_FLAGS_ENABLED; return 0; } #ifdef CONFIG_COMPAT static int dev_cpy_from_usr_compat( struct tegra_dc_ext_flip_windowattr *outptr, void *inptr, u32 usr_win_size, u32 win_num) { int i = 0; u8 *srcptr; for (i = 0; i < win_num; i++) { srcptr = (u8 *)inptr + (usr_win_size * i); if (copy_from_user(&outptr[i], compat_ptr((uintptr_t)srcptr), usr_win_size)) return -EFAULT; } return 0; } #endif static int dev_cpy_from_usr(struct tegra_dc_ext_flip_windowattr *outptr, void *inptr, u32 usr_win_size, u32 win_num) { int i = 0; u8 *srcptr; for (i = 0; i < win_num; i++) { srcptr = (u8 *)inptr + (usr_win_size * i); if (copy_from_user(&outptr[i], (void __user *) (uintptr_t)srcptr, usr_win_size)) return -EFAULT; } return 0; } static int dev_cpy_to_usr(void *outptr, u32 usr_win_size, struct tegra_dc_ext_flip_windowattr *inptr, u32 win_num) { int i = 0; u8 *dstptr; for (i = 0; i < win_num; i++) { dstptr = (u8 *)outptr + (usr_win_size * i); if (copy_to_user((void __user *) (uintptr_t)dstptr, &inptr[i], usr_win_size)) return -EFAULT; } return 0; } static int tegra_dc_get_cap_hdr_info(struct tegra_dc_ext_user *user, struct tegra_dc_ext_hdr_caps *hdr_cap_info) { int ret = 0; struct tegra_dc *dc = user->ext->dc; struct tegra_edid *dc_edid = dc->edid; /* Currently only dc->edid has this info. In future, * we have to provide info for non-edid interfaces * in the device tree. */ if (dc_edid) ret = tegra_edid_get_ex_hdr_cap_info(dc_edid, hdr_cap_info); return ret; } static int tegra_dc_get_cap_quant_info(struct tegra_dc_ext_user *user, struct tegra_dc_ext_quant_caps *quant_cap_info) { int ret = 0; struct tegra_dc *dc = user->ext->dc; struct tegra_edid *dc_edid = dc->edid; if (dc_edid) ret = tegra_edid_get_ex_quant_cap_info(dc_edid, quant_cap_info); return ret; } static int tegra_dc_get_caps(struct tegra_dc_ext_user *user, struct tegra_dc_ext_caps *caps, int nr_elements) { int i, ret = 0; for (i = 0; i < nr_elements; i++) { switch (caps[i].data_type) { case TEGRA_DC_EXT_CAP_TYPE_HDR_SINK: { struct tegra_dc_ext_hdr_caps hdr_cap_info; ret = tegra_dc_get_cap_hdr_info(user, &hdr_cap_info); if (copy_to_user((void __user *)(uintptr_t) caps[i].data, &hdr_cap_info, sizeof(hdr_cap_info))) { return -EFAULT; } break; } case TEGRA_DC_EXT_CAP_TYPE_QUANT_SELECTABLE: { struct tegra_dc_ext_quant_caps quant_cap_info; ret = tegra_dc_get_cap_quant_info(user, &quant_cap_info); if (copy_to_user((void __user *)(uintptr_t) caps[i].data, &quant_cap_info, sizeof(quant_cap_info))) { return -EFAULT; } break; } default: return -EINVAL; } } return ret; } static int tegra_dc_copy_syncpts_from_user(struct tegra_dc *dc, struct tegra_dc_ext_flip_user_data *flip_user_data, int nr_user_data, u32 **syncpt_id, u32 **syncpt_val, int **syncpt_fd, int *syncpt_idx) { struct tegra_dc_ext_flip_user_data *syncpt_user_data = NULL; int i; for (i = 0; i < nr_user_data; i++) { if (flip_user_data[i].data_type == TEGRA_DC_EXT_FLIP_USER_DATA_POST_SYNCPT) { /* Only one allowed. */ if (syncpt_user_data) { dev_err(&dc->ndev->dev, "Only one syncpt user data allowed!\n"); return -EINVAL; } syncpt_user_data = &flip_user_data[i]; *syncpt_idx = i; } } if (syncpt_user_data) { struct tegra_dc_ext_syncpt *ext_syncpt; u16 syncpt_type; syncpt_type = syncpt_user_data->flags & TEGRA_DC_EXT_FLIP_FLAG_POST_SYNCPT_TYPE_MASK; ext_syncpt = &syncpt_user_data->post_syncpt; switch (syncpt_type) { case TEGRA_DC_EXT_FLIP_FLAG_POST_SYNCPT_FD: *syncpt_fd = &ext_syncpt->syncpt_fd; break; case TEGRA_DC_EXT_FLIP_FLAG_POST_SYNCPT_RAW: *syncpt_id = &ext_syncpt->syncpt_id; *syncpt_val = &ext_syncpt->syncpt_val; break; default: dev_err(&dc->ndev->dev, "Unrecognized syncpt user data type!\n"); return -EINVAL; } } return 0; } static int tegra_dc_copy_syncpts_to_user( struct tegra_dc_ext_flip_user_data *flip_user_data, int syncpt_idx, u8 *base_addr) { u32 offset = sizeof(*flip_user_data) * syncpt_idx; u8 *dstptr = base_addr + offset; if (copy_to_user((void __user *)(uintptr_t)dstptr, &flip_user_data[syncpt_idx], sizeof(*flip_user_data))) return -EFAULT; return 0; } static int tegra_dc_crc_sanitize_args(struct tegra_dc_ext_crc_arg *args) { if (memcmp(args->magic, "TCRC", 4)) return -EINVAL; if (args->version >= TEGRA_DC_CRC_ARG_VERSION_MAX) return -ENOTSUPP; if (args->num_conf > TEGRA_DC_EXT_CRC_TYPE_MAX - 1 + TEGRA_DC_EXT_MAX_REGIONS) return -EINVAL; return 0; } static int tegra_dc_copy_crc_confs_from_user(struct tegra_dc_ext_crc_arg *args) { struct tegra_dc_ext_crc_conf *conf; struct tegra_dc_ext_crc_conf __user *user_conf; __u8 num_conf = args->num_conf; size_t sz = sizeof(*conf) * num_conf; conf = kzalloc(sz, GFP_KERNEL); if (!conf) return -ENOMEM; user_conf = (struct tegra_dc_ext_crc_conf *)args->conf; if (copy_from_user(conf, user_conf, sz)) { kfree(conf); return -EFAULT; } args->conf = (__u64)conf; return 0; } static int tegra_dc_copy_flip_id_to_user(struct tegra_dc_ext_flip_4 *args, struct tegra_dc_ext_flip_user_data *user_data, int nr_user_data, u64 flip_id) { int i; for (i = 0; i < nr_user_data; i++) { if (user_data[i].data_type == TEGRA_DC_EXT_FLIP_USER_DATA_GET_FLIP_INFO) { u32 offset = sizeof(*user_data) * i; offset += offsetof(struct tegra_dc_ext_flip_user_data, flip_info); offset += offsetof(struct tegra_dc_ext_flip_info, flip_id); if (copy_to_user((void *)args->data + offset, &flip_id, sizeof(u64))) { return -EFAULT; } break; } } return 0; } static long tegra_dc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { void __user *user_arg = (void __user *)arg; struct tegra_dc_ext_user *user = filp->private_data; int ret; switch (cmd) { case TEGRA_DC_EXT_SET_NVMAP_FD: return 0; case TEGRA_DC_EXT_GET_WINDOW: return tegra_dc_ext_get_window(user, arg); case TEGRA_DC_EXT_PUT_WINDOW: ret = tegra_dc_ext_put_window(user, arg); return ret; case TEGRA_DC_EXT_GET_WINMASK: { u32 winmask = tegra_dc_ext_get_winmask(user); if (copy_to_user(user_arg, &winmask, sizeof(winmask))) return -EFAULT; return 0; } case TEGRA_DC_EXT_SET_WINMASK: return tegra_dc_ext_set_winmask(user, arg); case TEGRA_DC_EXT_FLIP4: { int ret; int win_num; int nr_user_data; struct tegra_dc_ext_flip_4 args; struct tegra_dc_ext_flip_windowattr *win; struct tegra_dc_ext_flip_user_data *flip_user_data; bool bypass; u32 *syncpt_id = NULL, *syncpt_val = NULL; int *syncpt_fd = NULL; int syncpt_idx = -1; u64 flip_id; u32 usr_win_size = sizeof(struct tegra_dc_ext_flip_windowattr); if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; bypass = !!(args.flags & TEGRA_DC_EXT_FLIP_HEAD_FLAG_YUVBYPASS); if (tegra_dc_is_t21x()) { if (!!(user->ext->dc->mode.vmode & FB_VMODE_YUV_MASK) != bypass) return -EINVAL; } if (bypass != user->ext->dc->yuv_bypass) user->ext->dc->yuv_bypass_dirty = true; user->ext->dc->yuv_bypass = bypass; win_num = args.win_num; win = kzalloc(sizeof(*win) * win_num, GFP_KERNEL); if (dev_cpy_from_usr(win, (void *)args.win, usr_win_size, win_num)) { kfree(win); return -EFAULT; } nr_user_data = args.nr_elements; flip_user_data = kzalloc(sizeof(*flip_user_data) * nr_user_data, GFP_KERNEL); if (nr_user_data > 0) { if (copy_from_user(flip_user_data, (void __user *) (uintptr_t)args.data, sizeof(*flip_user_data) * nr_user_data)) { kfree(flip_user_data); kfree(win); return -EFAULT; } } /* * Check if the client is explicitly requesting syncpts via flip * user data. If so, populate the syncpt variables accordingly. * Else, default to sync fds and use the original post_syncpt_fd * fence. */ ret = tegra_dc_copy_syncpts_from_user(user->ext->dc, flip_user_data, nr_user_data, &syncpt_id, &syncpt_val, &syncpt_fd, &syncpt_idx); if (ret) { kfree(flip_user_data); kfree(win); return ret; } if (syncpt_idx == -1) syncpt_fd = &args.post_syncpt_fd; ret = tegra_dc_ext_flip(user, win, win_num, syncpt_id, syncpt_val, syncpt_fd, args.dirty_rect, args.flags, flip_user_data, nr_user_data, &flip_id); if (ret) { kfree(flip_user_data); kfree(win); return ret; } /* * If the client requested post syncpt values via user data, * copy them back. */ if (syncpt_idx > -1) { args.post_syncpt_fd = -1; ret = tegra_dc_copy_syncpts_to_user(flip_user_data, syncpt_idx, (u8 *)(void *)args.data); if (ret) { kfree(flip_user_data); kfree(win); return ret; } } if (dev_cpy_to_usr((void *)args.win, usr_win_size, win, win_num) || copy_to_user(user_arg, &args, sizeof(args))) { kfree(flip_user_data); kfree(win); return -EFAULT; } ret = tegra_dc_copy_flip_id_to_user(&args, flip_user_data, nr_user_data, flip_id); kfree(flip_user_data); kfree(win); return ret; } case TEGRA_DC_EXT_GET_IMP_USER_INFO: { struct tegra_dc_ext_imp_user_info *info; int ret = 0; if (!tegra_dc_is_nvdisplay()) return -EINVAL; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; if (copy_from_user(info, user_arg, sizeof(*info))) { kfree(info); return -EFAULT; } ret = tegra_nvdisp_get_imp_user_info(info); if (ret) { kfree(info); return ret; } if (copy_to_user(user_arg, info, sizeof(*info))) { kfree(info); return -EFAULT; } kfree(info); return ret; } #ifdef CONFIG_TEGRA_ISOMGR #ifdef CONFIG_COMPAT case TEGRA_DC_EXT_SET_PROPOSED_BW32: { int ret; int win_num; struct tegra_dc_ext_flip_2_32 args; struct tegra_dc_ext_flip_windowattr *win; /* Keeping window attribute size as version1 for old * legacy applications */ u32 usr_win_size = sizeof(struct tegra_dc_ext_flip_windowattr); if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; win_num = args.win_num; win = kzalloc(sizeof(*win) * win_num, GFP_KERNEL); if (dev_cpy_from_usr_compat(win, (void *)(uintptr_t)args.win, usr_win_size, win_num)) { kfree(win); return -EFAULT; } ret = tegra_dc_ext_negotiate_bw(user, win, win_num); kfree(win); return ret; } #endif case TEGRA_DC_EXT_SET_PROPOSED_BW: { int ret; int win_num; struct tegra_dc_ext_flip_2 args; struct tegra_dc_ext_flip_windowattr *win; /* Keeping window attribute size as version1 for old * legacy applications */ u32 usr_win_size = sizeof(struct tegra_dc_ext_flip_windowattr); if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; win_num = args.win_num; win = kzalloc(sizeof(*win) * win_num, GFP_KERNEL); if (dev_cpy_from_usr(win, (void *)args.win, usr_win_size, win_num)) { kfree(win); return -EFAULT; } ret = tegra_dc_ext_negotiate_bw(user, win, win_num); kfree(win); return ret; } case TEGRA_DC_EXT_SET_PROPOSED_BW_3: { int ret = 0; int win_num; int nr_user_data; struct tegra_dc_ext_flip_4 args; struct tegra_dc_ext_flip_windowattr *win = NULL; struct tegra_dc_ext_flip_user_data *flip_user_data = NULL; bool imp_proposed = false; /* Keeping window attribute size as version1 for old * legacy applications */ u32 usr_win_size = sizeof(struct tegra_dc_ext_flip_windowattr); if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; win_num = args.win_num; win = kzalloc(sizeof(*win) * win_num, GFP_KERNEL); if (!win) return -ENOMEM; if (dev_cpy_from_usr(win, (void *)args.win, usr_win_size, win_num)) { ret = -EFAULT; goto free_and_ret; } nr_user_data = args.nr_elements; flip_user_data = kzalloc(sizeof(*flip_user_data) * nr_user_data, GFP_KERNEL); if (!flip_user_data) { ret = -ENOMEM; goto free_and_ret; } if (nr_user_data > 0) { if (copy_from_user(flip_user_data, (void __user *) (uintptr_t)args.data, sizeof(*flip_user_data) * nr_user_data)) { ret = -EFAULT; goto free_and_ret; } if (flip_user_data[0].data_type == TEGRA_DC_EXT_FLIP_USER_DATA_IMP_DATA) { ret = tegra_dc_queue_imp_propose( user->ext->dc, flip_user_data); if (ret) goto free_and_ret; imp_proposed = true; } } if (!imp_proposed) ret = tegra_dc_ext_negotiate_bw(user, win, win_num); free_and_ret: kfree(flip_user_data); kfree(win); return ret; } #else /* if isomgr is not present, allow any request to pass. */ #ifdef CONFIG_COMPAT case TEGRA_DC_EXT_SET_PROPOSED_BW32: return 0; #endif case TEGRA_DC_EXT_SET_PROPOSED_BW: return 0; case TEGRA_DC_EXT_SET_PROPOSED_BW_3: return 0; #endif case TEGRA_DC_EXT_GET_CURSOR: return tegra_dc_ext_get_cursor(user); case TEGRA_DC_EXT_PUT_CURSOR: return tegra_dc_ext_put_cursor(user); case TEGRA_DC_EXT_SET_CURSOR_IMAGE: { struct tegra_dc_ext_cursor_image args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_cursor_image(user, &args); } case TEGRA_DC_EXT_SET_CURSOR: { struct tegra_dc_ext_cursor args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_cursor(user, &args); } case TEGRA_DC_EXT_SET_CSC: { if (tegra_dc_is_t21x()) { struct tegra_dc_ext_csc args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_win_csc(user, &args); } else { return -EACCES; } } case TEGRA_DC_EXT_SET_NVDISP_WIN_CSC: { if (tegra_dc_is_nvdisplay()) { struct tegra_dc_ext_nvdisp_win_csc args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_nvdisp_win_csc(user, &args); } else { return -EACCES; } } case TEGRA_DC_EXT_GET_VBLANK_SYNCPT: { u32 syncpt = tegra_dc_ext_get_vblank_syncpt(user); if (copy_to_user(user_arg, &syncpt, sizeof(syncpt))) return -EFAULT; return 0; } case TEGRA_DC_EXT_GET_STATUS: { struct tegra_dc_ext_status args; int ret; ret = tegra_dc_ext_get_status(user, &args); if (copy_to_user(user_arg, &args, sizeof(args))) return -EFAULT; return ret; } #ifdef CONFIG_COMPAT case TEGRA_DC_EXT_SET_LUT32: { struct tegra_dc_ext_lut32 args; struct tegra_dc_ext_lut tmp; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; /* translate 32-bit version to 64-bit */ tmp.win_index = args.win_index; tmp.flags = args.flags; tmp.start = args.start; tmp.len = args.len; tmp.r = compat_ptr(args.r); tmp.g = compat_ptr(args.g); tmp.b = compat_ptr(args.b); return tegra_dc_ext_set_lut(user, &tmp); } #endif case TEGRA_DC_EXT_SET_LUT: { struct tegra_dc_ext_lut args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_lut(user, &args); } case TEGRA_DC_EXT_CURSOR_CLIP: { int args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_cursor_clip(user, &args); } case TEGRA_DC_EXT_GET_CMU: { struct tegra_dc_ext_cmu *args; if (!tegra_dc_is_t21x()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; tegra_dc_ext_get_cmu(user, args, 0); if (copy_to_user(user_arg, args, sizeof(*args))) { kfree(args); return -EFAULT; } kfree(args); return 0; } case TEGRA_DC_EXT_GET_CUSTOM_CMU: { struct tegra_dc_ext_cmu *args; if (!tegra_dc_is_t21x()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (tegra_dc_ext_get_cmu(user, args, 1)) { kfree(args); return -EACCES; } if (copy_to_user(user_arg, args, sizeof(*args))) { kfree(args); return -EFAULT; } kfree(args); return 0; } case TEGRA_DC_EXT_GET_CMU_ADBRGB: { struct tegra_dc_ext_cmu *args; if (!tegra_dc_is_t21x()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (tegra_dc_ext_get_cmu_adbRGB(user, args)) { kfree(args); return -EACCES; } if (copy_to_user(user_arg, args, sizeof(*args))) { kfree(args); return -EFAULT; } kfree(args); return 0; } case TEGRA_DC_EXT_SET_CMU: { int ret; struct tegra_dc_ext_cmu *args; if (!tegra_dc_is_t21x()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (copy_from_user(args, user_arg, sizeof(*args))) { kfree(args); return -EFAULT; } ret = tegra_dc_ext_set_cmu(user, args); kfree(args); return ret; } case TEGRA_DC_EXT_GET_NVDISP_CMU: case TEGRA_DC_EXT_GET_CUSTOM_NVDISP_CMU: { struct tegra_dc_ext_nvdisp_cmu *args; bool custom_value = false; if (!tegra_dc_is_nvdisplay()) return -EACCES; if (cmd == TEGRA_DC_EXT_GET_CUSTOM_NVDISP_CMU) custom_value = true; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (tegra_dc_ext_get_nvdisp_cmu(user, args, custom_value)) { kfree(args); return -EACCES; } if (copy_to_user(user_arg, args, sizeof(*args))) { kfree(args); return -EFAULT; } kfree(args); return 0; } case TEGRA_DC_EXT_SET_NVDISP_CMU: { int ret; struct tegra_dc_ext_nvdisp_cmu *args; if (!tegra_dc_is_nvdisplay()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (copy_from_user(args, user_arg, sizeof(*args))) { kfree(args); return -EFAULT; } ret = tegra_dc_ext_set_nvdisp_cmu(user, args); kfree(args); return ret; } case TEGRA_DC_EXT_SET_VBLANK: { struct tegra_dc_ext_set_vblank args; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; return tegra_dc_ext_set_vblank(user->ext, args.enable); } /* Update only modified elements in CSC and LUT2. * Align writes to FRAME_END_INT */ case TEGRA_DC_EXT_SET_CMU_ALIGNED: { int ret; struct tegra_dc_ext_cmu *args; if (!tegra_dc_is_t21x()) return -EACCES; args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; if (copy_from_user(args, user_arg, sizeof(*args))) { kfree(args); return -EFAULT; } ret = tegra_dc_ext_set_cmu_aligned(user, args); kfree(args); return ret; } case TEGRA_DC_EXT_GET_CAP_INFO: { int ret = 0; struct tegra_dc_ext_caps *caps = NULL; u32 nr_elements = 0; ret = tegra_dc_ext_cpy_caps_from_user(user_arg, &caps, &nr_elements); if (ret) return ret; ret = tegra_dc_get_caps(user, caps, nr_elements); kfree(caps); return ret; } /* Screen Capture Support */ case TEGRA_DC_EXT_SCRNCAPT_GET_INFO: { #ifdef CONFIG_TEGRA_DC_SCREEN_CAPTURE struct tegra_dc_ext_scrncapt_get_info args; int ret; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_scrncapt_get_info(user, &args); if (copy_to_user(user_arg, &args, sizeof(args))) return -EFAULT; return ret; #else return -EINVAL; #endif } case TEGRA_DC_EXT_SCRNCAPT_DUP_FBUF: { #ifdef CONFIG_TEGRA_DC_SCREEN_CAPTURE struct tegra_dc_ext_scrncapt_dup_fbuf args; int ret; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_scrncapt_dup_fbuf(user, &args); if (copy_to_user(user_arg, &args, sizeof(args))) return -EFAULT; return ret; #else return -EINVAL; #endif } case TEGRA_DC_EXT_GET_SCANLINE: { u32 scanln; dev_dbg(&user->ext->dc->ndev->dev, "GET SCANLN IOCTL\n"); /* If display has been disconnected return with error. */ if (!user->ext->dc->connected) return -EACCES; scanln = tegra_dc_get_v_count(user->ext->dc); if (copy_to_user(user_arg, &scanln, sizeof(scanln))) return -EFAULT; return 0; } case TEGRA_DC_EXT_SET_SCANLINE: { struct tegra_dc_ext_scanline_info args; dev_dbg(&user->ext->dc->ndev->dev, "SET SCANLN IOCTL\n"); if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_ext_vpulse3(user->ext, &args); if (copy_to_user(user_arg, &args, sizeof(args))) return -EFAULT; return ret; } case TEGRA_DC_EXT_CRC_ENABLE: { struct tegra_dc_ext_crc_arg args; struct tegra_dc *dc = user->ext->dc; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_crc_sanitize_args(&args); if (ret) return ret; ret = tegra_dc_copy_crc_confs_from_user(&args); if (ret) return ret; ret = tegra_dc_crc_enable(dc, &args); kfree((struct tegra_dc_ext_crc_conf *)args.conf); return ret; } case TEGRA_DC_EXT_CRC_DISABLE: { struct tegra_dc_ext_crc_arg args; struct tegra_dc *dc = user->ext->dc; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_crc_sanitize_args(&args); if (ret) return ret; ret = tegra_dc_copy_crc_confs_from_user(&args); if (ret) return ret; ret = tegra_dc_crc_disable(dc, &args); kfree((struct tegra_dc_ext_crc_conf *)args.conf); return ret; } case TEGRA_DC_EXT_CRC_GET: { struct tegra_dc_ext_crc_arg args; struct tegra_dc_ext_crc_conf __user *user_conf; struct tegra_dc *dc = user->ext->dc; size_t sz; if (copy_from_user(&args, user_arg, sizeof(args))) return -EFAULT; ret = tegra_dc_crc_sanitize_args(&args); if (ret) return ret; user_conf = (struct tegra_dc_ext_crc_conf *)args.conf; ret = tegra_dc_copy_crc_confs_from_user(&args); if (ret) return ret; ret = tegra_dc_crc_get(dc, &args); if (ret) { kfree((struct tegra_dc_ext_crc_conf *)args.conf); return ret; } sz = args.num_conf * sizeof(struct tegra_dc_ext_crc_conf); if (copy_to_user(user_conf, (void *)args.conf, sz)) ret = -EFAULT; kfree((struct tegra_dc_ext_crc_conf *)args.conf); return ret; } default: return -EINVAL; } } static int tegra_dc_open(struct inode *inode, struct file *filp) { struct tegra_dc_ext_user *user; struct tegra_dc_ext *ext; int open_count; user = kzalloc(sizeof(*user), GFP_KERNEL); if (!user) return -ENOMEM; ext = container_of(inode->i_cdev, struct tegra_dc_ext, cdev); user->ext = ext; atomic_inc(&ext->users_count); filp->private_data = user; open_count = atomic_inc_return(&dc_open_count); if (open_count < 1) { pr_err("%s: unbalanced dc open count=%d\n", __func__, open_count); return -EINVAL; } return 0; } static int tegra_dc_release(struct inode *inode, struct file *filp) { struct tegra_dc_ext_user *user = filp->private_data; struct tegra_dc_ext *ext = user->ext; unsigned int i; unsigned long int windows = 0; int open_count; for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { if (ext->win[i].user == user) { tegra_dc_ext_put_window(user, i); windows |= BIT(i); } } if (ext->dc->enabled) { tegra_dc_blank_wins(ext->dc, windows); for_each_set_bit(i, &windows, tegra_dc_get_numof_dispwindows()) { tegra_dc_ext_unpin_window(&ext->win[i]); tegra_dc_disable_window(ext->dc, i); } } if (ext->cursor.user == user) tegra_dc_ext_put_cursor(user); kfree(user); open_count = atomic_dec_return(&dc_open_count); if (open_count < 0) { pr_err("%s: unbalanced dc open count=%d\n", __func__, open_count); return -EINVAL; } if (open_count == 0) tegra_dc_reset_imp_state(); if (!atomic_dec_return(&ext->users_count)) { tegra_dc_crc_drop_ref_cnts(ext->dc); if (tegra_fb_is_console_enabled(ext->dc->pdata)) { i = tegra_fb_redisplay_console(ext->dc->fb); if (i && i != -ENODEV) { pr_err("%s: redisplay console failed with error %d\n", __func__, i); return i; } } /* tegra_fb_is_console_enabled() */ } return 0; } static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext) { int i, ret; struct sched_param sparm = { .sched_priority = 1 }; for (i = 0; i < ext->dc->n_windows; i++) { struct tegra_dc_ext_win *win = &ext->win[i]; char name[32]; win->ext = ext; win->idx = i; snprintf(name, sizeof(name), "tegradc.%d/%c", ext->dc->ndev->id, 'a' + i); kthread_init_worker(&win->flip_worker); win->flip_kthread = kthread_run(&kthread_worker_fn, &win->flip_worker, name); if (!win->flip_kthread) { ret = -ENOMEM; goto cleanup; } sched_setscheduler(win->flip_kthread, SCHED_FIFO, &sparm); mutex_init(&win->lock); mutex_init(&win->queue_lock); INIT_LIST_HEAD(&win->timestamp_queue); } return 0; cleanup: while (i--) { struct tegra_dc_ext_win *win = &ext->win[i]; kthread_stop(win->flip_kthread); } return ret; } static const struct file_operations tegra_dc_devops = { .owner = THIS_MODULE, .open = tegra_dc_open, .release = tegra_dc_release, .unlocked_ioctl = tegra_dc_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = tegra_dc_ioctl, #endif }; struct tegra_dc_ext *tegra_dc_ext_register(struct platform_device *ndev, struct tegra_dc *dc) { int ret; struct tegra_dc_ext *ext; dev_t devno; char name[16]; struct sched_param sparm = { .sched_priority = 1 }; ext = kzalloc(sizeof(*ext), GFP_KERNEL); if (!ext) return ERR_PTR(-ENOMEM); BUG_ON(!tegra_dc_ext_devno); devno = tegra_dc_ext_devno + head_count + 1; cdev_init(&ext->cdev, &tegra_dc_devops); ext->cdev.owner = THIS_MODULE; ret = cdev_add(&ext->cdev, devno, 1); if (ret) { dev_err(&ndev->dev, "Failed to create character device\n"); goto cleanup_alloc; } ext->dev = device_create(tegra_dc_ext_class, &ndev->dev, devno, NULL, "tegra_dc_%d", ndev->id); if (IS_ERR(ext->dev)) { ret = PTR_ERR(ext->dev); goto cleanup_cdev; } ext->dc = dc; ret = tegra_dc_ext_setup_windows(ext); if (ret) goto cleanup_device; mutex_init(&ext->cursor.lock); /* Setup scanline workqueues */ ext->scanline_trigger = -1; snprintf(name, sizeof(name), "tegradc.%d/sl", dc->ndev->id); kthread_init_worker(&ext->scanline_worker); ext->scanline_task = kthread_run(kthread_worker_fn, &ext->scanline_worker, name); if (!ext->scanline_task) { ret = -ENOMEM; goto cleanup_device; } sched_setscheduler(ext->scanline_task, SCHED_FIFO, &sparm); head_count++; return ext; cleanup_device: device_del(ext->dev); cleanup_cdev: cdev_del(&ext->cdev); cleanup_alloc: kfree(ext); return ERR_PTR(ret); } void tegra_dc_ext_unregister(struct tegra_dc_ext *ext) { int i; for (i = 0; i < ext->dc->n_windows; i++) { struct tegra_dc_ext_win *win = &ext->win[i]; kthread_flush_worker(&win->flip_worker); kthread_stop(win->flip_kthread); } /* Remove scanline work */ kthread_flush_worker(&ext->scanline_worker); ext->scanline_task = NULL; /* Clear trigger line value */ ext->scanline_trigger = -1; /* Udate min val all the way to max. */ nvhost_syncpt_set_min_eq_max_ext(ext->dc->ndev, ext->dc->vpulse3_syncpt); device_del(ext->dev); cdev_del(&ext->cdev); kfree(ext); head_count--; } int __init tegra_dc_ext_module_init(void) { int ret, nheads = tegra_dc_get_numof_dispheads(); if (nheads <= 0) { pr_err("%s: max heads:%d cannot be negative or zero\n", __func__, nheads); return -EINVAL; } tegra_dc_ext_class = class_create(THIS_MODULE, "tegra_dc_ext"); if (!tegra_dc_ext_class) { printk(KERN_ERR "tegra_dc_ext: failed to create class\n"); return -ENOMEM; } /* Reserve one character device per head, plus the control device */ ret = alloc_chrdev_region(&tegra_dc_ext_devno, 0, nheads + 1, "tegra_dc_ext"); if (ret) goto cleanup_class; ret = tegra_dc_ext_control_init(); if (ret) goto cleanup_region; tegra_dc_scrncapt_init(); return 0; cleanup_region: unregister_chrdev_region(tegra_dc_ext_devno, nheads); cleanup_class: class_destroy(tegra_dc_ext_class); return ret; } void __exit tegra_dc_ext_module_exit(void) { tegra_dc_scrncapt_exit(); unregister_chrdev_region(tegra_dc_ext_devno, tegra_dc_get_numof_dispheads()); class_destroy(tegra_dc_ext_class); }