X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=drivers%2Fgpu%2Fdrm%2Frockchip%2Frockchip_drm_drv.c;h=2b236a533152930f1482bc9e63eac3589374af8a;hb=0d25ebc825bac75ae478c985eff837d16a486f54;hp=f22e1e1ee64aae52a1e3efb657a78790bd6ba573;hpb=1282ac407cf4c3eaeaddd8d776a7ffbd2b94c2e7;p=firefly-linux-kernel-4.4.55.git diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index f22e1e1ee64a..2b236a533152 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -17,18 +17,26 @@ #include #include +#include #include #include +#include +#include #include #include +#include #include +#include #include #include +#include +#include #include "rockchip_drm_drv.h" #include "rockchip_drm_fb.h" #include "rockchip_drm_fbdev.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_rga.h" #define DRIVER_NAME "rockchip" #define DRIVER_DESC "RockChip Soc DRM" @@ -36,6 +44,570 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 +static bool is_support_iommu = true; + +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; + bool ymirror; + 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 int init_loader_memory(struct drm_device *drm_dev) +{ + struct rockchip_drm_private *private = drm_dev->dev_private; + struct rockchip_logo *logo; + struct device_node *np = drm_dev->dev->of_node; + struct device_node *node; + unsigned long nr_pages; + struct page **pages; + struct sg_table *sgt; + DEFINE_DMA_ATTRS(attrs); + phys_addr_t start, size; + struct resource res; + int i, ret; + + logo = devm_kmalloc(drm_dev->dev, sizeof(*logo), GFP_KERNEL); + if (!logo) + return -ENOMEM; + + node = of_parse_phandle(np, "memory-region", 0); + if (!node) + return -ENOMEM; + + ret = of_address_to_resource(node, 0, &res); + if (ret) + return ret; + start = res.start; + size = resource_size(&res); + if (!size) + return -ENOMEM; + + nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + pages = kmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) + return -ENOMEM; + i = 0; + while (i < nr_pages) { + pages[i] = phys_to_page(start); + start += PAGE_SIZE; + i++; + } + sgt = drm_prime_pages_to_sg(pages, nr_pages); + if (IS_ERR(sgt)) { + kfree(pages); + return PTR_ERR(sgt); + } + + dma_set_attr(DMA_ATTR_SKIP_CPU_SYNC, &attrs); + dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &attrs); + dma_map_sg_attrs(drm_dev->dev, sgt->sgl, sgt->nents, + DMA_TO_DEVICE, &attrs); + logo->dma_addr = sg_dma_address(sgt->sgl); + logo->sgt = sgt; + logo->start = res.start; + logo->size = size; + logo->count = 0; + private->logo = logo; + + return 0; +} + +static struct drm_framebuffer * +get_framebuffer_by_node(struct drm_device *drm_dev, struct device_node *node) +{ + struct rockchip_drm_private *private = drm_dev->dev_private; + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + u32 val; + int bpp; + + if (WARN_ON(!private->logo)) + 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; + + switch (bpp) { + case 16: + mode_cmd.pixel_format = DRM_FORMAT_BGR565; + break; + case 24: + mode_cmd.pixel_format = DRM_FORMAT_BGR888; + break; + case 32: + mode_cmd.pixel_format = DRM_FORMAT_XBGR8888; + break; + default: + pr_err("%s: unsupport to logo bpp %d\n", __func__, bpp); + return NULL; + } + + return rockchip_fb_alloc(drm_dev, &mode_cmd, NULL, private->logo, 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; + const char *string; + 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; + + if (!of_property_read_u32(route, "logo,ymirror", &val)) + set->ymirror = val; + + set->ratio = 1; + if (!of_property_read_string(route, "logo,mode", &string) && + !strcmp(string, "fullscreen")) + set->ratio = 0; + + set->fb = fb; + set->crtc = crtc; + set->connector = connector; + + 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); + if (funcs->loader_protect) + funcs->loader_protect(connector, true); + 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); + ret = -EINVAL; + goto error; + } + + 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"); + ret = -EINVAL; + goto error; + } + } + } + + set->mode = mode; + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto error; + } + + 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) + goto error; + + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + goto error; + + crtc_state->active = true; + } + + if (!set->fb) { + ret = 0; + goto error; + } + primary_state = drm_atomic_get_plane_state(state, crtc->primary); + if (IS_ERR(primary_state)) { + ret = PTR_ERR(primary_state); + goto error; + } + + 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; + +error: + if (funcs->loader_protect) + funcs->loader_protect(connector, false); + return ret; +} + +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; + const struct drm_encoder_helper_funcs *encoder_helper_funcs; + const struct drm_connector_helper_funcs *connector_helper_funcs; + struct drm_encoder *encoder; + + funcs = crtc->helper_private; + connector_helper_funcs = connector->helper_private; + if (!funcs || !funcs->enable || + !connector_helper_funcs || + !connector_helper_funcs->best_encoder) + return -ENXIO; + encoder = connector_helper_funcs->best_encoder(connector); + encoder_helper_funcs = encoder->helper_private; + if (!encoder || !encoder_helper_funcs->atomic_check) + return -ENXIO; + ret = encoder_helper_funcs->atomic_check(encoder, crtc->state, + conn_state); + if (ret) + return ret; + 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); + + if (set->ymirror) + /* + * 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; + } + + if (init_loader_memory(drm_dev)) { + dev_warn(drm_dev->dev, "failed to parse loader memory\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) { + if (!of_device_is_available(route)) + continue; + + 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 @@ -47,6 +619,9 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping; int ret; + if (!is_support_iommu) + return 0; + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); if (ret) return ret; @@ -55,20 +630,21 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, return arm_iommu_attach_device(dev, mapping); } -EXPORT_SYMBOL_GPL(rockchip_drm_dma_attach_device); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev) { + if (!is_support_iommu) + return; + arm_iommu_detach_device(dev); } -EXPORT_SYMBOL_GPL(rockchip_drm_dma_detach_device); -int rockchip_register_crtc_funcs(struct drm_device *dev, - const struct rockchip_crtc_funcs *crtc_funcs, - int pipe) +int rockchip_register_crtc_funcs(struct drm_crtc *crtc, + const struct rockchip_crtc_funcs *crtc_funcs) { - struct rockchip_drm_private *priv = dev->dev_private; + int pipe = drm_crtc_index(crtc); + struct rockchip_drm_private *priv = crtc->dev->dev_private; if (pipe > ROCKCHIP_MAX_CRTC) return -EINVAL; @@ -77,18 +653,17 @@ int rockchip_register_crtc_funcs(struct drm_device *dev, return 0; } -EXPORT_SYMBOL_GPL(rockchip_register_crtc_funcs); -void rockchip_unregister_crtc_funcs(struct drm_device *dev, int pipe) +void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc) { - struct rockchip_drm_private *priv = dev->dev_private; + int pipe = drm_crtc_index(crtc); + struct rockchip_drm_private *priv = crtc->dev->dev_private; if (pipe > ROCKCHIP_MAX_CRTC) return; priv->crtc_funcs[pipe] = NULL; } -EXPORT_SYMBOL_GPL(rockchip_unregister_crtc_funcs); static struct drm_crtc *rockchip_crtc_from_pipe(struct drm_device *drm, int pipe) @@ -130,7 +705,7 @@ static void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) { struct rockchip_drm_private *private; - struct dma_iommu_mapping *mapping; + struct dma_iommu_mapping *mapping = NULL; struct device *dev = drm_dev->dev; struct drm_connector *connector; int ret; @@ -139,8 +714,16 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) if (!private) return -ENOMEM; + mutex_init(&private->commit.lock); + INIT_WORK(&private->commit.work, rockchip_drm_atomic_work); + drm_dev->dev_private = private; +#ifdef CONFIG_DRM_DMA_SYNC + private->cpu_fence_context = fence_context_alloc(1); + atomic_set(&private->cpu_fence_seqno, 0); +#endif + drm_mode_config_init(drm_dev); rockchip_drm_mode_config_init(drm_dev); @@ -152,23 +735,26 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) goto err_config_cleanup; } - /* TODO(djkurtz): fetch the mapping start/size from somewhere */ - mapping = arm_iommu_create_mapping(&platform_bus_type, 0x00000000, - SZ_2G); - if (IS_ERR(mapping)) { - ret = PTR_ERR(mapping); - goto err_config_cleanup; - } + if (is_support_iommu) { + /* TODO(djkurtz): fetch the mapping start/size from somewhere */ + mapping = arm_iommu_create_mapping(&platform_bus_type, + 0x00000000, + SZ_2G); + if (IS_ERR(mapping)) { + ret = PTR_ERR(mapping); + goto err_config_cleanup; + } - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) - goto err_release_mapping; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + goto err_release_mapping; - dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); - ret = arm_iommu_attach_device(dev, mapping); - if (ret) - goto err_release_mapping; + ret = arm_iommu_attach_device(dev, mapping); + if (ret) + goto err_release_mapping; + } /* Try to bind all sub drivers. */ ret = component_bind_all(dev, drm_dev); @@ -212,10 +798,18 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) */ drm_dev->vblank_disable_allowed = true; + drm_mode_config_reset(drm_dev); + + show_loader_logo(drm_dev); + ret = rockchip_drm_fbdev_init(drm_dev); if (ret) goto err_vblank_cleanup; + drm_dev->mode_config.allow_fb_modifiers = true; + + if (is_support_iommu) + arm_iommu_release_mapping(mapping); return 0; err_vblank_cleanup: drm_vblank_cleanup(drm_dev); @@ -224,9 +818,11 @@ err_kms_helper_poll_fini: err_unbind: component_unbind_all(dev, drm_dev); err_detach_device: - arm_iommu_detach_device(dev); + if (is_support_iommu) + arm_iommu_detach_device(dev); err_release_mapping: - arm_iommu_release_mapping(dev->archdata.mapping); + if (is_support_iommu) + arm_iommu_release_mapping(mapping); err_config_cleanup: drm_mode_config_cleanup(drm_dev); drm_dev->dev_private = NULL; @@ -241,21 +837,151 @@ static int rockchip_drm_unload(struct drm_device *drm_dev) drm_vblank_cleanup(drm_dev); drm_kms_helper_poll_fini(drm_dev); component_unbind_all(dev, drm_dev); - arm_iommu_detach_device(dev); - arm_iommu_release_mapping(dev->archdata.mapping); + if (is_support_iommu) + arm_iommu_detach_device(dev); drm_mode_config_cleanup(drm_dev); drm_dev->dev_private = NULL; return 0; } +static void rockchip_drm_crtc_cancel_pending_vblank(struct drm_crtc *crtc, + struct drm_file *file_priv) +{ + struct rockchip_drm_private *priv = crtc->dev->dev_private; + int pipe = drm_crtc_index(crtc); + + if (pipe < ROCKCHIP_MAX_CRTC && + priv->crtc_funcs[pipe] && + priv->crtc_funcs[pipe]->cancel_pending_vblank) + priv->crtc_funcs[pipe]->cancel_pending_vblank(crtc, file_priv); +} + +int rockchip_drm_register_subdrv(struct drm_rockchip_subdrv *subdrv) +{ + if (!subdrv) + return -EINVAL; + + mutex_lock(&subdrv_list_mutex); + list_add_tail(&subdrv->list, &rockchip_drm_subdrv_list); + mutex_unlock(&subdrv_list_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(rockchip_drm_register_subdrv); + +int rockchip_drm_unregister_subdrv(struct drm_rockchip_subdrv *subdrv) +{ + if (!subdrv) + return -EINVAL; + + mutex_lock(&subdrv_list_mutex); + list_del(&subdrv->list); + mutex_unlock(&subdrv_list_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(rockchip_drm_unregister_subdrv); + +static int rockchip_drm_open(struct drm_device *dev, struct drm_file *file) +{ + struct rockchip_drm_file_private *file_priv; + struct drm_rockchip_subdrv *subdrv; + int ret = 0; + + file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); + if (!file_priv) + return -ENOMEM; + INIT_LIST_HEAD(&file_priv->gem_cpu_acquire_list); + + file->driver_priv = file_priv; + + mutex_lock(&subdrv_list_mutex); + list_for_each_entry(subdrv, &rockchip_drm_subdrv_list, list) { + ret = subdrv->open(dev, subdrv->dev, file); + if (ret) { + mutex_unlock(&subdrv_list_mutex); + goto err_free_file_priv; + } + } + mutex_unlock(&subdrv_list_mutex); + + return 0; + +err_free_file_priv: + kfree(file_priv); + file_priv = NULL; + + return ret; +} + +static void rockchip_drm_preclose(struct drm_device *dev, + struct drm_file *file_priv) +{ + struct rockchip_drm_file_private *file_private = file_priv->driver_priv; + struct rockchip_gem_object_node *cur, *d; + struct drm_rockchip_subdrv *subdrv; + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + rockchip_drm_crtc_cancel_pending_vblank(crtc, file_priv); + + mutex_lock(&dev->struct_mutex); + list_for_each_entry_safe(cur, d, + &file_private->gem_cpu_acquire_list, list) { +#ifdef CONFIG_DRM_DMA_SYNC + BUG_ON(!cur->rockchip_gem_obj->acquire_fence); + drm_fence_signal_and_put(&cur->rockchip_gem_obj->acquire_fence); +#endif + drm_gem_object_unreference(&cur->rockchip_gem_obj->base); + kfree(cur); + } + /* since we are deleting the whole list, just initialize the header + * instead of calling list_del for every element + */ + INIT_LIST_HEAD(&file_private->gem_cpu_acquire_list); + mutex_unlock(&dev->struct_mutex); + + mutex_lock(&subdrv_list_mutex); + list_for_each_entry(subdrv, &rockchip_drm_subdrv_list, list) + subdrv->close(dev, subdrv->dev, file_priv); + mutex_unlock(&subdrv_list_mutex); +} + +static void rockchip_drm_postclose(struct drm_device *dev, struct drm_file *file) +{ + kfree(file->driver_priv); + file->driver_priv = NULL; +} + void rockchip_drm_lastclose(struct drm_device *dev) { struct rockchip_drm_private *priv = dev->dev_private; - drm_fb_helper_restore_fbdev_mode_unlocked(&priv->fbdev_helper); + drm_fb_helper_restore_fbdev_mode_unlocked(priv->fbdev_helper); } +static const struct drm_ioctl_desc rockchip_ioctls[] = { + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CREATE, rockchip_gem_create_ioctl, + DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_MAP_OFFSET, + rockchip_gem_map_offset_ioctl, + DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CPU_ACQUIRE, + rockchip_gem_cpu_acquire_ioctl, + DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CPU_RELEASE, + rockchip_gem_cpu_release_ioctl, + DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_GET_VER, rockchip_rga_get_ver_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_SET_CMDLIST, + rockchip_rga_set_cmdlist_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(ROCKCHIP_RGA_EXEC, rockchip_rga_exec_ioctl, + DRM_AUTH | DRM_RENDER_ALLOW), +}; + static const struct file_operations rockchip_drm_driver_fops = { .owner = THIS_MODULE, .open = drm_open, @@ -275,11 +1001,16 @@ const struct vm_operations_struct rockchip_drm_vm_ops = { }; static struct drm_driver rockchip_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .driver_features = DRIVER_MODESET | DRIVER_GEM | + DRIVER_PRIME | DRIVER_ATOMIC | + DRIVER_RENDER, .load = rockchip_drm_load, .unload = rockchip_drm_unload, + .preclose = rockchip_drm_preclose, .lastclose = rockchip_drm_lastclose, .get_vblank_counter = drm_vblank_no_hw_counter, + .open = rockchip_drm_open, + .postclose = rockchip_drm_postclose, .enable_vblank = rockchip_drm_crtc_enable_vblank, .disable_vblank = rockchip_drm_crtc_disable_vblank, .gem_vm_ops = &rockchip_drm_vm_ops, @@ -292,9 +1023,12 @@ static struct drm_driver rockchip_drm_driver = { .gem_prime_import = drm_gem_prime_import, .gem_prime_export = drm_gem_prime_export, .gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table, + .gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table, .gem_prime_vmap = rockchip_gem_prime_vmap, .gem_prime_vunmap = rockchip_gem_prime_vunmap, .gem_prime_mmap = rockchip_gem_mmap_buf, + .ioctls = rockchip_ioctls, + .num_ioctls = ARRAY_SIZE(rockchip_ioctls), .fops = &rockchip_drm_driver_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, @@ -304,25 +1038,38 @@ static struct drm_driver rockchip_drm_driver = { }; #ifdef CONFIG_PM_SLEEP -static int rockchip_drm_sys_suspend(struct device *dev) +void rockchip_drm_fb_suspend(struct drm_device *drm) { - struct drm_device *drm = dev_get_drvdata(dev); - struct drm_connector *connector; + struct rockchip_drm_private *priv = drm->dev_private; - if (!drm) - return 0; + console_lock(); + drm_fb_helper_set_suspend(priv->fbdev_helper, 1); + console_unlock(); +} - drm_modeset_lock_all(drm); - list_for_each_entry(connector, &drm->mode_config.connector_list, head) { - int old_dpms = connector->dpms; +void rockchip_drm_fb_resume(struct drm_device *drm) +{ + struct rockchip_drm_private *priv = drm->dev_private; - if (connector->funcs->dpms) - connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); + console_lock(); + drm_fb_helper_set_suspend(priv->fbdev_helper, 0); + console_unlock(); +} - /* Set the old mode back to the connector for resume */ - connector->dpms = old_dpms; +static int rockchip_drm_sys_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct rockchip_drm_private *priv = drm->dev_private; + + drm_kms_helper_poll_disable(drm); + rockchip_drm_fb_suspend(drm); + + priv->state = drm_atomic_helper_suspend(drm); + if (IS_ERR(priv->state)) { + rockchip_drm_fb_resume(drm); + drm_kms_helper_poll_enable(drm); + return PTR_ERR(priv->state); } - drm_modeset_unlock_all(drm); return 0; } @@ -330,47 +1077,11 @@ static int rockchip_drm_sys_suspend(struct device *dev) static int rockchip_drm_sys_resume(struct device *dev) { struct drm_device *drm = dev_get_drvdata(dev); - struct drm_connector *connector; - enum drm_connector_status status; - bool changed = false; - - if (!drm) - return 0; - - drm_modeset_lock_all(drm); - list_for_each_entry(connector, &drm->mode_config.connector_list, head) { - int desired_mode = connector->dpms; - - /* - * at suspend time, we save dpms to connector->dpms, - * restore the old_dpms, and at current time, the connector - * dpms status must be DRM_MODE_DPMS_OFF. - */ - connector->dpms = DRM_MODE_DPMS_OFF; - - /* - * If the connector has been disconnected during suspend, - * disconnect it from the encoder and leave it off. We'll notify - * userspace at the end. - */ - if (desired_mode == DRM_MODE_DPMS_ON) { - status = connector->funcs->detect(connector, true); - if (status == connector_status_disconnected) { - connector->encoder = NULL; - connector->status = status; - changed = true; - continue; - } - } - if (connector->funcs->dpms) - connector->funcs->dpms(connector, desired_mode); - } - drm_modeset_unlock_all(drm); - - drm_helper_resume_force_mode(drm); + struct rockchip_drm_private *priv = drm->dev_private; - if (changed) - drm_kms_helper_hotplug_event(drm); + drm_atomic_helper_resume(drm, priv->state); + rockchip_drm_fb_resume(drm); + drm_kms_helper_poll_enable(drm); return 0; } @@ -381,36 +1092,6 @@ static const struct dev_pm_ops rockchip_drm_pm_ops = { rockchip_drm_sys_resume) }; -/* - * @node: device tree node containing encoder input ports - * @encoder: drm_encoder - */ -int rockchip_drm_encoder_get_mux_id(struct device_node *node, - struct drm_encoder *encoder) -{ - struct device_node *ep; - struct drm_crtc *crtc = encoder->crtc; - struct of_endpoint endpoint; - struct device_node *port; - int ret; - - if (!node || !crtc) - return -EINVAL; - - for_each_endpoint_of_node(node, ep) { - port = of_graph_get_remote_port(ep); - of_node_put(port); - if (port == crtc->port) { - ret = of_graph_parse_endpoint(ep, &endpoint); - of_node_put(ep); - return ret ?: endpoint.id; - } - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(rockchip_drm_encoder_get_mux_id); - static int compare_of(struct device *dev, void *data) { struct device_node *np = data; @@ -497,6 +1178,8 @@ static int rockchip_drm_platform_probe(struct platform_device *pdev) * works as expected. */ for (i = 0;; i++) { + struct device_node *iommu; + port = of_parse_phandle(np, "ports", i); if (!port) break; @@ -506,6 +1189,17 @@ static int rockchip_drm_platform_probe(struct platform_device *pdev) continue; } + iommu = of_parse_phandle(port->parent, "iommus", 0); + if (!iommu || !of_device_is_available(iommu->parent)) { + dev_dbg(dev, "no iommu attached for %s, using non-iommu buffers\n", + port->parent->full_name); + /* + * if there is a crtc not support iommu, force set all + * crtc use non-iommu buffer. + */ + is_support_iommu = false; + } + component_match_add(dev, &match, compare_of, port->parent); of_node_put(port); }