Files
android_kernel_xiaomi_sm8450/drivers/input/misc/max8997_haptic.c
Linus Torvalds ecc5fbd5ef Merge tag 'pwm/for-4.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm
Pull pwm updates from Thierry Reding:
 "This set of changes introduces an atomic API to the PWM subsystem.
  This is influenced by the DRM atomic API that was introduced a while
  back, though it is obviously a lot simpler.  The fundamental idea
  remains the same, though: drivers provide a single callback to
  implement the atomic configuration of a PWM channel.

  As a side-effect the PWM subsystem gains the ability for initial state
  retrieval, so that the logical state mirrors that of the hardware.
  Many use-cases don't care about this, but for others it is essential.

  These new features require changes in all users, which these patches
  take care of.  The core is transitioned to use the atomic callback if
  available and provides a fallback mechanism for other drivers.

  Changes to transition users and drivers to the atomic API are
  postponed to v4.8"

* tag 'pwm/for-4.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (30 commits)
  pwm: Add information about polarity, duty cycle and period to debugfs
  pwm: Switch to the atomic API
  pwm: Update documentation
  pwm: Add core infrastructure to allow atomic updates
  pwm: Add hardware readout infrastructure
  pwm: Move the enabled/disabled info into pwm_state
  pwm: Introduce the pwm_state concept
  pwm: Keep PWM state in sync with hardware state
  ARM: Explicitly apply PWM config extracted from pwm_args
  drm: i915: Explicitly apply PWM config extracted from pwm_args
  input: misc: pwm-beeper: Explicitly apply PWM config extracted from pwm_args
  input: misc: max8997: Explicitly apply PWM config extracted from pwm_args
  backlight: lm3630a: explicitly apply PWM config extracted from pwm_args
  backlight: lp855x: Explicitly apply PWM config extracted from pwm_args
  backlight: lp8788: Explicitly apply PWM config extracted from pwm_args
  backlight: pwm_bl: Use pwm_get_args() where appropriate
  fbdev: ssd1307fb: Use pwm_get_args() where appropriate
  regulator: pwm: Use pwm_get_args() where appropriate
  leds: pwm: Use pwm_get_args() where appropriate
  input: misc: max77693: Use pwm_get_args() where appropriate
  ...
2016-05-25 10:40:15 -07:00

421 lines
10 KiB
C

/*
* 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/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) {
error = regulator_enable(chip->regulator);
if (error) {
dev_err(chip->dev, "Failed to enable regulator\n");
goto out;
}
max8997_haptic_configure(chip);
if (chip->mode == MAX8997_EXTERNAL_MODE) {
error = pwm_enable(chip->pwm);
if (error) {
dev_err(chip->dev, "Failed to enable PWM\n");
regulator_disable(chip->regulator);
goto out;
}
}
chip->enabled = true;
}
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 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 = NULL;
struct max8997_haptic *chip;
struct input_dev *input_dev;
int error;
if (pdata)
haptic_pdata = pdata->haptic_pdata;
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;
}
/*
* FIXME: pwm_apply_args() should be removed when switching to
* the atomic PWM API.
*/
pwm_apply_args(chip->pwm);
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 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;
}
static int __maybe_unused 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;
}
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(platform, max8997_haptic_id);
static struct platform_driver max8997_haptic_driver = {
.driver = {
.name = "max8997-haptic",
.pm = &max8997_haptic_pm_ops,
},
.probe = max8997_haptic_probe,
.remove = max8997_haptic_remove,
.id_table = max8997_haptic_id,
};
module_platform_driver(max8997_haptic_driver);
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_DESCRIPTION("max8997_haptic driver");
MODULE_LICENSE("GPL");