// 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 "meson_crtc.h" #include "meson_eDP.h" static const char eDP_aux_name[2][10] = {"eDP-A-aux", "eDP-B-aux"}; static const char *get_eDP_aux_name(int type) { if (type == DRM_MODE_CONNECTOR_MESON_EDP_B) return eDP_aux_name[1]; return eDP_aux_name[0]; } struct edptx_drv_s *meson_eDP_get_eDPTX_drv(int type) { #ifdef CONFIG_AMLOGIC_eDPTX if (type == DRM_MODE_CONNECTOR_MESON_EDP_A) return aml_edptx_get_driver(0); else if (type == DRM_MODE_CONNECTOR_MESON_EDP_B) return aml_edptx_get_driver(1); else return NULL; #else return NULL; #endif } static int meson_eDP_get_eDPTX_aux_bl_available(int type) { struct edptx_drv_s *dptx = meson_eDP_get_eDPTX_drv(type); if (!dptx) return 0; if (dptx->setting.use_aux_bl_as_possible) return dptx->sink.link[0].cap.DPCD_reg_func & BIT(2); return 0; } struct meson_eDP_framerate_map { int lcd_frac_hint; int drm_fps; }; struct meson_eDP_framerate_map eDP_framerate_map[] = { {36000, 360}, {34000, 340}, {33000, 330}, {28800, 288}, {24000, 240}, {23976, 239}, {20000, 200}, {19200, 192}, {19180, 191}, {18000, 180}, {17000, 170}, {16500, 165}, {14400, 144}, {12000, 120}, {11988, 119}, {10000, 100}, {9600, 96}, {9590, 95}, {6000, 60}, {5994, 59}, {5000, 50}, {4800, 48}, {4795, 47} }; static int find_frac_hint_by_fps(int fps) { int i; struct meson_eDP_framerate_map *map; int size = ARRAY_SIZE(eDP_framerate_map); for (i = 0; i < size; i++) { map = &eDP_framerate_map[i]; if (map->drm_fps == fps) return map->lcd_frac_hint; } return 6000; } int meson_eDP_get_modes(struct drm_connector *connector) { struct meson_eDP *am_eDP = connector_to_meson_eDP(connector); struct meson_panel_dev *eDP_dev = am_eDP->eDP_dev; struct drm_display_mode *modes, *mode; //struct edid *edid; int modes_cnt = 0; int i = 0, ret = 0; if (!eDP_dev->get_modes) { DRM_ERROR("No get Modes function."); return 0; } //edid = (struct edid *)meson_eDPTX_get_raw_edid(&tx_comm->base); //drm_connector_update_edid_property(connector, edid); if (eDP_dev->get_modes_vrr_range) { ret = eDP_dev->get_modes_vrr_range(eDP_dev, (void *)am_eDP->groups, MAX_VRR_MODE_GROUP, &am_eDP->num_vrr_group); if (ret || !am_eDP->num_vrr_group) drm_connector_set_vrr_capable_property(connector, false); else drm_connector_set_vrr_capable_property(connector, true); } else { drm_connector_set_vrr_capable_property(connector, false); } ret = eDP_dev->get_modes(eDP_dev, &modes, &modes_cnt); DRM_DEBUG("get modes %d, ret %d\n", modes_cnt, ret); if (ret == 0 && modes_cnt > 0) { if (!connector->display_info.width_mm || !connector->display_info.height_mm) { connector->display_info.width_mm = modes[i].width_mm; connector->display_info.height_mm = modes[i].height_mm; } for (i = 0; i < modes_cnt; i++) { DRM_DEBUG("[%d] mode_name-%s\n", __LINE__, modes[i].name); mode = drm_mode_duplicate(connector->dev, &modes[i]); if (mode) { drm_mode_probed_add(connector, mode); drm_mode_debug_printmodeline(mode); } } kfree(modes); } return modes_cnt; } enum drm_mode_status meson_eDP_check_mode(struct drm_connector *connector, struct drm_display_mode *mode) { return MODE_OK; } static struct drm_encoder *meson_eDP_best_encoder (struct drm_connector *connector) { struct meson_eDP *am_eDP = connector_to_meson_eDP(connector); return &am_eDP->encoder; } static const struct drm_connector_helper_funcs meson_eDP_helper_funcs = { .get_modes = meson_eDP_get_modes, .mode_valid = meson_eDP_check_mode, .best_encoder = meson_eDP_best_encoder, }; static enum drm_connector_status meson_eDP_detect(struct drm_connector *connector, bool force) { return connector_status_connected; } static void am_eDP_connector_destroy(struct drm_connector *connector) { DRM_DEBUG("[%d] called\n", __LINE__); drm_connector_unregister(connector); drm_connector_cleanup(connector); } static void am_eDP_connector_reset(struct drm_connector *connector) { DRM_DEBUG("[%d] called\n", __LINE__); drm_atomic_helper_connector_reset(connector); } static struct drm_connector_state * am_eDP_connector_duplicate_state(struct drm_connector *connector) { struct drm_connector_state *state; DRM_DEBUG("[%d] called\n", __LINE__); state = drm_atomic_helper_connector_duplicate_state(connector); return state; } static void am_eDP_connector_destroy_state(struct drm_connector *connector, struct drm_connector_state *state) { DRM_DEBUG("[%d] called\n", __LINE__); drm_atomic_helper_connector_destroy_state(connector, state); } int meson_eDP_atomic_set_property(struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, uint64_t val) { return -EINVAL; } int meson_eDP_atomic_get_property(struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, uint64_t *val) { struct meson_eDP *am_eDP = connector_to_meson_eDP(connector); if (property == am_eDP->panel_type_prop) { *val = am_eDP->panel_type; return 0; } else if (property == am_eDP->panel_ready_prop) { *val = am_eDP->panel_ready; return 0; } // } else if (property == am_eDP->panel_type_prop) return -EINVAL; // return 0; } void meson_eDP_atomic_print_state(struct drm_printer *p, const struct drm_connector_state *state) { int i; struct drm_vrr_mode_group *group; struct meson_eDP *am_eDP; struct drm_connector *conn = state->connector; if (!conn) return; am_eDP = connector_to_meson_eDP(conn); drm_printf(p, "\tdrm eDP state:\n"); drm_printf(p, "\t\t vrr range :\n"); drm_printf(p, "\t\t ready:[%d]\n", am_eDP->panel_ready); for (i = 0; i < am_eDP->num_vrr_group; i++) { group = &am_eDP->groups[i]; drm_printf(p, "\t\t %u,%u,%u-%u,%s\n", group->width, group->height, group->vrr_min, group->vrr_max, group->modename); } } static const struct drm_connector_funcs meson_eDP_funcs = { .detect = meson_eDP_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = am_eDP_connector_destroy, .reset = am_eDP_connector_reset, .atomic_duplicate_state = am_eDP_connector_duplicate_state, .atomic_destroy_state = am_eDP_connector_destroy_state, .atomic_set_property = meson_eDP_atomic_set_property, .atomic_get_property = meson_eDP_atomic_get_property, .atomic_print_state = meson_eDP_atomic_print_state, }; static void meson_eDP_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct am_meson_crtc *amcrtc = to_am_meson_crtc(crtc_state->crtc); struct meson_eDP *am_eDP = encoder_to_meson_eDP(encoder); struct meson_connector_dev *con_dev = am_eDP->con_dev; update_curr_vout_server(amcrtc->vout_index, con_dev->vout_serv); } static void meson_eDP_encoder_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct drm_crtc *crtc; int lcd_frac_hint; struct am_meson_crtc_state *meson_crtc_state = to_am_meson_crtc_state(encoder->crtc->state); struct am_meson_crtc *amcrtc = to_am_meson_crtc(encoder->crtc); struct drm_display_mode *mode = &meson_crtc_state->base.adjusted_mode; struct drm_crtc_state *old_crtc_state; struct am_meson_crtc_state *meson_old_crtc_state; enum vmode_e vmode = meson_crtc_state->vmode; unsigned int vrefresh = drm_mode_vrefresh(mode); struct meson_eDP *am_eDP = encoder_to_meson_eDP(encoder); struct meson_panel_dev *eDP_dev = am_eDP->eDP_dev; crtc = encoder->crtc; if ((vmode & VMODE_MODE_BIT_MASK) != VMODE_eDP) { DRM_DEBUG("enable fail! vmode:%d\n", vmode); return; } old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); if (!old_crtc_state) { DRM_ERROR("crtc state is NULL!\n"); return; } meson_old_crtc_state = to_am_meson_crtc_state(old_crtc_state); if (meson_crtc_state->uboot_mode_init == 1) vmode |= VMODE_INIT_BIT_MASK; DRM_INFO("[%s]-[%d] called, %d, %d\n", __func__, __LINE__, meson_crtc_state->prev_vrefresh, vrefresh); if (encoder->crtc->state->vrr_enabled) { /*for keystone projector, switch loopback may change connector's crtc*/ if (strcmp(meson_crtc_state->brr_mode, meson_old_crtc_state->brr_mode) || encoder->crtc->state->connectors_changed || encoder->crtc->state->active_changed) { meson_vout_notify_mode_change(amcrtc->vout_index, vmode, EVENT_MODE_SET_START); /*set mode timing*/ eDP_dev->set_mode_timing(eDP_dev, mode, vmode); meson_vout_notify_mode_change(amcrtc->vout_index, vmode, EVENT_MODE_SET_FINISH); meson_vout_update_mode_name(amcrtc->vout_index, mode->name, "eDP"); am_eDP->panel_ready = true; } if (vrefresh != meson_crtc_state->brr || meson_crtc_state->prev_vrefresh != vrefresh || encoder->crtc->state->connectors_changed || encoder->crtc->state->active_changed) { lcd_frac_hint = find_frac_hint_by_fps(vrefresh); vout_func_set_vframe_rate_hint(amcrtc->vout_index, lcd_frac_hint); DRM_INFO("vrr mode, use set_vframe_rate_hint, %s-%s, %d-%d, %d-%d\n", meson_crtc_state->brr_mode, meson_old_crtc_state->brr_mode, meson_crtc_state->brr, lcd_frac_hint, encoder->crtc->state->connectors_changed, encoder->crtc->state->active_changed); } meson_crtc_state->prev_vrefresh = vrefresh; return; } meson_crtc_state->prev_vrefresh = vrefresh; meson_crtc_state->prev_height = mode->vdisplay; meson_vout_notify_mode_change(amcrtc->vout_index, vmode, EVENT_MODE_SET_START); /*set mode timing*/ eDP_dev->set_mode_timing(eDP_dev, mode, vmode); meson_vout_notify_mode_change(amcrtc->vout_index, vmode, EVENT_MODE_SET_FINISH); meson_vout_update_mode_name(amcrtc->vout_index, mode->name, "eDP"); am_eDP->panel_ready = true; DRM_DEBUG("[%d] exit\n", __LINE__); } static void meson_eDP_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct meson_eDP *am_eDP = encoder_to_meson_eDP(encoder); am_eDP->panel_ready = false; DRM_DEBUG("[%d] called\n", __LINE__); } #if 0 static void meson_eDP_cal_brr(struct meson_eDP *am_lcd, struct am_meson_crtc_state *crtc_state, struct drm_display_mode *adj_mode) { int i; struct drm_vrr_mode_group *group; for (i = 0; i < am_lcd->num_vrr_group; i++) { group = &am_lcd->groups[i]; DRM_DEBUG("%s %u-%u,%u-%u,%u-%u,%u\n", __func__, group->width, adj_mode->hdisplay, group->height, adj_mode->vdisplay, group->vrr_min, group->vrr_max, drm_mode_vrefresh(adj_mode)); if (group->width == adj_mode->hdisplay && group->height == adj_mode->vdisplay && group->vrr_min / VRR_DIV <= drm_mode_vrefresh(adj_mode) && group->vrr_max / VRR_DIV >= drm_mode_vrefresh(adj_mode)) { break; } } DRM_DEBUG("%s, %d, %d\n", __func__, i, am_lcd->num_vrr_group); if (i != am_lcd->num_vrr_group) { strncpy(crtc_state->brr_mode, group->modename, DRM_DISPLAY_MODE_LEN); crtc_state->brr_mode[DRM_DISPLAY_MODE_LEN - 1] = '\0'; /*group->brr only used by lcd, not div 100*/ crtc_state->brr = group->brr; crtc_state->valid_brr = 1; } else { DRM_ERROR("%s, get panel brr fail\n", adj_mode->name); } } static int meson_encoder_vrr_change(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *conn_state; struct drm_crtc *crtc; struct drm_crtc_state *new_state, *old_state; struct drm_display_mode *new_mode, *old_mode; struct am_meson_crtc_state *meson_crtc_state; connector = drm_atomic_get_new_connector_for_encoder(state, encoder); if (!connector) return 0; conn_state = drm_atomic_get_new_connector_state(state, connector); if (!conn_state) return 0; crtc = conn_state->crtc; new_state = drm_atomic_get_new_crtc_state(state, crtc); old_state = drm_atomic_get_old_crtc_state(state, crtc); if (!new_state || !old_state) { DRM_INFO("%s crtc state is NULL!\n", __func__); return 0; } new_mode = &new_state->adjusted_mode; old_mode = &old_state->adjusted_mode; meson_crtc_state = to_am_meson_crtc_state(new_state); if (new_state->vrr_enabled && new_mode->hdisplay == old_mode->hdisplay && new_mode->vdisplay == old_mode->vdisplay && !(new_mode->flags & DRM_MODE_FLAG_INTERLACE)) { meson_crtc_state->seamless = true; } else { meson_crtc_state->seamless = false; } DRM_DEBUG("[%s], seamless is %d\n", __func__, meson_crtc_state->seamless); return meson_crtc_state->seamless; } static int meson_eDP_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct meson_eDP *am_lcd = encoder_to_meson_eDP(encoder); struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; struct am_meson_crtc_state *meson_crtc_state = to_am_meson_crtc_state(crtc_state); if (!crtc_state->vrr_enabled) memset(meson_crtc_state->brr_mode, 0, DRM_DISPLAY_MODE_LEN); if (crtc_state->vrr_enabled || meson_crtc_state->uboot_mode_init) meson_eDP_cal_brr(am_lcd, meson_crtc_state, adj_mode); meson_encoder_vrr_change(encoder, conn_state->state); DRM_DEBUG("[%s]-[%d] called\n", __func__, __LINE__); return 0; } #endif static int meson_eDP_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct am_meson_crtc_state *meson_crtc_state = to_am_meson_crtc_state(crtc_state); /*drm not find connector by vout_validate_vmode*/ meson_crtc_state->preset_vmode = VMODE_eDP; DRM_DEBUG("[%d] called\n", __LINE__); return 0; } static const struct drm_encoder_helper_funcs meson_eDP_encoder_helper_funcs = { .atomic_mode_set = meson_eDP_encoder_atomic_mode_set, .atomic_enable = meson_eDP_encoder_atomic_enable, .atomic_disable = meson_eDP_encoder_atomic_disable, .atomic_check = meson_eDP_encoder_atomic_check, }; static const struct drm_encoder_funcs meson_eDP_encoder_funcs = { .destroy = drm_encoder_cleanup, }; int eDP_panel_prepare(struct drm_panel *panel) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int eDP_panel_enable(struct drm_panel *panel) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int eDP_panel_disable(struct drm_panel *panel) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int eDP_panel_unprepare(struct drm_panel *panel) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int eDP_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int eDP_panel_get_orientation(struct drm_panel *panel) { DRM_DEBUG("[%d] called\n", __LINE__); return DRM_MODE_PANEL_ORIENTATION_NORMAL; } int eDP_panel_get_timings(struct drm_panel *panel, unsigned int num_timings, struct display_timing *timings) { DRM_DEBUG("[%d] called\n", __LINE__); return 0; } void eDP_panel_debugfs_init(struct drm_panel *panel, struct dentry *root) { DRM_DEBUG("[%d] called\n", __LINE__); } static const struct drm_panel_funcs meson_eDP_panel_funcs = { .prepare = eDP_panel_prepare, .enable = eDP_panel_enable, .disable = eDP_panel_disable, .unprepare = eDP_panel_unprepare, .get_modes = eDP_panel_get_modes, .get_orientation = eDP_panel_get_orientation, .get_timings = eDP_panel_get_timings, .debugfs_init = eDP_panel_debugfs_init, }; ssize_t eDP_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { //struct edptx_drv_s *dptx_drv; struct meson_eDP *am_eDP = aux_to_meson_eDP(aux); DRM_DEBUG("[%d] called[%u] [0x%x] req=%u rep=%u size=%zu\n", __LINE__, am_eDP->panel_type, msg->address, msg->request, msg->reply, msg->size); return msg->size; } // This is mainly useful for eDP panels drivers to wait for an eDP panel to finish powering on int eDP_wait_hpd_asserted(struct drm_dp_aux *aux, unsigned long wait_us) { struct edptx_drv_s *edp_drv; struct meson_eDP *am_eDP = aux_to_meson_eDP(aux); int hpd_level, readx; DRM_DEBUG("[%d] called %d, %lus\n", __LINE__, am_eDP->panel_type, wait_us); edp_drv = meson_eDP_get_eDPTX_drv(am_eDP->panel_type); if (!edp_drv) return -ETIMEDOUT; wait_us = (wait_us == 0 || wait_us > 1000) ? 1000 : wait_us; readx = readx_poll_timeout(aml_edptx_get_hpd_state, edp_drv->idx, hpd_level, hpd_level == 1, 10, wait_us); return readx; } static void meson_lcd_creat_ready_property(struct drm_device *drm_dev, struct meson_eDP *am_eDP) { struct drm_property *prop; prop = drm_property_create_range(drm_dev, 0, "ready", -1, INT_MAX);// if (prop) { am_eDP->panel_ready_prop = prop; drm_object_attach_property(&am_eDP->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to panel ready property\n"); } } int meson_eDP_dev_bind(struct drm_device *drm, int type, struct meson_connector_dev *intf) { struct drm_connector *connector = NULL; struct drm_encoder *encoder = NULL; struct meson_eDP *am_eDP = NULL; struct drm_property *type_prop = NULL; struct meson_drm *priv = drm->dev_private; char *connector_name = NULL; int connector_type = type; int encoder_type = DRM_MODE_ENCODER_TMDS; int ret = 0; struct edptx_drv_s *dptx_drv = meson_eDP_get_eDPTX_drv(type); DRM_INFO("[%s]-[%d] type=%d\n", __func__, __LINE__, type); am_eDP = kzalloc(sizeof(*am_eDP), GFP_KERNEL); if (!am_eDP) { DRM_ERROR("alloc am_eDP failed\n"); return -ENOMEM; } am_eDP->panel_type = type; am_eDP->drm = drm; am_eDP->eDP_dev = to_meson_panel_dev(intf); am_eDP->base.connector_type = type; am_eDP->base.drm_priv = priv; am_eDP->con_dev = intf; encoder = &am_eDP->encoder; if (!encoder) return -EINVAL; connector = &am_eDP->base.connector; intf->conn = connector; switch (type) { case DRM_MODE_CONNECTOR_MESON_EDP_A: connector_type = DRM_MODE_CONNECTOR_eDP; encoder_type = DRM_MODE_ENCODER_TMDS; connector_name = "EDP-A"; break; case DRM_MODE_CONNECTOR_MESON_EDP_B: connector_type = DRM_MODE_CONNECTOR_eDP; encoder_type = DRM_MODE_ENCODER_TMDS; connector_name = "EDP-B"; break; default: connector_type = DRM_MODE_CONNECTOR_Unknown; encoder_type = DRM_MODE_ENCODER_NONE; break; }; DRM_INFO("%s: bind %d -> connector-%s-%d,encoder-%d\n", __func__, type, connector_name, connector_type, encoder_type); /* Encoder */ encoder->possible_crtcs = priv->of_conf.crtc_masks[ENCODER_LCD]; drm_encoder_helper_add(encoder, &meson_eDP_encoder_helper_funcs); ret = drm_encoder_init(drm, encoder, &meson_eDP_encoder_funcs, encoder_type, "am_eDP_encoder"); if (ret) { DRM_ERROR("error:%d: Failed to init eDP encoder\n", __LINE__); goto free_resource; } /* Connector */ drm_connector_helper_add(connector, &meson_eDP_helper_funcs); ret = drm_connector_init(drm, connector, &meson_eDP_funcs, connector_type); if (ret) { DRM_ERROR("%d: Failed to init eDP connector\n", __LINE__); goto free_resource; } /*update name to amlogic name*/ if (connector_name) { kfree(connector->name); connector->name = kasprintf(GFP_KERNEL, "%s", connector_name); if (!connector->name) DRM_ERROR("alloc name failed\n"); } ret = drm_connector_attach_encoder(connector, encoder); if (ret != 0) { DRM_ERROR("%d: attach failed.\n", __LINE__); goto free_resource; } /*prop for userspace to acquire prop*/ meson_lcd_creat_ready_property(drm, am_eDP); type_prop = drm_property_create_range(drm, DRM_MODE_PROP_IMMUTABLE, MESON_CONNECTOR_TYPE_PROP_NAME, 0, INT_MAX); if (type_prop) { am_eDP->panel_type_prop = type_prop; drm_object_attach_property(&am_eDP->base.connector.base, type_prop, type); } else { DRM_ERROR("Failed to create property %s\n", MESON_CONNECTOR_TYPE_PROP_NAME); } drm_connector_attach_vrr_capable_property(connector); am_eDP->aux.drm_dev = drm; am_eDP->aux.name = get_eDP_aux_name(type); //am_eDP->aux.ddc = drm; am_eDP->aux.dev = &dptx_drv->pdev->dev; am_eDP->aux.transfer = eDP_aux_transfer; am_eDP->aux.wait_hpd_asserted = eDP_wait_hpd_asserted; am_eDP->aux.is_remote = 0; // am_eDP->aux.drm_dev = drm; ret = drm_dp_aux_register(&am_eDP->aux); if (ret) DRM_ERROR("Failed to drm_dp_aux_register"); drm_panel_init(&am_eDP->panel, &dptx_drv->pdev->dev, &meson_eDP_panel_funcs, DRM_MODE_CONNECTOR_eDP); if (meson_eDP_get_eDPTX_aux_bl_available(type)) { ret = drm_panel_dp_aux_backlight(&am_eDP->panel, &am_eDP->aux); if (ret) DRM_ERROR("Failed to get aux backlight\n"); } else { ret = drm_panel_of_backlight(&am_eDP->panel); if (ret) DRM_ERROR("Failed to get backlight\n"); } am_eDP->panel.funcs = &meson_eDP_panel_funcs; drm_panel_add(&am_eDP->panel); DRM_INFO("%s: bind %d success\n", __func__, type); return 0; free_resource: if (connector) drm_connector_cleanup(connector); if (encoder) drm_encoder_cleanup(encoder); kfree(am_eDP); DRM_DEBUG("%d Exit\n", ret); return ret; } int meson_eDP_dev_unbind(struct drm_device *drm, int type, struct meson_connector_dev *intf) { struct drm_connector *connector = intf->conn; struct meson_eDP *drm_eDP = 0; if (!connector) DRM_ERROR("got invalid connector id\n"); drm_eDP = connector_to_meson_eDP(connector); if (!drm_eDP) return -EINVAL; drm_eDP->base.connector.funcs->destroy(&drm_eDP->base.connector); drm_eDP->encoder.funcs->destroy(&drm_eDP->encoder); kfree(drm_eDP); DRM_DEBUG("[%d] called\n", __LINE__); return 0; } int am_meson_eDP_get_vrr_range(struct drm_connector *connector, struct drm_vrr_mode_group *groups, int max_group) { int ret, modes_cnt, i; struct meson_eDP *am_eDP = connector_to_meson_eDP(connector); struct meson_panel_dev *eDP_dev = am_eDP->eDP_dev; if (!eDP_dev->get_modes_vrr_range) return 0; ret = eDP_dev->get_modes_vrr_range(eDP_dev, (void *)groups, max_group, &modes_cnt); if (ret) { DRM_ERROR("get vrr error or not support qms\n"); return 0; } for (i = 0; i < modes_cnt; i++) { groups[i].vrr_min /= VRR_DIV; groups[i].vrr_max /= VRR_DIV; } return modes_cnt; }