// 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 "meson_crtc.h" #include "meson_lcd.h" struct meson_panel_framerate_map { int lcd_frac_hint; int drm_fps; }; struct meson_panel_framerate_map 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_panel_framerate_map *map; int size = ARRAY_SIZE(framerate_map); for (i = 0; i < size; i++) { map = &framerate_map[i]; if (map->drm_fps == fps) return map->lcd_frac_hint; } return 6000; } int meson_panel_get_modes(struct drm_connector *connector) { struct meson_panel *am_lcd = connector_to_meson_panel(connector); struct meson_panel_dev *panel_dev = am_lcd->panel_dev; struct drm_display_mode *modes, *mode; int modes_cnt = 0; int i = 0, ret = 0; if (!panel_dev->get_modes) { DRM_ERROR("No get Modes function."); return 0; } if (panel_dev->get_modes_vrr_range) { ret = panel_dev->get_modes_vrr_range(panel_dev, (void *)am_lcd->groups, MAX_VRR_MODE_GROUP, &am_lcd->num_vrr_group); if (ret || !am_lcd->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 = panel_dev->get_modes(panel_dev, &modes, &modes_cnt); DRM_DEBUG_KMS("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_panel_check_mode(struct drm_connector *connector, struct drm_display_mode *mode) { return MODE_OK; } static struct drm_encoder *meson_panel_best_encoder (struct drm_connector *connector) { struct meson_panel *lcd = connector_to_meson_panel(connector); return &lcd->encoder; } static const struct drm_connector_helper_funcs meson_panel_helper_funcs = { .get_modes = meson_panel_get_modes, .mode_valid = meson_panel_check_mode, .best_encoder = meson_panel_best_encoder, }; static enum drm_connector_status meson_panel_detect(struct drm_connector *connector, bool force) { return connector_status_connected; } static void am_lcd_connector_destroy(struct drm_connector *connector) { DRM_DEBUG_KMS("[%d] called\n", __LINE__); drm_connector_unregister(connector); drm_connector_cleanup(connector); } static void am_lcd_connector_reset(struct drm_connector *connector) { DRM_DEBUG_KMS("[%d] called\n", __LINE__); drm_atomic_helper_connector_reset(connector); } static struct drm_connector_state * am_lcd_connector_duplicate_state(struct drm_connector *connector) { struct drm_connector_state *state; DRM_DEBUG_KMS("[%d] called\n", __LINE__); state = drm_atomic_helper_connector_duplicate_state(connector); return state; } static void am_lcd_connector_destroy_state(struct drm_connector *connector, struct drm_connector_state *state) { DRM_DEBUG_KMS("[%d] called\n", __LINE__); drm_atomic_helper_connector_destroy_state(connector, state); } int meson_panel_atomic_set_property(struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, uint64_t val) { return -EINVAL; } int meson_panel_atomic_get_property(struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, uint64_t *val) { struct meson_panel *panel = connector_to_meson_panel(connector); if (property == panel->panel_type_prop) { *val = panel->panel_type; return 0; } else if (property == panel->panel_ready_prop) { *val = panel->panel_ready; return 0; } return -EINVAL; } void meson_panel_atomic_print_state(struct drm_printer *p, const struct drm_connector_state *state) { int i; struct drm_vrr_mode_group *group; struct meson_panel *am_lcd; struct drm_connector *conn = state->connector; if (!conn) return; am_lcd = connector_to_meson_panel(conn); drm_printf(p, "\tdrm lcd state:\n"); drm_printf(p, "\t\t vrr range :\n"); drm_printf(p, "\t\t ready:[%d]\n", am_lcd->panel_ready); for (i = 0; i < am_lcd->num_vrr_group; i++) { group = &am_lcd->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_panel_funcs = { .detect = meson_panel_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = am_lcd_connector_destroy, .reset = am_lcd_connector_reset, .atomic_duplicate_state = am_lcd_connector_duplicate_state, .atomic_destroy_state = am_lcd_connector_destroy_state, .atomic_set_property = meson_panel_atomic_set_property, .atomic_get_property = meson_panel_atomic_get_property, .atomic_print_state = meson_panel_atomic_print_state, }; static void meson_panel_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_panel *am_lcd = encoder_to_meson_panel(encoder); struct meson_connector_dev *con_dev = am_lcd->con_dev; update_curr_vout_server(amcrtc->vout_index, con_dev->vout_serv); } static void meson_panel_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_panel *panel = encoder_to_meson_panel(encoder); struct meson_panel_dev *panel_dev = panel->panel_dev; crtc = encoder->crtc; if ((vmode & VMODE_MODE_BIT_MASK) != VMODE_LCD) { DRM_DEBUG_KMS("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*/ panel_dev->set_mode_timing(panel_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, "lcd"); panel->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*/ panel_dev->set_mode_timing(panel_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, "lcd"); panel->panel_ready = true; DRM_DEBUG_KMS("[%d] exit\n", __LINE__); } static void meson_panel_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct meson_panel *panel = encoder_to_meson_panel(encoder); panel->panel_ready = false; DRM_DEBUG_KMS("[%d] called\n", __LINE__); } static void meson_panel_cal_brr(struct meson_panel *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_KMS("%u-%u,%u-%u,%u-%u,%u\n", 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_KMS("%d, %d\n", 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_KMS("seamless is %d\n", meson_crtc_state->seamless); return meson_crtc_state->seamless; } static int meson_panel_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct meson_panel *am_lcd = encoder_to_meson_panel(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_panel_cal_brr(am_lcd, meson_crtc_state, adj_mode); meson_encoder_vrr_change(encoder, conn_state->state); /*drm not find connector by vout_validate_vmode*/ meson_crtc_state->preset_vmode = VMODE_LCD; DRM_DEBUG_KMS("[%d] called\n", __LINE__); return 0; } static const struct drm_encoder_helper_funcs meson_panel_encoder_helper_funcs = { .atomic_mode_set = meson_panel_encoder_atomic_mode_set, .atomic_enable = meson_panel_encoder_atomic_enable, .atomic_disable = meson_panel_encoder_atomic_disable, .atomic_check = meson_panel_encoder_atomic_check, }; static const struct drm_encoder_funcs meson_panel_encoder_funcs = { .destroy = drm_encoder_cleanup, }; static void meson_lcd_creat_ready_property(struct drm_device *drm_dev, struct meson_panel *panel) { struct drm_property *prop; prop = drm_property_create_range(drm_dev, 0, "ready", -1, INT_MAX);// if (prop) { panel->panel_ready_prop = prop; drm_object_attach_property(&panel->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to panel ready property\n"); } } int meson_panel_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_panel *panel_instance = 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_LVDS; int ret = 0; DRM_INFO("[%s]-[%d] type=%d\n", __func__, __LINE__, type); panel_instance = kzalloc(sizeof(*panel_instance), GFP_KERNEL); if (!panel_instance) { DRM_ERROR("panel_instance failed\n"); return -ENOMEM; } panel_instance->panel_type = type; panel_instance->drm = drm; panel_instance->panel_dev = to_meson_panel_dev(intf); panel_instance->base.connector_type = type; panel_instance->base.drm_priv = priv; panel_instance->con_dev = intf; encoder = &panel_instance->encoder; if (!encoder) return -EINVAL; connector = &panel_instance->base.connector; intf->conn = connector; if (type > DRM_MODE_MESON_CONNECTOR_PANEL_START && type < DRM_MODE_MESON_CONNECTOR_PANEL_END) { switch (type) { case DRM_MODE_CONNECTOR_MESON_LVDS_A: connector_type = DRM_MODE_CONNECTOR_LVDS; encoder_type = DRM_MODE_ENCODER_LVDS; connector_name = "LVDS-A"; break; case DRM_MODE_CONNECTOR_MESON_LVDS_B: connector_type = DRM_MODE_CONNECTOR_LVDS; encoder_type = DRM_MODE_ENCODER_LVDS; connector_name = "LVDS-B"; break; case DRM_MODE_CONNECTOR_MESON_LVDS_C: connector_type = DRM_MODE_CONNECTOR_LVDS; encoder_type = DRM_MODE_ENCODER_LVDS; connector_name = "LVDS-C"; break; case DRM_MODE_CONNECTOR_MESON_VBYONE_A: connector_type = DRM_MODE_CONNECTOR_LVDS; encoder_type = DRM_MODE_ENCODER_LVDS; connector_name = "VBYONE-A"; break; case DRM_MODE_CONNECTOR_MESON_VBYONE_B: connector_type = DRM_MODE_CONNECTOR_LVDS; encoder_type = DRM_MODE_ENCODER_LVDS; connector_name = "VBYONE-B"; break; case DRM_MODE_CONNECTOR_MESON_MIPI_A: connector_type = DRM_MODE_CONNECTOR_DSI; encoder_type = DRM_MODE_ENCODER_DSI; connector_name = "MIPI-A"; break; case DRM_MODE_CONNECTOR_MESON_MIPI_B: connector_type = DRM_MODE_CONNECTOR_DSI; encoder_type = DRM_MODE_ENCODER_DSI; connector_name = "MIPI-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_panel_encoder_helper_funcs); ret = drm_encoder_init(drm, encoder, &meson_panel_encoder_funcs, encoder_type, "am_lcd_encoder"); if (ret) { DRM_ERROR("error:%d: Failed to init lcd encoder\n", __LINE__); goto free_resource; } /* Connector */ drm_connector_helper_add(connector, &meson_panel_helper_funcs); ret = drm_connector_init(drm, connector, &meson_panel_funcs, connector_type); if (ret) { DRM_ERROR("%d: Failed to init lcd 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; } meson_lcd_creat_ready_property(drm, panel_instance); /*prop for userspace to acquire prop*/ type_prop = drm_property_create_range(drm, DRM_MODE_PROP_IMMUTABLE, MESON_CONNECTOR_TYPE_PROP_NAME, 0, INT_MAX); if (type_prop) { panel_instance->panel_type_prop = type_prop; drm_object_attach_property(&panel_instance->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); 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(panel_instance); DRM_DEBUG_KMS("%d Exit\n", ret); return ret; } int meson_panel_dev_unbind(struct drm_device *drm, int type, struct meson_connector_dev *intf) { struct drm_connector *connector = intf->conn; struct meson_panel *drm_lcd = 0; if (!connector) DRM_ERROR("got invalid connector id\n"); drm_lcd = connector_to_meson_panel(connector); if (!drm_lcd) return -EINVAL; drm_lcd->base.connector.funcs->destroy(&drm_lcd->base.connector); drm_lcd->encoder.funcs->destroy(&drm_lcd->encoder); kfree(drm_lcd); DRM_DEBUG_KMS("[%d] called\n", __LINE__); return 0; } int am_meson_lcd_get_vrr_range(struct drm_connector *connector, struct drm_vrr_mode_group *groups, int max_group) { int ret, modes_cnt, i; struct meson_panel *am_lcd = connector_to_meson_panel(connector); struct meson_panel_dev *panel_dev = am_lcd->panel_dev; if (!panel_dev->get_modes_vrr_range) return 0; ret = panel_dev->get_modes_vrr_range(panel_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; }