X-Git-Url: http://plrg.eecs.uci.edu/git/?p=firefly-linux-kernel-4.4.55.git;a=blobdiff_plain;f=drivers%2Fgpu%2Fdrm%2Frockchip%2Frockchip_drm_drv.c;h=59605f9056af017a505795c3ccab1e9ec4da53ec;hp=f22e1e1ee64aae52a1e3efb657a78790bd6ba573;hb=eaef6c723fa503bb6438151aa565df487b40a88d;hpb=0aba0ab8915bcad3ae7e05e18bc2c5c55570e328 diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index f22e1e1ee64a..59605f9056af 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -14,21 +14,31 @@ * GNU General Public License for more details. */ -#include - #include +#include #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 +46,602 @@ #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); +static struct drm_driver rockchip_drm_driver; + +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; + 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)) { + ret = PTR_ERR(sgt); + goto err_free_pages; + } + + if (private->domain) { + memset(&logo->mm, 0, sizeof(logo->mm)); + ret = drm_mm_insert_node_generic(&private->mm, &logo->mm, + size, PAGE_SIZE, + 0, 0, 0); + if (ret < 0) { + DRM_ERROR("out of I/O virtual memory: %d\n", ret); + goto err_free_pages; + } + + logo->dma_addr = logo->mm.start; + + logo->iommu_map_size = iommu_map_sg(private->domain, + logo->dma_addr, sgt->sgl, + sgt->nents, IOMMU_READ); + if (logo->iommu_map_size < size) { + DRM_ERROR("failed to map buffer"); + ret = -ENOMEM; + goto err_remove_node; + } + } else { + dma_map_sg(drm_dev->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE); + 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; + +err_remove_node: + drm_mm_remove_node(&logo->mm); +err_free_pages: + kfree(pages); + + return ret; +} + +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_encoder_helper_funcs *encoder_helper_funcs; + const struct drm_connector_helper_funcs *connector_helper_funcs; + struct rockchip_drm_private *priv = drm_dev->dev_private; + struct drm_encoder *encoder; + int pipe = drm_crtc_index(crtc); + + connector_helper_funcs = connector->helper_private; + if (!priv->crtc_funcs[pipe] || + !priv->crtc_funcs[pipe]->loader_protect || + !connector_helper_funcs || + !connector_helper_funcs->best_encoder) + return -ENXIO; + encoder = connector_helper_funcs->best_encoder(connector); + if (!encoder) + return -ENXIO; + encoder_helper_funcs = encoder->helper_private; + if (!encoder_helper_funcs->atomic_check) + return -ENXIO; + ret = encoder_helper_funcs->atomic_check(encoder, crtc->state, + conn_state); + if (ret) + return ret; + if (encoder_helper_funcs->mode_set) + encoder_helper_funcs->mode_set(encoder, mode, mode); + priv->crtc_funcs[pipe]->loader_protect(crtc, true); + } + + 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 @@ -44,51 +650,57 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev) { - struct dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping; + struct rockchip_drm_private *private = drm_dev->dev_private; int ret; - ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); - if (ret) - return ret; + if (!is_support_iommu) + return 0; - dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); + ret = iommu_attach_device(private->domain, dev); + if (ret) { + dev_err(dev, "Failed to attach iommu device\n"); + return ret; + } - return arm_iommu_attach_device(dev, mapping); + return 0; } -EXPORT_SYMBOL_GPL(rockchip_drm_dma_attach_device); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev) { - arm_iommu_detach_device(dev); + struct rockchip_drm_private *private = drm_dev->dev_private; + struct iommu_domain *domain = private->domain; + + if (!is_support_iommu) + return; + + iommu_detach_device(domain, 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) + if (pipe >= ROCKCHIP_MAX_CRTC) return -EINVAL; priv->crtc_funcs[pipe] = crtc_funcs; 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) + 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) @@ -127,70 +739,152 @@ static void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, priv->crtc_funcs[pipe]->disable_vblank(crtc); } -static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) +static int rockchip_drm_init_iommu(struct drm_device *drm_dev) { - struct rockchip_drm_private *private; - struct dma_iommu_mapping *mapping; - struct device *dev = drm_dev->dev; - struct drm_connector *connector; - int ret; + struct rockchip_drm_private *private = drm_dev->dev_private; + struct iommu_domain_geometry *geometry; + u64 start, end; - private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL); - if (!private) + if (!is_support_iommu) + return 0; + + private->domain = iommu_domain_alloc(&platform_bus_type); + if (!private->domain) return -ENOMEM; - drm_dev->dev_private = private; + geometry = &private->domain->geometry; + start = geometry->aperture_start; + end = geometry->aperture_end; - drm_mode_config_init(drm_dev); + DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n", + start, end); + drm_mm_init(&private->mm, start, end - start + 1); + mutex_init(&private->mm_lock); - rockchip_drm_mode_config_init(drm_dev); + return 0; +} - dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms), - GFP_KERNEL); - if (!dev->dma_parms) { - ret = -ENOMEM; - goto err_config_cleanup; +static void rockchip_iommu_cleanup(struct drm_device *drm_dev) +{ + struct rockchip_drm_private *private = drm_dev->dev_private; + + if (!is_support_iommu) + return; + + drm_mm_takedown(&private->mm); + iommu_domain_free(private->domain); +} + +#ifdef CONFIG_DEBUG_FS +static int rockchip_drm_mm_dump(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct drm_minor *minor = node->minor; + struct drm_device *drm_dev = minor->dev; + struct rockchip_drm_private *priv = drm_dev->dev_private; + int ret; + + mutex_lock(&priv->mm_lock); + + ret = drm_mm_dump_table(s, &priv->mm); + + mutex_unlock(&priv->mm_lock); + + return ret; +} + +static int rockchip_drm_summary_show(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct drm_minor *minor = node->minor; + struct drm_device *drm_dev = minor->dev; + struct rockchip_drm_private *priv = drm_dev->dev_private; + struct drm_crtc *crtc; + + drm_for_each_crtc(crtc, drm_dev) { + int pipe = drm_crtc_index(crtc); + + if (priv->crtc_funcs[pipe] && + priv->crtc_funcs[pipe]->debugfs_dump) + priv->crtc_funcs[pipe]->debugfs_dump(crtc, s); } - /* 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; + return 0; +} + +static struct drm_info_list rockchip_debugfs_files[] = { + { "summary", rockchip_drm_summary_show, 0, NULL }, + { "mm_dump", rockchip_drm_mm_dump, 0, NULL }, +}; + +static int rockchip_drm_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + int ret; + + ret = drm_debugfs_create_files(rockchip_debugfs_files, + ARRAY_SIZE(rockchip_debugfs_files), + minor->debugfs_root, + minor); + if (ret) { + dev_err(dev->dev, "could not install rockchip_debugfs_list\n"); + return ret; } - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + return 0; +} + +static void rockchip_drm_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(rockchip_debugfs_files, + ARRAY_SIZE(rockchip_debugfs_files), minor); +} +#endif + +static int rockchip_drm_bind(struct device *dev) +{ + struct drm_device *drm_dev; + struct rockchip_drm_private *private; + int ret; + + drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev); + if (!drm_dev) + return -ENOMEM; + + ret = drm_dev_set_unique(drm_dev, "%s", dev_name(dev)); if (ret) - goto err_release_mapping; + goto err_free; - dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); + dev_set_drvdata(dev, drm_dev); - ret = arm_iommu_attach_device(dev, mapping); + private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL); + if (!private) { + ret = -ENOMEM; + goto err_free; + } + + 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); + + ret = rockchip_drm_init_iommu(drm_dev); if (ret) - goto err_release_mapping; + goto err_config_cleanup; /* Try to bind all sub drivers. */ ret = component_bind_all(dev, drm_dev); if (ret) - goto err_detach_device; - - /* - * All components are now added, we can publish the connector sysfs - * entries to userspace. This will generate hotplug events and so - * userspace will expect to be able to access DRM at this point. - */ - list_for_each_entry(connector, &drm_dev->mode_config.connector_list, - head) { - ret = drm_connector_register(connector); - if (ret) { - dev_err(drm_dev->dev, - "[CONNECTOR:%d:%s] drm_connector_register failed: %d\n", - connector->base.id, - connector->name, ret); - goto err_unbind; - } - } + goto err_iommu_cleanup; /* init kms poll for handling hpd */ drm_kms_helper_poll_init(drm_dev); @@ -212,50 +906,191 @@ 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; + + ret = drm_dev_register(drm_dev, 0); + if (ret) + goto err_fbdev_fini; + return 0; +err_fbdev_fini: + rockchip_drm_fbdev_fini(drm_dev); err_vblank_cleanup: drm_vblank_cleanup(drm_dev); err_kms_helper_poll_fini: drm_kms_helper_poll_fini(drm_dev); -err_unbind: component_unbind_all(dev, drm_dev); -err_detach_device: - arm_iommu_detach_device(dev); -err_release_mapping: - arm_iommu_release_mapping(dev->archdata.mapping); +err_iommu_cleanup: + rockchip_iommu_cleanup(drm_dev); err_config_cleanup: drm_mode_config_cleanup(drm_dev); drm_dev->dev_private = NULL; +err_free: + drm_dev_unref(drm_dev); return ret; } -static int rockchip_drm_unload(struct drm_device *drm_dev) +static void rockchip_drm_unbind(struct device *dev) { - struct device *dev = drm_dev->dev; + struct drm_device *drm_dev = dev_get_drvdata(dev); rockchip_drm_fbdev_fini(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); + rockchip_iommu_cleanup(drm_dev); drm_mode_config_cleanup(drm_dev); drm_dev->dev_private = NULL; + drm_dev_unregister(drm_dev); + drm_dev_unref(drm_dev); + dev_set_drvdata(dev, NULL); +} + +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 +1110,14 @@ const struct vm_operations_struct rockchip_drm_vm_ops = { }; static struct drm_driver rockchip_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, - .load = rockchip_drm_load, - .unload = rockchip_drm_unload, + .driver_features = DRIVER_MODESET | DRIVER_GEM | + DRIVER_PRIME | DRIVER_ATOMIC | + DRIVER_RENDER, + .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 +1130,16 @@ 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, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = rockchip_drm_debugfs_init, + .debugfs_cleanup = rockchip_drm_debugfs_cleanup, +#endif + .ioctls = rockchip_ioctls, + .num_ioctls = ARRAY_SIZE(rockchip_ioctls), .fops = &rockchip_drm_driver_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, @@ -304,25 +1149,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(); +} + +void rockchip_drm_fb_resume(struct drm_device *drm) +{ + struct rockchip_drm_private *priv = drm->dev_private; + + console_lock(); + drm_fb_helper_set_suspend(priv->fbdev_helper, 0); + console_unlock(); +} - drm_modeset_lock_all(drm); - list_for_each_entry(connector, &drm->mode_config.connector_list, head) { - int old_dpms = connector->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; - if (connector->funcs->dpms) - connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); + drm_kms_helper_poll_disable(drm); + rockchip_drm_fb_suspend(drm); - /* Set the old mode back to the connector for resume */ - connector->dpms = old_dpms; + 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 +1188,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; + struct rockchip_drm_private *priv = drm->dev_private; - 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); - - 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 +1203,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; @@ -441,41 +1233,6 @@ static void rockchip_add_endpoints(struct device *dev, } } -static int rockchip_drm_bind(struct device *dev) -{ - struct drm_device *drm; - int ret; - - drm = drm_dev_alloc(&rockchip_drm_driver, dev); - if (!drm) - return -ENOMEM; - - ret = drm_dev_set_unique(drm, "%s", dev_name(dev)); - if (ret) - goto err_free; - - ret = drm_dev_register(drm, 0); - if (ret) - goto err_free; - - dev_set_drvdata(dev, drm); - - return 0; - -err_free: - drm_dev_unref(drm); - return ret; -} - -static void rockchip_drm_unbind(struct device *dev) -{ - struct drm_device *drm = dev_get_drvdata(dev); - - drm_dev_unregister(drm); - drm_dev_unref(drm); - dev_set_drvdata(dev, NULL); -} - static const struct component_master_ops rockchip_drm_ops = { .bind = rockchip_drm_bind, .unbind = rockchip_drm_unbind, @@ -497,6 +1254,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 +1265,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); }