/*---------------------------------------------------------*\ | RGBController_ZotacBlackwellGPU.cpp | | | | RGBController for ZOTAC Blackwell (RTX 50 series) GPU | | | | Eder Sánchez 27 Mar 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ #include "RGBController_ZotacBlackwellGPU.h" #include "LogManager.h" #include "pci_ids.h" /**------------------------------------------------------------------*\ @name ZOTAC RTX 50 series GPU @category GPU @type I2C @save :robot: @direct :x: @effects :tools: @detectors DetectZotacBlackwellGPUControllersPCI @comment Supports ZOTAC Blackwell (RTX 50 series) GPUs. The zone layout varies per card and is resolved from a static table keyed on PCI device and sub-device IDs. The controller uses individual SMBus byte writes (registers 0x20-0x2F) with a 3ms delay between each transaction. To add new cards, add PCI ID entries in `pci_ids/pci_ids.h`, a zone config entry in `device_zone_configs` below, and a `REGISTER_I2C_PCI_DETECTOR` line in `Controllers/ZotacBlackwellGPUController/ZotacBlackwellGPUControllerDetect.cpp`. \*-------------------------------------------------------------------*/ const RGBController_ZotacBlackwellGPU::DeviceZoneConfig RGBController_ZotacBlackwellGPU::device_zone_configs[] = { { NVIDIA_RTX5080_DEV, ZOTAC_RTX5080_AMP_EXTREME_SUB_DEV, { "Logo", "Side Bar", "Infinity Mirror" }, 3 }, { NVIDIA_RTX5090_DEV, ZOTAC_RTX5090_SOLID_OC_SUB_DEV, { "ZOTAC Gaming", "Logo" }, 2 }, { 0, 0, { nullptr }, 0 } }; const RGBController_ZotacBlackwellGPU::DeviceZoneConfig* RGBController_ZotacBlackwellGPU::FindZoneConfig(uint16_t device, uint16_t subdevice) { for(const DeviceZoneConfig* cfg = device_zone_configs; cfg->zone_count != 0; cfg++) { if(cfg->device == device && cfg->subdevice == subdevice) { return cfg; } } return nullptr; } RGBController_ZotacBlackwellGPU::RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr, uint16_t device, uint16_t subdevice) { controller = controller_ptr; const DeviceZoneConfig* cfg = FindZoneConfig(device, subdevice); if(cfg == nullptr) { LOG_ERROR("[%s] Unrecognized PCI device/subdevice: %04X/%04X. Falling back to three generic zones.", controller->GetName().c_str(), device, subdevice); zone_names.push_back("Zone 0"); zone_names.push_back("Zone 1"); zone_names.push_back("Zone 2"); } else { for(uint8_t z = 0; z < cfg->zone_count; z++) { zone_names.push_back(cfg->zones[z]); } } name = controller->GetName(); vendor = "ZOTAC"; description = "ZOTAC RTX 50 series RGB GPU Device (" + controller->GetVersion() + ")"; location = controller->GetDeviceLocation(); type = DEVICE_TYPE_GPU; version = controller->GetVersion(); /*---------------------------------------------------------*\ | Static mode | \*---------------------------------------------------------*/ mode STATIC; STATIC.name = "Static"; STATIC.value = ZOTAC_BLACKWELL_GPU_MODE_STATIC; STATIC.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; STATIC.brightness_min = 0; STATIC.brightness_max = 100; STATIC.brightness = 100; STATIC.color_mode = MODE_COLORS_PER_LED; modes.push_back(STATIC); /*---------------------------------------------------------*\ | Breathe mode | \*---------------------------------------------------------*/ mode BREATHE; BREATHE.name = "Breathe"; BREATHE.value = ZOTAC_BLACKWELL_GPU_MODE_BREATHE; BREATHE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; BREATHE.brightness_min = 0; BREATHE.brightness_max = 100; BREATHE.brightness = 100; BREATHE.speed_min = 0; BREATHE.speed_max = 100; BREATHE.speed = 20; BREATHE.color_mode = MODE_COLORS_PER_LED; modes.push_back(BREATHE); /*---------------------------------------------------------*\ | Fade mode | \*---------------------------------------------------------*/ mode FADE; FADE.name = "Fade"; FADE.value = ZOTAC_BLACKWELL_GPU_MODE_FADE; FADE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED; FADE.speed_min = 0; FADE.speed_max = 100; FADE.speed = 20; FADE.color_mode = MODE_COLORS_NONE; modes.push_back(FADE); /*---------------------------------------------------------*\ | Wink mode | \*---------------------------------------------------------*/ mode WINK; WINK.name = "Wink"; WINK.value = ZOTAC_BLACKWELL_GPU_MODE_WINK; WINK.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; WINK.brightness_min = 0; WINK.brightness_max = 100; WINK.brightness = 100; WINK.speed_min = 0; WINK.speed_max = 100; WINK.speed = 20; WINK.color_mode = MODE_COLORS_PER_LED; modes.push_back(WINK); /*---------------------------------------------------------*\ | Glide mode | \*---------------------------------------------------------*/ mode GLIDE; GLIDE.name = "Glide"; GLIDE.value = ZOTAC_BLACKWELL_GPU_MODE_GLIDE; GLIDE.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_PER_LED_COLOR; GLIDE.brightness_min = 0; GLIDE.brightness_max = 100; GLIDE.brightness = 100; GLIDE.speed_min = 0; GLIDE.speed_max = 100; GLIDE.speed = 20; GLIDE.color_mode = MODE_COLORS_PER_LED; modes.push_back(GLIDE); /*---------------------------------------------------------*\ | Prism mode (called "Rainbow" in older firmware) | \*---------------------------------------------------------*/ mode PRISM; PRISM.name = "Prism"; PRISM.value = ZOTAC_BLACKWELL_GPU_MODE_PRISM; PRISM.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR; PRISM.speed_min = 0; PRISM.speed_max = 100; PRISM.speed = 20; PRISM.color_mode = MODE_COLORS_NONE; modes.push_back(PRISM); /*---------------------------------------------------------*\ | Bokeh mode | \*---------------------------------------------------------*/ mode BOKEH; BOKEH.name = "Bokeh"; BOKEH.value = ZOTAC_BLACKWELL_GPU_MODE_BOKEH; BOKEH.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; BOKEH.brightness_min = 0; BOKEH.brightness_max = 100; BOKEH.brightness = 100; BOKEH.speed_min = 0; BOKEH.speed_max = 100; BOKEH.speed = 20; BOKEH.color_mode = MODE_COLORS_PER_LED; modes.push_back(BOKEH); /*---------------------------------------------------------*\ | Beacon mode | \*---------------------------------------------------------*/ mode BEACON; BEACON.name = "Beacon"; BEACON.value = ZOTAC_BLACKWELL_GPU_MODE_BEACON; BEACON.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; BEACON.brightness_min = 0; BEACON.brightness_max = 100; BEACON.brightness = 100; BEACON.speed_min = 0; BEACON.speed_max = 100; BEACON.speed = 20; BEACON.color_mode = MODE_COLORS_PER_LED; modes.push_back(BEACON); /*---------------------------------------------------------*\ | Tandem mode | \*---------------------------------------------------------*/ mode TANDEM; TANDEM.name = "Tandem"; TANDEM.value = ZOTAC_BLACKWELL_GPU_MODE_TANDEM; TANDEM.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; TANDEM.brightness_min = 0; TANDEM.brightness_max = 100; TANDEM.brightness = 100; TANDEM.speed_min = 0; TANDEM.speed_max = 100; TANDEM.speed = 20; TANDEM.color_mode = MODE_COLORS_PER_LED; modes.push_back(TANDEM); /*---------------------------------------------------------*\ | Tidal mode | \*---------------------------------------------------------*/ mode TIDAL; TIDAL.name = "Tidal"; TIDAL.value = ZOTAC_BLACKWELL_GPU_MODE_TIDAL; TIDAL.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_PER_LED_COLOR; TIDAL.brightness_min = 0; TIDAL.brightness_max = 100; TIDAL.brightness = 100; TIDAL.speed_min = 0; TIDAL.speed_max = 100; TIDAL.speed = 20; TIDAL.color_mode = MODE_COLORS_PER_LED; modes.push_back(TIDAL); /*---------------------------------------------------------*\ | Astra mode | \*---------------------------------------------------------*/ mode ASTRA; ASTRA.name = "Astra"; ASTRA.value = ZOTAC_BLACKWELL_GPU_MODE_ASTRA; ASTRA.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; ASTRA.brightness_min = 0; ASTRA.brightness_max = 100; ASTRA.brightness = 100; ASTRA.speed_min = 0; ASTRA.speed_max = 100; ASTRA.speed = 20; ASTRA.color_mode = MODE_COLORS_PER_LED; modes.push_back(ASTRA); /*---------------------------------------------------------*\ | Cosmic mode | \*---------------------------------------------------------*/ mode COSMIC; COSMIC.name = "Cosmic"; COSMIC.value = ZOTAC_BLACKWELL_GPU_MODE_COSMIC; COSMIC.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; COSMIC.brightness_min = 0; COSMIC.brightness_max = 100; COSMIC.brightness = 100; COSMIC.speed_min = 0; COSMIC.speed_max = 100; COSMIC.speed = 20; COSMIC.color_mode = MODE_COLORS_PER_LED; modes.push_back(COSMIC); /*---------------------------------------------------------*\ | Volta mode | \*---------------------------------------------------------*/ mode VOLTA; VOLTA.name = "Volta"; VOLTA.value = ZOTAC_BLACKWELL_GPU_MODE_VOLTA; VOLTA.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR; VOLTA.brightness_min = 0; VOLTA.brightness_max = 100; VOLTA.brightness = 100; VOLTA.speed_min = 0; VOLTA.speed_max = 100; VOLTA.speed = 20; VOLTA.color_mode = MODE_COLORS_PER_LED; modes.push_back(VOLTA); SetupZones(); } RGBController_ZotacBlackwellGPU::~RGBController_ZotacBlackwellGPU() { delete controller; } void RGBController_ZotacBlackwellGPU::SetupZones() { /*---------------------------------------------------------*\ | One single-LED zone per name from the device_zone_configs | | table. The zone's index is its position here, which is | | written verbatim to the zone register (0x21) on update. | \*---------------------------------------------------------*/ for(const std::string& zone_name : zone_names) { zone new_zone; new_zone.name = zone_name; new_zone.type = ZONE_TYPE_SINGLE; new_zone.leds_min = 1; new_zone.leds_max = 1; new_zone.leds_count = 1; new_zone.matrix_map = NULL; zones.push_back(new_zone); led new_led; new_led.name = zone_name + " LED"; leds.push_back(new_led); } SetupColors(); } void RGBController_ZotacBlackwellGPU::ResizeZone(int /*zone*/, int /*new_size*/) { /*---------------------------------------------------------*\ | This device does not support resizing zones | \*---------------------------------------------------------*/ } void RGBController_ZotacBlackwellGPU::DeviceUpdateLEDs() { DeviceUpdateMode(); } void RGBController_ZotacBlackwellGPU::UpdateZoneLEDs(int zone) { DeviceUpdateZone(zone); } void RGBController_ZotacBlackwellGPU::UpdateSingleLED(int led) { DeviceUpdateZone(led); } void RGBController_ZotacBlackwellGPU::DeviceUpdateZone(int zone) { unsigned int mode_val = modes[active_mode].value; unsigned int brightness = modes[active_mode].brightness; unsigned int speed = modes[active_mode].speed; unsigned int direction = modes[active_mode].direction == MODE_DIRECTION_RIGHT ? ZOTAC_BLACKWELL_GPU_DIR_RIGHT : ZOTAC_BLACKWELL_GPU_DIR_LEFT; RGBColor color1; RGBColor color2 = ToRGBColor(0, 0, 0); switch(modes[active_mode].color_mode) { case MODE_COLORS_PER_LED: color1 = colors[zone]; break; case MODE_COLORS_MODE_SPECIFIC: color1 = (modes[active_mode].colors.size() >= 1) ? modes[active_mode].colors[0] : ToRGBColor(0, 0, 0); color2 = (modes[active_mode].colors.size() >= 2) ? modes[active_mode].colors[1] : ToRGBColor(0, 0, 0); break; default: color1 = ToRGBColor(0, 0, 0); break; } controller->SetMode(zone, mode_val, color1, color2, brightness, speed, direction); controller->Commit(); } void RGBController_ZotacBlackwellGPU::DeviceUpdateMode() { for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++) { DeviceUpdateZone(zone_idx); } }