// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "meson_fbdev.h" #ifdef CONFIG_AMLOGIC_DRM_USE_ION #include "meson_gem.h" #include "meson_fb.h" #endif #include "meson_drv.h" #include "meson_vpu.h" #include "meson_async_atomic.h" #include "meson_vpu_pipeline.h" #include "meson_crtc.h" #include "meson_sysfs.h" #include "meson_writeback.h" #include "meson_logo.h" #include "meson_plane.h" #include "meson_tx_helper.h" #include #include #include #include #include #include "meson_hdmi.h" #include "meson_user_fence.h" #define DRIVER_NAME "meson" #define DRIVER_DESC "Amlogic Meson DRM driver" #define MESON_VERSION_MAJOR 2 #define MESON_VERSION_MINOR 0 #define MAX_CONNECTOR_NUM (3) static int skip_logo; int recovery_mode; struct meson_drm_param am_drm_param; static int check_reboot_mode(char *str) { if (strncmp("qui", str, 3) == 0 || strncmp("rtc_qui", str, 7) == 0) skip_logo = 1; else skip_logo = 0; if (strncmp("factory", str, 7) == 0 || strncmp("recovery", str, 8) == 0) { recovery_mode = 1; } else { recovery_mode = 0; } return 1; } __setup("reboot_mode=", check_reboot_mode); static const struct drm_mode_config_funcs meson_mode_config_funcs = { .atomic_check = drm_atomic_helper_check, .atomic_commit = meson_atomic_commit, .atomic_state_free = meson_atomic_state_free, #ifdef CONFIG_AMLOGIC_DRM_USE_ION .fb_create = am_meson_fb_create, #else .fb_create = drm_gem_fb_create, #endif .get_format_info = am_meson_get_format_info, }; static const struct drm_mode_config_helper_funcs meson_mode_config_helpers = { .atomic_commit_tail = meson_atomic_helper_commit_tail, }; int am_meson_get_vrr_range_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { int num_group = 0; u32 conn_id; struct drm_connector *connector; struct drm_vrr_mode_groups *groups = data; struct drm_vrr_mode_group *group; int i = 0; conn_id = groups->conn_id; connector = drm_connector_lookup(dev, file_priv, conn_id); if (!connector) return -ENOENT; switch (connector->connector_type) { #if !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI) || !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI_MODERN) case DRM_MODE_CONNECTOR_HDMIA: num_group = am_meson_hdmi_get_vrr_range(dev, data, file_priv); break; #endif #ifndef CONFIG_AMLOGIC_ZAPPER_CUT case DRM_MODE_CONNECTOR_LVDS: num_group = am_meson_lcd_get_vrr_range(connector, groups->groups, MAX_VRR_MODE_GROUP); break; case DRM_MODE_CONNECTOR_eDP: num_group = am_meson_eDP_get_vrr_range(connector, groups->groups, MAX_VRR_MODE_GROUP); break; #endif default: return -ENOENT; } if (!num_group) { DRM_DEBUG("get vrr error or not support qms\n"); return -EINVAL; } groups->num = num_group; for (i = 0; i < num_group; i++) { group = &groups->groups[i]; DRM_DEBUG("%d, %d, %d, %d\n", group->vrr_max, group->vrr_min, group->width, group->height); } drm_connector_put(connector); return 0; } int am_meson_set_connector_force_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_connector *connector; struct drm_connector_list_iter conn_iter; enum drm_connector_force old_force; struct drm_meson_connector_info *connector_info = data; int ret, tmp = 0; char *name = connector_info->name; char *status = connector_info->status; drm_connector_list_iter_begin(dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { if (!strcmp(connector->name, name)) { tmp = 1; break; } } drm_connector_list_iter_end(&conn_iter); if (!tmp) return -EFAULT; ret = mutex_lock_interruptible(&dev->mode_config.mutex); if (ret) return ret; old_force = connector->force; if (!strcmp(status, "detect")) connector->force = 0; else if (!strcmp(status, "on")) connector->force = DRM_FORCE_ON; else if (!strcmp(status, "on-digital")) connector->force = DRM_FORCE_ON_DIGITAL; else if (!strcmp(status, "off")) connector->force = DRM_FORCE_OFF; else ret = -EINVAL; if (old_force != connector->force || !connector->force) { DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d or reprobing\n", connector->base.id, connector->name, old_force, connector->force); connector->funcs->fill_modes(connector, dev->mode_config.max_width, dev->mode_config.max_height); } mutex_unlock(&dev->mode_config.mutex); return ret ? ret : 0; } #if !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI) || !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI_MODERN) int am_meson_get_hdmitx_diag_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { am_meson_hdmi_get_hdmitx_diag(dev, data, file_priv); return 0; } #endif int meson_mode_testattr_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { int ret; struct drm_mode_object *mo; struct drm_connector *connector; struct drm_mode_test_attr *f = data; mo = drm_mode_object_find(dev, file_priv, f->conn_id, DRM_MODE_OBJECT_CONNECTOR); if (!mo) { DRM_ERROR("can not find obj\n"); return -EINVAL; } connector = obj_to_connector(mo); if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { #if !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI) ret = am_meson_mode_testattr_ioctl(dev, data, file_priv); drm_mode_object_put(mo); return ret; #endif } #if defined(CONFIG_AMLOGIC_DRM_DP) || defined(CONFIG_AMLOGIC_DRM_EDP) || \ !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI_MODERN) ret = meson_tx_mode_testattr_ioctl(dev, data, file_priv); #else DRM_ERROR("invalid config\n"); ret = -EINVAL; #endif drm_mode_object_put(mo); return ret; } static const struct drm_ioctl_desc meson_ioctls[] = { #ifdef CONFIG_AMLOGIC_DRM_USE_ION DRM_IOCTL_DEF_DRV(MESON_GEM_CREATE, am_meson_gem_create_ioctl, DRM_RENDER_ALLOW), #endif DRM_IOCTL_DEF_DRV(MESON_ASYNC_ATOMIC, meson_async_atomic_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_TESTATTR, meson_mode_testattr_ioctl, 0), #if !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI) || !defined(CONFIG_AMLOGIC_DRM_CUT_HDMI_MODERN) DRM_IOCTL_DEF_DRV(MESON_GET_HDMITX_DIAG, am_meson_get_hdmitx_diag_ioctl, 0), #endif DRM_IOCTL_DEF_DRV(MESON_GET_VRR_RANGE, am_meson_get_vrr_range_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_RMFB, am_meson_mode_rmfb_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_ADDFB2, am_meson_mode_addfb2_ioctl, 0), #if IS_ENABLED(CONFIG_SYNC_FILE) DRM_IOCTL_DEF_DRV(MESON_DMABUF_EXPORT_SYNC_FILE, am_meson_dmabuf_export_sync_file_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_CREAT_PRESENT_FENCE, meson_crtc_creat_present_fence_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_CREATE_FENCE, meson_create_fence_ioctl, DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(MESON_SIGNAL_FENCE, meson_signal_fence_ioctl, DRM_RENDER_ALLOW), #endif DRM_IOCTL_DEF_DRV(MESON_MUTE_PLANE, meson_plane_mute_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_SET_CONNECTOR_FORCE, am_meson_set_connector_force_ioctl, 0), DRM_IOCTL_DEF_DRV(MESON_PRIME_HANDLE_TO_FD, am_meson_prime_handle_to_fd, DRM_RENDER_ALLOW) }; DEFINE_DRM_GEM_FOPS(meson_drm_fops); static struct drm_driver meson_driver = { /*driver_features setting move to probe functions*/ .driver_features = 0, #ifdef CONFIG_DEBUG_FS .debugfs_init = meson_debugfs_init, #endif #ifdef CONFIG_AMLOGIC_DRM_USE_ION /* PRIME Ops */ .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_prime_import = am_meson_drm_gem_prime_import, /* * If gem_prime_import_sg_table is NULL,only buffer created * by meson driver can be imported ok. */ .gem_prime_import_sg_table = am_meson_gem_prime_import_sg_table, /* GEM Ops */ .dumb_create = am_meson_gem_dumb_create, .dumb_map_offset = am_meson_gem_dumb_map_offset, .ioctls = meson_ioctls, .num_ioctls = ARRAY_SIZE(meson_ioctls), #else /* PRIME Ops */ .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_prime_import = drm_gem_prime_import, .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, .gem_prime_mmap = drm_gem_prime_mmap, /* GEM Ops */ .dumb_create = drm_gem_cma_dumb_create, .dumb_map_offset = drm_gem_dumb_map_offset, .gem_free_object_unlocked = drm_gem_cma_free_object, .gem_vm_ops = &drm_gem_cma_vm_ops, #endif /* Misc */ .fops = &meson_drm_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = "20220603", .major = MESON_VERSION_MAJOR, .minor = MESON_VERSION_MINOR, .minor = 0, }; static int meson_worker_thread_init(struct meson_drm *priv, unsigned int num_crtcs) { int i, ret; struct sched_param param; struct kthread_worker *worker; char thread_name[16]; struct meson_drm_thread *drm_thread; struct drm_device *drm = priv->drm; param.sched_priority = 16; for (i = 0; i < num_crtcs; i++) { drm_thread = &priv->commit_thread[i]; worker = &drm_thread->worker; kthread_init_worker(worker); drm_thread->dev = drm; snprintf(thread_name, 16, "crtc%d_commit", i); drm_thread->thread = kthread_run(kthread_worker_fn, worker, thread_name); if (IS_ERR(drm_thread->thread)) { DRM_ERROR("failed to create commit thread\n"); priv->commit_thread[0].thread = NULL; return -1; } ret = sched_setscheduler_nocheck(drm_thread->thread, SCHED_FIFO, ¶m); if (ret) DRM_ERROR("failed to set priority\n"); } return 0; } static void meson_parse_max_config(struct device_node *node, u32 *max_width, u32 *max_height) { int ret; u32 sizes[2]; ret = of_property_read_u32_array(node, "max_sizes", sizes, 2); if (ret) { *max_width = 4096; *max_height = 4096; } else { *max_width = sizes[0]; *max_height = sizes[1]; } } static void meson_encoder_fill_possible_clone(struct drm_device *dev) { struct drm_encoder *encoder; u32 encoder_mask = 0; drm_for_each_encoder(encoder, dev) encoder_mask |= drm_encoder_mask(encoder); drm_for_each_encoder(encoder, dev) encoder->possible_clones = encoder_mask; } static int am_meson_drm_bind(struct device *dev) { struct meson_drm *priv; struct drm_device *drm; struct platform_device *pdev = to_platform_device(dev); u32 max_width, max_height; int logo_skip, ret = 0; meson_driver.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_RENDER; drm = drm_dev_alloc(&meson_driver, dev); if (!drm) return -ENOMEM; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) { ret = -ENOMEM; goto err_free1; } drm->dev_private = priv; priv->drm = drm; priv->dev = dev; /*bound data to be used by component driver.*/ priv->bound_data.drm = drm; priv->bound_data.connector_component_bind = meson_connector_dev_bind; priv->bound_data.connector_component_unbind = meson_connector_dev_unbind; priv->osd_occupied_index = -1; dev_set_drvdata(dev, priv); init_waitqueue_head(&priv->wq_shut_ctrl); priv->shutdown_on = false; #ifdef CONFIG_AMLOGIC_DRM_USE_ION ret = am_meson_gem_create(priv); if (ret) goto err_free2; #endif drm_mode_config_init(drm); vpu_topology_init(pdev, priv); /* init meson config before bind other component, * other component may use it. */ meson_parse_max_config(dev->of_node, &max_width, &max_height); drm->mode_config.max_width = max_width; drm->mode_config.max_height = max_height; drm->mode_config.funcs = &meson_mode_config_funcs; drm->mode_config.helper_private = &meson_mode_config_helpers; drm->mode_config.fb_modifiers_not_supported = !true; if (recovery_mode) priv->recovery_mode = true; /* Try to bind all sub drivers. */ ret = component_bind_all(dev, &priv->bound_data); if (ret) goto err_gem; /* Writeback should be registered after HDMI registration. */ ret = am_meson_writeback_create(drm); if (ret) goto err_gem; DRM_DEBUG("mode_config crtc number:%d\n", drm->mode_config.num_crtc); ret = meson_worker_thread_init(priv, drm->mode_config.num_crtc); if (ret) goto err_unbind_all; ret = drm_vblank_init(drm, drm->mode_config.num_crtc); if (ret) goto err_unbind_all; meson_encoder_fill_possible_clone(drm); drm_mode_config_reset(drm); /* * enable drm irq mode. * - with irq_enabled = true, we can use the vblank feature. */ priv->irq_enabled = true; am_drm_param.flush_time = 3; am_drm_param.osdscaler_v_filter_mode = -1; am_drm_param.osdscaler_h_filter_mode = -1; /* Add a 5% safety factor by default*/ am_drm_param.osdscaler_safety_factor = 105; am_drm_param.osd_hold_line = 0x08; /* same as VIU1_DEFAULT_HOLD_LINE in osd mif */ am_drm_param.async_commit_ioctl_threshold = 5; am_drm_param.fbdev_pan_display_threshold = 5; drm_kms_helper_poll_init(drm); logo_skip = 0; ret = of_property_read_u32(dev->of_node, "logo_skip", &logo_skip); if ((!ret && logo_skip == 1) || skip_logo) DRM_INFO("skip logo commit.logo_skip:%d,skip_logo:%d,ret:%d\n", logo_skip, skip_logo, ret); else am_meson_logo_init(drm); #ifdef CONFIG_AMLOGIC_DRM_EMULATE_FBDEV ret = am_meson_drm_fbdev_init(drm); if (ret) goto err_poll_fini; #endif ret = drm_dev_register(drm, 0); if (ret) goto err_fbdev_fini; ret = meson_drm_sysfs_register(drm); if (ret) goto err_drm_dev_unregister; return 0; err_drm_dev_unregister: drm_dev_unregister(drm); err_fbdev_fini: #ifdef CONFIG_AMLOGIC_DRM_EMULATE_FBDEV am_meson_drm_fbdev_fini(drm); err_poll_fini: #endif drm_kms_helper_poll_fini(drm); priv->irq_enabled = false; err_unbind_all: component_unbind_all(dev, drm); err_gem: drm_mode_config_cleanup(drm); #ifdef CONFIG_AMLOGIC_DRM_USE_ION am_meson_gem_cleanup(drm->dev_private); err_free2: #endif drm->dev_private = NULL; dev_set_drvdata(dev, NULL); err_free1: drm_dev_put(drm); return ret; } static void am_meson_drm_unbind(struct device *dev) { struct drm_device *drm = dev_get_drvdata(dev); struct meson_drm *priv = drm->dev_private; struct meson_drm_thread *drm_thread; int i; /* flush kworker of commit thread and stop the thread */ for (i = 0; i < drm->mode_config.num_crtc; i++) { drm_thread = &priv->commit_thread[i]; if (drm_thread->thread) { kthread_flush_worker(&drm_thread->worker); kthread_stop(drm_thread->thread); drm_thread->thread = NULL; } } meson_drm_sysfs_unregister(drm); drm_dev_unregister(drm); #ifdef CONFIG_AMLOGIC_DRM_EMULATE_FBDEV am_meson_drm_fbdev_fini(drm); #endif drm_kms_helper_poll_fini(drm); priv->irq_enabled = false; component_unbind_all(dev, drm); drm_mode_config_cleanup(drm); #ifdef CONFIG_AMLOGIC_DRM_USE_ION am_meson_gem_cleanup(drm->dev_private); #endif drm->dev_private = NULL; dev_set_drvdata(dev, NULL); drm_dev_put(drm); } static int compare_of(struct device *dev, void *data) { struct device_node *np = data; return dev->of_node == np; } static void meson_drm_match_remove(struct device *dev) { struct device_link *link, *tmp; list_for_each_entry_safe(link, tmp, &dev->links.consumers, s_node) device_link_del(link); } static void am_meson_add_endpoints(struct device *dev, struct component_match **match, struct device_node *port) { struct device_node *ep, *remote; struct platform_device *pdev; struct device_link *link; for_each_child_of_node(port, ep) { remote = of_graph_get_remote_port_parent(ep); if (!remote || !of_device_is_available(remote)) { of_node_put(remote); continue; } else if (!of_device_is_available(remote->parent)) { of_node_put(remote); continue; } pdev = of_find_device_by_node(remote); if (pdev) { link = device_link_add(dev, &pdev->dev, DL_FLAG_STATELESS); if (!link) { dev_warn(dev, "device_link_add FAILED: supplier=%s consumer=%s\n", dev_name(&pdev->dev), dev_name(dev)); } put_device(&pdev->dev); } component_match_add(dev, match, compare_of, remote); of_node_put(remote); } } static const struct component_master_ops am_meson_drm_ops = { .bind = am_meson_drm_bind, .unbind = am_meson_drm_unbind, }; static bool am_meson_drv_use_osd(void) { struct device_node *node; const char *str; int ret; node = of_find_node_by_path("/meson-fb"); if (node) { ret = of_property_read_string(node, "status", &str); if (ret) { DRM_INFO("get 'status' failed:%d\n", ret); return false; } if (strcmp(str, "okay") && strcmp(str, "ok")) { DRM_INFO("device %s status is %s\n", node->name, str); } else { DRM_INFO("device %s status is %s\n", node->name, str); return true; } } return false; } static int am_meson_drv_probe_prune(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct meson_drm *priv; struct drm_device *drm; int ret; /*driver_features reset to DRIVER_GEM | DRIVER_PRIME, for prune drm*/ meson_driver.driver_features = DRIVER_GEM; drm = drm_dev_alloc(&meson_driver, dev); if (!drm) return -ENOMEM; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) { ret = -ENOMEM; goto err_free1; } drm->dev_private = priv; priv->drm = drm; priv->dev = dev; platform_set_drvdata(pdev, priv); #ifdef CONFIG_AMLOGIC_DRM_USE_ION ret = am_meson_gem_create(priv); if (ret) goto err_free2; #endif ret = drm_dev_register(drm, 0); if (ret) goto err_gem; return 0; err_gem: #ifdef CONFIG_AMLOGIC_DRM_USE_ION am_meson_gem_cleanup(drm->dev_private); err_free2: #endif drm->dev_private = NULL; platform_set_drvdata(pdev, NULL); err_free1: drm_dev_put(drm); return ret; } static int am_meson_drv_remove_prune(struct platform_device *pdev) { struct drm_device *drm = platform_get_drvdata(pdev); drm_dev_unregister(drm); #ifdef CONFIG_AMLOGIC_DRM_USE_ION am_meson_gem_cleanup(drm->dev_private); #endif drm->dev_private = NULL; platform_set_drvdata(pdev, NULL); drm_dev_put(drm); return 0; } static int am_meson_drv_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct device_node *port; struct component_match *match = NULL; int i; if (am_meson_drv_use_osd()) return am_meson_drv_probe_prune(pdev); if (!np) return -ENODEV; /* * Bind the crtc ports first, so that * drm_of_find_possible_crtcs called from encoder .bind callbacks * works as expected. */ for (i = 0;; i++) { port = of_parse_phandle(np, "ports", i); if (!port) break; if (!of_device_is_available(port->parent)) { of_node_put(port); continue; } component_match_add(dev, &match, compare_of, port->parent); of_node_put(port); } if (i == 0) { dev_err(dev, "missing 'ports' property.\n"); return -ENODEV; } if (!match) { dev_err(dev, "No available vout found for display-subsystem.\n"); return -ENODEV; } /* * For each bound crtc, bind the encoders attached to its * remote endpoint. */ for (i = 0;; i++) { port = of_parse_phandle(np, "ports", i); if (!port) break; if (!of_device_is_available(port->parent)) { of_node_put(port); continue; } am_meson_add_endpoints(dev, &match, port); of_node_put(port); } if (IS_ERR(match)) meson_drm_match_remove(dev); #ifdef CONFIG_AMLOGIC_VOUT_SERVE disable_vout_mode_set_sysfs(); #endif return component_master_add_with_match(dev, &am_meson_drm_ops, match); } static void am_meson_drv_remove(struct platform_device *pdev) { if (am_meson_drv_use_osd()) { am_meson_drv_remove_prune(pdev); return; } component_master_del(&pdev->dev, &am_meson_drm_ops); } static const struct of_device_id am_meson_drm_dt_match[] = { { .compatible = "amlogic, drm-subsystem" }, {} }; MODULE_DEVICE_TABLE(of, am_meson_drm_dt_match); static void am_meson_drm_fb_suspend(struct drm_device *drm) { #ifdef CONFIG_AMLOGIC_DRM_EMULATE_FBDEV int i; struct meson_drm_fbdev *fbdev; struct meson_drm *priv = drm->dev_private; for (i = 0; i < MESON_MAX_OSD; i++) { fbdev = priv->osd_fbdevs[i]; if (fbdev) drm_fb_helper_set_suspend(&fbdev->base, 1); } #endif } static void am_meson_drm_fb_resume(struct drm_device *drm) { #ifdef CONFIG_AMLOGIC_DRM_EMULATE_FBDEV int i; struct meson_drm_fbdev *fbdev; struct meson_drm *priv = drm->dev_private; for (i = 0; i < MESON_MAX_OSD; i++) { fbdev = priv->osd_fbdevs[i]; if (fbdev) drm_fb_helper_set_suspend(&fbdev->base, 0); } #endif } static int am_meson_drm_pm_suspend(struct device *dev) { struct drm_device *drm; struct meson_drm *priv; priv = dev_get_drvdata(dev); if (!priv) { DRM_ERROR("Failed to get meson drm!\n"); return 0; } drm = priv->drm; if (!drm) { DRM_ERROR("Failed to get drm device!\n"); return 0; } priv->pm_state = DRM_PM_SUSPEND; drm_kms_helper_poll_disable(drm); am_meson_drm_fb_suspend(drm); priv->state = drm_atomic_helper_suspend(drm); priv->pm_state = DRM_PM_NONE; if (IS_ERR(priv->state)) { am_meson_drm_fb_resume(drm); drm_kms_helper_poll_enable(drm); DRM_INFO("%s: drm_atomic_helper_suspend fail.\n", __func__); return PTR_ERR(priv->state); } DRM_INFO("drm suspend done\n"); return 0; } static int am_meson_drm_pm_freeze(struct device *dev) { struct meson_drm *priv; int ret; priv = dev_get_drvdata(dev); if (!priv) { DRM_ERROR("Failed to get meson drm!\n"); return 0; } if (priv->logo && priv->logo->is_cma) { am_meson_logo_cma_alloc(dev, 0); am_meson_logo_cma_mem_reset_zero(priv->logo); } ret = am_meson_drm_pm_suspend(dev); DRM_INFO("drm freeze done\n"); return ret; } static int am_meson_drm_pm_resume(struct device *dev) { struct drm_device *drm; struct meson_drm *priv; priv = dev_get_drvdata(dev); if (!priv) { DRM_ERROR("Failed to get meson drm!\n"); return 0; } drm = priv->drm; if (!drm) { DRM_ERROR("Failed to get drm device!\n"); return 0; } /* *for save power consumption, suspend will turn off vpu power, we need to *do block register init again. */ vpu_pipeline_resume_init(priv->pipeline); drm_atomic_helper_resume(drm, priv->state); am_meson_drm_fb_resume(drm); drm_kms_helper_poll_enable(drm); DRM_INFO("drm resume done\n"); return 0; } static int am_meson_drm_pm_restore(struct device *dev) { struct meson_drm *priv; int ret; priv = dev_get_drvdata(dev); if (!priv) { DRM_ERROR("Failed to get meson drm!\n"); return 0; } ret = am_meson_drm_pm_resume(dev); if (priv->logo) priv->logo->is_std = 1; DRM_INFO("drm restore done\n"); return ret; } static const struct dev_pm_ops am_meson_drm_pm_ops = { .suspend = am_meson_drm_pm_suspend, .resume = am_meson_drm_pm_resume, .freeze = am_meson_drm_pm_freeze, .thaw = am_meson_drm_pm_restore, .poweroff = am_meson_drm_pm_suspend, .restore = am_meson_drm_pm_restore, }; static void am_meson_drv_shutdown(struct platform_device *pdev) { struct drm_modeset_acquire_ctx ctx; struct drm_device *dev; struct drm_crtc *crtc; struct am_meson_crtc *amcrtc; struct meson_drm_thread *drm_thread; struct meson_drm *priv; int ret; int i; priv = dev_get_drvdata(&pdev->dev); if (!priv) { DRM_ERROR("priv is NULL!\n"); return; } dev = priv->drm; /* prevent drm_wait_vblank_ioctl on waiting event*/ drm_atomic_helper_shutdown(dev); DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); /* suspend atomic ioctl thread */ drm_for_each_crtc(crtc, dev) { amcrtc = to_am_meson_crtc(crtc); disable_irq(amcrtc->irq); } priv->shutdown_on = true; /* prevent drm_wait_vblank_ioctl in its entry to avoid late lock */ dev->num_crtcs = 0; DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); /* flush kworker of commit thread and stop the thread */ DRM_INFO("%s: try to flush worker\n", __func__); for (i = 0; i < priv->num_crtcs; i++) { drm_thread = &priv->commit_thread[i]; if (drm_thread->thread) { kthread_flush_worker(&drm_thread->worker); kthread_stop(drm_thread->thread); drm_thread->thread = NULL; } } } static struct platform_driver am_meson_drm_platform_driver = { .probe = am_meson_drv_probe, .remove = am_meson_drv_remove, .shutdown = am_meson_drv_shutdown, .driver = { .owner = THIS_MODULE, .name = DRIVER_NAME, .of_match_table = am_meson_drm_dt_match, .pm = &am_meson_drm_pm_ops, }, }; int __init am_meson_drm_init(void) { return platform_driver_register(&am_meson_drm_platform_driver); } void __exit am_meson_drm_exit(void) { platform_driver_unregister(&am_meson_drm_platform_driver); } #ifndef MODULE module_init(am_meson_drm_init); module_exit(am_meson_drm_exit); #endif MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL");