* 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/fb.h>
-#include <linux/backlight.h>
-#include <linux/adb.h>
-#include <linux/pmu.h>
-#include <asm/atomic.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 <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/nvram.h>
#include <asm/backlight.h>
-#define OLD_BACKLIGHT_MAX 15
-
-static void pmac_backlight_key_worker(void *data);
-static void pmac_backlight_set_legacy_worker(void *data);
-
-static DECLARE_WORK(pmac_backlight_key_work, pmac_backlight_key_worker, NULL);
-static DECLARE_WORK(pmac_backlight_set_legacy_work, pmac_backlight_set_legacy_worker, NULL);
-
-/* Although these variables are used in interrupt context, it makes no sense to
- * protect them. No user is able to produce enough key events per second and
- * notice the errors that might happen.
- */
-static int pmac_backlight_key_queued;
-static int pmac_backlight_set_legacy_queued;
-
-/* The via-pmu code allows the backlight to be grabbed, in which case the
- * in-kernel control of the brightness needs to be disabled. This should
- * only be used by really old PowerBooks.
- */
-static atomic_t kernel_backlight_disabled = ATOMIC_INIT(0);
-
-/* Protect the pmac_backlight variable */
-DEFINE_MUTEX(pmac_backlight_mutex);
-
-/* 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;
-
-int pmac_has_backlight_type(const char *type)
-{
- struct device_node* bk_node = find_devices("backlight");
+#include <linux/adb.h>
+#include <linux/pmu.h>
- if (bk_node) {
- char *prop = get_property(bk_node, "backlight-control", NULL);
- if (prop && strncmp(prop, type, strlen(type)) == 0)
- return 1;
- }
+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;
- return 0;
-}
+static void backlight_callback(void *);
+static DECLARE_WORK(backlight_work, backlight_callback, NULL);
-int pmac_backlight_curve_lookup(struct fb_info *info, int value)
+void register_backlight_controller(struct backlight_controller *ctrler,
+ void *data, char *type)
{
- 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);
+ struct device_node* bk_node;
+ char *prop;
+ int valid = 0;
- /* 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;
- }
- }
+ /* 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
+ if (bk_node) {
+ prop = get_property(bk_node, "backlight-control", NULL);
+ if (prop && !strncmp(prop, type, strlen(type)))
+ valid = 1;
}
-
- return level;
-}
-
-static void pmac_backlight_key_worker(void *data)
-{
- if (atomic_read(&kernel_backlight_disabled))
+ 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;
+ }
- 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 +
- ((pmac_backlight_key_queued?-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);
+#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;
}
- mutex_unlock(&pmac_backlight_mutex);
+#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);
}
+EXPORT_SYMBOL(register_backlight_controller);
-/* This function is called in interrupt context */
-void pmac_backlight_key(int direction)
+void unregister_backlight_controller(struct backlight_controller
+ *ctrler, void *data)
{
- if (atomic_read(&kernel_backlight_disabled))
- return;
-
- /* we can receive multiple interrupts here, but the scheduled work
- * will run only once, with the last value
- */
- pmac_backlight_key_queued = direction;
- schedule_work(&pmac_backlight_key_work);
+ /* We keep the current backlight level (for now) */
+ if (ctrler == backlighter && data == backlighter_data)
+ backlighter = NULL;
}
+EXPORT_SYMBOL(unregister_backlight_controller);
-static int __pmac_backlight_set_legacy_brightness(int brightness)
+static int __set_backlight_enable(int enable)
{
- 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 + 1) /
- (OLD_BACKLIGHT_MAX + 1);
-
- if (props->brightness > props->max_brightness)
- props->brightness = props->max_brightness;
- else if (props->brightness < 0)
- props->brightness = 0;
-
- props->update_status(pmac_backlight);
- up(&pmac_backlight->sem);
-
- error = 0;
- }
- mutex_unlock(&pmac_backlight_mutex);
-
- return error;
+ 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;
}
-
-static void pmac_backlight_set_legacy_worker(void *data)
+int set_backlight_enable(int enable)
{
- if (atomic_read(&kernel_backlight_disabled))
- return;
-
- __pmac_backlight_set_legacy_brightness(pmac_backlight_set_legacy_queued);
+ if (!backlighter)
+ return -ENODEV;
+ backlight_req_enable = enable;
+ schedule_work(&backlight_work);
+ return 0;
}
-/* This function is called in interrupt context */
-void pmac_backlight_set_legacy_brightness_pmu(int brightness) {
- if (atomic_read(&kernel_backlight_disabled))
- return;
-
- pmac_backlight_set_legacy_queued = brightness;
- schedule_work(&pmac_backlight_set_legacy_work);
-}
+EXPORT_SYMBOL(set_backlight_enable);
-int pmac_backlight_set_legacy_brightness(int brightness)
+int get_backlight_enable(void)
{
- return __pmac_backlight_set_legacy_brightness(brightness);
+ if (!backlighter)
+ return -ENODEV;
+ return backlight_enabled;
}
+EXPORT_SYMBOL(get_backlight_enable);
-int pmac_backlight_get_legacy_brightness()
+static int __set_backlight_level(int level)
{
- int result = -ENXIO;
-
- mutex_lock(&pmac_backlight_mutex);
- if (pmac_backlight) {
- struct backlight_properties *props;
-
- down(&pmac_backlight->sem);
- props = pmac_backlight->props;
-
- result = props->brightness *
- (OLD_BACKLIGHT_MAX + 1) /
- (props->max_brightness + 1);
-
- up(&pmac_backlight->sem);
+ 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"
}
- mutex_unlock(&pmac_backlight_mutex);
-
- return result;
+ return rc;
}
-
-void pmac_backlight_disable()
+int set_backlight_level(int level)
{
- atomic_inc(&kernel_backlight_disabled);
+ if (!backlighter)
+ return -ENODEV;
+ backlight_req_level = level;
+ schedule_work(&backlight_work);
+ return 0;
}
-void pmac_backlight_enable()
+EXPORT_SYMBOL(set_backlight_level);
+
+int get_backlight_level(void)
{
- atomic_dec(&kernel_backlight_disabled);
+ if (!backlighter)
+ return -ENODEV;
+ return backlight_level;
}
+EXPORT_SYMBOL(get_backlight_level);
-EXPORT_SYMBOL_GPL(pmac_backlight);
-EXPORT_SYMBOL_GPL(pmac_backlight_mutex);
-EXPORT_SYMBOL_GPL(pmac_has_backlight_type);
+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);
+}