// 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"); 
 |