From: Mark Yao Date: Mon, 1 Aug 2016 10:00:50 +0000 (+0800) Subject: drm/rockchip: support loader display X-Git-Tag: firefly_0821_release~1916 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=firefly-linux-kernel-4.4.55.git;a=commitdiff_plain;h=b4b6beb8275cd686d1a37938eba28be763fbd1d1 drm/rockchip: support loader display Change-Id: Ia3708d4dff638380aa03f83e38248840454e2b70 Signed-off-by: Mark Yao --- diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 03ad601686cb..4027fa91c8ab 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -17,13 +17,16 @@ #include #include +#include #include #include #include #include #include #include +#include #include +#include #include #include #include @@ -44,6 +47,451 @@ static LIST_HEAD(rockchip_drm_subdrv_list); static DEFINE_MUTEX(subdrv_list_mutex); +struct rockchip_drm_mode_set { + struct list_head head; + struct drm_framebuffer *fb; + struct drm_connector *connector; + struct drm_crtc *crtc; + struct drm_display_mode *mode; + int hdisplay; + int vdisplay; + int vrefresh; + + bool mode_changed; + int ratio; +}; + +static struct drm_crtc *find_crtc_by_node(struct drm_device *drm_dev, + struct device_node *node) +{ + struct device_node *np_crtc; + struct drm_crtc *crtc; + + np_crtc = of_get_parent(node); + if (!np_crtc || !of_device_is_available(np_crtc)) + return NULL; + + drm_for_each_crtc(crtc, drm_dev) { + if (crtc->port == np_crtc) + return crtc; + } + + return NULL; +} + +static struct drm_connector *find_connector_by_node(struct drm_device *drm_dev, + struct device_node *node) +{ + struct device_node *np_connector; + struct drm_connector *connector; + + np_connector = of_graph_get_remote_port_parent(node); + if (!np_connector || !of_device_is_available(np_connector)) + return NULL; + + drm_for_each_connector(connector, drm_dev) { + if (connector->port == np_connector) + return connector; + } + + return NULL; +} + +static struct drm_framebuffer * +get_framebuffer_by_node(struct drm_device *drm_dev, struct device_node *node) +{ + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + struct device_node *memory; + struct resource res; + u32 val; + int bpp; + + memory = of_parse_phandle(node, "logo,mem", 0); + if (!memory) + return NULL; + + if (of_address_to_resource(memory, 0, &res)) { + pr_err("%s: could not get bootram phy addr\n", __func__); + return NULL; + } + + if (of_property_read_u32(node, "logo,offset", &val)) { + pr_err("%s: failed to get logo,offset\n", __func__); + return NULL; + } + mode_cmd.offsets[0] = val; + + if (of_property_read_u32(node, "logo,width", &val)) { + pr_err("%s: failed to get logo,width\n", __func__); + return NULL; + } + mode_cmd.width = val; + + if (of_property_read_u32(node, "logo,height", &val)) { + pr_err("%s: failed to get logo,height\n", __func__); + return NULL; + } + mode_cmd.height = val; + + if (of_property_read_u32(node, "logo,bpp", &val)) { + pr_err("%s: failed to get logo,bpp\n", __func__); + return NULL; + } + bpp = val; + + mode_cmd.pitches[0] = mode_cmd.width * bpp / 8; + mode_cmd.pixel_format = DRM_FORMAT_BGR888; + + return rockchip_fb_alloc(drm_dev, &mode_cmd, NULL, &res, 1); +} + +static struct rockchip_drm_mode_set * +of_parse_display_resource(struct drm_device *drm_dev, struct device_node *route) +{ + struct rockchip_drm_mode_set *set; + struct device_node *connect; + struct drm_framebuffer *fb; + struct drm_connector *connector; + struct drm_crtc *crtc; + u32 val; + + connect = of_parse_phandle(route, "connect", 0); + if (!connect) + return NULL; + + fb = get_framebuffer_by_node(drm_dev, route); + if (IS_ERR_OR_NULL(fb)) + return NULL; + + crtc = find_crtc_by_node(drm_dev, connect); + connector = find_connector_by_node(drm_dev, connect); + if (!crtc || !connector) { + dev_warn(drm_dev->dev, + "No available crtc or connector for display"); + drm_framebuffer_unreference(fb); + return NULL; + } + + set = kzalloc(sizeof(*set), GFP_KERNEL); + if (!set) + return NULL; + + if (!of_property_read_u32(route, "video,hdisplay", &val)) + set->hdisplay = val; + + if (!of_property_read_u32(route, "video,vdisplay", &val)) + set->vdisplay = val; + + if (!of_property_read_u32(route, "video,vrefresh", &val)) + set->vrefresh = val; + + set->fb = fb; + set->crtc = crtc; + set->connector = connector; + /* TODO: set display fullscreen or center */ + set->ratio = 0; + + return set; +} + +int setup_initial_state(struct drm_device *drm_dev, + struct drm_atomic_state *state, + struct rockchip_drm_mode_set *set) +{ + struct drm_connector *connector = set->connector; + struct drm_crtc *crtc = set->crtc; + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_plane_state *primary_state; + struct drm_display_mode *mode = NULL; + const struct drm_connector_helper_funcs *funcs; + bool is_crtc_enabled = true; + int hdisplay, vdisplay; + int fb_width, fb_height; + int found = 0, match = 0; + int num_modes; + int ret = 0; + + if (!set->hdisplay || !set->vdisplay || !set->vrefresh) + is_crtc_enabled = false; + + conn_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + funcs = connector->helper_private; + conn_state->best_encoder = funcs->best_encoder(connector); + num_modes = connector->funcs->fill_modes(connector, 4096, 4096); + if (!num_modes) { + dev_err(drm_dev->dev, "connector[%s] can't found any modes\n", + connector->name); + return -EINVAL; + } + + list_for_each_entry(mode, &connector->modes, head) { + if (mode->hdisplay == set->hdisplay && + mode->vdisplay == set->vdisplay && + drm_mode_vrefresh(mode) == set->vrefresh) { + found = 1; + match = 1; + break; + } + } + + if (!found) { + list_for_each_entry(mode, &connector->modes, head) { + if (mode->type & DRM_MODE_TYPE_PREFERRED) { + found = 1; + break; + } + } + + if (!found) { + mode = list_first_entry_or_null(&connector->modes, + struct drm_display_mode, + head); + if (!mode) { + pr_err("failed to find available modes\n"); + return -EINVAL; + } + } + } + + set->mode = mode; + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + drm_mode_copy(&crtc_state->adjusted_mode, mode); + if (!match || !is_crtc_enabled) { + set->mode_changed = true; + } else { + ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); + if (ret) + return ret; + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + return ret; + + crtc_state->active = true; + } + + if (!set->fb) + return 0; + primary_state = drm_atomic_get_plane_state(state, crtc->primary); + if (IS_ERR(primary_state)) + return PTR_ERR(primary_state); + + hdisplay = mode->hdisplay; + vdisplay = mode->vdisplay; + fb_width = set->fb->width; + fb_height = set->fb->height; + + primary_state->crtc = crtc; + primary_state->src_x = 0; + primary_state->src_y = 0; + primary_state->src_w = fb_width << 16; + primary_state->src_h = fb_height << 16; + if (set->ratio) { + if (set->fb->width >= hdisplay) { + primary_state->crtc_x = 0; + primary_state->crtc_w = hdisplay; + } else { + primary_state->crtc_x = (hdisplay - fb_width) / 2; + primary_state->crtc_w = set->fb->width; + } + + if (set->fb->height >= vdisplay) { + primary_state->crtc_y = 0; + primary_state->crtc_h = vdisplay; + } else { + primary_state->crtc_y = (vdisplay - fb_height) / 2; + primary_state->crtc_h = fb_height; + } + } else { + primary_state->crtc_x = 0; + primary_state->crtc_y = 0; + primary_state->crtc_w = hdisplay; + primary_state->crtc_h = vdisplay; + } + + return 0; +} + +static int update_state(struct drm_device *drm_dev, + struct drm_atomic_state *state, + struct rockchip_drm_mode_set *set, + unsigned int *plane_mask) +{ + struct drm_mode_config *mode_config = &drm_dev->mode_config; + struct drm_crtc *crtc = set->crtc; + struct drm_connector *connector = set->connector; + struct drm_display_mode *mode = set->mode; + struct drm_plane_state *primary_state; + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + conn_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + if (set->mode_changed) { + ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); + if (ret) + return ret; + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + return ret; + + crtc_state->active = true; + } else { + const struct drm_crtc_helper_funcs *funcs; + + funcs = crtc->helper_private; + if (!funcs || !funcs->enable) + return -ENXIO; + funcs->enable(crtc); + } + + primary_state = drm_atomic_get_plane_state(state, crtc->primary); + if (IS_ERR(primary_state)) + return PTR_ERR(primary_state); + + crtc_state->plane_mask = 1 << drm_plane_index(crtc->primary); + *plane_mask |= crtc_state->plane_mask; + + drm_atomic_set_fb_for_plane(primary_state, set->fb); + drm_framebuffer_unreference(set->fb); + ret = drm_atomic_set_crtc_for_plane(primary_state, crtc); + + /* + * TODO: + * some vop maybe not support ymirror, but force use it now. + */ + drm_atomic_plane_set_property(crtc->primary, primary_state, + mode_config->rotation_property, + BIT(DRM_REFLECT_Y)); + + return ret; +} + +static void show_loader_logo(struct drm_device *drm_dev) +{ + struct drm_atomic_state *state; + struct device_node *np = drm_dev->dev->of_node; + struct drm_mode_config *mode_config = &drm_dev->mode_config; + struct device_node *root, *route; + struct rockchip_drm_mode_set *set, *tmp; + struct list_head mode_set_list; + unsigned plane_mask = 0; + int ret; + + root = of_get_child_by_name(np, "route"); + if (!root) { + dev_warn(drm_dev->dev, "failed to parse display resources\n"); + return; + } + + INIT_LIST_HEAD(&mode_set_list); + drm_modeset_lock_all(drm_dev); + state = drm_atomic_state_alloc(drm_dev); + if (!state) { + dev_err(drm_dev->dev, "failed to alloc atomic state\n"); + ret = -ENOMEM; + goto err_unlock; + } + + state->acquire_ctx = mode_config->acquire_ctx; + for_each_child_of_node(root, route) { + set = of_parse_display_resource(drm_dev, route); + if (!set) + continue; + + if (setup_initial_state(drm_dev, state, set)) { + drm_framebuffer_unreference(set->fb); + kfree(set); + continue; + } + INIT_LIST_HEAD(&set->head); + list_add_tail(&set->head, &mode_set_list); + } + + if (list_empty(&mode_set_list)) { + dev_warn(drm_dev->dev, "can't not find any loader display\n"); + ret = -ENXIO; + goto err_free_state; + } + + /* + * The state save initial devices status, swap the state into + * drm deivces as old state, so if new state come, can compare + * with this state to judge which status need to update. + */ + drm_atomic_helper_swap_state(drm_dev, state); + drm_atomic_state_free(state); + state = drm_atomic_helper_duplicate_state(drm_dev, + mode_config->acquire_ctx); + if (IS_ERR(state)) { + dev_err(drm_dev->dev, "failed to duplicate atomic state\n"); + ret = PTR_ERR_OR_ZERO(state); + goto err_unlock; + } + state->acquire_ctx = mode_config->acquire_ctx; + list_for_each_entry(set, &mode_set_list, head) + /* + * We don't want to see any fail on update_state. + */ + WARN_ON(update_state(drm_dev, state, set, &plane_mask)); + + ret = drm_atomic_commit(state); + drm_atomic_clean_old_fb(drm_dev, plane_mask, ret); + + list_for_each_entry_safe(set, tmp, &mode_set_list, head) { + struct drm_crtc *crtc = set->crtc; + + list_del(&set->head); + kfree(set); + + /* FIXME: + * primary plane state rotation is not BIT(0), but we only want + * it effect on logo display, userspace may not known to clean + * this property, would get unexpect display, so force set + * primary rotation to BIT(0). + */ + if (!crtc->primary || !crtc->primary->state) + continue; + + drm_atomic_plane_set_property(crtc->primary, + crtc->primary->state, + mode_config->rotation_property, + BIT(0)); + } + + /* + * Is possible get deadlock here? + */ + WARN_ON(ret == -EDEADLK); + + if (ret) + goto err_free_state; + + drm_modeset_unlock_all(drm_dev); + return; + +err_free_state: + drm_atomic_state_free(state); +err_unlock: + drm_modeset_unlock_all(drm_dev); + if (ret) + dev_err(drm_dev->dev, "failed to show loader logo\n"); +} + /* * Attach a (component) device to the shared drm dma mapping from master drm * device. This is used by the VOPs to map GEM buffers to a common DMA @@ -255,6 +703,8 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) drm_mode_config_reset(drm_dev); + show_loader_logo(drm_dev); + ret = rockchip_drm_fbdev_init(drm_dev); if (ret) goto err_vblank_cleanup;