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 [EMAIL PROTECTED] #address-cells == 1 and #size-cells == 1:

  $ hd /proc/device-tree/[EMAIL PROTECTED]/[EMAIL PROTECTED]/[EMAIL 
PROTECTED]/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 <[EMAIL PROTECTED]>
 * Copyright 2004 Antonino Daplas <[EMAIL PROTECTED]>
 *
 * 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");

Reply via email to