[PATCH] backlight dimmer

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,
this patch implements a macbook like backlight dimmer on top of backlight.c.

The dimmer is entirely in kernelspace and is suitable for an embedded context in order to avoid the overhead of a daemon controlling the backlight. Implementing this functionality in userspace has other advantages and is a perfectly reasonable alternative, so this patch is
not the definitive solution.

activate dimmer:
echo 1 > /sys/devices/virtual/backlight/*/dimmer_control

other attributes (britness levels & timeout):
/sys/devices/virtual/backlight/*/dimmer_high_level
/sys/devices/virtual/backlight/*/dimmer_low_level
/sys/devices/virtual/backlight/*/dimmer_timeout

regards,
jacopo antonello



******************************* diff follows
--- linux-2.6.23.1/include/linux/backlight.h	2007-10-12 18:43:44.000000000 +0200
+++ b/include/linux/backlight.h	2007-10-28 13:45:21.000000000 +0100
@@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
+#include <linux/timeout.h>

/* Notes on locking:
*
@@ -70,6 +71,12 @@ struct backlight_device {
	/* The framebuffer notifier block */
	struct notifier_block fb_notif;

+  /* dimmer stuff */
+  struct timeout *timeout;
+  struct input_handler *input_handler;
+  unsigned short dimmer_low_level;
+  unsigned short dimmer_high_level;
+
	struct device dev;
};

--- linux-2.6.23.1/drivers/video/backlight/Makefile	2007-10-12 18:43:44.000000000 +0200
+++ b/drivers/video/backlight/Makefile	2007-10-28 13:45:21.000000000 +0100
@@ -1,7 +1,7 @@
# Backlight & LCD drivers

obj-$(CONFIG_LCD_CLASS_DEVICE)     += lcd.o
-obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
+obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o timeout.o
obj-$(CONFIG_BACKLIGHT_CORGI)	+= corgi_bl.o
obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
--- linux-2.6.23.1/include/linux/timeout.h	1970-01-01 01:00:00.000000000 +0100
+++ b/include/linux/timeout.h	2007-10-28 13:45:21.000000000 +0100
@@ -0,0 +1,66 @@
+/*
+ *  timeout.h - simple timeout
+ *
+ *
+ *  Copyright (C) 2007 Jacopo Antonello <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#ifndef _TIMEOUT_H_
+#define _TIMEOUT_H_
+
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+enum timeout_state {
+  TIMEOUT_RUNNING,
+  TIMEOUT_TRIGGERED,
+  TIMEOUT_FINILIZED
+};
+
+struct timeout {
+  struct mutex lock;
+  spinlock_t latest_lock;
+  struct delayed_work trigger_worker;
+  struct work_struct start_worker;
+  unsigned long latest;
+  enum timeout_state state;
+
+  unsigned long duration; /* client defined duration */
+  unsigned long data; /* client data */
+  void (*trigger)(unsigned long); /* client function */
+  void (*start)(unsigned long); /* client function */
+};
+
+extern void timeout_touch(struct timeout *timeout);
+
+extern void timeout_init(struct timeout *timeout);
+
+extern void timeout_kill(struct timeout *timeout);
+
+static inline int timeout_sec2jiffies(int secs)
+{
+    return secs * HZ;
+}
+
+static inline int timeout_jif2sec(unsigned long jif)
+{
+    return jif / HZ;
+}
+
+#endif
--- linux-2.6.23.1/drivers/video/backlight/timeout.c	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/video/backlight/timeout.c	2007-10-28 13:45:21.000000000 +0100
@@ -0,0 +1,143 @@
+/*
+ *  timeout.c - simple timeout
+ *
+ *
+ *  Copyright (C) 2007 Jacopo Antonello <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/timeout.h>
+
+
+/* atomic context helpers for timeout->latest */
+static inline unsigned long read_latest(struct timeout *timeout)
+{
+  unsigned long ret;
+  spin_lock(&timeout->latest_lock);
+  ret = timeout->latest;
+  spin_unlock(&timeout->latest_lock);
+  return ret;
+}
+
+static inline unsigned long elapsed(struct timeout *timeout)
+{
+  unsigned long elapsed;
+  spin_lock(&timeout->latest_lock);
+  elapsed = jiffies - timeout->latest;
+  spin_unlock(&timeout->latest_lock);
+  return elapsed;
+}
+
+static inline void touch_latest(struct timeout *timeout)
+{
+  spin_lock(&timeout->latest_lock);
+  timeout->latest = jiffies;
+  spin_unlock(&timeout->latest_lock);
+}
+
+/* non-atomic context */
+static void trigger_worker_function(struct work_struct *work)
+{
+  struct timeout *timeout = container_of(work, struct timeout, trigger_worker.work);
+
+  mutex_lock(&timeout->lock);
+
+  /* flag to kill worker */
+  if (timeout->state != TIMEOUT_RUNNING)
+    goto exit;
+
+  /* TODO beware of jiffies calculations! use helpers */
+  if ( elapsed(timeout) >= timeout->duration ) {
+    /*
+    printk(KERN_DEBUG "tigger_worker_function() -> timeout->trigger() (elapsed: %f) \n",
+        (elapsed(timeout) / (float) (HZ)));
+        */
+    printk(KERN_DEBUG "tigger_worker_function() -> timeout->trigger() (elapsed: %ld ms) \n",
+        (elapsed(timeout) * 1000 / (HZ)));
+    timeout->state = TIMEOUT_TRIGGERED;
+    timeout->trigger(timeout->data);
+  }
+  else {
+    unsigned long new_delay = ( read_latest(timeout) - jiffies ) + timeout->duration;
+    /*
+    printk(KERN_DEBUG "tigger_worker_function() -> schedule_delayed_work() (new_delay: %ld %f)\n",
+        new_delay, new_delay / (float) (HZ));
+        */
+    printk(KERN_DEBUG "tigger_worker_function() -> schedule_delayed_work() (new_delay: %ld jif %ld ms)\n",
+        new_delay, new_delay * 1000 / (HZ));
+    schedule_delayed_work(&timeout->trigger_worker,
+/*        ( jiffies - read_latest(timeout) ) + timeout->duration ); */
+        new_delay );
+
+  }
+
+  exit:
+    mutex_unlock(&timeout->lock);
+}
+
+/* non-atomic context */
+static void start_worker_function(struct work_struct *work)
+{
+  struct timeout *timeout = container_of(work, struct timeout, start_worker);
+
+  printk(KERN_DEBUG "start_worker_function() -> timeout->start()\n");
+  timeout->start(timeout->data);
+  schedule_delayed_work(&timeout->trigger_worker, timeout->duration);
+}
+
+/* atomic context touch */
+void timeout_touch(struct timeout *timeout)
+{
+  touch_latest(timeout);
+
+  if (timeout->state == TIMEOUT_TRIGGERED &&
+      mutex_trylock(&timeout->lock)) {
+
+    if (timeout->state == TIMEOUT_TRIGGERED) {
+      timeout->state = TIMEOUT_RUNNING;
+      schedule_work(&timeout->start_worker);
+    }
+
+    mutex_unlock(&timeout->lock);
+  }
+}
+EXPORT_SYMBOL(timeout_touch);
+
+void timeout_init(struct timeout *timeout)
+{
+  mutex_init(&timeout->lock);
+  spin_lock_init(&timeout->latest_lock);
+  INIT_DELAYED_WORK(&timeout->trigger_worker, trigger_worker_function);
+  INIT_WORK(&timeout->start_worker, start_worker_function);
+  timeout->state = TIMEOUT_TRIGGERED;
+}
+EXPORT_SYMBOL(timeout_init);
+
+void timeout_kill(struct timeout *timeout)
+{
+  mutex_lock(&timeout->lock);
+  if (timeout->state == TIMEOUT_RUNNING)
+    cancel_delayed_work_sync(&timeout->trigger_worker);
+
+  flush_scheduled_work();
+  timeout->state = TIMEOUT_FINILIZED;
+  mutex_unlock(&timeout->lock);
+}
+EXPORT_SYMBOL(timeout_kill);
--- linux-2.6.23.1/drivers/video/backlight/backlight.c	2007-10-12 18:43:44.000000000 +0200
+++ b/drivers/video/backlight/backlight.c	2007-10-28 13:45:21.000000000 +0100
@@ -8,7 +8,8 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
-#include <linux/backlight.h>
+#include <linux/input.h>
+#include <linux/backlight.h> #include <linux/notifier.h>
#include <linux/ctype.h>
#include <linux/err.h>
@@ -172,6 +173,267 @@ static void bl_device_release(struct dev
	kfree(bd);
}

+/*
+ * ---------------------------------------------------
+ *           backlight dimmer
+ * --------------------------------------------------
+ */
+
+static const struct input_device_id idi_dimmer[] = {
+  {
+    .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+    .evbit = { BIT(EV_KEY) },
+  }, /* key event */
+  {
+    .flags = INPUT_DEVICE_ID_MATCH_RELBIT,
+    .relbit = { BIT(REL_X) | BIT(REL_Y) | BIT(REL_WHEEL) },
+  },  /* rel axes */
+  {
+    .flags = INPUT_DEVICE_ID_MATCH_ABSBIT,
+    .absbit = { BIT(ABS_X) | BIT(ABS_Y) },
+  },  /* abs axes */
+  { }
+};
+
+static void handler_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
+{
+  struct backlight_device *dev = (struct backlight_device *)handle->private;
+
+  switch(type) {
+    case EV_KEY:
+    case EV_REL:
+      timeout_touch(dev->timeout);
+  }
+}
+
+static void dimmer_start(unsigned long data)
+{
+	struct backlight_device *bd = (struct backlight_device *)data;
+
+  printk(KERN_DEBUG "SWITCH TO HIGH\n");
+  bd->props.brightness = bd->dimmer_high_level;
+  backlight_update_status(bd);
+}
+
+static void dimmer_trigger(unsigned long data)
+{
+	struct backlight_device *bd = (struct backlight_device *)data;
+
+  printk(KERN_DEBUG "SWITCH TO LOW\n");
+  bd->props.brightness = bd->dimmer_low_level;
+  backlight_update_status(bd);
+}
+
+static int handler_connect(struct input_handler *handler, struct input_dev *dev,
+    const struct input_device_id *id)
+{
+  int error = 0;
+  struct input_handle *handle;
+
+
+  handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+  if (!handle)
+    return -ENOMEM;
+
+  handle->handler = handler;
+  handle->dev = dev;
+  handle->name = "backlight";
+  handle->private = handler->private;
+
+  error = input_register_handle(handle);
+  if (error)
+    goto err_free_handle;
+
+  error = input_open_device(handle);
+  if (error)
+    goto err_unregister_handle;
+
+  return 0;
+
+  err_unregister_handle:
+    input_unregister_handle(handle);
+  err_free_handle:
+    kfree(handle);
+  return error;
+}
+
+static void handler_disconnect(struct input_handle *handle)
+{
+  input_close_device(handle);
+  input_unregister_handle(handle);
+  kfree(handle);
+}
+
+static int enable_dimmer(struct backlight_device *dev)
+{
+  printk(KERN_DEBUG "enable_dimmer()...\n");
+  dev->input_handler = kzalloc(sizeof(struct input_handler), GFP_KERNEL);
+  if (!dev->input_handler)
+    return -ENOMEM;
+
+  if (!dev->dimmer_low_level)
+    dev->dimmer_low_level = dev->props.max_brightness/2;
+  if (!dev->dimmer_high_level)
+    dev->dimmer_high_level = dev->props.max_brightness;
+
+  dev->input_handler->event = handler_event;
+  dev->input_handler->connect = handler_connect;
+  dev->input_handler->disconnect = handler_disconnect;
+  dev->input_handler->name = "backlight";
+  dev->input_handler->id_table = idi_dimmer;
+  dev->input_handler->private = dev;
+
+  dev->timeout = kzalloc(sizeof(struct timeout), GFP_KERNEL);
+  if (!dev->timeout)
+    goto free_handler;
+
+  timeout_init(dev->timeout);
+  dev->timeout->duration = timeout_sec2jiffies(5);
+  dev->timeout->data = (unsigned long)dev;
+  dev->timeout->trigger = dimmer_trigger;
+  dev->timeout->start = dimmer_start;
+
+  if (!input_register_handler(dev->input_handler))
+    return 0; /* success */
+  /* else go on & cleanup */
+
+  free_handler:
+    kfree(dev->input_handler);
+    dev->input_handler=NULL;
+    return -ENOMEM;
+}
+
+static void disable_dimmer(struct backlight_device *dev)
+{
+  if (!dev->input_handler)
+    return;
+
+  input_unregister_handler(dev->input_handler);
+  timeout_kill(dev->timeout);
+  kfree(dev->input_handler);
+  kfree(dev->timeout);
+  dev->input_handler = NULL;
+  dev->timeout = NULL;
+  printk(KERN_DEBUG "disable_dimmer()\n");
+}
+
+/* show */
+static ssize_t show_dimmer_control(struct device *dev,
+    struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  return snprintf(buf, PAGE_SIZE, "%d\n",
+      (bd->input_handler != NULL));
+}
+
+static ssize_t show_dimmer_timeout(struct device *dev,
+    struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  if (bd->timeout)
+    return snprintf(buf, PAGE_SIZE, "%d\n",
+        timeout_jif2sec(bd->timeout->duration));
+  else
+    return 0;
+}
+
+static ssize_t show_dimmer_low_level(struct device *dev,
+    struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  return snprintf(buf, PAGE_SIZE, "%d\n",
+      bd->dimmer_low_level);
+}
+
+static ssize_t show_dimmer_high_level(struct device *dev,
+    struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  return snprintf(buf, PAGE_SIZE, "%d\n",
+      bd->dimmer_high_level);
+}
+
+/* store */
+static ssize_t store_dimmer_control(struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  char *endp;
+  int control = simple_strtoul(buf, &endp, 0);
+
+	mutex_lock(&bd->ops_lock);
+  if (control == 1 && !bd->input_handler)
+    enable_dimmer(bd);
+  else if (control == 0 && bd->input_handler)
+    disable_dimmer(bd);
+  mutex_unlock(&bd->ops_lock);
+
+	return count;
+}
+
+static ssize_t store_dimmer_timeout(struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  char *endp;
+  int timeout = simple_strtoul(buf, &endp, 0);
+
+  if (timeout <= 0 || timeout >= 60*30)
+    return -ENXIO;
+
+	mutex_lock(&bd->ops_lock);
+  if (!bd->timeout) {
+	  mutex_unlock(&bd->ops_lock);
+    return -ENXIO;
+  }
+  bd->timeout->duration = timeout_sec2jiffies(timeout);
+	mutex_unlock(&bd->ops_lock);
+
+  return count;
+}
+
+static ssize_t store_dimmer_low_level(struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  char *endp;
+  int low = simple_strtoul(buf, &endp, 0);
+
+  if (low < 0 || low > bd->props.max_brightness)
+    return -ENXIO;
+
+	mutex_lock(&bd->ops_lock);
+  bd->dimmer_low_level = low;
+	mutex_unlock(&bd->ops_lock);
+
+  return count;
+}
+
+static ssize_t store_dimmer_high_level(struct device *dev,
+    struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+  char *endp;
+  int high = simple_strtoul(buf, &endp, 0);
+
+  if (high < 0 || high > bd->props.max_brightness)
+    return -ENXIO;
+
+	mutex_lock(&bd->ops_lock);
+  bd->dimmer_high_level = high;
+	mutex_unlock(&bd->ops_lock);
+
+  return count;
+}
+
static struct device_attribute bl_device_attributes[] = {
	__ATTR(bl_power, 0644, backlight_show_power, backlight_store_power),
	__ATTR(brightness, 0644, backlight_show_brightness,
@@ -179,6 +441,10 @@ static struct device_attribute bl_device
	__ATTR(actual_brightness, 0444, backlight_show_actual_brightness,
		     NULL),
	__ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),
+	__ATTR(dimmer_control, 0666, show_dimmer_control, store_dimmer_control),
+	__ATTR(dimmer_timeout, 0666, show_dimmer_timeout, store_dimmer_timeout),
+	__ATTR(dimmer_low_level, 0666, show_dimmer_low_level, store_dimmer_low_level),
+	__ATTR(dimmer_high_level, 0666, show_dimmer_high_level, store_dimmer_high_level),
	__ATTR_NULL,
};

@@ -259,6 +525,7 @@ void backlight_device_unregister(struct #endif
	mutex_lock(&bd->ops_lock);
	bd->ops = NULL;
+  disable_dimmer(bd);
	mutex_unlock(&bd->ops_lock);

	backlight_unregister_fb(bd);



-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux