153 lines
5.0 KiB
C
153 lines
5.0 KiB
C
/*
|
|
* 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;
|
|
}
|