Merge ../linux-2.6
[pandora-kernel.git] / arch / powerpc / platforms / powermac / backlight.c
index 8be2f7d..d664154 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/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 <linux/adb.h>
+#include <linux/pmu.h>
+#include <asm/atomic.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;
+static void pmac_backlight_key_worker(void *data);
+static void pmac_backlight_set_legacy_worker(void *data);
 
-static void backlight_callback(void *);
-static DECLARE_WORK(backlight_work, backlight_callback, NULL);
+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);
 
-void register_backlight_controller(struct backlight_controller *ctrler,
-                                         void *data, char *type)
-{
-       struct device_node* bk_node;
-       char *prop;
-       int valid = 0;
+/* 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;
 
-       /* There's already a matching controller, bail out */
-       if (backlighter != NULL)
-               return;
+/* 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");
 
-       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;
+               char *prop = get_property(bk_node, "backlight-control", NULL);
+               if (prop && strncmp(prop, type, strlen(type)) == 0)
+                       return 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;
+
+       return 0;
+}
+
+int pmac_backlight_curve_lookup(struct fb_info *info, int value)
+{
+       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;
+                       }
+               }
+
        }
 
-#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;
+       return level;
+}
+
+static void pmac_backlight_key_worker(void *data)
+{
+       if (atomic_read(&kernel_backlight_disabled))
+               return;
+
+       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);
        }
-#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);
+       mutex_unlock(&pmac_backlight_mutex);
 }
-EXPORT_SYMBOL(register_backlight_controller);
 
-void unregister_backlight_controller(struct backlight_controller
-                                           *ctrler, void *data)
+/* This function is called in interrupt context */
+void pmac_backlight_key(int direction)
 {
-       /* We keep the current backlight level (for now) */
-       if (ctrler == backlighter && data == backlighter_data)
-               backlighter = NULL;
+       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);
 }
-EXPORT_SYMBOL(unregister_backlight_controller);
 
-static int __set_backlight_enable(int enable)
+static int __pmac_backlight_set_legacy_brightness(int brightness)
 {
-       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;
+       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 set_backlight_enable(int enable)
+
+static void pmac_backlight_set_legacy_worker(void *data)
 {
-       if (!backlighter)
-               return -ENODEV;
-       backlight_req_enable = enable;
-       schedule_work(&backlight_work);
-       return 0;
+       if (atomic_read(&kernel_backlight_disabled))
+               return;
+
+       __pmac_backlight_set_legacy_brightness(pmac_backlight_set_legacy_queued);
 }
 
-EXPORT_SYMBOL(set_backlight_enable);
+/* This function is called in interrupt context */
+void pmac_backlight_set_legacy_brightness_pmu(int brightness) {
+       if (atomic_read(&kernel_backlight_disabled))
+               return;
 
-int get_backlight_enable(void)
-{
-       if (!backlighter)
-               return -ENODEV;
-       return backlight_enabled;
+       pmac_backlight_set_legacy_queued = brightness;
+       schedule_work(&pmac_backlight_set_legacy_work);
 }
-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"
-       }
-       return rc;
+       return __pmac_backlight_set_legacy_brightness(brightness);
 }
-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)
+               down(&pmac_backlight->sem);
+               props = pmac_backlight->props;
+
+               result = props->brightness *
+                       (OLD_BACKLIGHT_MAX + 1) /
+                       (props->max_brightness + 1);
+
+               up(&pmac_backlight->sem);
+       }
+       mutex_unlock(&pmac_backlight_mutex);
+
+       return result;
+}
+
+void pmac_backlight_disable()
 {
-       if (!backlighter)
-               return -ENODEV;
-       return backlight_level;
+       atomic_inc(&kernel_backlight_disabled);
 }
-EXPORT_SYMBOL(get_backlight_level);
 
-static void backlight_callback(void *dummy)
+void pmac_backlight_enable()
 {
-       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);
+       atomic_dec(&kernel_backlight_disabled);
 }
+
+EXPORT_SYMBOL_GPL(pmac_backlight);
+EXPORT_SYMBOL_GPL(pmac_backlight_mutex);
+EXPORT_SYMBOL_GPL(pmac_has_backlight_type);