blob: 99e4474bfd5149f5a408e97d4fa9887fda983536 [file] [log] [blame]
/*
* drivers/haptic/haptic-samsung-pwm.c
*
* Copyright (C) 2008 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ctype.h>
#include <linux/haptic.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/pwm/pwm.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include "haptic.h"
#define PWM_HAPTIC_PERIOD 44640
#define PWM_HAPTIC_DEFAULT_LEVEL 2
static int haptic_levels[] = { 18360, 14880, 10860, 5280, 540, };
struct samsung_pwm_haptic {
struct haptic_classdev cdev;
struct work_struct work;
struct haptic_platform_data *pdata;
struct pwm_device *pwm;
struct timer_list timer;
int enable;
int powered;
int level;
int level_max;
};
static inline struct samsung_pwm_haptic *cdev_to_samsung_pwm_haptic(struct
haptic_classdev
*haptic_cdev)
{
return container_of(haptic_cdev, struct samsung_pwm_haptic, cdev);
}
static void samsung_pwm_haptic_power_on(struct samsung_pwm_haptic *haptic)
{
if (haptic->powered)
return;
haptic->powered = 1;
if (gpio_is_valid(haptic->pdata->gpio))
gpio_set_value(haptic->pdata->gpio, 1);
pwm_start(haptic->pwm);
}
static void samsung_pwm_haptic_power_off(struct samsung_pwm_haptic *haptic)
{
if (!haptic->powered)
return;
haptic->powered = 0;
if (gpio_is_valid(haptic->pdata->gpio))
gpio_set_value(haptic->pdata->gpio, 0);
pwm_stop(haptic->pwm);
}
static int samsung_pwm_haptic_set_pwm_cycle(struct samsung_pwm_haptic *haptic)
{
int duty = haptic_levels[haptic->level];
return pwm_set(haptic->pwm, PWM_HAPTIC_PERIOD, duty, 1);
}
static void samsung_pwm_haptic_work(struct work_struct *work)
{
struct samsung_pwm_haptic *haptic;
int r;
haptic = container_of(work, struct samsung_pwm_haptic, work);
if (haptic->enable) {
r = samsung_pwm_haptic_set_pwm_cycle(haptic);
if (r) {
dev_dbg(haptic->cdev.dev, "set_pwm_cycle failed\n");
return;
}
samsung_pwm_haptic_power_on(haptic);
} else {
samsung_pwm_haptic_power_off(haptic);
}
}
static void samsung_pwm_haptic_timer(unsigned long data)
{
struct samsung_pwm_haptic *haptic = (struct samsung_pwm_haptic *)data;
haptic->enable = 0;
samsung_pwm_haptic_power_off(haptic);
}
static void samsung_pwm_haptic_set(struct haptic_classdev *haptic_cdev,
enum haptic_value value)
{
struct samsung_pwm_haptic *haptic =
cdev_to_samsung_pwm_haptic(haptic_cdev);
switch (value) {
case HAPTIC_OFF:
haptic->enable = 0;
break;
case HAPTIC_HALF:
case HAPTIC_FULL:
default:
haptic->enable = 1;
break;
}
schedule_work(&haptic->work);
}
static enum haptic_value samsung_pwm_haptic_get(struct haptic_classdev
*haptic_cdev)
{
struct samsung_pwm_haptic *haptic =
cdev_to_samsung_pwm_haptic(haptic_cdev);
if (haptic->enable)
return HAPTIC_FULL;
return HAPTIC_OFF;
}
#define ATTR_DEF_SHOW(name) \
static ssize_t samsung_pwm_haptic_show_##name(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
struct samsung_pwm_haptic *haptic =\
cdev_to_samsung_pwm_haptic(haptic_cdev); \
\
return sprintf(buf, "%u\n", haptic->name) + 1; \
}
#define ATTR_DEF_STORE(name) \
static ssize_t samsung_pwm_haptic_store_##name(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t size) \
{ \
struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev); \
struct samsung_pwm_haptic *haptic =\
cdev_to_samsung_pwm_haptic(haptic_cdev); \
ssize_t ret = -EINVAL; \
unsigned long val; \
\
ret = strict_strtoul(buf, 10, &val); \
if (ret == 0) { \
ret = size; \
haptic->name = val; \
schedule_work(&haptic->work); \
} \
\
return ret; \
}
ATTR_DEF_SHOW(enable);
ATTR_DEF_STORE(enable);
static DEVICE_ATTR(enable, 0644, samsung_pwm_haptic_show_enable,
samsung_pwm_haptic_store_enable);
static ssize_t samsung_pwm_haptic_store_level(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
struct samsung_pwm_haptic *haptic =
cdev_to_samsung_pwm_haptic(haptic_cdev);
ssize_t ret = -EINVAL;
unsigned long val;
ret = strict_strtoul(buf, 10, &val);
if (ret == 0) {
ret = size;
if (haptic->level_max < val)
val = haptic->level_max;
haptic->level = val;
schedule_work(&haptic->work);
}
return ret;
}
ATTR_DEF_SHOW(level);
static DEVICE_ATTR(level, 0644, samsung_pwm_haptic_show_level,
samsung_pwm_haptic_store_level);
ATTR_DEF_SHOW(level_max);
static DEVICE_ATTR(level_max, 0444, samsung_pwm_haptic_show_level_max, NULL);
static ssize_t samsung_pwm_haptic_store_oneshot(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct haptic_classdev *haptic_cdev = dev_get_drvdata(dev);
struct samsung_pwm_haptic *haptic =
cdev_to_samsung_pwm_haptic(haptic_cdev);
ssize_t ret = -EINVAL;
unsigned long val;
ret = strict_strtoul(buf, 10, &val);
if (ret == 0) {
ret = size;
haptic->enable = 1;
mod_timer(&haptic->timer, jiffies + val * HZ / 1000);
schedule_work(&haptic->work);
}
return ret;
}
static DEVICE_ATTR(oneshot, 0200, NULL, samsung_pwm_haptic_store_oneshot);
static struct attribute *haptic_attributes[] = {
&dev_attr_enable.attr,
&dev_attr_level.attr,
&dev_attr_level_max.attr,
&dev_attr_oneshot.attr,
NULL,
};
static const struct attribute_group haptic_group = {
.attrs = haptic_attributes,
};
static int samsung_pwm_haptic_probe(struct platform_device *pdev)
{
struct haptic_platform_data *pdata = pdev->dev.platform_data;
struct samsung_pwm_haptic *haptic;
int ret;
haptic = kzalloc(sizeof(struct samsung_pwm_haptic), GFP_KERNEL);
if (!haptic) {
dev_err(&pdev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, haptic);
haptic->cdev.set = samsung_pwm_haptic_set;
haptic->cdev.get = samsung_pwm_haptic_get;
haptic->cdev.show_enable = samsung_pwm_haptic_show_enable;
haptic->cdev.store_enable = samsung_pwm_haptic_store_enable;
haptic->cdev.store_oneshot = samsung_pwm_haptic_store_oneshot;
haptic->cdev.show_level = samsung_pwm_haptic_show_level;
haptic->cdev.store_level = samsung_pwm_haptic_store_level;
haptic->cdev.show_level_max = samsung_pwm_haptic_show_level_max;
haptic->cdev.name = pdata->name;
haptic->pdata = pdata;
haptic->enable = 0;
haptic->level = PWM_HAPTIC_DEFAULT_LEVEL;
haptic->level_max = ARRAY_SIZE(haptic_levels);
if (pdata->setup_pin)
pdata->setup_pin();
INIT_WORK(&haptic->work, samsung_pwm_haptic_work);
/* register our new haptic device */
ret = haptic_classdev_register(&pdev->dev, &haptic->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "haptic_classdev_register failed\n");
goto error_classdev;
}
haptic->pwm = pwm_request(pdata->pwm_id, "haptic");
if (IS_ERR(haptic->pwm)) {
dev_err(&pdev->dev, "unable to request PWM for haptic\n");
ret = PTR_ERR(haptic->pwm);
goto err_pwm;
} else
dev_dbg(&pdev->dev, "got pwm for haptic\n");
ret = sysfs_create_group(&haptic->cdev.dev->kobj, &haptic_group);
if (ret)
goto error_enable;
if (gpio_is_valid(pdata->gpio)) {
printk(KERN_INFO "Motor enable gpio %d\n", pdata->gpio);
ret = gpio_request(pdata->gpio, "haptic enable");
if (ret)
goto error_gpio;
gpio_direction_output(pdata->gpio, 0);
}
init_timer(&haptic->timer);
haptic->timer.data = (unsigned long)haptic;
haptic->timer.function = &samsung_pwm_haptic_timer;
printk(KERN_INFO "samsung %s registed\n", pdata->name);
return 0;
error_gpio:
sysfs_remove_group(&haptic->cdev.dev->kobj, &haptic_group);
error_enable:
pwm_release(haptic->pwm);
err_pwm:
haptic_classdev_unregister(&haptic->cdev);
error_classdev:
kfree(haptic);
return ret;
}
static int samsung_pwm_haptic_remove(struct platform_device *pdev)
{
struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
samsung_pwm_haptic_set(&haptic->cdev, HAPTIC_OFF);
del_timer_sync(&haptic->timer);
if (haptic->pdata->gpio)
gpio_free(haptic->pdata->gpio);
device_remove_file(haptic->cdev.dev, &dev_attr_enable);
haptic_classdev_unregister(&haptic->cdev);
kfree(haptic);
return 0;
}
#ifdef CONFIG_PM
static int samsung_pwm_haptic_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
haptic_classdev_suspend(&haptic->cdev);
return 0;
}
static int samsung_pwm_haptic_resume(struct platform_device *pdev)
{
struct samsung_pwm_haptic *haptic = platform_get_drvdata(pdev);
haptic_classdev_resume(&haptic->cdev);
return 0;
}
#else
#define samsung_pwm_haptic_suspend NULL
#define samsung_pwm_haptic_resume NULL
#endif
static struct platform_driver samsung_pwm_haptic_driver = {
.probe = samsung_pwm_haptic_probe,
.remove = samsung_pwm_haptic_remove,
.suspend = samsung_pwm_haptic_suspend,
.resume = samsung_pwm_haptic_resume,
.driver = {
.name = "samsung_pwm_haptic",
.owner = THIS_MODULE,
},
};
static int __init samsung_pwm_haptic_init(void)
{
return platform_driver_register(&samsung_pwm_haptic_driver);
}
module_init(samsung_pwm_haptic_init);
static void __exit samsung_pwm_haptic_exit(void)
{
platform_driver_unregister(&samsung_pwm_haptic_driver);
}
module_exit(samsung_pwm_haptic_exit);
MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
MODULE_DESCRIPTION("samsung PWM haptic driver");
MODULE_LICENSE("GPL");