| /* |
| * Copyright (C) 2013 Google, Inc. |
| * Copyright (c) 2014, NVIDIA CORPORATION, All rights reserved. |
| * |
| * modified from drivers/video/tegra/dc/{mode.c,ext/dev.c} |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/memblock.h> |
| #include <linux/gfp.h> |
| #include <media/videobuf2-dma-contig.h> |
| #include <video/adf.h> |
| #include <video/adf_client.h> |
| #include <video/adf_fbdev.h> |
| #include <video/adf_format.h> |
| #include <video/adf_memblock.h> |
| |
| #include "dc/dc_config.h" |
| #include "dc/dc_priv.h" |
| #include "tegra_adf.h" |
| |
| struct tegra_adf_info { |
| struct adf_device base; |
| struct adf_interface intf; |
| struct adf_overlay_engine eng; |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| struct adf_fbdev fbdev; |
| #endif |
| struct tegra_dc *dc; |
| struct tegra_fb_data *fb_data; |
| void *vb2_dma_conf; |
| }; |
| |
| struct tegra_adf_flip_data { |
| u32 syncpt_max[DC_N_WINDOWS]; |
| __u16 dirty_rect[4]; |
| bool dirty_rect_valid; |
| }; |
| |
| #define adf_dev_to_tegra(p) \ |
| container_of(p, struct tegra_adf_info, base) |
| |
| #define adf_intf_to_tegra(p) \ |
| container_of(p, struct tegra_adf_info, intf) |
| |
| const u8 tegra_adf_fourcc_to_dc_fmt(u32 fourcc) |
| { |
| switch (fourcc) { |
| case TEGRA_ADF_FORMAT_P1: |
| return TEGRA_WIN_FMT_P1; |
| case TEGRA_ADF_FORMAT_P2: |
| return TEGRA_WIN_FMT_P2; |
| case TEGRA_ADF_FORMAT_P4: |
| return TEGRA_WIN_FMT_P4; |
| case TEGRA_ADF_FORMAT_P8: |
| return TEGRA_WIN_FMT_P8; |
| case DRM_FORMAT_BGRA4444: |
| return TEGRA_WIN_FMT_B4G4R4A4; |
| case DRM_FORMAT_BGRA5551: |
| return TEGRA_WIN_FMT_B5G5R5A; |
| case DRM_FORMAT_BGR565: |
| return TEGRA_WIN_FMT_B5G6R5; |
| case DRM_FORMAT_BGRA8888: |
| case DRM_FORMAT_BGRX8888: |
| return TEGRA_WIN_FMT_B8G8R8A8; |
| case DRM_FORMAT_RGBA8888: |
| case DRM_FORMAT_RGBX8888: |
| return TEGRA_WIN_FMT_R8G8B8A8; |
| case TEGRA_ADF_FORMAT_B6x2G6x2R6x2A8: |
| return TEGRA_WIN_FMT_B6x2G6x2R6x2A8; |
| case TEGRA_ADF_FORMAT_R6x2G6x2B6x2A8: |
| return TEGRA_WIN_FMT_R6x2G6x2B6x2A8; |
| case DRM_FORMAT_YUV420: |
| return TEGRA_WIN_FMT_YCbCr420P; |
| case DRM_FORMAT_YUV422: |
| return TEGRA_WIN_FMT_YCbCr422P; |
| case TEGRA_ADF_FORMAT_YCbCr422R: |
| return TEGRA_WIN_FMT_YCbCr422R; |
| case DRM_FORMAT_UYVY: |
| return TEGRA_WIN_FMT_YCbCr422; |
| case DRM_FORMAT_NV12: |
| return TEGRA_WIN_FMT_YCbCr420SP; |
| case DRM_FORMAT_NV21: |
| return TEGRA_WIN_FMT_YCrCb420SP; |
| default: |
| BUG(); |
| } |
| } |
| |
| static int tegra_dc_to_drm_modeinfo(struct drm_mode_modeinfo *dmode, |
| const struct tegra_dc_mode *mode) |
| { |
| long mode_pclk; |
| |
| if (!dmode || !mode || !mode->pclk) |
| return -EINVAL; |
| if (mode->rated_pclk >= 1000) /* handle DSI one-shot modes */ |
| mode_pclk = mode->rated_pclk; |
| else if (mode->pclk >= 1000) /* normal continous modes */ |
| mode_pclk = mode->pclk; |
| else |
| mode_pclk = 0; |
| memset(dmode, 0, sizeof(*dmode)); |
| dmode->hdisplay = mode->h_active; |
| dmode->vdisplay = mode->v_active; |
| dmode->hsync_start = dmode->hdisplay + mode->h_front_porch; |
| dmode->vsync_start = dmode->vdisplay + mode->v_front_porch; |
| dmode->hsync_end = dmode->hsync_start + mode->h_sync_width; |
| dmode->vsync_end = dmode->vsync_start + mode->v_sync_width; |
| dmode->htotal = dmode->hsync_end + mode->h_back_porch; |
| dmode->vtotal = dmode->vsync_end + mode->v_back_porch; |
| #if 0 |
| if (mode->stereo_mode) { |
| #ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT |
| /* Double the pixel clock and update v_active only for |
| * frame packed mode */ |
| mode_pclk /= 2; |
| /* total v_active = yres*2 + activespace */ |
| fbmode->vdisplay = (mode->v_active - mode->v_sync_width - |
| mode->v_back_porch - mode->v_front_porch) / 2; |
| fbmode->vmode |= FB_VMODE_STEREO_FRAME_PACK; |
| #else |
| fbmode->vmode |= FB_VMODE_STEREO_LEFT_RIGHT; |
| #endif |
| } |
| #endif |
| |
| if (mode->flags & FB_VMODE_INTERLACED) |
| dmode->flags |= DRM_MODE_FLAG_INTERLACE; |
| |
| if ((mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC)) |
| dmode->flags |= DRM_MODE_FLAG_NHSYNC; |
| else |
| dmode->flags |= DRM_MODE_FLAG_PHSYNC; |
| if ((mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC)) |
| dmode->flags |= DRM_MODE_FLAG_NVSYNC; |
| else |
| dmode->flags |= DRM_MODE_FLAG_PVSYNC; |
| #if 0 |
| if (mode->avi_m == TEGRA_DC_MODE_AVI_M_16_9) |
| fbmode->flag |= FB_FLAG_RATIO_16_9; |
| else if (mode->avi_m == TEGRA_DC_MODE_AVI_M_4_3) |
| fbmode->flag |= FB_FLAG_RATIO_4_3; |
| #endif |
| |
| dmode->clock = mode_pclk / 1000; |
| dmode->vrefresh = tegra_dc_calc_refresh(mode) / 1000; |
| |
| adf_modeinfo_set_name(dmode); |
| |
| return 0; |
| } |
| |
| static int tegra_adf_convert_monspecs(struct tegra_adf_info *adf_info, |
| struct fb_monspecs *specs, struct drm_mode_modeinfo **modelist, |
| size_t *n_modes) |
| { |
| struct tegra_dc *dc = adf_info->dc; |
| struct drm_mode_modeinfo *modes; |
| size_t n = 0; |
| u32 i; |
| |
| modes = kmalloc(specs->modedb_len * sizeof(modes[0]), GFP_KERNEL); |
| if (!modes) |
| return -ENOMEM; |
| |
| for (i = 0; i < specs->modedb_len; i++) { |
| struct fb_videomode *fb_mode = &specs->modedb[i]; |
| if (dc->out_ops->mode_filter && |
| !dc->out_ops->mode_filter(dc, fb_mode)) |
| continue; |
| adf_modeinfo_from_fb_videomode(fb_mode, &modes[n]); |
| n++; |
| } |
| |
| *modelist = modes; |
| *n_modes = n; |
| return 0; |
| } |
| |
| static int tegra_adf_convert_builtin_modes(struct tegra_adf_info *adf_info, |
| struct drm_mode_modeinfo **modelist, size_t *n_modes) |
| { |
| struct tegra_dc *dc = adf_info->dc; |
| struct drm_mode_modeinfo *modes; |
| u32 i; |
| |
| modes = kmalloc(dc->out->n_modes * sizeof(modes[0]), GFP_KERNEL); |
| if (!modes) |
| return -ENOMEM; |
| |
| for (i = 0; i < dc->out->n_modes; i++) { |
| int err = tegra_dc_to_drm_modeinfo(&modes[i], |
| &dc->out->modes[i]); |
| if (err < 0) |
| return err; |
| } |
| |
| *modelist = modes; |
| *n_modes = dc->out->n_modes; |
| return 0; |
| } |
| |
| static int tegra_adf_do_hotplug(struct tegra_adf_info *adf_info, |
| struct drm_mode_modeinfo *modelist, size_t n_modes) |
| { |
| int err = tegra_dc_set_drm_mode(adf_info->dc, modelist, false); |
| if (err < 0) |
| return err; |
| memcpy(&adf_info->intf.current_mode, &modelist[0], sizeof(modelist[0])); |
| |
| return adf_hotplug_notify_connected(&adf_info->intf, modelist, n_modes); |
| } |
| |
| int tegra_adf_process_hotplug_connected(struct tegra_adf_info *adf_info, |
| struct fb_monspecs *specs) |
| { |
| struct tegra_dc_out *out = adf_info->dc->out; |
| struct drm_mode_modeinfo *modes; |
| size_t n_modes; |
| int err; |
| |
| if (!specs && !out->modes) { |
| struct drm_mode_modeinfo reset_mode = {0}; |
| return tegra_adf_do_hotplug(adf_info, &reset_mode, 1); |
| } |
| |
| if (specs) |
| err = tegra_adf_convert_monspecs(adf_info, specs, &modes, |
| &n_modes); |
| else |
| err = tegra_adf_convert_builtin_modes(adf_info, &modes, |
| &n_modes); |
| |
| if (err < 0) |
| return err; |
| |
| err = tegra_adf_do_hotplug(adf_info, modes, n_modes); |
| kfree(modes); |
| return err; |
| } |
| |
| void tegra_adf_process_hotplug_disconnected(struct tegra_adf_info *adf_info) |
| { |
| adf_interface_blank(&adf_info->intf, DRM_MODE_DPMS_OFF); |
| adf_hotplug_notify_disconnected(&adf_info->intf); |
| } |
| |
| static int tegra_adf_dev_custom_data(struct adf_obj *obj, void *data, |
| size_t *size) |
| { |
| struct tegra_adf_capabilities *caps = data; |
| |
| caps->caps = TEGRA_ADF_CAPABILITIES_CURSOR_MODE | |
| TEGRA_ADF_CAPABILITIES_BLOCKLINEAR; |
| *size = sizeof(*caps); |
| return 0; |
| } |
| |
| static int tegra_adf_dev_validate_custom_format(struct adf_device *dev, |
| struct adf_buffer *buf) |
| { |
| u8 dc_fmt = tegra_adf_fourcc_to_dc_fmt(buf->format); |
| |
| if (tegra_dc_is_yuv(dc_fmt)) { |
| u8 cpp[3] = { 1, 1, 1 }; |
| return adf_format_validate_yuv(dev, buf, ARRAY_SIZE(cpp), 1, 2, |
| cpp); |
| } else { |
| u8 cpp = tegra_dc_fmt_bpp(dc_fmt) / 8; |
| return adf_format_validate_rgb(dev, buf, cpp); |
| } |
| |
| return 0; |
| } |
| |
| const u32 tegra_adf_formats[] = { |
| TEGRA_ADF_FORMAT_P1, |
| TEGRA_ADF_FORMAT_P2, |
| TEGRA_ADF_FORMAT_P4, |
| TEGRA_ADF_FORMAT_P8, |
| DRM_FORMAT_BGRA4444, |
| DRM_FORMAT_BGRA5551, |
| DRM_FORMAT_BGR565, |
| DRM_FORMAT_ABGR1555, |
| DRM_FORMAT_BGRA8888, |
| DRM_FORMAT_BGRX8888, |
| DRM_FORMAT_RGBA8888, |
| DRM_FORMAT_RGBX8888, |
| TEGRA_ADF_FORMAT_B6x2G6x2R6x2A8, |
| TEGRA_ADF_FORMAT_R6x2G6x2B6x2A8, |
| TEGRA_ADF_FORMAT_R6x2G6x2B6x2A8, |
| DRM_FORMAT_YUV420, |
| DRM_FORMAT_YUV422, |
| TEGRA_ADF_FORMAT_YCbCr422R, |
| DRM_FORMAT_UYVY, |
| DRM_FORMAT_NV12, |
| DRM_FORMAT_NV21, |
| }; |
| |
| static inline int test_bit_u32(int nr, const u32 *addr) |
| { |
| return 1UL & (addr[nr / 32] >> (nr & 31)); |
| } |
| |
| static int tegra_adf_check_windowattr(struct tegra_adf_info *adf_info, |
| const struct tegra_adf_flip_windowattr *attr, u32 fourcc) |
| { |
| u32 *addr; |
| struct tegra_dc *dc = adf_info->dc; |
| struct device *dev = &adf_info->base.base.dev; |
| u8 fmt = tegra_adf_fourcc_to_dc_fmt(fourcc); |
| |
| addr = tegra_dc_parse_feature(dc, attr->win_index, GET_WIN_FORMATS); |
| /* Check if the window exists */ |
| if (!addr) { |
| dev_err(dev, "window %d feature is not found.\n", |
| attr->win_index); |
| goto fail; |
| } |
| /* Check the window format */ |
| if (!test_bit_u32(fmt, addr)) { |
| dev_err(dev, |
| "Color format of window %d is invalid.\n", |
| attr->win_index); |
| goto fail; |
| } |
| |
| /* Check window size */ |
| addr = tegra_dc_parse_feature(dc, attr->win_index, GET_WIN_SIZE); |
| if (CHECK_SIZE(attr->out_w, addr[MIN_WIDTH], addr[MAX_WIDTH]) || |
| CHECK_SIZE(attr->out_h, addr[MIN_HEIGHT], addr[MAX_HEIGHT])) { |
| dev_err(dev, |
| "Size of window %d is invalid with %d wide %d high.\n", |
| attr->win_index, attr->out_w, attr->out_h); |
| goto fail; |
| } |
| |
| if (attr->flags & TEGRA_ADF_FLIP_FLAG_BLOCKLINEAR) { |
| if (attr->flags & TEGRA_ADF_FLIP_FLAG_TILED) { |
| dev_err(&dc->ndev->dev, "Layout cannot be both blocklinear and tile for window %d.\n", |
| attr->win_index); |
| goto fail; |
| } |
| |
| /* TODO: also check current window blocklinear support */ |
| } |
| |
| if ((attr->flags & TEGRA_ADF_FLIP_FLAG_SCAN_COLUMN) && |
| !tegra_dc_feature_has_scan_column(dc, |
| attr->win_index)) { |
| dev_err(&dc->ndev->dev, "rotation not supported for window %d.\n", |
| attr->win_index); |
| goto fail; |
| } |
| |
| return 0; |
| fail: |
| return -EINVAL; |
| } |
| |
| static int tegra_adf_sanitize_flip_args(struct tegra_adf_info *adf_info, |
| struct adf_post *cfg, |
| struct tegra_adf_flip_windowattr *win, int win_num, |
| __u16 *dirty_rect[4]) |
| { |
| struct device *dev = &adf_info->base.base.dev; |
| struct tegra_dc *dc = adf_info->dc; |
| int i, used_windows = 0; |
| |
| if (win_num > DC_N_WINDOWS) { |
| dev_err(dev, "too many windows (%u > %u)\n", win_num, |
| DC_N_WINDOWS); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < win_num; i++) { |
| int index = win[i].win_index; |
| int buf_index = win[i].buf_index; |
| int err; |
| |
| if (index < 0) |
| continue; |
| |
| if (index >= DC_N_WINDOWS || |
| !test_bit(index, &dc->valid_windows)) { |
| dev_err(dev, "invalid window index %u\n", index); |
| return -EINVAL; |
| } |
| |
| if (used_windows & BIT(index)) { |
| dev_err(dev, "window index %u already used\n", index); |
| return -EINVAL; |
| } |
| |
| if (buf_index >= 0) { |
| if (buf_index >= cfg->n_bufs) { |
| dev_err(dev, "invalid buffer index %d (n_bufs = %zu)\n", |
| buf_index, cfg->n_bufs); |
| return -EINVAL; |
| } |
| |
| err = tegra_adf_check_windowattr(adf_info, &win[i], |
| cfg->bufs[buf_index].format); |
| if (err < 0) |
| return err; |
| } |
| |
| used_windows |= BIT(index); |
| } |
| |
| if (!used_windows) { |
| dev_err(dev, "no windows used\n"); |
| 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]; |
| struct tegra_dc *dc = adf_info->dc; |
| |
| 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 = 0; |
| } 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; |
| } |
| |
| int tegra_adf_dev_validate(struct adf_device *dev, struct adf_post *cfg, |
| void **driver_state) |
| { |
| struct tegra_adf_flip *args = cfg->custom_data; |
| struct tegra_adf_info *adf_info = adf_dev_to_tegra(dev); |
| struct tegra_adf_flip_windowattr *win; |
| struct tegra_adf_flip_data *data; |
| unsigned int win_num; |
| size_t custom_data_size = sizeof(*args); |
| __u16 *dirty_rect; |
| int ret = 0; |
| |
| if (cfg->custom_data_size < custom_data_size) { |
| dev_err(dev->dev, "custom data size too small (%zu < %zu)\n", |
| cfg->custom_data_size, custom_data_size); |
| return -EINVAL; |
| } |
| |
| win = args->win; |
| win_num = args->win_num; |
| |
| custom_data_size += win_num * sizeof(win[0]); |
| if (cfg->custom_data_size != custom_data_size) { |
| dev_err(dev->dev, "expected %zu bytes of custom data for %u windows, received %zu\n", |
| custom_data_size, args->win_num, |
| cfg->custom_data_size); |
| return -EINVAL; |
| } |
| |
| data = kzalloc(sizeof(*data), GFP_KERNEL); |
| if (!data) { |
| dev_err(dev->dev, "failed to allocate driver state\n"); |
| return -ENOMEM; |
| } |
| |
| dirty_rect = args->dirty_rect; |
| ret = tegra_adf_sanitize_flip_args(adf_info, cfg, win, win_num, |
| &dirty_rect); |
| if (ret < 0) |
| goto done; |
| |
| if (dirty_rect) { |
| memcpy(data->dirty_rect, dirty_rect, sizeof(data->dirty_rect)); |
| data->dirty_rect_valid = true; |
| } |
| |
| BUG_ON(win_num > DC_N_WINDOWS); |
| |
| done: |
| if (ret < 0) |
| kfree(data); |
| else |
| *driver_state = data; |
| return ret; |
| } |
| |
| static inline dma_addr_t tegra_adf_phys_addr(struct adf_buffer *buf, |
| struct adf_buffer_mapping *mapping, |
| size_t plane) |
| { |
| struct scatterlist *sgl = buf->dma_bufs[plane] ? |
| mapping->sg_tables[plane]->sgl : |
| mapping->sg_tables[TEGRA_DC_Y]->sgl; |
| |
| dma_addr_t addr = sg_dma_address(sgl); |
| if (!addr) |
| addr = sg_phys(sgl); |
| addr += buf->offset[plane]; |
| return addr; |
| } |
| |
| static void tegra_adf_set_windowattr_basic(struct tegra_dc_win *win, |
| const struct tegra_adf_flip_windowattr *attr, |
| u32 format, u32 w, u32 h) |
| { |
| win->flags = TEGRA_WIN_FLAG_ENABLED; |
| if (attr->blend == TEGRA_DC_EXT_BLEND_PREMULT) |
| win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT; |
| else if (attr->blend == TEGRA_DC_EXT_BLEND_COVERAGE) |
| win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE; |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_TILED) |
| win->flags |= TEGRA_WIN_FLAG_TILED; |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_H) |
| win->flags |= TEGRA_WIN_FLAG_INVERT_H; |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_V) |
| win->flags |= TEGRA_WIN_FLAG_INVERT_V; |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_GLOBAL_ALPHA) |
| win->global_alpha = attr->global_alpha; |
| else |
| win->global_alpha = 255; |
| #if defined(CONFIG_TEGRA_DC_SCAN_COLUMN) |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_SCAN_COLUMN) |
| win->flags |= TEGRA_WIN_FLAG_SCAN_COLUMN; |
| #endif |
| #if defined(CONFIG_TEGRA_DC_BLOCK_LINEAR) |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_BLOCKLINEAR) { |
| win->flags |= TEGRA_WIN_FLAG_BLOCKLINEAR; |
| win->block_height_log2 = attr->block_height_log2; |
| } |
| #endif |
| #if defined(CONFIG_TEGRA_DC_INTERLACE) |
| if (attr->flags & TEGRA_DC_EXT_FLIP_FLAG_INTERLACE) |
| win->flags |= TEGRA_WIN_FLAG_INTERLACE; |
| #endif |
| |
| win->fmt = tegra_adf_fourcc_to_dc_fmt(format); |
| win->x.full = attr->x; |
| win->y.full = attr->y; |
| win->w.full = dfixed_const(w); |
| win->h.full = dfixed_const(h); |
| /* XXX verify that this doesn't go outside display's active region */ |
| win->out_x = attr->out_x; |
| win->out_y = attr->out_y; |
| win->out_w = attr->out_w; |
| win->out_h = attr->out_h; |
| win->z = attr->z; |
| } |
| |
| static void tegra_adf_set_windowattr(struct tegra_adf_info *adf_info, |
| struct tegra_dc_win *win, |
| const struct tegra_adf_flip_windowattr *attr, |
| struct adf_buffer *buf, struct adf_buffer_mapping *mapping) |
| { |
| if (!buf) { |
| win->flags = 0; |
| return; |
| } |
| |
| tegra_adf_set_windowattr_basic(win, attr, buf->format, buf->w, buf->h); |
| |
| win->stride = buf->pitch[0]; |
| win->stride_uv = buf->pitch[1]; |
| |
| /* XXX verify that this won't read outside of the surface */ |
| win->phys_addr = tegra_adf_phys_addr(buf, mapping, TEGRA_DC_Y); |
| win->phys_addr_u = tegra_adf_phys_addr(buf, mapping, TEGRA_DC_U); |
| win->phys_addr_v = tegra_adf_phys_addr(buf, mapping, TEGRA_DC_V); |
| |
| #if defined(CONFIG_TEGRA_DC_INTERLACE) |
| if (adf_info->dc->mode.vmode == FB_VMODE_INTERLACED) { |
| if (attr->flags & TEGRA_ADF_FLIP_FLAG_INTERLACE) { |
| win->phys_addr2 = win->phys_addr + attr->offset2; |
| win->phys_addr_u2 = win->phys_addr_u + attr->offset_u2; |
| win->phys_addr_v2 = win->phys_addr_v + attr->offset_v2; |
| } else { |
| win->phys_addr2 = win->phys_addr; |
| win->phys_addr_u2 = win->phys_addr_u; |
| win->phys_addr_v2 = win->phys_addr_v; |
| } |
| } |
| #endif |
| |
| if (tegra_platform_is_silicon()) { |
| dev_WARN_ONCE(&adf_info->base.base.dev, attr->timestamp_ns, |
| "timestamping not implemented\n"); |
| /* TODO: implement timestamping */ |
| #if 0 |
| if (timestamp_ns) { |
| /* XXX: Should timestamping be overridden by "no_vsync" |
| * flag */ |
| 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); |
| } |
| #endif |
| } |
| } |
| |
| static void tegra_adf_dev_post(struct adf_device *dev, struct adf_post *cfg, |
| void *driver_state) |
| { |
| struct tegra_adf_info *adf_info = adf_dev_to_tegra(dev); |
| struct tegra_adf_flip *args = cfg->custom_data; |
| struct tegra_adf_flip_data *data = driver_state; |
| int win_num = args->win_num; |
| struct tegra_dc_win *wins[DC_N_WINDOWS]; |
| int i, nr_win = 0; |
| bool skip_flip = false; |
| |
| BUG_ON(win_num > DC_N_WINDOWS); |
| for (i = 0; i < win_num; i++) { |
| struct tegra_adf_flip_windowattr *attr = &args->win[i]; |
| int index = attr->win_index; |
| struct adf_buffer *buf; |
| struct adf_buffer_mapping *mapping; |
| struct tegra_dc_win *win; |
| |
| if (index < 0) |
| continue; |
| |
| if (attr->buf_index < 0) { |
| buf = NULL; |
| mapping = NULL; |
| } else { |
| buf = &cfg->bufs[attr->buf_index]; |
| mapping = &cfg->mappings[attr->buf_index]; |
| } |
| |
| win = tegra_dc_get_window(adf_info->dc, index); |
| |
| #if 0 |
| if (flip_win->flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR) |
| 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)) |
| dev_err(&win->dc->ndev->dev, |
| "work queue did NOT dequeue head!!!"); |
| else |
| head_timestamp = |
| timespec_to_ns(&flip_win->attr.timestamp); |
| } else { |
| s64 timestamp = |
| timespec_to_ns(&temp->win[i].attr.timestamp); |
| |
| skip_flip = !tegra_dc_does_vsync_separate(ext->dc, |
| timestamp, head_timestamp); |
| /* Look ahead only one flip */ |
| break; |
| } |
| j++; |
| } |
| if (!list_empty(&ext_win->timestamp_queue)) |
| list_del(&data->timestamp_node); |
| mutex_unlock(&ext_win->queue_lock); |
| |
| if (skip_flip) |
| 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 (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 (!skip_flip) |
| #endif |
| tegra_adf_set_windowattr(adf_info, win, attr, buf, |
| mapping); |
| |
| wins[nr_win++] = win; |
| } |
| |
| if (!skip_flip) { |
| tegra_dc_update_windows(wins, nr_win, |
| data->dirty_rect_valid ? data->dirty_rect : NULL); |
| /* TODO: implement swapinterval here */ |
| tegra_dc_sync_windows(wins, nr_win); |
| tegra_dc_program_bandwidth(adf_info->dc, true); |
| if (!tegra_dc_has_multiple_dc()) |
| tegra_dc_call_flip_callback(); |
| } |
| } |
| |
| static struct sync_fence *tegra_adf_dev_complete_fence(struct adf_device *dev, |
| struct adf_post *cfg, void *driver_state) |
| { |
| struct tegra_adf_info *adf_info = adf_dev_to_tegra(dev); |
| struct tegra_adf_flip *args = cfg->custom_data; |
| struct tegra_adf_flip_windowattr *win = args->win; |
| struct tegra_adf_flip_data *data = driver_state; |
| u32 syncpt_val; |
| int work_index = -1; |
| unsigned int win_num = args->win_num, i; |
| |
| for (i = 0; i < win_num; i++) { |
| int index = win[i].win_index; |
| |
| if (index < 0) |
| continue; |
| |
| data->syncpt_max[i] = tegra_dc_incr_syncpt_max(adf_info->dc, |
| index); |
| |
| /* |
| * Any of these windows' syncpoints should be equivalent for |
| * the client, so we just send back an arbitrary one of them |
| */ |
| syncpt_val = data->syncpt_max[i]; |
| work_index = index; |
| } |
| if (work_index < 0) |
| return ERR_PTR(-EINVAL); |
| |
| return tegra_dc_create_fence(adf_info->dc, work_index, syncpt_val + 1); |
| } |
| |
| static void tegra_adf_dev_advance_timeline(struct adf_device *dev, |
| struct adf_post *cfg, void *driver_state) |
| { |
| struct tegra_adf_info *adf_info = adf_dev_to_tegra(dev); |
| struct tegra_adf_flip *args = cfg->custom_data; |
| struct tegra_adf_flip_windowattr *win = args->win; |
| u32 *syncpt_max = driver_state; |
| unsigned int win_num = args->win_num, i; |
| |
| for (i = 0; i < win_num; i++) { |
| int index = win[i].win_index; |
| |
| if (index < 0) |
| continue; |
| |
| tegra_dc_incr_syncpt_min(adf_info->dc, index, syncpt_max[i]); |
| } |
| } |
| |
| static void tegra_adf_dev_state_free(struct adf_device *dev, void *driver_state) |
| { |
| kfree(driver_state); |
| } |
| |
| static int tegra_adf_sanitize_proposed_bw(struct tegra_adf_info *adf_info, |
| const struct tegra_adf_proposed_bw *bw, u8 win_num) |
| { |
| struct device *dev = &adf_info->base.base.dev; |
| struct tegra_dc *dc = adf_info->dc; |
| u8 i; |
| |
| if (win_num != bw->win_num) |
| return -EINVAL; |
| |
| if (win_num > DC_N_WINDOWS) { |
| dev_err(dev, "too many windows (%u > %u)\n", win_num, |
| DC_N_WINDOWS); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < win_num; i++) { |
| s32 index = bw->win[i].attr.win_index; |
| |
| if (index < 0 || |
| index >= DC_N_WINDOWS || |
| !test_bit(index, &dc->valid_windows)) { |
| dev_err(dev, "invalid window index %u\n", index); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int tegra_adf_negotiate_bw(struct tegra_adf_info *adf_info, |
| struct tegra_adf_proposed_bw *bw) |
| { |
| #ifdef CONFIG_TEGRA_ISOMGR |
| struct tegra_dc_win *dc_wins[DC_N_WINDOWS]; |
| struct tegra_dc *dc = adf_info->dc; |
| struct adf_overlay_engine *eng = &adf_info->eng; |
| u8 i; |
| |
| /* If display has been disconnected return with error. */ |
| if (!dc->connected) |
| return -1; |
| |
| for (i = 0; i < bw->win_num; i++) { |
| struct tegra_adf_flip_windowattr *attr = &bw->win[i].attr; |
| s32 idx = attr->win_index; |
| |
| if (attr->buf_index >= 0) { |
| u32 fourcc = bw->win[i].format; |
| if (!adf_overlay_engine_supports_format(eng, fourcc)) { |
| char format_str[ADF_FORMAT_STR_SIZE]; |
| adf_format_str(fourcc, format_str); |
| dev_err(&eng->base.dev, "%s: unsupported format %s\n", |
| __func__, format_str); |
| return -EINVAL; |
| } |
| |
| tegra_adf_set_windowattr_basic(&dc->tmp_wins[idx], |
| attr, fourcc, bw->win[i].w, |
| bw->win[i].h); |
| } else { |
| dc->tmp_wins[i].flags = 0; |
| } |
| |
| dc_wins[i] = &dc->tmp_wins[idx]; |
| } |
| |
| return tegra_dc_bandwidth_negotiate_bw(dc, dc_wins, bw->win_num); |
| #else |
| return -EINVAL; |
| #endif |
| } |
| |
| static int tegra_adf_set_proposed_bw(struct tegra_adf_info *adf_info, |
| struct tegra_adf_proposed_bw __user *arg) |
| { |
| u8 win_num; |
| size_t bw_size; |
| struct tegra_adf_proposed_bw *bw; |
| int ret; |
| |
| if (get_user(win_num, &arg->win_num)) |
| return -EFAULT; |
| |
| bw_size = sizeof(*bw) + sizeof(bw->win[0]) * win_num; |
| bw = kmalloc(bw_size, GFP_KERNEL); |
| if (!bw) |
| return -ENOMEM; |
| |
| if (copy_from_user(bw, arg, bw_size)) { |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| ret = tegra_adf_sanitize_proposed_bw(adf_info, bw, win_num); |
| if (ret < 0) |
| goto done; |
| |
| ret = tegra_adf_negotiate_bw(adf_info, bw); |
| done: |
| kfree(bw); |
| return ret; |
| } |
| |
| static long tegra_adf_dev_ioctl(struct adf_obj *obj, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct adf_device *dev = adf_obj_to_device(obj); |
| struct tegra_adf_info *adf_info = adf_dev_to_tegra(dev); |
| |
| switch (cmd) { |
| case TEGRA_ADF_SET_PROPOSED_BW: |
| return tegra_adf_set_proposed_bw(adf_info, |
| (struct tegra_adf_proposed_bw __user *)arg); |
| |
| default: |
| return -ENOTTY; |
| } |
| } |
| |
| |
| void tegra_adf_process_vblank(struct tegra_adf_info *adf_info, |
| ktime_t timestamp) |
| { |
| if (unlikely(!adf_info)) |
| pr_debug("%s: suppressing vblank event since ADF is not finished probing\n", |
| __func__); |
| else |
| adf_vsync_notify(&adf_info->intf, timestamp); |
| } |
| |
| static bool tegra_adf_intf_supports_event(struct adf_obj *obj, |
| enum adf_event_type type) |
| { |
| struct adf_interface *intf = adf_obj_to_interface(obj); |
| struct tegra_adf_info *tegra_adf = adf_intf_to_tegra(intf); |
| |
| switch (type) { |
| case ADF_EVENT_VSYNC: |
| return true; |
| case ADF_EVENT_HOTPLUG: |
| return tegra_dc_get_out(tegra_adf->dc) == TEGRA_DC_OUT_HDMI; |
| default: |
| return false; |
| } |
| } |
| |
| static void tegra_adf_set_vsync(struct tegra_adf_info *tegra_adf, bool enabled) |
| { |
| if (enabled) { |
| tegra_dc_hold_dc_out(tegra_adf->dc); |
| tegra_dc_vsync_enable(tegra_adf->dc); |
| } else { |
| tegra_dc_vsync_disable(tegra_adf->dc); |
| tegra_dc_release_dc_out(tegra_adf->dc); |
| } |
| } |
| |
| static void tegra_adf_intf_set_event(struct adf_obj *obj, |
| enum adf_event_type type, bool enabled) |
| { |
| struct adf_interface *intf = adf_obj_to_interface(obj); |
| struct tegra_adf_info *tegra_adf = adf_intf_to_tegra(intf); |
| |
| switch (type) { |
| case ADF_EVENT_VSYNC: |
| tegra_adf_set_vsync(tegra_adf, enabled); |
| return; |
| |
| case ADF_EVENT_HOTPLUG: |
| return; |
| |
| default: |
| BUG(); |
| } |
| } |
| |
| static enum adf_interface_type tegra_adf_interface_type(struct tegra_dc *dc) |
| { |
| /* TODO: can RGB and LVDS be mapped to existing ADF_INTF types? |
| * Should they be added to ADF's list? */ |
| switch (tegra_dc_get_out(dc)) { |
| case TEGRA_DC_OUT_RGB: |
| return TEGRA_ADF_INTF_RGB; |
| case TEGRA_DC_OUT_HDMI: |
| return ADF_INTF_HDMI; |
| case TEGRA_DC_OUT_DSI: |
| return ADF_INTF_DSI; |
| case TEGRA_DC_OUT_DP: |
| return ADF_INTF_eDP; |
| case TEGRA_DC_OUT_LVDS: |
| return TEGRA_ADF_INTF_LVDS; |
| default: |
| BUG(); |
| } |
| } |
| |
| static const char *tegra_adf_intf_type_str(struct adf_interface *intf) |
| { |
| switch ((enum tegra_adf_interface_type)intf->type) { |
| case TEGRA_ADF_INTF_RGB: |
| return "RGB"; |
| case TEGRA_ADF_INTF_LVDS: |
| return "LVDS"; |
| default: |
| BUG(); |
| } |
| } |
| |
| static int tegra_adf_dpms_to_fb_blank(u8 dpms_state) |
| { |
| switch (dpms_state) { |
| case DRM_MODE_DPMS_ON: |
| return FB_BLANK_UNBLANK; |
| case DRM_MODE_DPMS_STANDBY: |
| return FB_BLANK_HSYNC_SUSPEND; |
| case DRM_MODE_DPMS_SUSPEND: |
| return FB_BLANK_VSYNC_SUSPEND; |
| case DRM_MODE_DPMS_OFF: |
| return FB_BLANK_POWERDOWN; |
| default: |
| BUG(); |
| } |
| } |
| |
| static int tegra_adf_intf_blank(struct adf_interface *intf, u8 state) |
| { |
| struct tegra_adf_info *adf_info = adf_intf_to_tegra(intf); |
| |
| switch (state) { |
| case DRM_MODE_DPMS_ON: |
| tegra_dc_enable(adf_info->dc); |
| break; |
| |
| case DRM_MODE_DPMS_STANDBY: |
| tegra_dc_blank(adf_info->dc, BLANK_ALL); |
| break; |
| |
| case DRM_MODE_DPMS_SUSPEND: |
| case DRM_MODE_DPMS_OFF: |
| tegra_dc_disable(adf_info->dc); |
| break; |
| |
| default: |
| return -ENOTTY; |
| } |
| |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| if (intf->flags & ADF_INTF_FLAG_PRIMARY) { |
| struct fb_event event; |
| int fb_state = tegra_adf_dpms_to_fb_blank(state); |
| |
| event.info = adf_info->fbdev.info; |
| event.data = &fb_state; |
| fb_notifier_call_chain(FB_EVENT_BLANK, &event); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int tegra_adf_intf_alloc_simple_buffer(struct adf_interface *intf, |
| u16 w, u16 h, u32 format, |
| struct dma_buf **dma_buf, u32 *offset, u32 *pitch) |
| { |
| size_t i; |
| struct tegra_adf_info *adf_info = adf_intf_to_tegra(intf); |
| const struct vb2_mem_ops *mem_ops = &vb2_dma_contig_memops; |
| void *vb2_buf; |
| bool format_valid = false; |
| struct dma_buf *ret; |
| |
| for (i = 0; i < ARRAY_SIZE(tegra_adf_formats); i++) { |
| if (tegra_adf_formats[i] == format) { |
| format_valid = true; |
| break; |
| } |
| } |
| |
| if (!format_valid) |
| return -EINVAL; |
| |
| *offset = 0; |
| *pitch = ALIGN(w * adf_format_bpp(format) / 8, 64); |
| |
| vb2_buf = mem_ops->alloc(adf_info->vb2_dma_conf, |
| h * *pitch, __GFP_HIGHMEM); |
| if (IS_ERR(vb2_buf)) |
| return PTR_ERR(vb2_buf); |
| |
| ret = mem_ops->get_dmabuf(vb2_buf); |
| mem_ops->put(vb2_buf); |
| if (!ret) |
| return -ENOMEM; |
| ret->file->f_mode |= FMODE_WRITE; |
| *dma_buf = ret; |
| |
| return 0; |
| } |
| |
| static int tegra_adf_intf_describe_simple_post(struct adf_interface *intf, |
| struct adf_buffer *fb, void *data, size_t *size) |
| { |
| struct tegra_adf_info *adf_info = adf_intf_to_tegra(intf); |
| struct tegra_adf_flip *args = data; |
| int i; |
| |
| args->win_num = 0; |
| for_each_set_bit(i, &adf_info->dc->valid_windows, DC_N_WINDOWS) { |
| struct tegra_adf_flip_windowattr *win = |
| &args->win[args->win_num]; |
| win->win_index = i; |
| if (i == adf_info->fb_data->win) { |
| win->buf_index = 0; |
| win->out_w = intf->current_mode.hdisplay; |
| win->out_h = intf->current_mode.vdisplay; |
| } else { |
| win->buf_index = -1; |
| } |
| args->win_num++; |
| } |
| |
| *size = sizeof(*args) + args->win_num * sizeof(args->win[0]); |
| return 0; |
| } |
| |
| static int tegra_adf_intf_modeset(struct adf_interface *intf, |
| struct drm_mode_modeinfo *mode) |
| { |
| struct tegra_adf_info *adf_info = adf_intf_to_tegra(intf); |
| return tegra_dc_set_drm_mode(adf_info->dc, mode, false); |
| } |
| |
| static int tegra_adf_intf_screen_size(struct adf_interface *intf, u16 *width_mm, |
| u16 *height_mm) |
| { |
| struct tegra_adf_info *adf_info = adf_intf_to_tegra(intf); |
| struct tegra_dc_out *out = adf_info->dc->out; |
| |
| if (!out->height) |
| return -EINVAL; |
| |
| *width_mm = out->width; |
| *height_mm = out->height; |
| return 0; |
| } |
| |
| struct adf_device_ops tegra_adf_dev_ops = { |
| .owner = THIS_MODULE, |
| .base = { |
| .custom_data = tegra_adf_dev_custom_data, |
| .ioctl = tegra_adf_dev_ioctl, |
| }, |
| .validate_custom_format = tegra_adf_dev_validate_custom_format, |
| .validate = tegra_adf_dev_validate, |
| .complete_fence = tegra_adf_dev_complete_fence, |
| .post = tegra_adf_dev_post, |
| .advance_timeline = tegra_adf_dev_advance_timeline, |
| .state_free = tegra_adf_dev_state_free, |
| }; |
| |
| struct adf_interface_ops tegra_adf_intf_ops = { |
| .base = { |
| .supports_event = tegra_adf_intf_supports_event, |
| .set_event = tegra_adf_intf_set_event, |
| }, |
| .blank = tegra_adf_intf_blank, |
| .alloc_simple_buffer = tegra_adf_intf_alloc_simple_buffer, |
| .describe_simple_post = tegra_adf_intf_describe_simple_post, |
| .modeset = tegra_adf_intf_modeset, |
| .screen_size = tegra_adf_intf_screen_size, |
| .type_str = tegra_adf_intf_type_str, |
| }; |
| |
| struct adf_overlay_engine_ops tegra_adf_eng_ops = { |
| .supported_formats = tegra_adf_formats, |
| .n_supported_formats = ARRAY_SIZE(tegra_adf_formats), |
| }; |
| |
| int tegra_adf_process_bandwidth_renegotiate(struct tegra_adf_info *adf_info, |
| struct tegra_dc_bw_data *bw) |
| { |
| struct tegra_adf_event_bandwidth event; |
| |
| if (unlikely(!adf_info)) { |
| pr_debug("%s: suppressing bandwidth event since ADF is not finished probing\n", |
| __func__); |
| return 0; |
| } |
| |
| event.base.type = TEGRA_ADF_EVENT_BANDWIDTH_RENEGOTIATE; |
| event.base.length = sizeof(event); |
| if (bw == NULL) { |
| event.total_bw = 0; |
| event.avail_bw = 0; |
| event.resvd_bw = 0; |
| } else { |
| event.total_bw = bw->total_bw; |
| event.avail_bw = bw->avail_bw; |
| event.resvd_bw = bw->resvd_bw; |
| } |
| return adf_event_notify(&adf_info->base.base, &event.base); |
| } |
| |
| struct fb_ops tegra_adf_fb_ops = { |
| .owner = THIS_MODULE, |
| .fb_open = adf_fbdev_open, |
| .fb_release = adf_fbdev_release, |
| .fb_check_var = adf_fbdev_check_var, |
| .fb_set_par = adf_fbdev_set_par, |
| .fb_blank = adf_fbdev_blank, |
| .fb_pan_display = adf_fbdev_pan_display, |
| .fb_mmap = adf_fbdev_mmap, |
| }; |
| |
| static void tegra_adf_save_bootloader_logo(struct tegra_adf_info *adf_info, |
| struct resource *fb_mem) |
| { |
| struct device *dev = adf_info->base.dev; |
| struct adf_buffer logo; |
| struct sync_fence *fence; |
| |
| memset(&logo, 0, sizeof(logo)); |
| logo.dma_bufs[0] = adf_memblock_export(fb_mem->start, |
| resource_size(fb_mem), 0); |
| if (IS_ERR(logo.dma_bufs[0])) { |
| dev_warn(dev, "failed to export bootloader logo: %ld\n", |
| PTR_ERR(logo.dma_bufs[0])); |
| return; |
| } |
| |
| logo.overlay_engine = &adf_info->eng; |
| logo.w = adf_info->fb_data->xres; |
| logo.h = adf_info->fb_data->yres; |
| logo.format = adf_info->fb_data->bits_per_pixel == 16 ? |
| DRM_FORMAT_RGB565 : |
| DRM_FORMAT_RGBA8888; |
| logo.pitch[0] = logo.w * adf_info->fb_data->bits_per_pixel / 8; |
| logo.n_planes = 1; |
| |
| fence = adf_interface_simple_post(&adf_info->intf, &logo); |
| if (IS_ERR(fence)) |
| dev_warn(dev, "failed to post bootloader logo: %ld\n", |
| PTR_ERR(fence)); |
| else |
| sync_fence_put(fence); |
| |
| dma_buf_put(logo.dma_bufs[0]); |
| } |
| |
| struct tegra_adf_info *tegra_adf_init(struct platform_device *ndev, |
| struct tegra_dc *dc, |
| struct tegra_fb_data *fb_data, |
| struct resource *fb_mem) |
| { |
| struct tegra_adf_info *adf_info; |
| int err; |
| enum adf_interface_type intf_type; |
| u32 intf_flags = 0; |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| u32 fb_format; |
| #endif |
| |
| adf_info = kzalloc(sizeof(*adf_info), GFP_KERNEL); |
| if (!adf_info) |
| return ERR_PTR(-ENOMEM); |
| |
| adf_info->dc = dc; |
| adf_info->fb_data = fb_data; |
| |
| err = adf_device_init(&adf_info->base, &ndev->dev, |
| &tegra_adf_dev_ops, "%s", dev_name(&ndev->dev)); |
| if (err < 0) |
| goto err_dev_init; |
| |
| intf_type = tegra_adf_interface_type(dc); |
| |
| if (ndev->id == 0) |
| intf_flags |= ADF_INTF_FLAG_PRIMARY; |
| |
| if (intf_type == ADF_INTF_HDMI) |
| intf_flags |= ADF_INTF_FLAG_EXTERNAL; |
| |
| err = adf_interface_init(&adf_info->intf, &adf_info->base, |
| intf_type, 0, intf_flags, |
| &tegra_adf_intf_ops, "%s", dev_name(&ndev->dev)); |
| if (err < 0) |
| goto err_intf_init; |
| |
| err = adf_overlay_engine_init(&adf_info->eng, &adf_info->base, |
| &tegra_adf_eng_ops, "%s", dev_name(&ndev->dev)); |
| if (err < 0) |
| goto err_eng_init; |
| |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| fb_format = fb_data->bits_per_pixel == 16 ? DRM_FORMAT_RGB565 : |
| DRM_FORMAT_RGBA8888; |
| err = adf_fbdev_init(&adf_info->fbdev, &adf_info->intf, |
| &adf_info->eng, fb_data->xres, fb_data->yres * 2, fb_format, |
| &tegra_adf_fb_ops, "%s", dev_name(&ndev->dev)); |
| if (err < 0) |
| goto err_fbdev; |
| #endif |
| |
| err = adf_attachment_allow(&adf_info->base, &adf_info->eng, |
| &adf_info->intf); |
| if (err < 0) |
| goto err_attach; |
| |
| adf_info->vb2_dma_conf = vb2_dma_contig_init_ctx(&ndev->dev); |
| if ((err = IS_ERR(adf_info->vb2_dma_conf))) |
| goto err_attach; |
| |
| if (dc->out->n_modes) { |
| err = tegra_adf_process_hotplug_connected(adf_info, NULL); |
| if (err < 0) |
| goto err_attach; |
| } |
| |
| if (dc->enabled) |
| adf_info->intf.dpms_state = DRM_MODE_DPMS_ON; |
| |
| if (fb_data->flags & TEGRA_FB_FLIP_ON_PROBE) |
| tegra_adf_save_bootloader_logo(adf_info, fb_mem); |
| else |
| memblock_free(fb_mem->start, resource_size(fb_mem)); |
| |
| dev_info(&ndev->dev, "ADF initialized\n"); |
| |
| return adf_info; |
| |
| err_attach: |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| adf_fbdev_destroy(&adf_info->fbdev); |
| |
| err_fbdev: |
| #endif |
| adf_overlay_engine_destroy(&adf_info->eng); |
| |
| err_eng_init: |
| adf_interface_destroy(&adf_info->intf); |
| |
| err_intf_init: |
| adf_device_destroy(&adf_info->base); |
| |
| err_dev_init: |
| kfree(adf_info); |
| return ERR_PTR(err); |
| } |
| |
| void tegra_adf_unregister(struct tegra_adf_info *adf_info) |
| { |
| #if IS_ENABLED(CONFIG_ADF_TEGRA_FBDEV) |
| adf_fbdev_destroy(&adf_info->fbdev); |
| #endif |
| adf_overlay_engine_destroy(&adf_info->eng); |
| adf_interface_destroy(&adf_info->intf); |
| adf_device_destroy(&adf_info->base); |
| kfree(adf_info); |
| } |