494 lines
12 KiB
C
Executable File
494 lines
12 KiB
C
Executable File
/* drivers/motor/isa1000.c
|
|
|
|
* Copyright (C) 2014 Samsung Electronics Co. Ltd. All Rights Reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/isa1000.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include "../staging/android/timed_output.h"
|
|
|
|
// #ifdef DEBUG_MOTOR
|
|
|
|
#define MAX_INTENSITY 10000
|
|
|
|
struct isa1000_ddata {
|
|
struct isa1000_pdata *pdata;
|
|
struct pwm_device *pwm;
|
|
struct regulator *regulator;
|
|
struct timed_output_dev dev;
|
|
struct hrtimer timer;
|
|
struct work_struct work;
|
|
spinlock_t lock;
|
|
bool running;
|
|
u32 intensity;
|
|
u32 timeout;
|
|
int duty;
|
|
int gpio_en;
|
|
};
|
|
|
|
static enum hrtimer_restart isa1000_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct isa1000_ddata *ddata
|
|
= container_of(timer, struct isa1000_ddata, timer);
|
|
|
|
ddata->timeout = 0;
|
|
|
|
schedule_work(&ddata->work);
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static int isa1000_get_time(struct timed_output_dev *dev)
|
|
{
|
|
struct isa1000_ddata *ddata
|
|
= container_of(dev, struct isa1000_ddata, dev);
|
|
|
|
struct hrtimer *timer = &ddata->timer;
|
|
if (hrtimer_active(timer)) {
|
|
ktime_t remain = hrtimer_get_remaining(timer);
|
|
struct timeval t = ktime_to_timeval(remain);
|
|
return t.tv_sec * 1000 + t.tv_usec / 1000;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static void isa1000_enable(struct timed_output_dev *dev, int value)
|
|
{
|
|
struct isa1000_ddata *ddata
|
|
= container_of(dev, struct isa1000_ddata, dev);
|
|
struct hrtimer *timer = &ddata->timer;
|
|
unsigned long flags;
|
|
cancel_work_sync(&ddata->work);
|
|
hrtimer_cancel(timer);
|
|
|
|
if (value > ddata->pdata->max_timeout)
|
|
value = ddata->pdata->max_timeout;
|
|
spin_lock_irqsave(&ddata->lock, flags);
|
|
ddata->timeout = value;
|
|
spin_unlock_irqrestore(&ddata->lock, flags);
|
|
|
|
schedule_work(&ddata->work);
|
|
}
|
|
|
|
static void isa1000_pwm_config(struct isa1000_ddata *ddata, int duty)
|
|
{
|
|
#if 0
|
|
int max_duty = ddata->pdata->duty;
|
|
int min_duty = ddata->pdata->period - max_duty;
|
|
|
|
/* check the max dury ragne */
|
|
if (duty > max_duty)
|
|
duty = max_duty;
|
|
else if (duty < min_duty)
|
|
duty = min_duty;
|
|
#endif
|
|
printk("[VIB] : %s : pwm_duty = %d, pwm_period = %d \n", __func__, duty, ddata->pdata->period);
|
|
pwm_config(ddata->pwm, duty,
|
|
ddata->pdata->period);
|
|
}
|
|
|
|
static void isa1000_pwm_en(struct isa1000_ddata *ddata, bool en)
|
|
{
|
|
if (en) {
|
|
pwm_enable(ddata->pwm);
|
|
pr_info("[VIB] pwm enable\n");
|
|
}
|
|
else {
|
|
pwm_disable(ddata->pwm);
|
|
pr_info("[VIB] pwm disable\n");
|
|
}
|
|
}
|
|
|
|
static void isa1000_regulator_en(struct isa1000_ddata *ddata, bool en)
|
|
{
|
|
int ret;
|
|
|
|
if (!ddata->regulator)
|
|
return;
|
|
|
|
if (en && !regulator_is_enabled(ddata->regulator)) {
|
|
ret = regulator_enable(ddata->regulator);
|
|
pr_info("[VIB] regulator_enable returns %d\n", ret);
|
|
}
|
|
else if (!en && regulator_is_enabled(ddata->regulator)) {
|
|
ret = regulator_disable(ddata->regulator);
|
|
pr_info("[VIB] regulator_disable returns %d\n", ret);
|
|
}
|
|
}
|
|
|
|
static void isa1000_en(struct isa1000_ddata *ddata, bool en)
|
|
{
|
|
printk(KERN_DEBUG "[VIB] %s\n", en ? "on" : "off");
|
|
|
|
gpio_direction_output(ddata->pdata->gpio_en, en);
|
|
|
|
if (en)
|
|
msleep(20);
|
|
}
|
|
|
|
static void isa1000_work_func(struct work_struct *work)
|
|
{
|
|
struct isa1000_ddata *ddata =
|
|
container_of(work, struct isa1000_ddata, work);
|
|
struct hrtimer *timer = &ddata->timer;
|
|
|
|
if (ddata->timeout) {
|
|
ddata->running = true;
|
|
|
|
if (ddata->pdata->gpio_en > 0)
|
|
isa1000_en(ddata, true);
|
|
|
|
isa1000_pwm_config(ddata, ddata->duty);
|
|
isa1000_pwm_en(ddata, true);
|
|
isa1000_regulator_en(ddata, true);
|
|
hrtimer_start(timer, ns_to_ktime((u64)ddata->timeout * NSEC_PER_MSEC), HRTIMER_MODE_REL);
|
|
} else {
|
|
ddata->running = false;
|
|
isa1000_pwm_en(ddata, false);
|
|
|
|
if (ddata->pdata->gpio_en > 0)
|
|
isa1000_en(ddata, false);
|
|
|
|
isa1000_regulator_en(ddata, false);
|
|
hrtimer_cancel(timer);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static ssize_t intensity_store(struct device *dev,
|
|
struct device_attribute *devattr, const char *buf, size_t count)
|
|
{
|
|
struct timed_output_dev *tdev = dev_get_drvdata(dev);
|
|
struct isa1000_ddata *drvdata
|
|
= container_of(tdev, struct isa1000_ddata, dev);
|
|
int duty = drvdata->pdata->period >> 1;
|
|
int intensity = 0, ret = 0;
|
|
|
|
ret = kstrtoint(buf, 0, &intensity);
|
|
|
|
if (intensity < 0 || MAX_INTENSITY < intensity) {
|
|
pr_err("out of rage\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (MAX_INTENSITY == intensity)
|
|
duty = drvdata->pdata->duty;
|
|
else if (0 != intensity) {
|
|
long long tmp = drvdata->pdata->duty >> 1;
|
|
|
|
tmp *= (intensity / 100);
|
|
duty += (int)(tmp / 100);
|
|
}
|
|
|
|
drvdata->intensity = intensity;
|
|
drvdata->duty = duty;
|
|
printk("[VIB] : %s : intensity = %d, duty = %d \n",__func__, intensity, duty);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t intensity_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct timed_output_dev *tdev = dev_get_drvdata(dev);
|
|
struct isa1000_ddata *drvdata
|
|
= container_of(tdev, struct isa1000_ddata, dev);
|
|
|
|
return sprintf(buf, "intensity: %u\n", drvdata->intensity);
|
|
}
|
|
|
|
static DEVICE_ATTR(intensity, 0660, intensity_show, intensity_store);
|
|
|
|
#ifdef DEBUG_MOTOR
|
|
static ssize_t duty_store(struct device *dev,
|
|
struct device_attribute *devattr, const char *buf, size_t count)
|
|
{
|
|
struct timed_output_dev *tdev = dev_get_drvdata(dev);
|
|
struct isa1000_ddata *drvdata
|
|
= container_of(tdev, struct isa1000_ddata, dev);
|
|
int duty = drvdata->pdata->period >> 1;
|
|
int duty_rate = 0, ret = 0, temp=0;
|
|
|
|
ret = kstrtoint(buf, 0, &duty_rate);
|
|
|
|
if (duty_rate < 0 || duty_rate > 1000) {
|
|
pr_err("out of rage \n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
temp = duty * duty_rate /1000;
|
|
duty = duty + temp;
|
|
|
|
drvdata->duty = duty;
|
|
printk("[VIB] : %s : duty_rate = %d, duty = %d, temp = %d \n",__func__, duty_rate, duty, temp);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t duty_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct timed_output_dev *tdev = dev_get_drvdata(dev);
|
|
struct isa1000_ddata *drvdata
|
|
= container_of(tdev, struct isa1000_ddata, dev);
|
|
|
|
|
|
int duty = drvdata->pdata->period >> 1;
|
|
int duty_rate=0;
|
|
duty_rate = ((drvdata->duty-duty)*1000 / duty);
|
|
|
|
printk("[VIB] : %s : duty = %d, drvdata->duty=%d, duty_rate = %d \n",__func__, duty, drvdata->duty, duty_rate);
|
|
return sprintf(buf, "current duty rate : %u %% \n", duty_rate);
|
|
}
|
|
|
|
static DEVICE_ATTR(duty, 0660, duty_show, duty_store);
|
|
#endif
|
|
|
|
static struct isa1000_pdata *
|
|
isa1000_get_devtree_pdata(struct device *dev)
|
|
{
|
|
struct device_node *node, *child_node=NULL;
|
|
struct isa1000_pdata *pdata;
|
|
const char *dt_status;
|
|
int ret = 0;
|
|
|
|
node = dev->of_node;
|
|
if (!node) {
|
|
ret = -ENODEV;
|
|
goto err_out;
|
|
}
|
|
|
|
child_node = of_get_next_child(node, child_node);
|
|
if (!child_node) {
|
|
printk("[VIB] failed to get dt node\n");
|
|
ret = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
if (of_property_read_string(child_node, "status", &dt_status) < 0) {
|
|
printk("[VIB] isa1000 dt status read fail \n");
|
|
} else {
|
|
printk("[VIB] isa1000 dt status : %s \n", dt_status);
|
|
if (strcmp(dt_status, "okay")) {
|
|
printk("[VIB] isa1000 dt status is disabled so return error \n");
|
|
ret = -ENODEV;
|
|
goto err_out;
|
|
}
|
|
}
|
|
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata) {
|
|
printk("[VIB] failed to alloc\n");
|
|
ret = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
of_property_read_u32(child_node, "isa1000,max_timeout", &pdata->max_timeout);
|
|
of_property_read_u32(child_node, "isa1000,duty", &pdata->duty);
|
|
of_property_read_u32(child_node, "isa1000,period", &pdata->period);
|
|
of_property_read_u32(child_node, "isa1000,pwm_id", &pdata->pwm_id);
|
|
of_property_read_u32(child_node, "isa1000,pwm_use", &pdata->pwm_use);
|
|
|
|
|
|
if (of_property_read_string(child_node, "isa1000,regulator_name", &pdata->regulator_name) < 0) {
|
|
pr_err("%s - get regulator error\n", __func__);
|
|
pdata->regulator_name = NULL;
|
|
}
|
|
|
|
pdata->gpio_en = of_get_named_gpio(child_node, "isa1000,gpio_en", 0);
|
|
|
|
if (pdata->gpio_en > 0)
|
|
gpio_request(pdata->gpio_en, "isa1000,gpio_en");
|
|
|
|
printk("[VIB] max_timeout = %d\n", pdata->max_timeout);
|
|
printk("[VIB] duty = %d\n", pdata->duty);
|
|
printk("[VIB] period = %d\n", pdata->period);
|
|
printk("[VIB] pwm_id = %d\n", pdata->pwm_id);
|
|
printk("[VIB] gpio_en = %d\n", pdata->gpio_en);
|
|
printk("[VIB] pwm_use = %d\n", pdata->pwm_use);
|
|
printk("[VIB] %s : regulator_name = %s \n",__func__, pdata->regulator_name);
|
|
|
|
return pdata;
|
|
|
|
err_out:
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static int isa1000_probe(struct platform_device *pdev)
|
|
{
|
|
struct isa1000_pdata *pdata = dev_get_platdata(&pdev->dev);
|
|
struct isa1000_ddata *ddata;
|
|
int ret = 0;
|
|
|
|
if (!pdata) {
|
|
#if defined(CONFIG_OF)
|
|
pdata = isa1000_get_devtree_pdata(&pdev->dev);
|
|
if (IS_ERR(pdata)) {
|
|
printk(KERN_ERR "[VIB] there is no device tree!\n");
|
|
ret = -ENODEV;
|
|
goto err_pdata;
|
|
}
|
|
#else
|
|
printk(KERN_ERR "[VIB] there is no platform data!\n");
|
|
#endif
|
|
}
|
|
|
|
ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
|
|
if (!ddata) {
|
|
printk(KERN_ERR "[VIB] failed to alloc\n");
|
|
ret = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
hrtimer_init(&ddata->timer, CLOCK_MONOTONIC,
|
|
HRTIMER_MODE_REL);
|
|
spin_lock_init(&ddata->lock);
|
|
INIT_WORK(&ddata->work, isa1000_work_func);
|
|
|
|
ddata->pdata = pdata;
|
|
ddata->timer.function = isa1000_timer_func;
|
|
ddata->pwm = pwm_request(ddata->pdata->pwm_id, "vibrator");
|
|
if (IS_ERR(ddata->pwm)) {
|
|
printk(KERN_ERR "[VIB] failed to request pwm\n");
|
|
ret = -EFAULT;
|
|
goto err_pwm_request;
|
|
}
|
|
|
|
|
|
if (ddata->pdata->regulator_name) {
|
|
ddata->regulator = regulator_get(NULL, ddata->pdata->regulator_name);
|
|
if (IS_ERR(ddata->regulator)) {
|
|
ret = -EFAULT;
|
|
pr_err("[VIB] Failed to get vmoter regulator, err num: %d\n", ret);
|
|
goto err_regulator_get;
|
|
}
|
|
} else
|
|
ddata->regulator = NULL;
|
|
|
|
ddata->dev.name = "vibrator";
|
|
ddata->dev.get_time = isa1000_get_time;
|
|
ddata->dev.enable = isa1000_enable;
|
|
|
|
ret = timed_output_dev_register(&ddata->dev);
|
|
if (ret < 0) {
|
|
printk(KERN_ERR "[VIB] failed to register timed output\n");
|
|
goto err_dev_reg;
|
|
}
|
|
|
|
if(ddata->pdata->pwm_use)
|
|
{
|
|
ret = sysfs_create_file(&ddata->dev.dev->kobj, &dev_attr_intensity.attr);
|
|
if (ret < 0) {
|
|
pr_err("Failed to register intensity sysfs : %d\n", ret);
|
|
goto err_dev_reg;
|
|
}
|
|
#ifdef DEBUG_MOTOR
|
|
ret = sysfs_create_file(&ddata->dev.dev->kobj, &dev_attr_duty.attr);
|
|
if (ret < 0) {
|
|
pr_err("Failed to register duty sysfs : %d\n", ret);
|
|
goto err_dev_reg;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
platform_set_drvdata(pdev, ddata);
|
|
|
|
return ret;
|
|
|
|
err_dev_reg:
|
|
if (!ddata->regulator)
|
|
regulator_put(ddata->regulator);
|
|
err_regulator_get:
|
|
pwm_free(ddata->pwm);
|
|
err_pwm_request:
|
|
kfree(ddata);
|
|
err_alloc:
|
|
err_pdata:
|
|
return ret;
|
|
}
|
|
|
|
static int isa1000_remove(struct platform_device *pdev)
|
|
{
|
|
struct isa1000_ddata *ddata = platform_get_drvdata(pdev);
|
|
if (!ddata->regulator)
|
|
regulator_put(ddata->regulator);
|
|
pwm_free(ddata->pwm);
|
|
timed_output_dev_unregister(&ddata->dev);
|
|
kfree(ddata);
|
|
return 0;
|
|
}
|
|
|
|
static int isa1000_suspend(struct platform_device *pdev,
|
|
pm_message_t state)
|
|
{
|
|
struct isa1000_ddata *ddata = platform_get_drvdata(pdev);
|
|
|
|
isa1000_regulator_en(ddata, false);
|
|
isa1000_pwm_en(ddata, false);
|
|
|
|
if (ddata->pdata->gpio_en > 0)
|
|
isa1000_en(ddata, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isa1000_resume(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_OF)
|
|
static struct of_device_id isa1000_dt_ids[] = {
|
|
{ .compatible = "isa1000" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, isa1000_dt_ids);
|
|
#endif /* CONFIG_OF */
|
|
|
|
static struct platform_driver isa1000_driver = {
|
|
.probe = isa1000_probe,
|
|
.remove = isa1000_remove,
|
|
.suspend = isa1000_suspend,
|
|
.resume = isa1000_resume,
|
|
.driver = {
|
|
.name = "isa1000",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(isa1000_dt_ids),
|
|
},
|
|
};
|
|
|
|
static int __init isa1000_init(void)
|
|
{
|
|
return platform_driver_register(&isa1000_driver);
|
|
}
|
|
module_init(isa1000_init);
|
|
|
|
static void __exit isa1000_exit(void)
|
|
{
|
|
platform_driver_unregister(&isa1000_driver);
|
|
}
|
|
module_exit(isa1000_exit);
|
|
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("isa1000 vibrator driver");
|