Files

153 lines
5.0 KiB
C
Raw Permalink Normal View History

2026-08-14 13:49:01 +03:00
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#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 <get|set|up|down> [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;
}