// SPDX-License-Identifier: GPL-2.0-or-later /* * MAX8997-haptic controller driver * * Copyright (C) 2012 Samsung Electronics * Donggeun Kim * * This program is not provided / owned by Maxim Integrated Products. */ #include #include #include #include #include #include #include #include #include /* 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 { u8 duty_index = 0; duty_index = DIV_ROUND_UP(chip->level * 64, 100); 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; guard(mutex)(&chip->mutex); error = max8997_haptic_set_duty_cycle(chip); if (error) { dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error); return; } if (!chip->enabled) { error = regulator_enable(chip->regulator); if (error) { dev_err(chip->dev, "Failed to enable regulator\n"); return; } 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); return; } } chip->enabled = true; } } static void max8997_haptic_disable(struct max8997_haptic *chip) { guard(mutex)(&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); } } 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(*chip), 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_get(&pdev->dev, NULL); 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_put(chip->pwm); err_free_mem: input_free_device(input_dev); kfree(chip); return error; } static void 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_put(chip->pwm); kfree(chip); } 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; } static DEFINE_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 = pm_sleep_ptr(&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 "); MODULE_DESCRIPTION("max8997_haptic driver"); MODULE_LICENSE("GPL");