Commit 5474c120 authored by Michael Hanselmann's avatar Michael Hanselmann Committed by Linus Torvalds
Browse files

[PATCH] Rewritten backlight infrastructure for portable Apple computers



This patch contains a total rewrite of the backlight infrastructure for
portable Apple computers.  Backward compatibility is retained.  A sysfs
interface allows userland to control the brightness with more steps than
before.  Userland is allowed to upload a brightness curve for different
monitors, similar to Mac OS X.

[akpm@osdl.org: add needed exports]
Signed-off-by: default avatarMichael Hanselmann <linux-kernel@hansmi.ch>
Acked-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Richard Purdie <rpurdie@rpsys.net>
Cc: "Antonino A. Daplas" <adaplas@pol.net>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 17660bdd
......@@ -32,6 +32,7 @@
#include <linux/delay.h>
#include <linux/kprobes.h>
#include <linux/kexec.h>
#include <linux/backlight.h>
#include <asm/kdebug.h>
#include <asm/pgtable.h>
......@@ -105,10 +106,18 @@ int die(const char *str, struct pt_regs *regs, long err)
spin_lock_irq(&die_lock);
bust_spinlocks(1);
#ifdef CONFIG_PMAC_BACKLIGHT
if (machine_is(powermac)) {
set_backlight_enable(1);
set_backlight_level(BACKLIGHT_MAX);
mutex_lock(&pmac_backlight_mutex);
if (machine_is(powermac) && pmac_backlight) {
struct backlight_properties *props;
down(&pmac_backlight->sem);
props = pmac_backlight->props;
props->brightness = props->max_brightness;
props->power = FB_BLANK_UNBLANK;
props->update_status(pmac_backlight);
up(&pmac_backlight->sem);
}
mutex_unlock(&pmac_backlight_mutex);
#endif
printk("Oops: %s, sig: %ld [#%d]\n", str, err, ++die_counter);
#ifdef CONFIG_PREEMPT
......
......@@ -3,200 +3,148 @@
* Contains support for the backlight.
*
* Copyright (C) 2000 Benjamin Herrenschmidt
* Copyright (C) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
*
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/reboot.h>
#include <linux/nvram.h>
#include <linux/console.h>
#include <asm/sections.h>
#include <asm/ptrace.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/nvram.h>
#include <asm/backlight.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#define OLD_BACKLIGHT_MAX 15
static struct backlight_controller *backlighter;
static void* backlighter_data;
static int backlight_autosave;
static int backlight_level = BACKLIGHT_MAX;
static int backlight_enabled = 1;
static int backlight_req_level = -1;
static int backlight_req_enable = -1;
/* Protect the pmac_backlight variable */
DEFINE_MUTEX(pmac_backlight_mutex);
static void backlight_callback(void *);
static DECLARE_WORK(backlight_work, backlight_callback, NULL);
/* Main backlight storage
*
* Backlight drivers in this variable are required to have the "props"
* attribute set and to have an update_status function.
*
* We can only store one backlight here, but since Apple laptops have only one
* internal display, it doesn't matter. Other backlight drivers can be used
* independently.
*
* Lock ordering:
* pmac_backlight_mutex (global, main backlight)
* pmac_backlight->sem (backlight class)
*/
struct backlight_device *pmac_backlight;
void register_backlight_controller(struct backlight_controller *ctrler,
void *data, char *type)
int pmac_has_backlight_type(const char *type)
{
struct device_node* bk_node;
char *prop;
int valid = 0;
/* There's already a matching controller, bail out */
if (backlighter != NULL)
return;
bk_node = find_devices("backlight");
#ifdef CONFIG_ADB_PMU
/* Special case for the old PowerBook since I can't test on it */
backlight_autosave = machine_is_compatible("AAPL,3400/2400")
|| machine_is_compatible("AAPL,3500");
if ((backlight_autosave
|| machine_is_compatible("AAPL,PowerBook1998")
|| machine_is_compatible("PowerBook1,1"))
&& !strcmp(type, "pmu"))
valid = 1;
#endif
struct device_node* bk_node = find_devices("backlight");
if (bk_node) {
prop = get_property(bk_node, "backlight-control", NULL);
if (prop && !strncmp(prop, type, strlen(type)))
valid = 1;
}
if (!valid)
return;
backlighter = ctrler;
backlighter_data = data;
if (bk_node && !backlight_autosave)
prop = get_property(bk_node, "bklt", NULL);
else
prop = NULL;
if (prop) {
backlight_level = ((*prop)+1) >> 1;
if (backlight_level > BACKLIGHT_MAX)
backlight_level = BACKLIGHT_MAX;
char *prop = get_property(bk_node, "backlight-control", NULL);
if (prop && strncmp(prop, type, strlen(type)) == 0)
return 1;
}
#ifdef CONFIG_ADB_PMU
if (backlight_autosave) {
struct adb_request req;
pmu_request(&req, NULL, 2, 0xd9, 0);
while (!req.complete)
pmu_poll();
backlight_level = req.reply[0] >> 4;
}
#endif
acquire_console_sem();
if (!backlighter->set_enable(1, backlight_level, data))
backlight_enabled = 1;
release_console_sem();
printk(KERN_INFO "Registered \"%s\" backlight controller,"
"level: %d/15\n", type, backlight_level);
return 0;
}
EXPORT_SYMBOL(register_backlight_controller);
void unregister_backlight_controller(struct backlight_controller
*ctrler, void *data)
int pmac_backlight_curve_lookup(struct fb_info *info, int value)
{
/* We keep the current backlight level (for now) */
if (ctrler == backlighter && data == backlighter_data)
backlighter = NULL;
int level = (FB_BACKLIGHT_LEVELS - 1);
if (info && info->bl_dev) {
int i, max = 0;
/* Look for biggest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++)
max = max((int)info->bl_curve[i], max);
/* Look for nearest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) {
int diff = abs(info->bl_curve[i] - value);
if (diff < max) {
max = diff;
level = i;
}
}
}
return level;
}
EXPORT_SYMBOL(unregister_backlight_controller);
static int __set_backlight_enable(int enable)
static void pmac_backlight_key(int direction)
{
int rc;
if (!backlighter)
return -ENODEV;
acquire_console_sem();
rc = backlighter->set_enable(enable, backlight_level,
backlighter_data);
if (!rc)
backlight_enabled = enable;
release_console_sem();
return rc;
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
int brightness;
down(&pmac_backlight->sem);
props = pmac_backlight->props;
brightness = props->brightness +
((direction?-1:1) * (props->max_brightness / 15));
if (brightness < 0)
brightness = 0;
else if (brightness > props->max_brightness)
brightness = props->max_brightness;
props->brightness = brightness;
props->update_status(pmac_backlight);
up(&pmac_backlight->sem);
}
mutex_unlock(&pmac_backlight_mutex);
}
int set_backlight_enable(int enable)
void pmac_backlight_key_up()
{
if (!backlighter)
return -ENODEV;
backlight_req_enable = enable;
schedule_work(&backlight_work);
return 0;
pmac_backlight_key(0);
}
EXPORT_SYMBOL(set_backlight_enable);
int get_backlight_enable(void)
void pmac_backlight_key_down()
{
if (!backlighter)
return -ENODEV;
return backlight_enabled;
pmac_backlight_key(1);
}
EXPORT_SYMBOL(get_backlight_enable);
static int __set_backlight_level(int level)
int pmac_backlight_set_legacy_brightness(int brightness)
{
int rc = 0;
if (!backlighter)
return -ENODEV;
if (level < BACKLIGHT_MIN)
level = BACKLIGHT_OFF;
if (level > BACKLIGHT_MAX)
level = BACKLIGHT_MAX;
acquire_console_sem();
if (backlight_enabled)
rc = backlighter->set_level(level, backlighter_data);
if (!rc)
backlight_level = level;
release_console_sem();
if (!rc && !backlight_autosave) {
level <<=1;
if (level & 0x10)
level |= 0x01;
// -- todo: save to property "bklt"
int error = -ENXIO;
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
down(&pmac_backlight->sem);
props = pmac_backlight->props;
props->brightness = brightness *
props->max_brightness / OLD_BACKLIGHT_MAX;
props->update_status(pmac_backlight);
up(&pmac_backlight->sem);
error = 0;
}
return rc;
mutex_unlock(&pmac_backlight_mutex);
return error;
}
int set_backlight_level(int level)
int pmac_backlight_get_legacy_brightness()
{
if (!backlighter)
return -ENODEV;
backlight_req_level = level;
schedule_work(&backlight_work);
return 0;
}
int result = -ENXIO;
EXPORT_SYMBOL(set_backlight_level);
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
int get_backlight_level(void)
{
if (!backlighter)
return -ENODEV;
return backlight_level;
}
EXPORT_SYMBOL(get_backlight_level);
down(&pmac_backlight->sem);
props = pmac_backlight->props;
result = props->brightness *
OLD_BACKLIGHT_MAX / props->max_brightness;
up(&pmac_backlight->sem);
}
mutex_unlock(&pmac_backlight_mutex);
static void backlight_callback(void *dummy)
{
int level, enable;
do {
level = backlight_req_level;
enable = backlight_req_enable;
mb();
if (level >= 0)
__set_backlight_level(level);
if (enable >= 0)
__set_backlight_enable(enable);
} while(cmpxchg(&backlight_req_level, level, -1) != level ||
cmpxchg(&backlight_req_enable, enable, -1) != enable);
return result;
}
......@@ -26,9 +26,6 @@
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/xmon.h>
#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif
#include <asm/processor.h>
#include <asm/pgtable.h>
#include <asm/mmu.h>
......
......@@ -99,17 +99,22 @@ config PMAC_MEDIABAY
devices are not fully supported in the bay as I never had one to
try with
# made a separate option since backlight may end up beeing used
# on non-powerbook machines (but only on PMU based ones AFAIK)
config PMAC_BACKLIGHT
bool "Backlight control for LCD screens"
depends on ADB_PMU && (BROKEN || !PPC64)
help
Say Y here to build in code to manage the LCD backlight on a
Macintosh PowerBook. With this code, the backlight will be turned
on and off appropriately on power-management and lid-open/lid-closed
events; also, the PowerBook button device will be enabled so you can
change the screen brightness.
Say Y here to enable Macintosh specific extensions of the generic
backlight code. With this enabled, the brightness keys on older
PowerBooks will be enabled so you can change the screen brightness.
Newer models should use an userspace daemon like pbbuttonsd.
config PMAC_BACKLIGHT_LEGACY
bool "Provide legacy ioctl's on /dev/pmu for the backlight"
depends on PMAC_BACKLIGHT && (BROKEN || !PPC64)
help
Say Y if you want to enable legacy ioctl's on /dev/pmu. This is for
programs which use this old interface. New and updated programs
should use the backlight classes in sysfs.
config ADB_MACIO
bool "Include MacIO (CHRP) ADB driver"
......
......@@ -12,6 +12,7 @@ obj-$(CONFIG_INPUT_ADBHID) += adbhid.o
obj-$(CONFIG_ANSLCD) += ans-lcd.o
obj-$(CONFIG_ADB_PMU) += via-pmu.o
obj-$(CONFIG_PMAC_BACKLIGHT) += via-pmu-backlight.o
obj-$(CONFIG_ADB_CUDA) += via-cuda.o
obj-$(CONFIG_PMAC_APM_EMU) += apm_emu.o
obj-$(CONFIG_PMAC_SMU) += smu.o
......
......@@ -503,9 +503,7 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto
case 0x1f: /* Powerbook button device */
{
int down = (data[1] == (data[1] & 0xf));
#ifdef CONFIG_PMAC_BACKLIGHT
int backlight = get_backlight_level();
#endif
/*
* XXX: Where is the contrast control for the passive?
* -- Cort
......@@ -530,29 +528,17 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto
case 0xa: /* brightness decrease */
#ifdef CONFIG_PMAC_BACKLIGHT
if (!disable_kernel_backlight) {
if (down && backlight >= 0) {
if (backlight > BACKLIGHT_OFF)
set_backlight_level(backlight-1);
else
set_backlight_level(BACKLIGHT_OFF);
}
}
#endif /* CONFIG_PMAC_BACKLIGHT */
if (!disable_kernel_backlight && down)
pmac_backlight_key_down();
#endif
input_report_key(adbhid[id]->input, KEY_BRIGHTNESSDOWN, down);
break;
case 0x9: /* brightness increase */
#ifdef CONFIG_PMAC_BACKLIGHT
if (!disable_kernel_backlight) {
if (down && backlight >= 0) {
if (backlight < BACKLIGHT_MAX)
set_backlight_level(backlight+1);
else
set_backlight_level(BACKLIGHT_MAX);
}
}
#endif /* CONFIG_PMAC_BACKLIGHT */
if (!disable_kernel_backlight && down)
pmac_backlight_key_up();
#endif
input_report_key(adbhid[id]->input, KEY_BRIGHTNESSUP, down);
break;
......
/*
* Backlight code for via-pmu
*
* Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
* Copyright (C) 2001-2002 Benjamin Herrenschmidt
* Copyright (C) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
*
*/
#include <asm/ptrace.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <asm/backlight.h>
#include <asm/prom.h>
#define MAX_PMU_LEVEL 0xFF
static struct device_node *vias;
static struct backlight_properties pmu_backlight_data;
static int pmu_backlight_get_level_brightness(struct fb_info *info,
int level)
{
int pmulevel;
/* Get and convert the value */
mutex_lock(&info->bl_mutex);
pmulevel = info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_PMU_LEVEL;
mutex_unlock(&info->bl_mutex);
if (pmulevel < 0)
pmulevel = 0;
else if (pmulevel > MAX_PMU_LEVEL)
pmulevel = MAX_PMU_LEVEL;
return pmulevel;
}
static int pmu_backlight_update_status(struct backlight_device *bd)
{
struct fb_info *info = class_get_devdata(&bd->class_dev);
struct adb_request req;
int pmulevel, level = bd->props->brightness;
if (vias == NULL)
return -ENODEV;
if (bd->props->power != FB_BLANK_UNBLANK ||
bd->props->fb_blank != FB_BLANK_UNBLANK)
level = 0;
pmulevel = pmu_backlight_get_level_brightness(info, level);
pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, pmulevel);
pmu_wait_complete(&req);
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | (level > 0 ? PMU_POW_ON : PMU_POW_OFF));
pmu_wait_complete(&req);
return 0;
}
static int pmu_backlight_get_brightness(struct backlight_device *bd)
{
return bd->props->brightness;
}
static struct backlight_properties pmu_backlight_data = {
.owner = THIS_MODULE,
.get_brightness = pmu_backlight_get_brightness,
.update_status = pmu_backlight_update_status,
.max_brightness = (FB_BACKLIGHT_LEVELS - 1),
};
void __init pmu_backlight_init(struct device_node *in_vias)
{
struct backlight_device *bd;
struct fb_info *info;
char name[10];
int level, autosave;
vias = in_vias;
/* Special case for the old PowerBook since I can't test on it */
autosave =
machine_is_compatible("AAPL,3400/2400") ||
machine_is_compatible("AAPL,3500");
if (!autosave &&
!pmac_has_backlight_type("pmu") &&
!machine_is_compatible("AAPL,PowerBook1998") &&
!machine_is_compatible("PowerBook1,1"))
return;
/* Actually, this is a hack, but I don't know of a better way
* to get the first framebuffer device.
*/
info = registered_fb[0];
if (!info) {
printk("pmubl: No framebuffer found\n");
goto error;
}
snprintf(name, sizeof(name), "pmubl%d", info->node);
bd = backlight_device_register(name, info, &pmu_backlight_data);
if (IS_ERR(bd)) {
printk("pmubl: Backlight registration failed\n");
goto error;
}
mutex_lock(&info->bl_mutex);
info->bl_dev = bd;
fb_bl_default_curve(info, 0x7F, 0x46, 0x0E);
mutex_unlock(&info->bl_mutex);
level = pmu_backlight_data.max_brightness;
if (autosave) {
/* read autosaved value if available */
struct adb_request req;
pmu_request(&req, NULL, 2, 0xd9, 0);
pmu_wait_complete(&req);
mutex_lock(&info->bl_mutex);
level = pmac_backlight_curve_lookup(info,
(req.reply[0] >> 4) *
pmu_backlight_data.max_brightness / 15);
mutex_unlock(&info->bl_mutex);
}
up(&bd->sem);
bd->props->brightness = level;
bd->props->power = FB_BLANK_UNBLANK;
bd->props->update_status(bd);
down(&bd->sem);
mutex_lock(&pmac_backlight_mutex);
if (!pmac_backlight)
pmac_backlight = bd;
mutex_unlock(&pmac_backlight_mutex);
printk("pmubl: Backlight initialized (%s)\n", name);
return;
error:
return;
}
......@@ -144,7 +144,6 @@ static int data_index;
static int data_len;
static volatile int adb_int_pending;
static volatile int disable_poll;
static struct adb_request bright_req_1, bright_req_2;
static struct device_node *vias;
static int pmu_kind = PMU_UNKNOWN;
static int pmu_fully_inited = 0;
......@@ -161,7 +160,7 @@ static int drop_interrupts;
#if defined(CONFIG_PM) && defined(CONFIG_PPC32)
static int option_lid_wakeup = 1;
#endif /* CONFIG_PM && CONFIG_PPC32 */
#if (defined(CONFIG_PM)&&defined(CONFIG_PPC32))||defined(CONFIG_PMAC_BACKLIGHT)
#if (defined(CONFIG_PM)&&defined(CONFIG_PPC32))||defined(CONFIG_PMAC_BACKLIGHT_LEGACY)
static int sleep_in_progress;
#endif
static unsigned long async_req_locks;
......@@ -208,10 +207,6 @@ static int proc_get_info(char *page, char **start, off_t off,
int count, int *eof, void *data);
static int proc_get_irqstats(char *page, char **start, off_t off,