linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / arch / powerpc / platforms / powermac / backlight.c
index d664154..8be2f7d 100644 (file)
  * 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);
+}