#include "redox_glue.h" /* Global state */ static struct drm_device g_drm_dev; static struct device g_device; static struct pci_dev *g_pci_dev; static void __iomem *g_mmio_base; static size_t g_mmio_size; static u64 g_fb_phys; static size_t g_fb_size; static int g_asic_family = -1; enum { ASIC_NAVI10 = 1, ASIC_NAVI14, ASIC_NAVI12, ASIC_NAVI21, ASIC_NAVI22, ASIC_NAVI23, ASIC_NAVI24, ASIC_NAVI31, ASIC_NAVI32, ASIC_NAVI33, ASIC_MAX, }; struct asic_props { int family; const char *name; const char *dmcub_firmware; u32 otg_base[4]; u32 hubp_base[4]; u32 hpd_status_reg; int max_connectors; int max_crtcs; }; static const struct asic_props g_asic_table[] = { [ASIC_NAVI10] = { .family = ASIC_NAVI10, .name = "Navi10 (RDNA1)", .dmcub_firmware = "dmcub_dcn20.bin", .otg_base = {0x4000, 0x4800, 0x5000, 0x5800}, .hubp_base = {0x5000, 0x5400, 0x5800, 0x5c00}, .hpd_status_reg = 0x1e74, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI14] = { .family = ASIC_NAVI14, .name = "Navi14 (RDNA1)", .dmcub_firmware = "dmcub_dcn20.bin", .otg_base = {0x4000, 0x4800, 0x5000, 0x5800}, .hubp_base = {0x5000, 0x5400, 0x5800, 0x5c00}, .hpd_status_reg = 0x1e74, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI12] = { .family = ASIC_NAVI12, .name = "Navi12 (RDNA1)", .dmcub_firmware = "dmcub_dcn20.bin", .otg_base = {0x4000, 0x4800, 0x5000, 0x5800}, .hubp_base = {0x5000, 0x5400, 0x5800, 0x5c00}, .hpd_status_reg = 0x1e74, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI21] = { .family = ASIC_NAVI21, .name = "Navi21 / Sienna Cichlid (RDNA2)", .dmcub_firmware = "dmcub_dcn31.bin", .otg_base = {0x4800, 0x5000, 0x5800, 0x6000}, .hubp_base = {0x5800, 0x5c00, 0x6000, 0x6400}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI22] = { .family = ASIC_NAVI22, .name = "Navi22 / Navy Flounder (RDNA2)", .dmcub_firmware = "dmcub_dcn31.bin", .otg_base = {0x4800, 0x5000, 0x5800, 0x6000}, .hubp_base = {0x5800, 0x5c00, 0x6000, 0x6400}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI23] = { .family = ASIC_NAVI23, .name = "Navi23 / Dimgrey Cavefish (RDNA2)", .dmcub_firmware = "dmcub_dcn31.bin", .otg_base = {0x4800, 0x5000, 0x5800, 0x6000}, .hubp_base = {0x5800, 0x5c00, 0x6000, 0x6400}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI24] = { .family = ASIC_NAVI24, .name = "Navi24 / Beige Goby (RDNA2)", .dmcub_firmware = "dmcub_dcn31.bin", .otg_base = {0x4800, 0x5000, 0x5800, 0x6000}, .hubp_base = {0x5800, 0x5c00, 0x6000, 0x6400}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI31] = { .family = ASIC_NAVI31, .name = "Navi31 / Plum Bonito (RDNA3)", .dmcub_firmware = "dmcub_dcn32.bin", .otg_base = {0x5000, 0x5800, 0x6000, 0x6800}, .hubp_base = {0x6000, 0x6400, 0x6800, 0x6c00}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI32] = { .family = ASIC_NAVI32, .name = "Navi32 / Wheat Nas (RDNA3)", .dmcub_firmware = "dmcub_dcn32.bin", .otg_base = {0x5000, 0x5800, 0x6000, 0x6800}, .hubp_base = {0x6000, 0x6400, 0x6800, 0x6c00}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, [ASIC_NAVI33] = { .family = ASIC_NAVI33, .name = "Navi33 / Hotpink Bonefish (RDNA3)", .dmcub_firmware = "dmcub_dcn32.bin", .otg_base = {0x5000, 0x5800, 0x6000, 0x6800}, .hubp_base = {0x6000, 0x6400, 0x6800, 0x6c00}, .hpd_status_reg = 0x4A00, .max_connectors = 4, .max_crtcs = 4, }, }; static int asic_from_device_id(u16 device_id) { switch (device_id & 0xFFF0) { case 0x7310: return ASIC_NAVI10; case 0x7340: return ASIC_NAVI14; case 0x7360: return ASIC_NAVI12; case 0x73A0: return ASIC_NAVI21; case 0x73C0: return ASIC_NAVI22; case 0x73E0: return ASIC_NAVI23; case 0x7420: return ASIC_NAVI24; case 0x7440: return ASIC_NAVI31; case 0x7480: return ASIC_NAVI32; case 0x74A0: return ASIC_NAVI33; default: break; } /* Wider ranges for variant device IDs within each family */ if (device_id >= 0x7300 && device_id < 0x7320) return ASIC_NAVI10; if (device_id >= 0x7340 && device_id < 0x7360) return ASIC_NAVI14; if (device_id >= 0x7360 && device_id < 0x7380) return ASIC_NAVI12; if (device_id >= 0x73A0 && device_id < 0x73C0) return ASIC_NAVI21; if (device_id >= 0x73C0 && device_id < 0x73E0) return ASIC_NAVI22; if (device_id >= 0x73E0 && device_id < 0x7400) return ASIC_NAVI23; if (device_id >= 0x7420 && device_id < 0x7440) return ASIC_NAVI24; if (device_id >= 0x7440 && device_id < 0x7480) return ASIC_NAVI31; if (device_id >= 0x7480 && device_id < 0x74A0) return ASIC_NAVI32; if (device_id >= 0x74A0 && device_id < 0x74C0) return ASIC_NAVI33; return -1; } static const struct asic_props *asic_props(void) { if (g_asic_family > 0 && g_asic_family < ASIC_MAX) { return &g_asic_table[g_asic_family]; } return NULL; } #define AMDGPU_DC_BYTES_PER_PIXEL 4U #define AMDGPU_DC_PIXEL_FORMAT_ARGB8888 3U #define AMDGPU_DC_OTG_CONTROL 0x00 #define AMDGPU_DC_OTG_VIEWPORT_SIZE 0x10 #define AMDGPU_DC_OTG_VSYNC_ADJUST 0x14 #define AMDGPU_DC_OTG_H_TOTAL 0x18 #define AMDGPU_DC_OTG_V_TOTAL 0x1C #define AMDGPU_DC_OTG_VSTARTUP 0x20 #define AMDGPU_DC_HUBP_PRIMARY_ADDR_LOW 0x00 #define AMDGPU_DC_HUBP_PRIMARY_ADDR_HIGH 0x04 #define AMDGPU_DC_HUBP_SURFACE_PITCH 0x08 #define AMDGPU_DC_HUBP_SURFACE_CONFIG 0x0C #define AMDGPU_DC_HUBP_VIEWPORT_START 0x10 #define AMDGPU_DC_HUBP_VIEWPORT_SIZE 0x14 #define AMDGPU_DC_HUBP_FLIP_CONTROL 0x18 #define AMDGPU_DC_HUBP_FLIP_ADDR_LOW 0x1C #define AMDGPU_DC_HUBP_FLIP_ADDR_HIGH 0x20 struct connector_info_ffi { int id; int connector_type; int connector_type_id; int connection; int mm_width; int mm_height; int encoder_id; }; struct amdgpu_redox_connector_desc { int id; u32 hpd_mask; int connector_type; int connector_type_id; int encoder_id; int mm_width; int mm_height; }; static const struct amdgpu_redox_connector_desc g_connector_descs_desktop_dgpu[] = { { .id = 1, .hpd_mask = 0x01, .connector_type = 10, .connector_type_id = 1, .encoder_id = 1, .mm_width = 0, .mm_height = 0 }, { .id = 2, .hpd_mask = 0x02, .connector_type = 10, .connector_type_id = 2, .encoder_id = 2, .mm_width = 0, .mm_height = 0 }, { .id = 3, .hpd_mask = 0x04, .connector_type = 10, .connector_type_id = 3, .encoder_id = 3, .mm_width = 0, .mm_height = 0 }, { .id = 4, .hpd_mask = 0x08, .connector_type = 11, .connector_type_id = 4, .encoder_id = 4, .mm_width = 0, .mm_height = 0 }, }; static const struct amdgpu_redox_connector_desc g_connector_descs_apu[] = { { .id = 1, .hpd_mask = 0x01, .connector_type = 11, .connector_type_id = 1, .encoder_id = 1, .mm_width = 0, .mm_height = 0 }, { .id = 2, .hpd_mask = 0x02, .connector_type = 10, .connector_type_id = 2, .encoder_id = 2, .mm_width = 0, .mm_height = 0 }, { .id = 3, .hpd_mask = 0x04, .connector_type = 14, .connector_type_id = 3, .encoder_id = 3, .mm_width = 0, .mm_height = 0 }, }; static inline void __iomem *amdgpu_dc_reg_ptr(u32 base, u32 offset) { return (u8 __iomem *)g_mmio_base + base + offset; } static int amdgpu_dc_validate_mmio_access(u32 base, u32 offset) { u64 end = (u64)base + (u64)offset + sizeof(u32); if (!g_mmio_base) { return -ENODEV; } if (end > g_mmio_size) { pr_err("amdgpu_redox: MMIO access %#x+%#x outside aperture %zu\n", base, offset, g_mmio_size); return -EINVAL; } return 0; } static inline void amdgpu_dc_write_reg(u32 base, u32 offset, u32 value) { if (amdgpu_dc_validate_mmio_access(base, offset) != 0) { return; } writel(value, amdgpu_dc_reg_ptr(base, offset)); } static inline u32 amdgpu_dc_read_reg(u32 base, u32 offset) { if (amdgpu_dc_validate_mmio_access(base, offset) != 0) { return 0; } return readl(amdgpu_dc_reg_ptr(base, offset)); } static inline u32 amdgpu_dc_hpd_status(void) { u32 hpd_reg; const struct asic_props *props = asic_props(); if (!props) { return 0; } hpd_reg = props->hpd_status_reg; if (amdgpu_dc_validate_mmio_access(0, hpd_reg) != 0) { return 0; } return readl((u8 __iomem *)g_mmio_base + hpd_reg); } static const struct amdgpu_redox_connector_desc *connector_table(int *count) { if (g_asic_family == ASIC_NAVI14 || g_asic_family == ASIC_NAVI33) { *count = (int)ARRAY_SIZE(g_connector_descs_apu); return g_connector_descs_apu; } *count = (int)ARRAY_SIZE(g_connector_descs_desktop_dgpu); return g_connector_descs_desktop_dgpu; } static void amdgpu_redox_log_irq_expectation(u64 quirk_flags) { const char *policy = "MSI-X first, then MSI, then legacy IRQ fallback"; if ((quirk_flags & PCI_QUIRK_FORCE_LEGACY) != 0 || ((quirk_flags & PCI_QUIRK_NO_MSIX) != 0 && (quirk_flags & PCI_QUIRK_NO_MSI) != 0)) { policy = "legacy IRQ only"; } else if ((quirk_flags & PCI_QUIRK_NO_MSIX) != 0) { policy = "avoid MSI-X, prefer MSI with legacy fallback"; } else if ((quirk_flags & PCI_QUIRK_NO_MSI) != 0) { policy = "avoid MSI, prefer MSI-X with legacy fallback"; } printk("amdgpu_redox: quirk-aware IRQ expectation: %s\n", policy); } /* Initialize AMD Display Core */ int amdgpu_dc_init(void *mmio_base, size_t mmio_size) { int ret = 0; u16 device_id; const char *firmware_name = NULL; u64 quirk_flags = 0; printk("amdgpu_redox: initializing AMD Display Core\n"); if (!mmio_base || mmio_size < sizeof(u32)) { pr_err("amdgpu_redox: invalid MMIO for DC init\n"); return -EINVAL; } if (!g_pci_dev) { pr_err("amdgpu_redox: no PCI device info; call redox_pci_set_device_info first\n"); return -ENODEV; } device_id = g_pci_dev->device; printk("amdgpu_redox: PCI device_id = %#06x\n", device_id); printk("amdgpu_redox: MMIO identity register = %#010x (not used for ASIC detection)\n", readl(mmio_base)); { quirk_flags = pci_get_quirk_flags(g_pci_dev); printk("amdgpu_redox: PCI %02x:%02x.%u quirk flags = %#llx\n", g_pci_dev->bus_number, g_pci_dev->dev_number, g_pci_dev->func_number, (unsigned long long)quirk_flags); if (pci_has_quirk(g_pci_dev, PCI_QUIRK_NO_ASPM)) { pr_warn("amdgpu_redox: NO_ASPM quirk active; skipping any future ASPM-dependent assumptions\n"); } if (pci_has_quirk(g_pci_dev, PCI_QUIRK_NEED_IOMMU)) { pr_warn("amdgpu_redox: NEED_IOMMU quirk active; runtime must provide a functional IOMMU path\n"); } if (pci_has_quirk(g_pci_dev, PCI_QUIRK_NO_MSIX)) { pr_warn("amdgpu_redox: NO_MSIX quirk active; IRQ setup must avoid MSI-X\n"); } if (pci_has_quirk(g_pci_dev, PCI_QUIRK_NO_MSI)) { pr_warn("amdgpu_redox: NO_MSI quirk active; IRQ setup must avoid MSI\n"); } amdgpu_redox_log_irq_expectation(quirk_flags); } g_asic_family = asic_from_device_id(device_id); if (g_asic_family < 0) { pr_err("amdgpu_redox: unknown AMD GPU device_id %#06x — cannot identify ASIC family\n", device_id); return -ENODEV; } { const struct asic_props *props = asic_props(); if (!props) { pr_err("amdgpu_redox: no properties for ASIC family %d\n", g_asic_family); return -ENODEV; } firmware_name = props->dmcub_firmware; printk("amdgpu_redox: identified %s, loading firmware: %s\n", props->name, firmware_name); } { const struct firmware *fw = NULL; int fw_ret = request_firmware(&fw, firmware_name, NULL); if (fw_ret != 0 || !fw) { pr_warn("amdgpu_redox: firmware %s not available in backend load path (err=%d), continuing with Rust-side quirk policy already applied (quirks=%#llx)\n", firmware_name, fw_ret, (unsigned long long)quirk_flags); } else { printk("amdgpu_redox: firmware %s loaded (%zu bytes)\n", firmware_name, fw->size); release_firmware(fw); } } return ret; } int amdgpu_dc_upload_firmware(void) { const struct asic_props *props = asic_props(); printk("amdgpu_redox: firmware upload stub — DMUB upload requires imported DC tree\n"); if (!props) { pr_err("amdgpu_redox: cannot determine DMUB firmware target without ASIC identification\n"); return -ENODEV; } printk("amdgpu_redox: DMUB firmware upload not yet implemented for %s.\n", props->name); printk("amdgpu_redox: Required: compile display/dmub/ from imported Linux DC tree,\n"); printk("amdgpu_redox: then call dmub_srv_create() / dmub_srv_hw_init() / dmub_srv_cmd_submit().\n"); printk("amdgpu_redox: Until DC tree is compiled, DMUB-assisted features (PSR, ABM, etc.) are unavailable.\n"); return 0; } /* Initialize AMD GPU hardware for display */ int amdgpu_redox_init(void *mmio_base, size_t mmio_size, uint64_t fb_phys, size_t fb_size) { int ret; printk("amdgpu_redox: initializing AMD GPU display\n"); printk("amdgpu_redox: MMIO base=%p size=%zu\n", mmio_base, mmio_size); printk("amdgpu_redox: FB phys=%#llx size=%zu\n", (unsigned long long)fb_phys, fb_size); if (!mmio_base || mmio_size == 0) { pr_err("amdgpu_redox: invalid MMIO mapping provided by redox-drm\n"); return -EINVAL; } memset(&g_drm_dev, 0, sizeof(g_drm_dev)); memset(&g_device, 0, sizeof(g_device)); g_mmio_base = mmio_base; g_mmio_size = mmio_size; g_fb_phys = fb_phys; g_fb_size = fb_size; g_pci_dev = redox_pci_find_amd_gpu(); if (!g_pci_dev) { pr_err("amdgpu_redox: no AMD PCI device available from integration layer\n"); return -ENODEV; } g_pci_dev->mmio_base = g_mmio_base; g_pci_dev->resource_len[0] = g_mmio_size; g_device.pci_dev = g_pci_dev; g_drm_dev.dev = &g_device; ret = amdgpu_dc_init(mmio_base, mmio_size); if (ret != 0) { pr_err("amdgpu_redox: failed to initialize DC\n"); return ret; } return 0; } /* Cleanup */ void amdgpu_redox_cleanup(void) { printk("amdgpu_redox: cleanup\n"); if (g_pci_dev) { redox_pci_dev_put(g_pci_dev); g_pci_dev = NULL; } g_mmio_base = NULL; g_mmio_size = 0; g_fb_phys = 0; g_fb_size = 0; memset(&g_drm_dev, 0, sizeof(g_drm_dev)); memset(&g_device, 0, sizeof(g_device)); } /* Get connector info — called by redox-drm */ int amdgpu_dc_detect_connectors(void) { int num_connectors = 0; if (!g_mmio_base) { pr_err("amdgpu_redox: detect_connectors called before init\n"); return -ENODEV; } #ifdef __redox__ u32 hpd_status = amdgpu_dc_hpd_status(); int table_count; const struct amdgpu_redox_connector_desc *table = connector_table(&table_count); int i; for (i = 0; i < table_count; ++i) { if (hpd_status & table[i].hpd_mask) { num_connectors++; } } printk("amdgpu_redox: detected %d connector(s) via HPD status %#010x\n", num_connectors, hpd_status); #else printk("amdgpu_redox: running on Linux, using AMD DC detection\n"); #endif return num_connectors; } /* Get connector info by index */ int amdgpu_dc_get_connector_info(int idx, void *info) { struct connector_info_ffi *ffi_info = (struct connector_info_ffi *)info; if (!g_mmio_base) { pr_err("amdgpu_redox: get_connector_info called before init\n"); return -ENODEV; } if (idx < 0 || !ffi_info) { return -EINVAL; } #ifdef __redox__ { u32 hpd_status = amdgpu_dc_hpd_status(); int table_count; const struct amdgpu_redox_connector_desc *table = connector_table(&table_count); int active_index = 0; int i; for (i = 0; i < table_count; ++i) { const struct amdgpu_redox_connector_desc *desc = &table[i]; if (!(hpd_status & desc->hpd_mask)) { continue; } if (active_index == idx) { ffi_info->id = desc->id; ffi_info->connector_type = desc->connector_type; ffi_info->connector_type_id = desc->connector_type_id; ffi_info->connection = 1; ffi_info->mm_width = desc->mm_width; ffi_info->mm_height = desc->mm_height; ffi_info->encoder_id = desc->encoder_id; return 0; } int amdgpu_dc_flip_surface(int crtc_id, uint64_t fb_addr) { u32 hubp_base; if (!g_mmio_base) { pr_err("amdgpu_redox: flip_surface called before init\n"); return -ENODEV; } { const struct asic_props *props = asic_props(); if (!props || crtc_id >= props->max_crtcs) { pr_err("amdgpu_redox: no register layout for ASIC family %d crtc %d\n", g_asic_family, crtc_id); return -EINVAL; } hubp_base = props->hubp_base[crtc_id]; } if (amdgpu_dc_validate_mmio_access(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH) != 0 || amdgpu_dc_validate_mmio_access(hubp_base, AMDGPU_DC_HUBP_FLIP_CONTROL) != 0) { return -EINVAL; } amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_LOW, (u32)(fb_addr & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH, (u32)((fb_addr >> 32) & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_CONTROL, 1); return 0; } int amdgpu_dc_set_dpms(int crtc_id, int mode) { u32 otg_base; u32 otg_control; if (!g_mmio_base) { pr_err("amdgpu_redox: dpms called before init\n"); return -ENODEV; } { const struct asic_props *props = asic_props(); if (!props || crtc_id >= props->max_crtcs) { pr_err("amdgpu_redox: no register layout for ASIC family %d crtc %d\n", g_asic_family, crtc_id); return -EINVAL; } otg_base = props->otg_base[crtc_id]; } if (amdgpu_dc_validate_mmio_access(otg_base, AMDGPU_DC_OTG_CONTROL) != 0) { return -EINVAL; } otg_control = amdgpu_dc_read_reg(otg_base, AMDGPU_DC_OTG_CONTROL); switch (mode) { case DRM_MODE_DPMS_ON: otg_control |= 0x01U; printk("amdgpu_redox: DPMS ON crtc %d\n", crtc_id); break; case DRM_MODE_DPMS_OFF: otg_control &= ~0x01U; printk("amdgpu_redox: DPMS OFF crtc %d\n", crtc_id); break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: printk("amdgpu_redox: DPMS mode %d on crtc %d (no hardware support)\n", mode, crtc_id); return 0; } mb(); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_CONTROL, otg_control); mb(); return 0; } active_index++; } } #endif return -ENOENT; } /* Set CRTC mode — called by redox-drm for modesetting */ int amdgpu_dc_set_crtc(int crtc_id, uint64_t fb_addr, uint32_t width, uint32_t height) { printk("amdgpu_redox: set_crtc(%d, fb=%#llx, %ux%u)\n", crtc_id, (unsigned long long)fb_addr, width, height); if (!g_mmio_base) { pr_err("amdgpu_redox: set_crtc called before amdgpu_redox_init\n"); return -ENODEV; } #ifdef __redox__ const u32 bytes_per_pixel = AMDGPU_DC_BYTES_PER_PIXEL; u32 pitch; u32 viewport_size; const u32 h_total = width + 160U; const u32 v_total = height + 45U; const u32 v_sync_start = height + 3U; const u32 v_sync_end = v_sync_start + 5U; const u32 v_sync_adjust = (v_sync_start & 0xFFFFU) | (v_sync_end << 16); const u32 vstartup = v_sync_start > 1U ? (v_sync_start - 1U) : 0U; u64 required_bytes; if (crtc_id < 0 || crtc_id > 3) { pr_err("amdgpu_redox: invalid crtc_id %d\n", crtc_id); return -EINVAL; } if (width == 0 || height == 0 || width > 0xFFFFU || height > 0xFFFFU) { pr_err("amdgpu_redox: invalid mode %ux%u\n", width, height); return -EINVAL; } if (width > (UINT32_MAX / bytes_per_pixel)) { pr_err("amdgpu_redox: pitch overflow for width %u\n", width); return -EINVAL; } pitch = width * bytes_per_pixel; viewport_size = (width & 0xFFFFU) | (height << 16); required_bytes = (u64)pitch * (u64)height; /* The Rust-side allocates scanout buffers via GTT VA space (0..256MiB). * The display controller programs these GPU-virtual addresses directly; * the GTT hardware translates them to physical backing pages at runtime. * Validate only that the address + size fits in a u64 and that the * programmed registers can hold the values. */ if (required_bytes == 0) { pr_err("amdgpu_redox: zero-sized framebuffer for crtc %d\n", crtc_id); return -EINVAL; } u32 otg_base; u32 hubp_base; u32 otg_control; { const struct asic_props *props = asic_props(); if (!props || crtc_id >= props->max_crtcs) { pr_err("amdgpu_redox: no register layout for ASIC family %d crtc %d\n", g_asic_family, crtc_id); return -EINVAL; } otg_base = props->otg_base[crtc_id]; hubp_base = props->hubp_base[crtc_id]; } if (amdgpu_dc_validate_mmio_access(otg_base, AMDGPU_DC_OTG_VSTARTUP) != 0 || amdgpu_dc_validate_mmio_access(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH) != 0) { return -EINVAL; } otg_control = amdgpu_dc_read_reg(otg_base, AMDGPU_DC_OTG_CONTROL); otg_control &= ~0x01U; amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_CONTROL, otg_control); mb(); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_PRIMARY_ADDR_LOW, (u32)(fb_addr & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_PRIMARY_ADDR_HIGH, (u32)((fb_addr >> 32) & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_SURFACE_PITCH, pitch); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_SURFACE_CONFIG, AMDGPU_DC_PIXEL_FORMAT_ARGB8888); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_VIEWPORT_START, 0); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_VIEWPORT_SIZE, viewport_size); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_LOW, (u32)(fb_addr & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH, (u32)((fb_addr >> 32) & 0xFFFFFFFFULL)); amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_CONTROL, 0); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_VIEWPORT_SIZE, viewport_size); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_VSYNC_ADJUST, v_sync_adjust); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_H_TOTAL, h_total); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_V_TOTAL, v_total); amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_VSTARTUP, vstartup); mb(); otg_control |= 0x01; amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_CONTROL, otg_control); printk("amdgpu_redox: CRTC %d enabled at %ux%u, fb=%#llx\n", crtc_id, width, height, (unsigned long long)fb_addr); #else printk("amdgpu_redox: running on Linux, using AMD DC modesetting\n"); #endif return 0; }