blob: 829b1f11cf21812d00a5b332993ac1a60226e8c0 [file] [log] [blame]
/*
* arch/arm/mach-msm/flashlight.c - flashlight driver
*
* Copyright (C) 2009 zion huang <zion_huang@htc.com>
*
* 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 <linux/delay.h>
#include <linux/earlysuspend.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/wakelock.h>
#include <linux/hrtimer.h>
#include <mach/msm_iomap.h>
#include <asm/gpio.h>
#include <asm/io.h>
#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 <zion_huang@htc.com>");
MODULE_DESCRIPTION("flash light driver");
MODULE_LICENSE("GPL");