HID: add backlight support to PicoLCD device
[pandora-kernel.git] / drivers / hid / hid-picolcd.c
index e144647..0ea7a3f 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <linux/fb.h>
 #include <linux/vmalloc.h>
+#include <linux/backlight.h>
 
 #include <linux/seq_file.h>
 #include <linux/debugfs.h>
@@ -183,6 +184,11 @@ struct picolcd_data {
        struct fb_info *fb_info;
        struct fb_deferred_io fb_defio;
 #endif /* CONFIG_FB */
+#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE)
+       struct backlight_device *backlight;
+       u8 lcd_brightness;
+       u8 lcd_power;
+#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */
 
        /* Housekeeping stuff */
        spinlock_t lock;
@@ -729,7 +735,7 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data)
        kfree(fb_vbitmap);
 }
 
-
+#define picolcd_fbinfo(d) ((d)->fb_info)
 #else
 static inline int picolcd_fb_reset(struct picolcd_data *data, int clear)
 {
@@ -742,8 +748,107 @@ static inline int picolcd_init_framebuffer(struct picolcd_data *data)
 static void picolcd_exit_framebuffer(struct picolcd_data *data)
 {
 }
+#define picolcd_fbinfo(d) NULL
 #endif /* CONFIG_FB */
 
+#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE)
+/*
+ * backlight class device
+ */
+static int picolcd_get_brightness(struct backlight_device *bdev)
+{
+       struct picolcd_data *data = bl_get_data(bdev);
+       return data->lcd_brightness;
+}
+
+static int picolcd_set_brightness(struct backlight_device *bdev)
+{
+       struct picolcd_data *data = bl_get_data(bdev);
+       struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev);
+       unsigned long flags;
+
+       if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
+               return -ENODEV;
+
+       data->lcd_brightness = bdev->props.brightness & 0x0ff;
+       data->lcd_power      = bdev->props.power;
+       spin_lock_irqsave(&data->lock, flags);
+       hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0);
+       usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
+       spin_unlock_irqrestore(&data->lock, flags);
+       return 0;
+}
+
+static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb)
+{
+       return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev));
+}
+
+static const struct backlight_ops picolcd_blops = {
+       .update_status  = picolcd_set_brightness,
+       .get_brightness = picolcd_get_brightness,
+       .check_fb       = picolcd_check_bl_fb,
+};
+
+static int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report)
+{
+       struct device *dev = &data->hdev->dev;
+       struct backlight_device *bdev;
+       struct backlight_properties props;
+       if (!report)
+               return -ENODEV;
+       if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
+                       report->field[0]->report_size != 8) {
+               dev_err(dev, "unsupported BRIGHTNESS report");
+               return -EINVAL;
+       }
+
+       memset(&props, 0, sizeof(props));
+       props.max_brightness = 0xff;
+       bdev = backlight_device_register(dev_name(dev), dev, data,
+                       &picolcd_blops, &props);
+       if (IS_ERR(bdev)) {
+               dev_err(dev, "failed to register backlight\n");
+               return PTR_ERR(bdev);
+       }
+       bdev->props.brightness     = 0xff;
+       data->lcd_brightness       = 0xff;
+       data->backlight            = bdev;
+       picolcd_set_brightness(bdev);
+       return 0;
+}
+
+static void picolcd_exit_backlight(struct picolcd_data *data)
+{
+       struct backlight_device *bdev = data->backlight;
+
+       data->backlight = NULL;
+       if (bdev)
+               backlight_device_unregister(bdev);
+}
+
+static inline int picolcd_resume_backlight(struct picolcd_data *data)
+{
+       if (!data->backlight)
+               return 0;
+       return picolcd_set_brightness(data->backlight);
+}
+
+#else
+static inline int picolcd_init_backlight(struct picolcd_data *data,
+               struct hid_report *report)
+{
+       return 0;
+}
+static inline void picolcd_exit_backlight(struct picolcd_data *data)
+{
+}
+static inline int picolcd_resume_backlight(struct picolcd_data *data)
+{
+       return 0;
+}
+#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */
+
 /*
  * input class device
  */
@@ -879,6 +984,7 @@ static int picolcd_reset(struct hid_device *hdev)
        if (error)
                return error;
 
+       picolcd_resume_backlight(data);
 #if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE)
        if (data->fb_info)
                schedule_delayed_work(&data->fb_info->deferred_work, 0);
@@ -1567,6 +1673,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
        if (error)
                goto err;
 
+       /* Setup backlight class device */
+       error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev));
+       if (error)
+               goto err;
+
 #ifdef CONFIG_DEBUG_FS
        report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
        if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
@@ -1576,6 +1687,7 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 #endif
        return 0;
 err:
+       picolcd_exit_backlight(data);
        picolcd_exit_framebuffer(data);
        picolcd_exit_cir(data);
        picolcd_exit_keys(data);
@@ -1707,6 +1819,7 @@ static void picolcd_remove(struct hid_device *hdev)
        spin_unlock_irqrestore(&data->lock, flags);
 
        /* Clean up the framebuffer */
+       picolcd_exit_backlight(data);
        picolcd_exit_framebuffer(data);
        /* Cleanup input */
        picolcd_exit_cir(data);