// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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.
*/
#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");