/* * nvbright - NVIDIA GPU backlight control for Apple MacBooks (FreeBSD) * * On NVIDIA-based Apple hardware (e.g. MacBook Air A1370 / GeForce 320M, * MCP89 / GT218) the LCD backlight is driven by a PWM signal generated by * the GPU's display engine (PDISPLAY). The firmware normally reprograms it * through an SMI triggered by legacy I/O ports, but that path is dead under * FreeBSD (EFI boot), so we program the GPU MMIO PWM registers directly. * This mirrors the Linux nouveau nv50 / nva3 backlight driver: * * NV50_PDISP_SOR_PWM_DIV(i) = 0x0061c080 + i * 0x800 * NV50_PDISP_SOR_PWM_CTL(i) = 0x0061c084 + i * 0x800 * CTL_NEW = 0x80000000 (latch / apply new value) * CTL_UNK = 0x40000000 (required on nva3-class chips) * * The backlight SOR is located at runtime (the one with a programmed * PWM_DIV), and the GPU's MMIO register BAR is mapped via /dev/mem. * Requires root to run. * * Created by Luxferre in 2026, released into the public domain. */ #include #include #include #include #include #include #include #include #include #define PWM_DIV(i) (0x0061c080UL + (i) * 0x800UL) #define PWM_CTL(i) (0x0061c084UL + (i) * 0x800UL) #define CTL_NEW 0x80000000u #define CTL_UNK 0x40000000u #define MAX_SOR 8 #define STEP 10 #define FIXED_DIV 1025u /* used only if no SOR has a programmed PWM_DIV */ static int g_memfd = -1; static volatile uint32_t *g_bar; static int g_sor = -1; static uint32_t g_div; static void die(const char *msg) { fprintf(stderr, "nvbright: %s: %s\n", msg, strerror(errno)); exit(1); } static uint32_t rd(uint32_t off) { return g_bar[off / 4]; } static void wr(uint32_t off, uint32_t v) { g_bar[off / 4] = v; } /* Locate the NVIDIA VGA device's MMIO register BAR (BAR0) via pciconf. */ static void locate_bar(uint64_t *base, uint64_t *size) { FILE *fp = popen("/usr/sbin/pciconf -l -b", "r"); if(!fp) die("popen /usr/sbin/pciconf"); char line[1024]; int in_nv = 0; *base = 0; *size = 0; while(fgets(line, sizeof(line), fp)) { if(line[0] != ' ' && line[0] != '\t') { in_nv = (strstr(line, "vendor=0x10de") && (strstr(line, "class=0x030000") || strstr(line, "vgapci"))); continue; } if(!in_nv) continue; if(strstr(line, "bar") && strstr(line, "[10]")) { char *b = strstr(line, "base"); char *s = strstr(line, "size"); unsigned long long bb = 0, sz = 0; if(b) sscanf(b, "base 0x%llx", &bb); if(s) sscanf(s, "size %llu", &sz); *base = (uint64_t)bb; *size = (uint64_t)sz; break; } } pclose(fp); if(!*base) die("could not locate NVIDIA GPU MMIO BAR (is this an nvidia vga?)"); } /* Pick the SOR whose PWM the firmware already programmed (the backlight). */ static void detect_sor(void) { for(int i = 0; i < MAX_SOR; i++) { uint32_t div = rd(PWM_DIV(i)); if(div != 0) { g_sor = i; g_div = div; return; } } g_sor = 0; /* fallback: nv50-style fixed period on SOR0 */ g_div = 0; } static int bl_get(void) { uint32_t ctl = rd(PWM_CTL(g_sor)); uint32_t val = ctl & 0x00ffffffu; uint32_t div = g_div ? g_div : FIXED_DIV; long lvl = ((long)val * 100 + (long)(div / 2)) / (long)div; if(lvl < 0) lvl = 0; if(lvl > 100) lvl = 100; return (int)lvl; } static void bl_set(int level) { if(level < 0) level = 0; if(level > 100) level = 100; uint32_t div = g_div ? g_div : FIXED_DIV; uint32_t val = (uint32_t)(((uint64_t)level * div) / 100); wr(PWM_DIV(g_sor), div); wr(PWM_CTL(g_sor), val | CTL_NEW | CTL_UNK); } int main(int argc, char **argv) { if(argc < 2) { fprintf(stderr, "Usage: %s [value 0-100]\n", argv[0]); fprintf(stderr, " get print current brightness (0-100)\n"); fprintf(stderr, " set <0-100> set brightness\n"); fprintf(stderr, " up / down step brightness by %d\n", STEP); return 1; } uint64_t base = 0, size = 0; locate_bar(&base, &size); if(size == 0) size = 0x1000000UL; g_memfd = open("/dev/mem", O_RDWR); if(g_memfd < 0) die("open /dev/mem (must run as root)"); void *m = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, g_memfd, (off_t)base); if(m == MAP_FAILED) die("mmap /dev/mem"); g_bar = (volatile uint32_t *)m; detect_sor(); if(strcmp(argv[1], "get") == 0) { printf("Brightness: %d / 100 (SOR%d, pwm_div=%u)\n", bl_get(), g_sor, g_div ? g_div : FIXED_DIV); } else if(strcmp(argv[1], "set") == 0 && argc >= 3) { bl_set(atoi(argv[2])); printf("Set brightness: %d / 100\n", bl_get()); } else if(strcmp(argv[1], "up") == 0) { int v = bl_get() + STEP; if(v > 100) v = 100; bl_set(v); printf("Brightness up: %d / 100\n", bl_get()); } else if(strcmp(argv[1], "down") == 0) { int v = bl_get() - STEP; if(v < 0) v = 0; bl_set(v); printf("Brightness down: %d / 100\n", bl_get()); } else { fprintf(stderr, "Unknown command: %s\n", argv[1]); return 1; } return 0; }