diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 603d143c1401..a55dbf94aef6 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -4,7 +4,8 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ - rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o \ + rockchip_drm_gem.o rockchip_drm_logo.o \ + rockchip_drm_vop.o rockchip_vop_reg.o \ rockchip_drm_vop2.o rockchip_vop2_reg.o rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 07817911858a..4f69e531f9b7 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -32,6 +32,7 @@ #include "rockchip_drm_fb.h" #include "rockchip_drm_fbdev.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_logo.h" #include "../drm_crtc_internal.h" @@ -44,29 +45,6 @@ static bool is_support_iommu = true; 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; - int flags; - int picture_aspect_ratio; - int crtc_hsync_end; - int crtc_vsync_end; - - int left_margin; - int right_margin; - int top_margin; - int bottom_margin; - - bool mode_changed; - int ratio; -}; - /** * rockchip_drm_of_find_possible_crtcs - find the possible CRTCs for an active * encoder port @@ -624,10 +602,6 @@ static int rockchip_drm_bind(struct device *dev) */ drm_dev->irq_enabled = true; - ret = rockchip_drm_fbdev_init(drm_dev); - if (ret) - goto err_unbind_all; - /* init kms poll for handling hpd */ drm_kms_helper_poll_init(drm_dev); @@ -636,6 +610,12 @@ static int rockchip_drm_bind(struct device *dev) if (ret) DRM_DEBUG_KMS("No reserved memory region assign to drm\n"); + rockchip_drm_show_logo(drm_dev); + + ret = rockchip_drm_fbdev_init(drm_dev); + if (ret) + goto err_unbind_all; + drm_dev->mode_config.allow_fb_modifiers = true; ret = drm_dev_register(drm_dev, 0); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index f79368fc8f8c..e8487b0ec403 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -16,6 +16,8 @@ #include #include +#include "../panel/panel-simple.h" + #define ROCKCHIP_MAX_FB_BUFFER 3 #define ROCKCHIP_MAX_CONNECTOR 2 #define ROCKCHIP_MAX_CRTC 4 @@ -44,6 +46,7 @@ struct rockchip_drm_sub_dev { struct list_head list; struct drm_connector *connector; struct device_node *of_node; + void (*loader_protect)(struct drm_encoder *encoder, bool on); }; struct rockchip_sdr2hdr_state { @@ -162,6 +165,7 @@ struct rockchip_crtc_funcs { * @mm_lock: protect drm_mm on multi-threads. */ struct rockchip_drm_private { + struct rockchip_logo *logo; struct drm_fb_helper *fbdev_helper; struct drm_gem_object *fbdev_bo; struct iommu_domain *domain; @@ -184,6 +188,12 @@ struct rockchip_drm_private { const struct rockchip_crtc_funcs *crtc_funcs[ROCKCHIP_MAX_CRTC]; struct rockchip_drm_vcnt vcnt[ROCKCHIP_MAX_CRTC]; + /** + * @loader_protect + * ignore restore_fbdev_mode_atomic when in logo on state + */ + bool loader_protect; + dma_addr_t cubic_lut_dma_addr; void *cubic_lut_kvaddr; struct loader_cubic_lut cubic_lut[ROCKCHIP_MAX_CRTC]; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index ce697e4ac157..8599a32b7a68 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -29,8 +29,6 @@ static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb) { int i = 0; - for (i = 0; i < 4; i++) - drm_gem_object_put(fb->obj[i]); drm_framebuffer_cleanup(fb); if (is_rockchip_logo_fb(fb)) { @@ -38,6 +36,11 @@ static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb) kfree(rockchip_logo_fb); } else { + for (i = 0; i < 4; i++) { + if (fb->obj[i]) + drm_gem_object_put(fb->obj[i]); + } + kfree(fb); } } @@ -103,6 +106,9 @@ rockchip_drm_logo_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 fb->flags |= ROCKCHIP_DRM_MODE_LOGO_FB; rockchip_logo_fb->logo = logo; + rockchip_logo_fb->fb.obj[0] = &rockchip_logo_fb->rk_obj.base; + rockchip_logo_fb->rk_obj.dma_addr = logo->dma_addr; + rockchip_logo_fb->rk_obj.kvaddr = logo->kvaddr; logo->count++; return &rockchip_logo_fb->fb; @@ -196,7 +202,7 @@ rockchip_fb_create(struct drm_device *dev, struct drm_file *file, } if (drm_is_afbc(mode_cmd->modifier[0])) { - int ret, i; + int i; ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb); if (ret) { @@ -213,9 +219,18 @@ rockchip_fb_create(struct drm_device *dev, struct drm_file *file, return &afbc_fb->base; } +static void rockchip_drm_output_poll_changed(struct drm_device *dev) +{ + struct rockchip_drm_private *private = dev->dev_private; + struct drm_fb_helper *fb_helper = private->fbdev_helper; + + if (fb_helper && dev->mode_config.poll_enabled && !private->loader_protect) + drm_fb_helper_hotplug_event(fb_helper); +} + static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { .fb_create = rockchip_fb_create, - .output_poll_changed = drm_fb_helper_output_poll_changed, + .output_poll_changed = rockchip_drm_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, }; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h index 94c04ce80683..3b8755e9f191 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h @@ -7,6 +7,8 @@ #ifndef _ROCKCHIP_DRM_FB_H #define _ROCKCHIP_DRM_FB_H +#include "rockchip_drm_gem.h" + #define ROCKCHIP_DRM_MODE_LOGO_FB (1<<31) /* used for kernel logo, follow the define: DRM_MODE_FB_MODIFIERS at drm_mode.h */ struct drm_framebuffer * @@ -25,6 +27,7 @@ rockchip_drm_logo_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 struct rockchip_drm_logo_fb { struct drm_framebuffer fb; struct rockchip_logo *logo; + struct rockchip_gem_object rk_obj; }; #endif /* _ROCKCHIP_DRM_FB_H */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_logo.c b/drivers/gpu/drm/rockchip/rockchip_drm_logo.c new file mode 100644 index 000000000000..ae1435edaeff --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_logo.c @@ -0,0 +1,1070 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2021 Rockchip Electronics Co., Ltd. + * Author: Sandy Huang + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_fb.h" +#include "rockchip_drm_logo.h" + +static bool is_support_hotplug(uint32_t output_type) +{ + switch (output_type) { + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_DVIA: + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + case DRM_MODE_CONNECTOR_TV: + return true; + default: + return false; + } +} + +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 rockchip_drm_sub_dev * +find_sub_dev_by_node(struct drm_device *drm_dev, struct device_node *node) +{ + struct device_node *np_connector; + struct rockchip_drm_sub_dev *sub_dev; + + np_connector = of_graph_get_remote_port_parent(node); + if (!np_connector || !of_device_is_available(np_connector)) + return NULL; + + sub_dev = rockchip_drm_get_sub_dev(np_connector); + if (!sub_dev) + return NULL; + + return sub_dev; +} + +static struct rockchip_drm_sub_dev * +find_sub_dev_by_bridge(struct drm_device *drm_dev, struct device_node *node) +{ + struct device_node *np_encoder, *np_connector = NULL; + struct rockchip_drm_sub_dev *sub_dev = NULL; + struct device_node *port, *endpoint; + + np_encoder = of_graph_get_remote_port_parent(node); + if (!np_encoder || !of_device_is_available(np_encoder)) + goto err_put_encoder; + + port = of_graph_get_port_by_id(np_encoder, 1); + if (!port) { + dev_err(drm_dev->dev, "can't found port point!\n"); + goto err_put_encoder; + } + + for_each_child_of_node(port, endpoint) { + np_connector = of_graph_get_remote_port_parent(endpoint); + if (!np_connector) { + dev_err(drm_dev->dev, + "can't found connector node, please init!\n"); + goto err_put_port; + } + if (!of_device_is_available(np_connector)) { + of_node_put(np_connector); + np_connector = NULL; + continue; + } else { + break; + } + } + if (!np_connector) { + dev_err(drm_dev->dev, "can't found available connector node!\n"); + goto err_put_port; + } + +sub_dev = rockchip_drm_get_sub_dev(np_connector); + if (!sub_dev) + goto err_put_port; + + of_node_put(np_connector); +err_put_port: + of_node_put(port); +err_put_encoder: + of_node_put(np_encoder); + + return sub_dev; +} + +static unsigned long +rockchip_drm_free_reserved_area(void *start, void *end, int poison, const char *s) +{ + void *pos; + unsigned long pages = 0; + + start = (void *)PAGE_ALIGN((unsigned long)start); + end = (void *)((unsigned long)end & PAGE_MASK); + for (pos = start; pos < end; pos += PAGE_SIZE, pages++) { + struct page *page = virt_to_page(pos); + void *direct_map_addr; + + /* + * 'direct_map_addr' might be different from 'pos' + * because some architectures' virt_to_page() + * work with aliases. Getting the direct map + * address ensures that we get a _writeable_ + * alias for the memset(). + */ + direct_map_addr = page_address(page); + /* + * Perform a kasan-unchecked memset() since this memory + * has not been initialized. + */ + direct_map_addr = kasan_reset_tag(direct_map_addr); + if ((unsigned int)poison <= 0xFF) + memset(direct_map_addr, poison, PAGE_SIZE); + + free_reserved_page(page); + } + + if (pages && s) + pr_info("Freeing %s memory: %ldK\n", s, pages << (PAGE_SHIFT - 10)); + + return pages; +} + +static void rockchip_free_loader_memory(struct drm_device *drm) +{ + struct rockchip_drm_private *private = drm->dev_private; + struct rockchip_logo *logo; + void *start, *end; + + if (!private || !private->logo || --private->logo->count) + return; + + logo = private->logo; + start = phys_to_virt(logo->dma_addr); + end = phys_to_virt(logo->dma_addr + logo->size); + + if (private->domain) { + u32 pg_size = 1UL << __ffs(private->domain->pgsize_bitmap); + + iommu_unmap(private->domain, logo->dma_addr, ALIGN(logo->size, pg_size)); + } + + memblock_free(logo->start, logo->size); + rockchip_drm_free_reserved_area(start, end, -1, "drm_logo"); + kfree(logo); + private->logo = NULL; + private->loader_protect = false; +} + +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; + phys_addr_t start, size; + u32 pg_size = PAGE_SIZE; + struct resource res; + int ret, idx; + + idx = of_property_match_string(np, "memory-region-names", "drm-logo"); + if (idx >= 0) + node = of_parse_phandle(np, "memory-region", idx); + else + node = of_parse_phandle(np, "logo-memory-region", 0); + if (!node) + return -ENOMEM; + + ret = of_address_to_resource(node, 0, &res); + if (ret) + return ret; + if (private->domain) + pg_size = 1UL << __ffs(private->domain->pgsize_bitmap); + start = ALIGN_DOWN(res.start, pg_size); + size = resource_size(&res); + if (!size) + return -ENOMEM; + + logo = kmalloc(sizeof(*logo), GFP_KERNEL); + if (!logo) + return -ENOMEM; + + logo->kvaddr = phys_to_virt(start); + + if (private->domain) { + ret = iommu_map(private->domain, start, start, ALIGN(size, pg_size), + IOMMU_WRITE | IOMMU_READ); + if (ret) { + dev_err(drm_dev->dev, "failed to create 1v1 mapping\n"); + goto err_free_logo; + } + } + + logo->dma_addr = start; + logo->size = size; + logo->count = 1; + private->logo = logo; + + idx = of_property_match_string(np, "memory-region-names", "drm-cubic-lut"); + if (idx < 0) + return 0; + + node = of_parse_phandle(np, "memory-region", idx); + if (!node) + return -ENOMEM; + + ret = of_address_to_resource(node, 0, &res); + if (ret) + return ret; + start = ALIGN_DOWN(res.start, pg_size); + size = resource_size(&res); + if (!size) + return 0; + + private->cubic_lut_kvaddr = phys_to_virt(start); + if (private->domain) { + ret = iommu_map(private->domain, start, start, ALIGN(size, pg_size), + IOMMU_WRITE | IOMMU_READ); + if (ret) { + dev_err(drm_dev->dev, "failed to create 1v1 mapping for cubic lut\n"); + goto err_free_logo; + } + } + private->cubic_lut_dma_addr = start; + + return 0; + +err_free_logo: + kfree(logo); + + 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] = ALIGN(mode_cmd.width * bpp, 32) / 8; + + switch (bpp) { + case 16: + mode_cmd.pixel_format = DRM_FORMAT_RGB565; + break; + case 24: + mode_cmd.pixel_format = DRM_FORMAT_RGB888; + break; + case 32: + mode_cmd.pixel_format = DRM_FORMAT_XRGB8888; + break; + default: + pr_err("%s: unsupported to logo bpp %d\n", __func__, bpp); + return NULL; + } + + return rockchip_drm_logo_fb_alloc(drm_dev, &mode_cmd, private->logo); +} + +static struct rockchip_drm_mode_set * +of_parse_display_resource(struct drm_device *drm_dev, struct device_node *route) +{ + struct rockchip_drm_private *private = drm_dev->dev_private; + struct rockchip_drm_mode_set *set; + struct device_node *connect; + struct drm_framebuffer *fb; + struct rockchip_drm_sub_dev *sub_dev; + 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); + + sub_dev = find_sub_dev_by_node(drm_dev, connect); + + if (!sub_dev) + sub_dev = find_sub_dev_by_bridge(drm_dev, connect); + + if (!crtc || !sub_dev) { + dev_warn(drm_dev->dev, + "No available crtc or connector for display"); + drm_framebuffer_put(fb); + return NULL; + } + + set = kzalloc(sizeof(*set), GFP_KERNEL); + if (!set) + return NULL; + + if (!of_property_read_u32(route, "video,clock", &val)) + set->clock = val; + + 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,crtc_hsync_end", &val)) + set->crtc_hsync_end = val; + + if (!of_property_read_u32(route, "video,crtc_vsync_end", &val)) + set->crtc_vsync_end = val; + + if (!of_property_read_u32(route, "video,vrefresh", &val)) + set->vrefresh = val; + + if (!of_property_read_u32(route, "video,flags", &val)) + set->flags = val; + + if (!of_property_read_u32(route, "video,aspect_ratio", &val)) + set->picture_aspect_ratio = val; + + if (!of_property_read_u32(route, "overscan,left_margin", &val)) + set->left_margin = val; + + if (!of_property_read_u32(route, "overscan,right_margin", &val)) + set->right_margin = val; + + if (!of_property_read_u32(route, "overscan,top_margin", &val)) + set->top_margin = val; + + if (!of_property_read_u32(route, "overscan,bottom_margin", &val)) + set->bottom_margin = val; + + if (!of_property_read_u32(route, "bcsh,brightness", &val)) + set->brightness = val; + else + set->brightness = 50; + + if (!of_property_read_u32(route, "bcsh,contrast", &val)) + set->contrast = val; + else + set->contrast = 50; + + if (!of_property_read_u32(route, "bcsh,saturation", &val)) + set->saturation = val; + else + set->saturation = 50; + + if (!of_property_read_u32(route, "bcsh,hue", &val)) + set->hue = val; + else + set->hue = 50; + + if (!of_property_read_u32(route, "cubic_lut,offset", &val)) { + private->cubic_lut[crtc->index].enable = true; + private->cubic_lut[crtc->index].offset = 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->sub_dev = sub_dev; + + return set; +} + +static int rockchip_drm_fill_connector_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + const struct drm_connector_helper_funcs *connector_funcs = + connector->helper_private; + int count = 0; + bool verbose_prune = true; + enum drm_connector_status old_status; + + WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, + connector->name); + /* set all modes to the unverified state */ + list_for_each_entry(mode, &connector->modes, head) + mode->status = MODE_STALE; + + if (connector->force) { + if (connector->force == DRM_FORCE_ON || + connector->force == DRM_FORCE_ON_DIGITAL) + connector->status = connector_status_connected; + else + connector->status = connector_status_disconnected; + if (connector->funcs->force) + connector->funcs->force(connector); + } else { + old_status = connector->status; + + if (connector->funcs->detect) + connector->status = connector->funcs->detect(connector, true); + else + connector->status = connector_status_connected; + /* + * Normally either the driver's hpd code or the poll loop should + * pick up any changes and fire the hotplug event. But if + * userspace sneaks in a probe, we might miss a change. Hence + * check here, and if anything changed start the hotplug code. + */ + if (old_status != connector->status) { + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %d to %d\n", + connector->base.id, + connector->name, + old_status, connector->status); + + /* + * The hotplug event code might call into the fb + * helpers, and so expects that we do not hold any + * locks. Fire up the poll struct instead, it will + * disable itself again. + */ + dev->mode_config.delayed_event = true; + if (dev->mode_config.poll_enabled) + schedule_delayed_work(&dev->mode_config.output_poll_work, + 0); + } + } + + /* Re-enable polling in case the global poll config changed. */ + if (!dev->mode_config.poll_running) + drm_kms_helper_poll_enable(dev); + + dev->mode_config.poll_running = true; + + if (connector->status == connector_status_disconnected) { + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n", + connector->base.id, connector->name); + drm_connector_update_edid_property(connector, NULL); + verbose_prune = false; + goto prune; + } + + count = (*connector_funcs->get_modes)(connector); + + if (count == 0 && connector->status == connector_status_connected) + count = drm_add_modes_noedid(connector, 1024, 768); + if (count == 0) + goto prune; + + drm_connector_list_update(connector); + + list_for_each_entry(mode, &connector->modes, head) { + if (mode->status == MODE_OK) + mode->status = drm_mode_validate_driver(dev, mode); + + if (mode->status == MODE_OK) + mode->status = drm_mode_validate_size(mode, maxX, maxY); + + /** + * if (mode->status == MODE_OK) + * mode->status = drm_mode_validate_flag(mode, mode_flags); + */ + if (mode->status == MODE_OK && connector_funcs->mode_valid) + mode->status = connector_funcs->mode_valid(connector, + mode); + if (mode->status == MODE_OK) + mode->status = drm_mode_validate_ycbcr420(mode, + connector); + } + +prune: + drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); + + if (list_empty(&connector->modes)) + return 0; + + drm_mode_sort(&connector->modes); + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] probed modes :\n", connector->base.id, + connector->name); + list_for_each_entry(mode, &connector->modes, head) { + drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); + drm_mode_debug_printmodeline(mode); + } + + return count; +} + +/* + * For connectors that support multiple encoders, either the + * .atomic_best_encoder() or .best_encoder() operation must be implemented. + */ +static struct drm_encoder * +rockchip_drm_connector_get_single_encoder(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + + WARN_ON(hweight32(connector->possible_encoders) > 1); + drm_connector_for_each_possible_encoder(connector, encoder) + return encoder; + + return NULL; +} + +static int setup_initial_state(struct drm_device *drm_dev, + struct drm_atomic_state *state, + struct rockchip_drm_mode_set *set) +{ + struct rockchip_drm_private *priv = drm_dev->dev_private; + struct drm_connector *connector = set->sub_dev->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; + int pipe = drm_crtc_index(crtc); + bool is_crtc_enabled = true; + int hdisplay, vdisplay; + int fb_width, fb_height; + int found = 0, match = 0; + int num_modes; + int ret = 0; + struct rockchip_crtc_state *s = NULL; + + 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; + + if (funcs->best_encoder) + conn_state->best_encoder = funcs->best_encoder(connector); + else + conn_state->best_encoder = rockchip_drm_connector_get_single_encoder(connector); + + if (set->sub_dev->loader_protect) + set->sub_dev->loader_protect(conn_state->best_encoder, true); + num_modes = rockchip_drm_fill_connector_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_conn; + } + + list_for_each_entry(mode, &connector->modes, head) { + if (mode->clock == set->clock && + mode->hdisplay == set->hdisplay && + mode->vdisplay == set->vdisplay && + mode->crtc_hsync_end == set->crtc_hsync_end && + mode->crtc_vsync_end == set->crtc_vsync_end && + drm_mode_vrefresh(mode) == set->vrefresh && + /* we just need to focus on DRM_MODE_FLAG_ALL flag, so here + * we compare mode->flags with set->flags & DRM_MODE_FLAG_ALL. + */ + mode->flags == (set->flags & DRM_MODE_FLAG_ALL) && + mode->picture_aspect_ratio == set->picture_aspect_ratio) { + found = 1; + match = 1; + break; + } + } + + if (!found) { + ret = -EINVAL; + connector->status = connector_status_disconnected; + dev_err(drm_dev->dev, "connector[%s] can't found any match mode\n", + connector->name); + DRM_INFO("%s support modes:\n\n", connector->name); + list_for_each_entry(mode, &connector->modes, head) { + DRM_INFO(DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); + } + DRM_INFO("uboot set mode: h/v display[%d,%d] h/v sync_end[%d,%d] vfresh[%d], flags[0x%x], aspect_ratio[%d]\n", + set->hdisplay, set->vdisplay, set->crtc_hsync_end, set->crtc_vsync_end, + set->vrefresh, set->flags, set->picture_aspect_ratio); + goto error_conn; + } + + conn_state->tv.brightness = set->brightness; + conn_state->tv.contrast = set->contrast; + conn_state->tv.saturation = set->saturation; + conn_state->tv.hue = set->hue; + set->mode = mode; + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto error_conn; + } + + 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_conn; + + mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; + ret = drm_atomic_set_mode_for_crtc(crtc_state, mode); + if (ret) + goto error_conn; + + crtc_state->active = true; + + if (priv->crtc_funcs[pipe] && + priv->crtc_funcs[pipe]->loader_protect) + priv->crtc_funcs[pipe]->loader_protect(crtc, true); + } + + if (!set->fb) { + ret = 0; + goto error_crtc; + } + primary_state = drm_atomic_get_plane_state(state, crtc->primary); + if (IS_ERR(primary_state)) { + ret = PTR_ERR(primary_state); + goto error_crtc; + } + + 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; + } + s = to_rockchip_crtc_state(crtc->state); + s->output_type = connector->connector_type; + + return 0; + +error_crtc: + if (priv->crtc_funcs[pipe] && priv->crtc_funcs[pipe]->loader_protect) + priv->crtc_funcs[pipe]->loader_protect(crtc, false); +error_conn: + if (set->sub_dev->loader_protect) + set->sub_dev->loader_protect(conn_state->best_encoder, 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_crtc *crtc = set->crtc; + struct drm_connector *connector = set->sub_dev->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; + struct rockchip_crtc_state *s; + + 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); + s = to_rockchip_crtc_state(crtc_state); + s->left_margin = set->left_margin; + s->right_margin = set->right_margin; + s->top_margin = set->top_margin; + s->bottom_margin = set->bottom_margin; + + 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 drm_encoder *encoder; + + connector_helper_funcs = connector->helper_private; + if (!connector_helper_funcs) + return -ENXIO; + if (connector_helper_funcs->best_encoder) + encoder = connector_helper_funcs->best_encoder(connector); + else + encoder = rockchip_drm_connector_get_single_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->atomic_mode_set) + encoder_helper_funcs->atomic_mode_set(encoder, + crtc_state, + conn_state); + else if (encoder_helper_funcs->mode_set) + encoder_helper_funcs->mode_set(encoder, mode, mode); + } + + 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_put(set->fb); + ret = drm_atomic_set_crtc_for_plane(primary_state, crtc); + + return ret; +} + +void rockchip_drm_show_logo(struct drm_device *drm_dev) +{ + struct drm_atomic_state *state, *old_state; + struct device_node *np = drm_dev->dev->of_node; + struct drm_mode_config *mode_config = &drm_dev->mode_config; + struct rockchip_drm_private *private = drm_dev->dev_private; + struct device_node *root, *route; + struct rockchip_drm_mode_set *set, *tmp, *unset; + struct list_head mode_set_list; + struct list_head mode_unset_list; + unsigned int plane_mask = 0; + struct drm_crtc *crtc; + int ret, i; + + root = of_get_child_by_name(np, "route"); + if (!root) { + dev_warn(drm_dev->dev, "failed to parse resources for logo display\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); + INIT_LIST_HEAD(&mode_unset_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 for logo display\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_put(set->fb); + INIT_LIST_HEAD(&set->head); + list_add_tail(&set->head, &mode_unset_list); + continue; + } + + INIT_LIST_HEAD(&set->head); + list_add_tail(&set->head, &mode_set_list); + } + + /* + * the mode_unset_list store the unconnected route, if route's crtc + * isn't used, we should close it. + */ + list_for_each_entry_safe(unset, tmp, &mode_unset_list, head) { + struct rockchip_drm_mode_set *tmp_set; + int find_used_crtc = 0; + + list_for_each_entry_safe(set, tmp_set, &mode_set_list, head) { + if (set->crtc == unset->crtc) { + find_used_crtc = 1; + continue; + } + } + + if (!find_used_crtc) { + struct drm_crtc *crtc = unset->crtc; + int pipe = drm_crtc_index(crtc); + struct rockchip_drm_private *priv = + drm_dev->dev_private; + + if (unset->hdisplay && unset->vdisplay) { + if (priv->crtc_funcs[pipe] && + priv->crtc_funcs[pipe]->loader_protect) + priv->crtc_funcs[pipe]->loader_protect(crtc, true); + priv->crtc_funcs[pipe]->crtc_close(crtc); + if (priv->crtc_funcs[pipe] && + priv->crtc_funcs[pipe]->loader_protect) + priv->crtc_funcs[pipe]->loader_protect(crtc, false); + } + } + + list_del(&unset->head); + kfree(unset); + } + + if (list_empty(&mode_set_list)) { + dev_warn(drm_dev->dev, "can't not find any logo display\n"); + ret = -ENXIO; + goto err_free_state; + } + + /* + * The state save initial devices status, swap the state into + * drm devices as old state, so if new state come, can compare + * with this state to judge which status need to update. + */ + WARN_ON(drm_atomic_helper_swap_state(state, false)); + drm_atomic_state_put(state); + old_state = drm_atomic_helper_duplicate_state(drm_dev, + mode_config->acquire_ctx); + if (IS_ERR(old_state)) { + dev_err(drm_dev->dev, "failed to duplicate atomic state for logo display\n"); + ret = PTR_ERR_OR_ZERO(old_state); + goto err_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 for logo display\n"); + ret = PTR_ERR_OR_ZERO(state); + goto err_free_old_state; + } + 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)); + + for (i = 0; i < state->num_connector; i++) { + if (state->connectors[i].new_state->connector->status != + connector_status_connected) + state->connectors[i].new_state->best_encoder = NULL; + } + + ret = drm_atomic_commit(state); + /** + * todo + * drm_atomic_clean_old_fb(drm_dev, plane_mask, ret); + */ + + list_for_each_entry_safe(set, tmp, &mode_set_list, head) { + list_del(&set->head); + kfree(set); + } + + /* + * Is possible get deadlock here? + */ + WARN_ON(ret == -EDEADLK); + + if (ret) { + /* + * restore display status if atomic commit failed. + */ + WARN_ON(drm_atomic_helper_swap_state(old_state, false)); + goto err_free_state; + } + + rockchip_free_loader_memory(drm_dev); + drm_atomic_state_put(old_state); + drm_atomic_state_put(state); + + private->loader_protect = true; + drm_modeset_unlock_all(drm_dev); + + drm_for_each_crtc(crtc, drm_dev) { + struct drm_fb_helper *helper = private->fbdev_helper; + struct rockchip_crtc_state *s = NULL; + + if (!helper) + break; + + s = to_rockchip_crtc_state(crtc->state); + if (is_support_hotplug(s->output_type)) + drm_framebuffer_get(helper->fb); + } + + return; +err_free_old_state: + drm_atomic_state_put(old_state); +err_free_state: + drm_atomic_state_put(state); +err_unlock: + drm_modeset_unlock_all(drm_dev); + if (ret) + dev_err(drm_dev->dev, "failed to show kernel logo\n"); +} + +#ifndef MODULE +static const char *const loader_protect_clocks[] __initconst = { + "hclk_vio", + "hclk_vop", + "hclk_vopb", + "hclk_vopl", + "aclk_vio", + "aclk_vio0", + "aclk_vio1", + "aclk_vop", + "aclk_vopb", + "aclk_vopl", + "aclk_vo_pre", + "aclk_vio_pre", + "dclk_vop", + "dclk_vop0", + "dclk_vop1", + "dclk_vopb", + "dclk_vopl", +}; + +static struct clk **loader_clocks __initdata; +static int __init rockchip_clocks_loader_protect(void) +{ + int nclocks = ARRAY_SIZE(loader_protect_clocks); + struct clk *clk; + int i; + + loader_clocks = kcalloc(nclocks, sizeof(void *), GFP_KERNEL); + if (!loader_clocks) + return -ENOMEM; + + for (i = 0; i < nclocks; i++) { + clk = __clk_lookup(loader_protect_clocks[i]); + + if (clk) { + loader_clocks[i] = clk; + clk_prepare_enable(clk); + } + } + + return 0; +} +arch_initcall_sync(rockchip_clocks_loader_protect); + +static int __init rockchip_clocks_loader_unprotect(void) +{ + int i; + + if (!loader_clocks) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(loader_protect_clocks); i++) { + struct clk *clk = loader_clocks[i]; + + if (clk) + clk_disable_unprepare(clk); + } + kfree(loader_clocks); + + return 0; +} +late_initcall_sync(rockchip_clocks_loader_unprotect); +#endif diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_logo.h b/drivers/gpu/drm/rockchip/rockchip_drm_logo.h new file mode 100644 index 000000000000..dd28a9917d93 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_logo.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2021 Rockchip Electronics Co., Ltd. + * Author: Sandy Huang + */ + +#ifndef ROCKCHIP_DRM_LOGO_H +#define ROCKCHIP_DRM_LOGO_H + +struct rockchip_drm_mode_set { + struct list_head head; + struct drm_framebuffer *fb; + struct rockchip_drm_sub_dev *sub_dev; + struct drm_crtc *crtc; + struct drm_display_mode *mode; + int clock; + int hdisplay; + int vdisplay; + int vrefresh; + int flags; + int picture_aspect_ratio; + int crtc_hsync_end; + int crtc_vsync_end; + + int left_margin; + int right_margin; + int top_margin; + int bottom_margin; + + unsigned int brightness; + unsigned int contrast; + unsigned int saturation; + unsigned int hue; + + bool mode_changed; + int ratio; +}; + +void rockchip_drm_show_logo(struct drm_device *drm_dev); + +#endif