// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "meson_writeback.h" #include "meson_vpu.h" #include "meson_crtc.h" u32 writeback_fmts[TVIN_PIXEL_FORMAT_NUM]; static int meson_writeback_connector_get_modes(struct drm_connector *connector) { struct drm_device *dev = connector->dev; return drm_add_modes_noedid(connector, dev->mode_config.max_width, dev->mode_config.max_height); } enum drm_mode_status meson_writeback_connector_check_mode(struct drm_connector *connector, struct drm_display_mode *mode) { struct drm_device *dev = connector->dev; struct drm_mode_config *mode_config = &dev->mode_config; int w = mode->hdisplay, h = mode->vdisplay; if (w < mode_config->min_width || w > mode_config->max_width) return MODE_BAD_HVALUE; if (h < mode_config->min_height || h > mode_config->max_height) return MODE_BAD_VVALUE; return MODE_OK; } static int meson_writeback_connector_atomic_check(struct drm_connector *conn, struct drm_atomic_state *state) { struct drm_connector_state *conn_state; struct drm_crtc_state *crtc_state; struct drm_framebuffer *fb; int i; DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); conn_state = drm_atomic_get_new_connector_state(state, conn); if (!conn_state->writeback_job) return 0; crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); if (!crtc_state) return -EINVAL; fb = conn_state->writeback_job->fb; for (i = 0; i < ARRAY_SIZE(writeback_fmts); i++) { if (fb->format->format == writeback_fmts[i]) break; } if (WARN_ON(i == ARRAY_SIZE(writeback_fmts))) { DRM_ERROR("pixel format not support!\n"); return -EINVAL; } DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); return 0; } u32 meson_writeback_format_covert2drm(enum tvin_pixel_format_e pixel_value) { u32 writeback_fmt = DRM_FORMAT_RGB888; switch (pixel_value) { case TVIN_PIXEL_RGB444: writeback_fmt = DRM_FORMAT_BGR888; break; case TVIN_PIXEL_YUV422: writeback_fmt = DRM_FORMAT_YUV422; break; case TVIN_PIXEL_UYVY444: writeback_fmt = DRM_FORMAT_UYVY; break; case TVIN_PIXEL_NV12: writeback_fmt = DRM_FORMAT_NV12; break; case TVIN_PIXEL_NV21: writeback_fmt = DRM_FORMAT_NV21; break; case TVIN_PIXEL_RGBA8888: writeback_fmt = DRM_FORMAT_ABGR8888; break; default: break; } return writeback_fmt; } enum tvin_pixel_format_e meson_writeback_format_covert2vdin(u32 writeback_fmt) { enum tvin_pixel_format_e pixel_value = TVIN_PIXEL_RGB444; switch (writeback_fmt) { case DRM_FORMAT_BGR888: pixel_value = TVIN_PIXEL_RGB444; break; case DRM_FORMAT_YUV422: pixel_value = TVIN_PIXEL_YUV422; break; case DRM_FORMAT_UYVY: pixel_value = TVIN_PIXEL_UYVY444; break; case DRM_FORMAT_NV12: pixel_value = TVIN_PIXEL_NV12; break; case DRM_FORMAT_NV21: pixel_value = TVIN_PIXEL_NV21; break; case DRM_FORMAT_ABGR8888: pixel_value = TVIN_PIXEL_RGBA8888; break; default: break; } return pixel_value; } int meson_writeback_capture_picture(struct drm_framebuffer *fb, u32 port, u32 hdisplay, u32 vdisplay) { struct am_meson_fb *meson_fb; struct dma_buf *dmabuf = NULL; struct vdin_parm_s vdin_pram; u32 format; int ret = 0; meson_fb = container_of(fb, struct am_meson_fb, base); if (!meson_fb || !meson_fb->bufp[0]) return -EINVAL; memset(&vdin_pram, 0, sizeof(vdin_pram)); vdin_pram.h_active = hdisplay; vdin_pram.v_active = vdisplay; vdin_pram.dest_h_active = fb->width; vdin_pram.dest_v_active = fb->height; vdin_pram.scan_mode = TVIN_SCAN_MODE_PROGRESSIVE; vdin_pram.frame_rate = 60; vdin_pram.reserved |= PARAM_STATE_HISTGRAM; vdin_pram.fmt = TVIN_SIG_FMT_MAX; format = fb->format->format; vdin_pram.port = (enum tvin_port_e)port; vdin_pram.cfmt = (enum tvin_color_fmt_e)meson_writeback_format_covert2vdin(format); vdin_pram.dfmt = (enum tvin_color_fmt_e)meson_writeback_format_covert2vdin(format); dmabuf = meson_fb->bufp[0]->dmabuf; if (!dmabuf) { dmabuf = meson_fb->bufp[0]->base.dma_buf; if (!dmabuf) { DRM_INFO("dmabuf is NULL!\n"); return -EINVAL; } } get_dma_buf(dmabuf); DRM_DEBUG("dmabuf-%px, h&v(%d-%d), cfmt-%d, port-%d\n", dmabuf, vdin_pram.h_active, vdin_pram.v_active, vdin_pram.cfmt, vdin_pram.port); #ifdef CONFIG_AMLOGIC_MEDIA_VDIN ret = vdin_capture_picture(&vdin_pram, dmabuf); if (ret) { DRM_ERROR("writeback capture picture fail!\n"); return ret; } #endif return ret; } static void meson_writeback_capture_work(struct work_struct *work) { struct am_drm_writeback *drm_writeback = container_of(work, struct am_drm_writeback, writeback_work); meson_writeback_capture_picture(drm_writeback->fb, drm_writeback->capture_port, drm_writeback->hdisplay, drm_writeback->vdisplay); drm_writeback_signal_completion(&drm_writeback->wb_connector, 0); DRM_DEBUG("%d capture done!\n", __LINE__); } static void meson_writeback_connector_atomic_commit(struct drm_connector *conn, struct drm_atomic_state *old_atomic_state) { struct drm_framebuffer *fb; struct am_drm_writeback *drm_writeback; struct drm_connector_state *conn_state; struct drm_crtc_state *crtc_state; int i; DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); conn_state = drm_atomic_get_new_connector_state(old_atomic_state, conn); if (WARN_ON(!conn_state)) return; if (WARN_ON(!conn_state->writeback_job)) return; crtc_state = drm_atomic_get_new_crtc_state(old_atomic_state, conn_state->crtc); if (!crtc_state) return; drm_writeback = connector_to_am_writeback(conn); drm_writeback->hdisplay = crtc_state->mode.hdisplay; drm_writeback->vdisplay = crtc_state->mode.vdisplay; fb = conn_state->writeback_job->fb; drm_writeback->fb = fb; for (i = 0; i < ARRAY_SIZE(writeback_fmts); i++) { if (fb->format->format == writeback_fmts[i]) break; } if (WARN_ON(i == ARRAY_SIZE(writeback_fmts))) { DRM_ERROR("pixel format not support!\n"); return; } drm_writeback_queue_job(&drm_writeback->wb_connector, conn_state); queue_work(drm_writeback->writeback_wq, &drm_writeback->writeback_work); DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); } static const struct drm_connector_helper_funcs am_writeback_connector_helper_funcs = { .get_modes = meson_writeback_connector_get_modes, .mode_valid = meson_writeback_connector_check_mode, .atomic_check = meson_writeback_connector_atomic_check, .atomic_commit = meson_writeback_connector_atomic_commit, }; static enum drm_connector_status am_writeback_connector_detect(struct drm_connector *connector, bool force) { return connector_status_connected; } static void am_writeback_connector_destroy(struct drm_connector *connector) { drm_connector_cleanup(connector); } static struct drm_connector_state * am_writeback_connector_duplicate_state(struct drm_connector *connector) { struct am_drm_writeback_state *writeback_state; DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); writeback_state = kzalloc(sizeof(*writeback_state), GFP_KERNEL); if (!writeback_state) return NULL; __drm_atomic_helper_connector_duplicate_state(connector, &writeback_state->base); return &writeback_state->base; } static void am_writeback_connector_destroy_state(struct drm_connector *connector, struct drm_connector_state *state) { struct am_drm_writeback_state *writeback_state; DRM_DEBUG("am_drm_writeback: %d\n", __LINE__); writeback_state = to_am_writeback_state(state); __drm_atomic_helper_connector_destroy_state(&writeback_state->base); kfree(writeback_state); } static int am_writeback_connector_atomic_set_property (struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, uint64_t val) { struct am_drm_writeback *drm_writeback; drm_writeback = connector_to_am_writeback(connector); if (property == drm_writeback->capture_port_prop) { drm_writeback->capture_port = val; return 0; } return -EINVAL; } static int am_writeback_connector_atomic_get_property (struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, uint64_t *val) { struct am_drm_writeback *drm_writeback; drm_writeback = connector_to_am_writeback(connector); if (property == drm_writeback->capture_port_prop) { *val = drm_writeback->capture_port; return 0; } return -EINVAL; } static const struct drm_connector_funcs am_writeback_connector_funcs = { .detect = am_writeback_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = am_writeback_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = am_writeback_connector_duplicate_state, .atomic_destroy_state = am_writeback_connector_destroy_state, .atomic_set_property = am_writeback_connector_atomic_set_property, .atomic_get_property = am_writeback_connector_atomic_get_property, }; void meson_writeback_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { /*TODO*/ } void meson_writeback_encoder_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { /*TODO*/ } void meson_writeback_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { /*TODO*/ DRM_INFO("[%s]\n", __func__); } static const struct drm_encoder_helper_funcs am_writeback_encoder_helper_funcs = { .atomic_mode_set = meson_writeback_encoder_atomic_mode_set, .atomic_enable = meson_writeback_encoder_atomic_enable, .atomic_disable = meson_writeback_encoder_atomic_disable, }; /* Optional capture port properties. */ static const struct drm_prop_enum_list writeback_capture_port_enum_list[] = { { TVIN_PORT_VIU1_WB0_VD1, "video" }, { TVIN_PORT_VIU1_WB0_OSD1, "osd" }, { TVIN_PORT_VIU1_WB0_VPP, "video&osd" }, }; static int meson_writeback_port_property(struct drm_device *drm_dev, struct am_drm_writeback *drm_writeback) { struct drm_property *prop; struct drm_mode_object *mode_obj; int capture_port = TVIN_PORT_VIU1_WB0_VPP; prop = drm_property_create_enum(drm_dev, 0, "CAPTURE_PORT", writeback_capture_port_enum_list, ARRAY_SIZE(writeback_capture_port_enum_list)); if (prop) { drm_writeback->capture_port_prop = prop; mode_obj = &drm_writeback->wb_connector.base.base; drm_object_attach_property(mode_obj, prop, capture_port); } else { DRM_ERROR("Failed to UPDATE property\n"); return -ENOMEM; } return 0; } int meson_writeback_get_format(u32 *writeback_fmts) { struct support_pixel_format pixel_format; int i; #ifdef CONFIG_AMLOGIC_MEDIA_VDIN int ret; ret = vdin_get_support_pixel_format(&pixel_format); if (ret) return ret; #else pixel_format.pixel_value[0] = TVIN_PIXEL_RGB444; pixel_format.pixel_value[1] = TVIN_PIXEL_YUV422; pixel_format.pixel_value[2] = TVIN_PIXEL_UYVY444; pixel_format.pixel_value[3] = TVIN_PIXEL_NV12; pixel_format.pixel_value[4] = TVIN_PIXEL_NV21; pixel_format.pixel_value[5] = TVIN_PIXEL_RGBA8888; #endif for (i = 0; i < TVIN_PIXEL_FORMAT_NUM; i++) writeback_fmts[i] = meson_writeback_format_covert2drm(pixel_format.pixel_value[i]); return 0; } int am_meson_writeback_create(struct drm_device *drm) { struct drm_writeback_connector *wb_connector; struct am_drm_writeback *drm_writeback; struct meson_connector *mesonconn; int ret = 0; drm_writeback = kzalloc(sizeof(*drm_writeback), GFP_KERNEL); if (!drm_writeback) return -ENOMEM; mesonconn = &drm_writeback->base; mesonconn->drm_priv = drm->dev_private; mesonconn->update = NULL; wb_connector = &drm_writeback->wb_connector; wb_connector->encoder.possible_crtcs = BIT(0); drm_writeback->drm_dev = *drm; drm_writeback->capture_port = TVIN_PORT_VIU1_WB0_VPP; /*get pixel format*/ ret = meson_writeback_get_format(writeback_fmts); if (ret) { DRM_ERROR("Failed to get pixel format\n"); return ret; } /* register Connector */ drm_connector_helper_add(&wb_connector->base, &am_writeback_connector_helper_funcs); ret = drm_writeback_connector_init(drm, wb_connector, &am_writeback_connector_funcs, &am_writeback_encoder_helper_funcs, writeback_fmts, ARRAY_SIZE(writeback_fmts), wb_connector->encoder.possible_crtcs); if (ret) { DRM_ERROR("Failed to init writeback connector\n"); return ret; } /*amlogic prop*/ ret = meson_writeback_port_property(drm, drm_writeback); if (ret) { DRM_ERROR("Failed to attach capture port prop\n"); return ret; } /*create writeback capture workqueue*/ drm_writeback->writeback_wq = alloc_workqueue("writeback_capture", WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0); INIT_WORK(&drm_writeback->writeback_work, meson_writeback_capture_work); DRM_DEBUG("am_drm_writeback creat ok\n"); return ret; }