| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <drm/drm_modeset_helper.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_connector.h> |
| #include <drm/drm_modeset_lock.h> |
| #include <drm/drm_probe_helper.h> |
| |
| #include <linux/component.h> |
| #include <linux/irq.h> |
| #include <linux/io.h> |
| #include <linux/device.h> |
| #include <linux/amlogic/media/frame_provider/tvin/tvin_v4l2.h> |
| |
| #include "meson_writeback.h" |
| #include "meson_vpu.h" |
| #include "meson_crtc.h" |
| |
| u32 writeback_fmts[TVIN_COLOR_FMT_MAX]; |
| |
| 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: %s %d\n", __func__, __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); |
| fb = conn_state->writeback_job->fb; |
| |
| if (fb->width != crtc_state->mode.hdisplay || |
| fb->height != crtc_state->mode.vdisplay) { |
| DRM_ERROR("Invalid framebuffer size %ux%u\n", |
| fb->width, fb->height); |
| return -EINVAL; |
| } |
| |
| 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("%s pixel format not support!\n", __func__); |
| return -EINVAL; |
| } |
| |
| DRM_DEBUG("am_drm_writeback: %s %d\n", __func__, __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_RGB888; |
| 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; |
| 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_RGB888: |
| 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; |
| default: |
| break; |
| } |
| |
| return pixel_value; |
| } |
| |
| int meson_writeback_capture_picture(struct drm_framebuffer *fb, u32 port) |
| { |
| struct am_meson_fb *meson_fb; |
| struct dma_buf *dmabuf; |
| struct vdin_parm_s vdin_pram; |
| u32 format; |
| int ret = 0; |
| |
| meson_fb = container_of(fb, struct am_meson_fb, base); |
| if (!meson_fb) |
| return -EINVAL; |
| |
| vdin_pram.h_active = fb->width; |
| vdin_pram.v_active = fb->height; |
| vdin_pram.dest_h_active = fb->width; |
| vdin_pram.dest_v_active = fb->height; |
| vdin_pram.scan_mode = 1; |
| vdin_pram.frame_rate = 60; |
| 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; |
| get_dma_buf(dmabuf); |
| |
| DRM_DEBUG("%s dmabuf-%px, h&v(%d-%d), cfmt-%d, port-%d\n", |
| __func__, dmabuf, vdin_pram.h_active, vdin_pram.v_active, |
| vdin_pram.cfmt, vdin_pram.port); |
| |
| ret = vdin_capture_picture(&vdin_pram, dmabuf); |
| if (ret) { |
| DRM_ERROR("writeback capture picture fail!\n"); |
| return ret; |
| } |
| |
| 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_signal_completion(&drm_writeback->wb_connector, 0); |
| DRM_DEBUG("%s %d capture done!\n", __func__, __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; |
| int i; |
| |
| DRM_DEBUG("am_drm_writeback: %s %d\n", __func__, __LINE__); |
| |
| conn_state = drm_atomic_get_old_connector_state(old_atomic_state, conn); |
| if (WARN_ON(!conn_state)) |
| return; |
| |
| if (WARN_ON(!conn_state->writeback_job)) |
| return; |
| |
| drm_writeback = connector_to_am_writeback(conn); |
| 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("%s pixel format not support!\n", __func__); |
| 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: %s %d\n", __func__, __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: %s %d\n", __func__, __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: %s %d\n", __func__, __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, ret; |
| |
| ret = vdin_get_support_pixel_format(&pixel_format); |
| if (ret) |
| return ret; |
| |
| 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)); |
| 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; |
| } |