/* * arch/arm/mach-msm/flashlight.c - flashlight driver * * Copyright (C) 2009 zion huang * * 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; version 2 of the License. */ #define DEBUG #include #include #include #include #include #include #include #include #include #include "board-mahimahi-flashlight.h" struct flashlight_struct { struct led_classdev fl_lcdev; struct early_suspend early_suspend_flashlight; spinlock_t spin_lock; struct hrtimer timer; int brightness; int gpio_torch; int gpio_flash; int flash_duration_ms; }; static struct flashlight_struct the_fl; static inline void toggle(void) { gpio_direction_output(the_fl.gpio_torch, 0); udelay(2); gpio_direction_output(the_fl.gpio_torch, 1); udelay(2); } static void flashlight_hw_command(uint8_t addr, uint8_t data) { int i; for (i = 0; i < addr + 17; i++) toggle(); udelay(500); for (i = 0; i < data; i++) toggle(); udelay(500); } static enum hrtimer_restart flashlight_timeout(struct hrtimer *timer) { unsigned long flags; pr_debug("%s\n", __func__); spin_lock_irqsave(&the_fl.spin_lock, flags); gpio_direction_output(the_fl.gpio_flash, 0); the_fl.brightness = LED_OFF; spin_unlock_irqrestore(&the_fl.spin_lock, flags); return HRTIMER_NORESTART; } int flashlight_control(int mode) { int ret = 0; unsigned long flags; pr_debug("%s: mode %d -> %d\n", __func__, the_fl.brightness, mode); spin_lock_irqsave(&the_fl.spin_lock, flags); the_fl.brightness = mode; switch (mode) { case FLASHLIGHT_TORCH: pr_info("%s: half\n", __func__); /* If we are transitioning from flash to torch, make sure to * cancel the flash timeout timer, otherwise when it expires, * the torch will go off as well. */ hrtimer_cancel(&the_fl.timer); flashlight_hw_command(2, 4); break; case FLASHLIGHT_FLASH: pr_info("%s: full\n", __func__); hrtimer_cancel(&the_fl.timer); gpio_direction_output(the_fl.gpio_flash, 0); udelay(40); gpio_direction_output(the_fl.gpio_flash, 1); hrtimer_start(&the_fl.timer, ktime_set(the_fl.flash_duration_ms / 1000, (the_fl.flash_duration_ms % 1000) * NSEC_PER_MSEC), HRTIMER_MODE_REL); /* Flash overrides torch mode, and after the flash period, the * flash LED will turn off. */ mode = LED_OFF; break; case FLASHLIGHT_OFF: pr_info("%s: off\n", __func__); gpio_direction_output(the_fl.gpio_flash, 0); gpio_direction_output(the_fl.gpio_torch, 0); break; default: pr_err("%s: unknown flash_light flags: %d\n", __func__, mode); ret = -EINVAL; goto done; } done: spin_unlock_irqrestore(&the_fl.spin_lock, flags); return ret; } EXPORT_SYMBOL(flashlight_control); static void fl_lcdev_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { int level; switch (brightness) { case LED_HALF: level = FLASHLIGHT_TORCH; break; case LED_FULL: level = FLASHLIGHT_FLASH; break; case LED_OFF: default: level = FLASHLIGHT_OFF; }; flashlight_control(level); } static void flashlight_early_suspend(struct early_suspend *handler) { flashlight_control(FLASHLIGHT_OFF); } static int flashlight_setup_gpio(struct flashlight_platform_data *fl_pdata) { int ret; pr_debug("%s\n", __func__); if (fl_pdata->gpio_init) { ret = fl_pdata->gpio_init(); if (ret < 0) { pr_err("%s: gpio init failed: %d\n", __func__, ret); return ret; } } if (fl_pdata->torch) { ret = gpio_request(fl_pdata->torch, "flashlight_torch"); if (ret < 0) { pr_err("%s: gpio_request failed\n", __func__); return ret; } } if (fl_pdata->flash) { ret = gpio_request(fl_pdata->flash, "flashlight_flash"); if (ret < 0) { pr_err("%s: gpio_request failed\n", __func__); gpio_free(fl_pdata->torch); return ret; } } the_fl.gpio_torch = fl_pdata->torch; the_fl.gpio_flash = fl_pdata->flash; the_fl.flash_duration_ms = fl_pdata->flash_duration_ms; return 0; } static int flashlight_probe(struct platform_device *pdev) { struct flashlight_platform_data *fl_pdata = pdev->dev.platform_data; int err = 0; pr_debug("%s\n", __func__); err = flashlight_setup_gpio(fl_pdata); if (err < 0) { pr_err("%s: setup GPIO failed\n", __func__); goto fail_free_mem; } spin_lock_init(&the_fl.spin_lock); the_fl.fl_lcdev.name = pdev->name; the_fl.fl_lcdev.brightness_set = fl_lcdev_brightness_set; the_fl.fl_lcdev.brightness = LED_OFF; err = led_classdev_register(&pdev->dev, &the_fl.fl_lcdev); if (err < 0) { pr_err("failed on led_classdev_register\n"); goto fail_free_gpio; } hrtimer_init(&the_fl.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); the_fl.timer.function = flashlight_timeout; #ifdef CONFIG_HAS_EARLYSUSPEND the_fl.early_suspend_flashlight.suspend = flashlight_early_suspend; the_fl.early_suspend_flashlight.resume = NULL; register_early_suspend(&the_fl.early_suspend_flashlight); #endif return 0; fail_free_gpio: if (fl_pdata->torch) gpio_free(fl_pdata->torch); if (fl_pdata->flash) gpio_free(fl_pdata->flash); fail_free_mem: return err; } static int flashlight_remove(struct platform_device *pdev) { struct flashlight_platform_data *fl_pdata = pdev->dev.platform_data; pr_debug("%s\n", __func__); hrtimer_cancel(&the_fl.timer); unregister_early_suspend(&the_fl.early_suspend_flashlight); flashlight_control(FLASHLIGHT_OFF); led_classdev_unregister(&the_fl.fl_lcdev); if (fl_pdata->torch) gpio_free(fl_pdata->torch); if (fl_pdata->flash) gpio_free(fl_pdata->flash); return 0; } static struct platform_driver flashlight_driver = { .probe = flashlight_probe, .remove = flashlight_remove, .driver = { .name = FLASHLIGHT_NAME, .owner = THIS_MODULE, }, }; static int __init flashlight_init(void) { pr_debug("%s\n", __func__); return platform_driver_register(&flashlight_driver); } static void __exit flashlight_exit(void) { pr_debug("%s\n", __func__); platform_driver_unregister(&flashlight_driver); } module_init(flashlight_init); module_exit(flashlight_exit); MODULE_AUTHOR("Zion Huang "); MODULE_DESCRIPTION("flash light driver"); MODULE_LICENSE("GPL");