280 lines
6.3 KiB
C
280 lines
6.3 KiB
C
/*
|
|
* 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");
|