[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <4724B48D.6090405@antonello.org>
Date: Sun, 28 Oct 2007 17:10:53 +0100
From: jack@...onello.org
To: linux-kernel@...r.kernel.org
CC: rpurdie@...ys.net
Subject: [PATCH] backlight dimmer
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 <jack@...onello.org>
+ *
+ * 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 <jack@...onello.org>
+ *
+ * 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 majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists