The MAX8997-haptic function can be used to control motor. User can control the haptic driver by using force feedback framework. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Acked-by: Samuel Ortiz <sameo@linux.intel.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>tirimbino
parent
145e97348a
commit
104594b01c
@ -0,0 +1,407 @@ |
||||
/*
|
||||
* MAX8997-haptic controller driver |
||||
* |
||||
* Copyright (C) 2012 Samsung Electronics |
||||
* Donggeun Kim <dg77.kim@samsung.com> |
||||
* |
||||
* This program is not provided / owned by Maxim Integrated Products. |
||||
* |
||||
* 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; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* 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. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
* |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/err.h> |
||||
#include <linux/pwm.h> |
||||
#include <linux/input.h> |
||||
#include <linux/mfd/max8997-private.h> |
||||
#include <linux/mfd/max8997.h> |
||||
#include <linux/regulator/consumer.h> |
||||
|
||||
/* Haptic configuration 2 register */ |
||||
#define MAX8997_MOTOR_TYPE_SHIFT 7 |
||||
#define MAX8997_ENABLE_SHIFT 6 |
||||
#define MAX8997_MODE_SHIFT 5 |
||||
|
||||
/* Haptic driver configuration register */ |
||||
#define MAX8997_CYCLE_SHIFT 6 |
||||
#define MAX8997_SIG_PERIOD_SHIFT 4 |
||||
#define MAX8997_SIG_DUTY_SHIFT 2 |
||||
#define MAX8997_PWM_DUTY_SHIFT 0 |
||||
|
||||
struct max8997_haptic { |
||||
struct device *dev; |
||||
struct i2c_client *client; |
||||
struct input_dev *input_dev; |
||||
struct regulator *regulator; |
||||
|
||||
struct work_struct work; |
||||
struct mutex mutex; |
||||
|
||||
bool enabled; |
||||
unsigned int level; |
||||
|
||||
struct pwm_device *pwm; |
||||
unsigned int pwm_period; |
||||
enum max8997_haptic_pwm_divisor pwm_divisor; |
||||
|
||||
enum max8997_haptic_motor_type type; |
||||
enum max8997_haptic_pulse_mode mode; |
||||
|
||||
unsigned int internal_mode_pattern; |
||||
unsigned int pattern_cycle; |
||||
unsigned int pattern_signal_period; |
||||
}; |
||||
|
||||
static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip) |
||||
{ |
||||
int ret = 0; |
||||
|
||||
if (chip->mode == MAX8997_EXTERNAL_MODE) { |
||||
unsigned int duty = chip->pwm_period * chip->level / 100; |
||||
ret = pwm_config(chip->pwm, duty, chip->pwm_period); |
||||
} else { |
||||
int i; |
||||
u8 duty_index = 0; |
||||
|
||||
for (i = 0; i <= 64; i++) { |
||||
if (chip->level <= i * 100 / 64) { |
||||
duty_index = i; |
||||
break; |
||||
} |
||||
} |
||||
switch (chip->internal_mode_pattern) { |
||||
case 0: |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index); |
||||
break; |
||||
case 1: |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index); |
||||
break; |
||||
case 2: |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index); |
||||
break; |
||||
case 3: |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
static void max8997_haptic_configure(struct max8997_haptic *chip) |
||||
{ |
||||
u8 value; |
||||
|
||||
value = chip->type << MAX8997_MOTOR_TYPE_SHIFT | |
||||
chip->enabled << MAX8997_ENABLE_SHIFT | |
||||
chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor; |
||||
max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value); |
||||
|
||||
if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) { |
||||
value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT | |
||||
chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT | |
||||
chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT | |
||||
chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_DRVCONF, value); |
||||
|
||||
switch (chip->internal_mode_pattern) { |
||||
case 0: |
||||
value = chip->pattern_cycle << 4; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_CYCLECONF1, value); |
||||
value = chip->pattern_signal_period; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGCONF1, value); |
||||
break; |
||||
|
||||
case 1: |
||||
value = chip->pattern_cycle; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_CYCLECONF1, value); |
||||
value = chip->pattern_signal_period; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGCONF2, value); |
||||
break; |
||||
|
||||
case 2: |
||||
value = chip->pattern_cycle << 4; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_CYCLECONF2, value); |
||||
value = chip->pattern_signal_period; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGCONF3, value); |
||||
break; |
||||
|
||||
case 3: |
||||
value = chip->pattern_cycle; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_CYCLECONF2, value); |
||||
value = chip->pattern_signal_period; |
||||
max8997_write_reg(chip->client, |
||||
MAX8997_HAPTIC_REG_SIGCONF4, value); |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void max8997_haptic_enable(struct max8997_haptic *chip) |
||||
{ |
||||
int error; |
||||
|
||||
mutex_lock(&chip->mutex); |
||||
|
||||
error = max8997_haptic_set_duty_cycle(chip); |
||||
if (error) { |
||||
dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error); |
||||
goto out; |
||||
} |
||||
|
||||
if (!chip->enabled) { |
||||
chip->enabled = true; |
||||
regulator_enable(chip->regulator); |
||||
max8997_haptic_configure(chip); |
||||
if (chip->mode == MAX8997_EXTERNAL_MODE) |
||||
pwm_enable(chip->pwm); |
||||
} |
||||
|
||||
out: |
||||
mutex_unlock(&chip->mutex); |
||||
} |
||||
|
||||
static void max8997_haptic_disable(struct max8997_haptic *chip) |
||||
{ |
||||
mutex_lock(&chip->mutex); |
||||
|
||||
if (chip->enabled) { |
||||
chip->enabled = false; |
||||
max8997_haptic_configure(chip); |
||||
if (chip->mode == MAX8997_EXTERNAL_MODE) |
||||
pwm_disable(chip->pwm); |
||||
regulator_disable(chip->regulator); |
||||
} |
||||
|
||||
mutex_unlock(&chip->mutex); |
||||
} |
||||
|
||||
static void max8997_haptic_play_effect_work(struct work_struct *work) |
||||
{ |
||||
struct max8997_haptic *chip = |
||||
container_of(work, struct max8997_haptic, work); |
||||
|
||||
if (chip->level) |
||||
max8997_haptic_enable(chip); |
||||
else |
||||
max8997_haptic_disable(chip); |
||||
} |
||||
|
||||
static int max8997_haptic_play_effect(struct input_dev *dev, void *data, |
||||
struct ff_effect *effect) |
||||
{ |
||||
struct max8997_haptic *chip = input_get_drvdata(dev); |
||||
|
||||
chip->level = effect->u.rumble.strong_magnitude; |
||||
if (!chip->level) |
||||
chip->level = effect->u.rumble.weak_magnitude; |
||||
|
||||
schedule_work(&chip->work); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void max8997_haptic_close(struct input_dev *dev) |
||||
{ |
||||
struct max8997_haptic *chip = input_get_drvdata(dev); |
||||
|
||||
cancel_work_sync(&chip->work); |
||||
max8997_haptic_disable(chip); |
||||
} |
||||
|
||||
static int __devinit max8997_haptic_probe(struct platform_device *pdev) |
||||
{ |
||||
struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); |
||||
const struct max8997_platform_data *pdata = |
||||
dev_get_platdata(iodev->dev); |
||||
const struct max8997_haptic_platform_data *haptic_pdata = |
||||
pdata->haptic_pdata; |
||||
struct max8997_haptic *chip; |
||||
struct input_dev *input_dev; |
||||
int error; |
||||
|
||||
if (!haptic_pdata) { |
||||
dev_err(&pdev->dev, "no haptic platform data\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
chip = kzalloc(sizeof(struct max8997_haptic), GFP_KERNEL); |
||||
input_dev = input_allocate_device(); |
||||
if (!chip || !input_dev) { |
||||
dev_err(&pdev->dev, "unable to allocate memory\n"); |
||||
error = -ENOMEM; |
||||
goto err_free_mem; |
||||
} |
||||
|
||||
INIT_WORK(&chip->work, max8997_haptic_play_effect_work); |
||||
mutex_init(&chip->mutex); |
||||
|
||||
chip->client = iodev->haptic; |
||||
chip->dev = &pdev->dev; |
||||
chip->input_dev = input_dev; |
||||
chip->pwm_period = haptic_pdata->pwm_period; |
||||
chip->type = haptic_pdata->type; |
||||
chip->mode = haptic_pdata->mode; |
||||
chip->pwm_divisor = haptic_pdata->pwm_divisor; |
||||
|
||||
switch (chip->mode) { |
||||
case MAX8997_INTERNAL_MODE: |
||||
chip->internal_mode_pattern = |
||||
haptic_pdata->internal_mode_pattern; |
||||
chip->pattern_cycle = haptic_pdata->pattern_cycle; |
||||
chip->pattern_signal_period = |
||||
haptic_pdata->pattern_signal_period; |
||||
break; |
||||
|
||||
case MAX8997_EXTERNAL_MODE: |
||||
chip->pwm = pwm_request(haptic_pdata->pwm_channel_id, |
||||
"max8997-haptic"); |
||||
if (IS_ERR(chip->pwm)) { |
||||
error = PTR_ERR(chip->pwm); |
||||
dev_err(&pdev->dev, |
||||
"unable to request PWM for haptic, error: %d\n", |
||||
error); |
||||
goto err_free_mem; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
dev_err(&pdev->dev, |
||||
"Invalid chip mode specified (%d)\n", chip->mode); |
||||
error = -EINVAL; |
||||
goto err_free_mem; |
||||
} |
||||
|
||||
chip->regulator = regulator_get(&pdev->dev, "inmotor"); |
||||
if (IS_ERR(chip->regulator)) { |
||||
error = PTR_ERR(chip->regulator); |
||||
dev_err(&pdev->dev, |
||||
"unable to get regulator, error: %d\n", |
||||
error); |
||||
goto err_free_pwm; |
||||
} |
||||
|
||||
input_dev->name = "max8997-haptic"; |
||||
input_dev->id.version = 1; |
||||
input_dev->dev.parent = &pdev->dev; |
||||
input_dev->close = max8997_haptic_close; |
||||
input_set_drvdata(input_dev, chip); |
||||
input_set_capability(input_dev, EV_FF, FF_RUMBLE); |
||||
|
||||
error = input_ff_create_memless(input_dev, NULL, |
||||
max8997_haptic_play_effect); |
||||
if (error) { |
||||
dev_err(&pdev->dev, |
||||
"unable to create FF device, error: %d\n", |
||||
error); |
||||
goto err_put_regulator; |
||||
} |
||||
|
||||
error = input_register_device(input_dev); |
||||
if (error) { |
||||
dev_err(&pdev->dev, |
||||
"unable to register input device, error: %d\n", |
||||
error); |
||||
goto err_destroy_ff; |
||||
} |
||||
|
||||
platform_set_drvdata(pdev, chip); |
||||
return 0; |
||||
|
||||
err_destroy_ff: |
||||
input_ff_destroy(input_dev); |
||||
err_put_regulator: |
||||
regulator_put(chip->regulator); |
||||
err_free_pwm: |
||||
if (chip->mode == MAX8997_EXTERNAL_MODE) |
||||
pwm_free(chip->pwm); |
||||
err_free_mem: |
||||
input_free_device(input_dev); |
||||
kfree(chip); |
||||
|
||||
return error; |
||||
} |
||||
|
||||
static int __devexit max8997_haptic_remove(struct platform_device *pdev) |
||||
{ |
||||
struct max8997_haptic *chip = platform_get_drvdata(pdev); |
||||
|
||||
input_unregister_device(chip->input_dev); |
||||
regulator_put(chip->regulator); |
||||
|
||||
if (chip->mode == MAX8997_EXTERNAL_MODE) |
||||
pwm_free(chip->pwm); |
||||
|
||||
kfree(chip); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
#ifdef CONFIG_PM_SLEEP |
||||
static int max8997_haptic_suspend(struct device *dev) |
||||
{ |
||||
struct platform_device *pdev = to_platform_device(dev); |
||||
struct max8997_haptic *chip = platform_get_drvdata(pdev); |
||||
|
||||
max8997_haptic_disable(chip); |
||||
|
||||
return 0; |
||||
} |
||||
#endif |
||||
|
||||
static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL); |
||||
|
||||
static const struct platform_device_id max8997_haptic_id[] = { |
||||
{ "max8997-haptic", 0 }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(i2c, max8997_haptic_id); |
||||
|
||||
static struct platform_driver max8997_haptic_driver = { |
||||
.driver = { |
||||
.name = "max8997-haptic", |
||||
.owner = THIS_MODULE, |
||||
.pm = &max8997_haptic_pm_ops, |
||||
}, |
||||
.probe = max8997_haptic_probe, |
||||
.remove = __devexit_p(max8997_haptic_remove), |
||||
.id_table = max8997_haptic_id, |
||||
}; |
||||
module_platform_driver(max8997_haptic_driver); |
||||
|
||||
MODULE_ALIAS("platform:max8997-haptic"); |
||||
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
||||
MODULE_DESCRIPTION("max8997_haptic driver"); |
||||
MODULE_LICENSE("GPL"); |
Loading…
Reference in new issue