backlight module for nvidia cards -- control backlight even with offb

From: Andy Wingo
Date: Sat Jan 12 2008 - 17:39:28 EST


Hi all,

I have a 12" powerbook, one of the last G4's, and have long been
irritated that I couldn't use offb because it has no backlight control.
This is more irritating now that the nouveau project's X drivers are
starting to work for me on PPC, but are incompatible with the nvidiafb
frame buffer.

I decided to rip out the backlight code from the nvidia frame buffer
into a separate module that can be loaded even when using offb as the
frame buffer. I am attaching the source, but you may find a tarball with
a makefile here:

http://wingolog.org/pub/nvbacklight-0.1.tar.bz2

I do not know what the correct solution is. Ideally offb would export a
backlight device. I tried getting open firmware to give me the needed
information, but the "reg" entry for the backlight seems short, given
that the mac-io@17 #address-cells == 1 and #size-cells == 1:

$ hd /proc/device-tree/pci@f2000000/mac-io@17/backlight@f300/reg
00000000 00 00 f3 00 |....|
00000004

For that reason I'm copying Ben Herrenschmidt to see if he knows
something about a proper solution. For now I'll just add nvbacklight to
my /etc/modules. Ideas about a "proper solution" are appreciated.

Regards,

Andy

/*
* nvbacklight.c: Backlight driver for nVidia graphics cards
*
* Copyright 2008 Andy Wingo <wingo@xxxxxxxxx>
* Copyright 2004 Antonino Daplas <adaplas@xxxxxxx>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
*/


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/backlight.h>

#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif


struct nvbacklight_par {
struct pci_dev *pci_dev;
struct fb_info *info;
struct backlight_device *bd;

volatile u32 __iomem *REGS;
volatile u32 __iomem *PCRTC0;
volatile u32 __iomem *PCRTC;
volatile u32 __iomem *PRAMDAC0;
volatile u32 __iomem *PFB;
volatile u32 __iomem *PFIFO;
volatile u32 __iomem *PGRAPH;
volatile u32 __iomem *PEXTDEV;
volatile u32 __iomem *PTIMER;
volatile u32 __iomem *PMC;
volatile u32 __iomem *PRAMIN;
volatile u32 __iomem *FIFO;
volatile u32 __iomem *CURSOR;
volatile u8 __iomem *PCIO0;
volatile u8 __iomem *PCIO;
volatile u8 __iomem *PVIO;
volatile u8 __iomem *PDIO0;
volatile u8 __iomem *PDIO;
volatile u32 __iomem *PRAMDAC;

u32 fpSyncs;
};


/* We do not have any information about which values are allowed, thus
* we used safe values.
*/
#define MIN_LEVEL 0x158
#define MAX_LEVEL 0x534
#define LEVEL_STEP ((MAX_LEVEL - MIN_LEVEL) / FB_BACKLIGHT_MAX)

#define NV_WR32(p,i,d) (__raw_writel((d), (void __iomem *)(p) + (i)))
#define NV_RD32(p,i) (__raw_readl((void __iomem *)(p) + (i)))


static int nvidia_bl_get_level_brightness(struct fb_info *info, int level)
{
int nlevel;

/* Get and convert the value */
/* No locking of bl_curve since we read a single value */
nlevel = MIN_LEVEL + info->bl_curve[level] * LEVEL_STEP;

if (nlevel < 0)
nlevel = 0;
else if (nlevel < MIN_LEVEL)
nlevel = MIN_LEVEL;
else if (nlevel > MAX_LEVEL)
nlevel = MAX_LEVEL;

return nlevel;
}

static int nvidia_bl_update_status(struct backlight_device *bd)
{
struct nvbacklight_par *par = bl_get_data(bd);
u32 tmp_pcrt, tmp_pmc, fpcontrol;
int level;

if (bd->props.power != FB_BLANK_UNBLANK ||
bd->props.fb_blank != FB_BLANK_UNBLANK)
level = 0;
else
level = bd->props.brightness;

tmp_pmc = NV_RD32(par->PMC, 0x10F0) & 0x0000FFFF;
tmp_pcrt = NV_RD32(par->PCRTC0, 0x081C) & 0xFFFFFFFC;
fpcontrol = NV_RD32(par->PRAMDAC, 0x0848) & 0xCFFFFFCC;

if (level > 0) {
tmp_pcrt |= 0x1;
tmp_pmc |= (1 << 31); /* backlight bit */
tmp_pmc |= nvidia_bl_get_level_brightness(par->info, level) << 16;
fpcontrol |= par->fpSyncs;
} else
fpcontrol |= 0x20000022;

NV_WR32(par->PCRTC0, 0x081C, tmp_pcrt);
NV_WR32(par->PMC, 0x10F0, tmp_pmc);
NV_WR32(par->PRAMDAC, 0x848, fpcontrol);

return 0;
}

static int nvidia_bl_get_brightness(struct backlight_device *bd)
{
return bd->props.brightness;
}

static struct backlight_ops nvidia_bl_ops = {
.get_brightness = nvidia_bl_get_brightness,
.update_status = nvidia_bl_update_status,
};

static struct fb_info *nvbacklight_attach(struct pci_dev *pd)
{
struct nvbacklight_par *par;
struct fb_info *info;
struct backlight_device *bd;

info = framebuffer_alloc(sizeof(struct nvbacklight_par),
&pd->dev);
if (!info)
goto framebuffer_alloc_failed;

par = info->par;
par->pci_dev = pd;
par->info = info;

par->REGS = ioremap(pci_resource_start(pd, 0),
pci_resource_len(pd, 0));

if (!par->REGS) {
printk(KERN_ERR "nvbacklight: cannot ioremap MMIO base\n");
goto regs_map_failed;
}

par->PRAMIN = par->REGS + (0x00710000 / 4);
par->PCRTC0 = par->REGS + (0x00600000 / 4);
par->PRAMDAC0 = par->REGS + (0x00680000 / 4);
par->PFB = par->REGS + (0x00100000 / 4);
par->PFIFO = par->REGS + (0x00002000 / 4);
par->PGRAPH = par->REGS + (0x00400000 / 4);
par->PEXTDEV = par->REGS + (0x00101000 / 4);
par->PTIMER = par->REGS + (0x00009000 / 4);
par->PMC = par->REGS + (0x00000000 / 4);
par->FIFO = par->REGS + (0x00800000 / 4);

/* 8 bit registers */
par->PCIO0 = (u8 __iomem *) par->REGS + 0x00601000;
par->PDIO0 = (u8 __iomem *) par->REGS + 0x00681000;
par->PVIO = (u8 __iomem *) par->REGS + 0x000C0000;

/* see nv_setup.c:NVSelectHeadRegisters */
par->PCIO = par->PCIO0;
par->PCRTC = par->PCRTC0;
par->PRAMDAC = par->PRAMDAC0;
par->PDIO = par->PDIO0;

par->fpSyncs = NV_RD32(par->PRAMDAC, 0x0848) & 0x30000033;

bd = backlight_device_register("nvbacklight", &pd->dev, par,
&nvidia_bl_ops);
if (IS_ERR(bd)) {
printk(KERN_WARNING "nvbacklight: registration failed\n");
goto bl_device_register_failed;
}

par->bd = bd;
fb_bl_default_curve(info, 0,
0x158 * FB_BACKLIGHT_MAX / MAX_LEVEL,
0x534 * FB_BACKLIGHT_MAX / MAX_LEVEL);

bd->props.max_brightness = FB_BACKLIGHT_LEVELS - 1;
bd->props.brightness = bd->props.max_brightness;
bd->props.power = FB_BLANK_UNBLANK;
backlight_update_status(bd);

printk("nvbacklight: initialized\n");

return info;

bl_device_register_failed:
iounmap(par->REGS);
regs_map_failed:
framebuffer_release (info);
framebuffer_alloc_failed:
return NULL;
}

static void __devexit nvbacklight_detach(struct fb_info *info)
{
struct nvbacklight_par *par = info->par;

backlight_device_unregister(par->bd);
iounmap(par->REGS);
pci_dev_put(par->pci_dev);
framebuffer_release(info);
printk("nvbacklight: Backlight unloaded\n");
}

static struct fb_info *bl_fb_info;

static int __init nvbacklight_init(void)
{
struct pci_dev *pd = NULL;

pd = pci_get_device (PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, pd);
if (pd)
bl_fb_info = nvbacklight_attach(pd);

return 0;
}

static void __exit
nvbacklight_exit(void)
{
if (bl_fb_info) {
nvbacklight_detach (bl_fb_info);
bl_fb_info = NULL;
}
}

module_init(nvbacklight_init);
module_exit(nvbacklight_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andy Wingo");
MODULE_DESCRIPTION("Backlight control for nVidia graphics chipset");