// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "afk.h" #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" #include "parser.h" #include "trace.h" #define APPLE_DCP_COPROC_CPU_CONTROL 0x44 #define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) #define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) static bool show_notch; module_param(show_notch, bool, 0644); MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { unsigned long flags; spin_lock_irqsave(&crtc->base.dev->event_lock, flags); if (crtc->event) { drm_crtc_send_vblank_event(&crtc->base, crtc->event); crtc->event = NULL; } spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } void dcp_set_dimensions(struct apple_dcp *dcp) { int i; int width_mm = dcp->width_mm; int height_mm = dcp->height_mm; if (width_mm == 0 || height_mm == 0) { width_mm = dcp->panel.width_mm; height_mm = dcp->panel.height_mm; } /* Set the connector info */ if (dcp->connector) { struct drm_connector *connector = &dcp->connector->base; mutex_lock(&connector->dev->mode_config.mutex); connector->display_info.width_mm = width_mm; connector->display_info.height_mm = height_mm; mutex_unlock(&connector->dev->mode_config.mutex); } /* * Fix up any probed modes. Modes are created when parsing * TimingElements, dimensions are calculated when parsing * DisplayAttributes, and TimingElements may be sent first */ for (i = 0; i < dcp->nr_modes; ++i) { dcp->modes[i].mode.width_mm = width_mm; dcp->modes[i].mode.height_mm = height_mm; } } bool dcp_has_panel(struct apple_dcp *dcp) { return dcp->panel.width_mm > 0; } /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks * send a vblank event via a workqueue. */ static void dcp_delayed_vblank(struct work_struct *work) { struct apple_dcp *dcp; dcp = container_of(work, struct apple_dcp, vblank_wq); mdelay(5); dcp_drm_crtc_vblank(dcp->crtc); } static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) { struct apple_dcp *dcp = cookie; trace_dcp_recv_msg(dcp, endpoint, message); switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); case SYSTEM_ENDPOINT: afk_receive_message(dcp->systemep, message); return; case DISP0_ENDPOINT: afk_receive_message(dcp->ibootep, message); return; case DPTX_ENDPOINT: afk_receive_message(dcp->dptxep, message); return; default: WARN(endpoint, "unknown DCP endpoint %hhu\n", endpoint); } } static void dcp_rtk_crashed(void *cookie) { struct apple_dcp *dcp = cookie; dcp->crashed = true; dev_err(dcp->dev, "DCP has crashed\n"); if (dcp->connector) { dcp->connector->connected = 0; schedule_work(&dcp->connector->hotplug_wq); } complete(&dcp->start_done); } static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) { struct apple_dcp *dcp = cookie; if (bfr->iova) { struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); phys_addr_t phy_addr; if (!domain) return -ENOMEM; // TODO: get map from device-tree phy_addr = iommu_iova_to_phys(domain, bfr->iova); if (!phy_addr) return -ENOMEM; // TODO: verify phy_addr, cache attribute bfr->buffer = memremap(phy_addr, bfr->size, MEMREMAP_WB); if (!bfr->buffer) return -ENOMEM; bfr->is_mapped = true; dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx\n", (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); } else { bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL); if (!bfr->buffer) return -ENOMEM; dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx\n", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } return 0; } static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) { struct apple_dcp *dcp = cookie; if (bfr->is_mapped) memunmap(bfr->buffer); else dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); } static struct apple_rtkit_ops rtkit_ops = { .crashed = dcp_rtk_crashed, .recv_message = dcp_recv_msg, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) { trace_dcp_send_msg(dcp, endpoint, message); apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, true); } int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { struct platform_device *pdev = to_apple_crtc(crtc)->dcp; struct apple_dcp *dcp = platform_get_drvdata(pdev); struct drm_plane_state *new_state; struct drm_plane *plane; struct drm_crtc_state *crtc_state; int plane_idx, plane_count = 0; bool needs_modeset; if (dcp->crashed) return -EINVAL; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; if (!needs_modeset && !dcp->connector->connected) { dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n"); return -EINVAL; } for_each_new_plane_in_state(state, plane, new_state, plane_idx) { /* skip planes not for this crtc */ if (new_state->crtc != crtc) continue; plane_count += 1; } if (plane_count > DCP_MAX_PLANES) { dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!\n"); return -EINVAL; } return 0; } EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); int dcp_get_connector_type(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); return (dcp->connector_type); } EXPORT_SYMBOL_GPL(dcp_get_connector_type); #define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000) static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { int ret = 0; if (!dcp->phy) { dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; } dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port); ret = -ENODEV; goto out_unlock; } if (dcp->dptxport[port].connected) goto out_unlock; reinit_completion(&dcp->dptxport[port].linkcfg_completion); dcp->dptxport[port].atcphy = dcp->phy; dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); dptxport_request_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = true; mutex_unlock(&dcp->hpd_mutex); ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, DPTX_CONNECT_TIMEOUT); if (ret < 0) dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n", port, ret); else dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); usleep_range(5, 10); return 0; out_unlock: mutex_unlock(&dcp->hpd_mutex); return ret; } static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) { dptxport_release_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = false; } mutex_unlock(&dcp->hpd_mutex); return 0; } static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { struct apple_dcp *dcp = data; bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); /* do nothing on disconnect and trust that dcp detects it itself. * Parallel disconnect HPDs result drm disabling the CRTC even when it * should not. * The interrupt should be changed to rising but for now the disconnect * IRQs might be helpful for debugging. */ dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected); if (connected) dcp_dptx_connect(dcp, 0); return IRQ_HANDLED; } void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { struct apple_dcp *dcp = platform_get_drvdata(pdev); dcp->crtc = crtc; dcp->connector = connector; } EXPORT_SYMBOL_GPL(dcp_link); int dcp_start(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; init_completion(&dcp->start_done); /* start RTKit endpoints */ ret = systemep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n", ret); ret = dptxep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n", ret); else if (dcp->dptxport[0].enabled) { bool connected; /* force disconnect on start - necessary if the display * is already up from m1n1 */ dptxport_set_hpd(dcp->dptxport[0].service, false); dptxport_release_display(dcp->dptxport[0].service); usleep_range(10 * USEC_PER_MSEC, 25 * USEC_PER_MSEC); connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); // necessary on j473/j474 but not on j314c if (connected) dcp_dptx_connect(dcp, 0); } } else if (dcp->phy) dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); ret = iomfb_start_rtkit(dcp); if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); return ret; } EXPORT_SYMBOL(dcp_start); static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); return 0; } int dcp_wait_ready(struct platform_device *pdev, u64 timeout) { struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; if (dcp->crashed) return -ENODEV; if (dcp->active) return dcp_enable_dp2hdmi_hpd(dcp);; if (timeout <= 0) return -ETIMEDOUT; ret = wait_for_completion_timeout(&dcp->start_done, timeout); if (ret < 0) return ret; if (dcp->crashed) return -ENODEV; if (dcp->active) dcp_enable_dp2hdmi_hpd(dcp); return dcp->active ? 0 : -ETIMEDOUT; } EXPORT_SYMBOL(dcp_wait_ready); static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) { switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: iomfb_sleep_v12_3(dcp); break; case DCP_FIRMWARE_V_13_5: iomfb_sleep_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); break; } } void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); if (connected) dcp_dptx_connect(dcp, 0); } switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: iomfb_poweron_v12_3(dcp); break; case DCP_FIRMWARE_V_13_5: iomfb_poweron_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); break; } } EXPORT_SYMBOL(dcp_poweron); void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); break; case DCP_FIRMWARE_V_13_5: iomfb_poweroff_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); break; } if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); if (!connected) dcp_dptx_disconnect(dcp, 0); } } EXPORT_SYMBOL(dcp_poweroff); static void dcp_work_register_backlight(struct work_struct *work) { int ret; struct apple_dcp *dcp; dcp = container_of(work, struct apple_dcp, bl_register_wq); mutex_lock(&dcp->bl_register_mutex); if (dcp->brightness.bl_dev) goto out_unlock; /* try to register backlight device, */ ret = dcp_backlight_register(dcp); if (ret) { dev_err(dcp->dev, "Unable to register backlight device\n"); dcp->brightness.maximum = 0; } out_unlock: mutex_unlock(&dcp->bl_register_mutex); } static void dcp_work_update_backlight(struct work_struct *work) { struct apple_dcp *dcp; dcp = container_of(work, struct apple_dcp, bl_update_wq); dcp_backlight_update(dcp); } static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) { int ret; struct device_node *node = of_get_child_by_name(dcp->dev->of_node, "piodma"); if (!node) return dev_err_probe(dcp->dev, -ENODEV, "Failed to get piodma child DT node\n"); dcp->piodma = of_platform_device_create(node, NULL, dcp->dev); if (!dcp->piodma) { of_node_put(node); return dev_err_probe(dcp->dev, -ENODEV, "Failed to gcreate piodma pdev for %pOF\n", node); } ret = dma_set_mask_and_coherent(&dcp->piodma->dev, DMA_BIT_MASK(42)); if (ret) goto err_destroy_pdev; ret = of_dma_configure(&dcp->piodma->dev, node, true); if (ret) { ret = dev_err_probe(dcp->dev, ret, "Failed to configure IOMMU child DMA\n"); goto err_destroy_pdev; } of_node_put(node); dcp->iommu_dom = iommu_domain_alloc(&platform_bus_type); if (!dcp->iommu_dom) { ret = -ENOMEM; goto err_destroy_pdev; } ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev); if (ret) { ret = dev_err_probe(dcp->dev, ret, "Failed to attach IOMMU child domain\n"); goto err_free_domain; } return 0; err_free_domain: iommu_domain_free(dcp->iommu_dom); err_destroy_pdev: of_node_put(node); of_platform_device_destroy(&dcp->piodma->dev, NULL); return ret; } static int dcp_get_bw_scratch_reg(struct apple_dcp *dcp, u32 expected) { struct of_phandle_args ph_args; u32 addr_idx, disp_idx, offset; int ret; ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-scratch", "#apple,bw-scratch-cells", 0, &ph_args); if (ret < 0) { dev_err(dcp->dev, "Failed to read 'apple,bw-scratch': %d\n", ret); return ret; } if (ph_args.args_count != 3) { dev_err(dcp->dev, "Unexpected 'apple,bw-scratch' arg count %d\n", ph_args.args_count); ret = -EINVAL; goto err_of_node_put; } addr_idx = ph_args.args[0]; disp_idx = ph_args.args[1]; offset = ph_args.args[2]; if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-scratch': %d\n", disp_idx); ret = -EINVAL; goto err_of_node_put; } ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_scratch_res); if (ret < 0) { dev_err(dcp->dev, "Failed to get 'apple,bw-scratch' resource %d from %pOF\n", addr_idx, ph_args.np); goto err_of_node_put; } if (offset > resource_size(&dcp->disp_bw_scratch_res) - 4) { ret = -EINVAL; goto err_of_node_put; } dcp->disp_registers[disp_idx] = &dcp->disp_bw_scratch_res; dcp->disp_bw_scratch_index = disp_idx; dcp->disp_bw_scratch_offset = offset; ret = 0; err_of_node_put: of_node_put(ph_args.np); return ret; } static int dcp_get_bw_doorbell_reg(struct apple_dcp *dcp, u32 expected) { struct of_phandle_args ph_args; u32 addr_idx, disp_idx; int ret; ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-doorbell", "#apple,bw-doorbell-cells", 0, &ph_args); if (ret < 0) { dev_err(dcp->dev, "Failed to read 'apple,bw-doorbell': %d\n", ret); return ret; } if (ph_args.args_count != 2) { dev_err(dcp->dev, "Unexpected 'apple,bw-doorbell' arg count %d\n", ph_args.args_count); ret = -EINVAL; goto err_of_node_put; } addr_idx = ph_args.args[0]; disp_idx = ph_args.args[1]; if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-doorbell': %d\n", disp_idx); ret = -EINVAL; goto err_of_node_put; } ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_doorbell_res); if (ret < 0) { dev_err(dcp->dev, "Failed to get 'apple,bw-doorbell' resource %d from %pOF\n", addr_idx, ph_args.np); goto err_of_node_put; } dcp->disp_bw_doorbell_index = disp_idx; dcp->disp_registers[disp_idx] = &dcp->disp_bw_doorbell_res; ret = 0; err_of_node_put: of_node_put(ph_args.np); return ret; } static int dcp_get_disp_regs(struct apple_dcp *dcp) { struct platform_device *pdev = to_platform_device(dcp->dev); int count = pdev->num_resources - 1; int i, ret; if (count <= 0 || count > MAX_DISP_REGISTERS) return -EINVAL; for (i = 0; i < count; ++i) { dcp->disp_registers[i] = platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); } /* load pmgr bandwidth scratch resource and offset */ ret = dcp_get_bw_scratch_reg(dcp, count); if (ret < 0) return ret; count += 1; /* load pmgr bandwidth doorbell resource if present (only on t8103) */ if (of_property_present(dcp->dev->of_node, "apple,bw-doorbell")) { ret = dcp_get_bw_doorbell_reg(dcp, count); if (ret < 0) return ret; count += 1; } dcp->nr_disp_registers = count; return 0; } #define DCP_FW_VERSION_MIN_LEN 3 #define DCP_FW_VERSION_MAX_LEN 5 #define DCP_FW_VERSION_STR_LEN (DCP_FW_VERSION_MAX_LEN * 4) static int dcp_read_fw_version(struct device *dev, const char *name, char *version_str) { u32 ver[DCP_FW_VERSION_MAX_LEN]; int len_str; int len; len = of_property_read_variable_u32_array(dev->of_node, name, ver, DCP_FW_VERSION_MIN_LEN, DCP_FW_VERSION_MAX_LEN); switch (len) { case 3: len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, "%d.%d.%d", ver[0], ver[1], ver[2]); break; case 4: len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, "%d.%d.%d.%d", ver[0], ver[1], ver[2], ver[3]); break; case 5: len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, "%d.%d.%d.%d.%d", ver[0], ver[1], ver[2], ver[3], ver[4]); break; default: len_str = strscpy(version_str, "UNKNOWN", DCP_FW_VERSION_STR_LEN); if (len >= 0) len = -EOVERFLOW; break; } if (len_str >= DCP_FW_VERSION_STR_LEN) dev_warn(dev, "'%s' truncated: '%s'\n", name, version_str); return len; } static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) { char compat_str[DCP_FW_VERSION_STR_LEN]; char fw_str[DCP_FW_VERSION_STR_LEN]; int ret; /* firmware version is just informative */ dcp_read_fw_version(dev, "apple,firmware-version", fw_str); ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); if (ret < 0) { dev_err(dev, "Could not read 'apple,firmware-compat': %d\n", ret); return DCP_FIRMWARE_UNKNOWN; } if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; /* * m1n1 reports firmware version 13.5 as compatible with 13.3. This is * only true for the iomfb endpoint. The interface for the dptx-port * endpoint changed between 13.3 and 13.5. The driver will only support * firmware 13.5. Check the actual firmware version for compat version * 13.3 until m1n1 reports 13.5 as "firmware-compat". */ else if ((strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) && (strncmp(fw_str, "13.5.0", sizeof(compat_str)) == 0)) return DCP_FIRMWARE_V_13_5; else if (strncmp(compat_str, "13.5.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_13_5; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); return DCP_FIRMWARE_UNKNOWN; } static int dcp_comp_bind(struct device *dev, struct device *main, void *data) { struct device_node *panel_np; struct apple_dcp *dcp = dev_get_drvdata(dev); u32 cpu_ctrl; int ret; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); if (ret) return ret; dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc"); if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); of_property_read_u32(dev->of_node, "apple,dptx-phy", &dcp->dptx_phy); of_property_read_u32(dev->of_node, "apple,dptx-die", &dcp->dptx_die); if (dcp->index || dcp->dptx_phy || dcp->dptx_die) dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", dcp->index, dcp->dptx_phy, dcp->dptx_die); rw_init(&dcp->hpd_mutex, "aplhpd"); if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); if (dcp->notch_height > MAX_NOTCH_HEIGHT) dcp->notch_height = MAX_NOTCH_HEIGHT; if (dcp->notch_height > 0) dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); /* initialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); if (panel_np) dcp->panel.has_mini_led = true; else panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; if (of_device_is_available(panel_np)) { ret = of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum); if (ret) dev_err(dev, "Missing property 'apple,max-brightness'\n"); } of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); /* use adjusted height as long as the notch is hidden */ of_property_read_u32(panel_np, height_prop[!dcp->notch_height], &dcp->panel.height_mm); of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); rw_init(&dcp->bl_register_mutex, "dcpbl"); INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight); } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort; else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_USB; else dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; ret = dcp_create_piodma_iommu_dev(dcp); if (ret) return dev_err_probe(dev, ret, "Failed to created PIODMA iommu child device"); ret = dcp_get_disp_regs(dcp); if (ret) { dev_err(dev, "failed to find display registers\n"); return ret; } dcp->clk = devm_clk_get(dev, NULL); if (IS_ERR(dcp->clk)) return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); if (IS_ERR(dcp->rtk)) return dev_err_probe(dev, PTR_ERR(dcp->rtk), "Failed to initialize RTKit\n"); ret = apple_rtkit_wake(dcp->rtk); if (ret) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d\n", ret); return ret; } /* * We need to shutdown DCP before tearing down the display subsystem. Otherwise * the DCP will crash and briefly flash a green screen of death. */ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { struct apple_dcp *dcp = dev_get_drvdata(dev); if (dcp->hdmi_hpd_irq) disable_irq(dcp->hdmi_hpd_irq); if (dcp && dcp->shmem) iomfb_shutdown(dcp); if (dcp->piodma) { iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); iommu_domain_free(dcp->iommu_dom); /* TODO: the piodma platform device has to be destroyed but * doing so leads to all kind of breakage. */ // of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } devm_clk_put(dev, dcp->clk); dcp->clk = NULL; } static const struct component_ops dcp_comp_ops = { .bind = dcp_comp_bind, .unbind = dcp_comp_unbind, }; static int dcp_platform_probe(struct platform_device *pdev) { enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; u32 mux_index; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) return -ENODEV; /* Check for "apple,bw-scratch" to avoid probing appledrm with outdated * device trees. This prevents replacing simpledrm and ending up without * display. */ if (!of_property_present(dev->of_node, "apple,bw-scratch")) return dev_err_probe(dev, -ENODEV, "Incompatible devicetree! " "Use devicetree matching this kernel.\n"); dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); if (!dcp) return -ENOMEM; dcp->fw_compat = fw_compat; dcp->dev = dev; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); platform_set_drvdata(pdev, dcp); dcp->phy = devm_phy_optional_get(dev, "dp-phy"); if (IS_ERR(dcp->phy)) { dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy)); return PTR_ERR(dcp->phy); } if (dcp->phy) { int ret; /* * Request DP2HDMI related GPIOs as optional for DP-altmode * compatibility. J180D misses a dp2hdmi-pwren GPIO in the * template ADT. TODO: check device ADT */ dcp->hdmi_hpd = devm_gpiod_get_optional(dev, "hdmi-hpd", GPIOD_IN); if (IS_ERR(dcp->hdmi_hpd)) return PTR_ERR(dcp->hdmi_hpd); if (dcp->hdmi_hpd) { int irq = gpiod_to_irq(dcp->hdmi_hpd); if (irq < 0) { dev_err(dev, "failed to translate HDMI hpd GPIO to IRQ\n"); return irq; } dcp->hdmi_hpd_irq = irq; ret = devm_request_threaded_irq(dev, dcp->hdmi_hpd_irq, NULL, dcp_dp2hdmi_hpd, IRQF_ONESHOT | IRQF_NO_AUTOEN | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "dp2hdmi-hpd-irq", dcp); if (ret < 0) { dev_err(dev, "failed to request HDMI hpd irq %d: %d\n", irq, ret); return ret; } } /* * Power DP2HDMI on as it is required for the HPD irq. * TODO: check if one is sufficient for the hpd to save power * on battery powered Macbooks. */ dcp->hdmi_pwren = devm_gpiod_get_optional(dev, "hdmi-pwren", GPIOD_OUT_HIGH); if (IS_ERR(dcp->hdmi_pwren)) return PTR_ERR(dcp->hdmi_pwren); dcp->dp2hdmi_pwren = devm_gpiod_get_optional(dev, "dp2hdmi-pwren", GPIOD_OUT_HIGH); if (IS_ERR(dcp->dp2hdmi_pwren)) return PTR_ERR(dcp->dp2hdmi_pwren); ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index); if (!ret) { dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); if (IS_ERR(dcp->xbar)) { dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar)); return PTR_ERR(dcp->xbar); } ret = mux_control_select(dcp->xbar, mux_index); if (ret) dev_warn(dev, "mux_control_select failed: %d\n", ret); } } return component_add(&pdev->dev, &dcp_comp_ops); } #ifdef __linux__ static int dcp_platform_remove(struct platform_device *pdev) { component_del(&pdev->dev, &dcp_comp_ops); return 0; } static void dcp_platform_shutdown(struct platform_device *pdev) { component_del(&pdev->dev, &dcp_comp_ops); } #endif static int dcp_platform_suspend(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); dcp_dptx_disconnect(dcp, 0); } /* * Set the device as a wakeup device, which forces its power * domains to stay on. We need this as we do not support full * shutdown properly yet. */ device_set_wakeup_path(dev); return 0; } static int dcp_platform_resume(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "resume: HPD connected:%d\n", connected); if (connected) dcp_dptx_connect(dcp, 0); } return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, dcp_platform_suspend, dcp_platform_resume); static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = { .num_dptx_ports = 1, }; static const struct apple_dcp_hw_data apple_dcp_hw_t8112 = { .num_dptx_ports = 2, }; static const struct apple_dcp_hw_data apple_dcp_hw_dcp = { .num_dptx_ports = 0, }; static const struct apple_dcp_hw_data apple_dcp_hw_dcpext = { .num_dptx_ports = 2, }; static const struct of_device_id of_match[] = { { .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020, }, { .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112, }, { .compatible = "apple,dcp", .data = &apple_dcp_hw_dcp, }, { .compatible = "apple,dcpext", .data = &apple_dcp_hw_dcpext, }, {} }; MODULE_DEVICE_TABLE(of, of_match); #ifdef __linux__ static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, .remove = dcp_platform_remove, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", .of_match_table = of_match, .pm = pm_sleep_ptr(&dcp_platform_pm_ops), }, }; drm_module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); MODULE_LICENSE("Dual MIT/GPL"); #endif