// SPDX-License-Identifier: GPL-2.0-only 
 | 
/* 
 | 
 * Arizona haptics driver 
 | 
 * 
 | 
 * Copyright 2012 Wolfson Microelectronics plc 
 | 
 * 
 | 
 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 
 | 
 */ 
 | 
  
 | 
#include <linux/module.h> 
 | 
#include <linux/platform_device.h> 
 | 
#include <linux/input.h> 
 | 
#include <linux/slab.h> 
 | 
  
 | 
#include <sound/soc.h> 
 | 
#include <sound/soc-dapm.h> 
 | 
  
 | 
#include <linux/mfd/arizona/core.h> 
 | 
#include <linux/mfd/arizona/pdata.h> 
 | 
#include <linux/mfd/arizona/registers.h> 
 | 
  
 | 
struct arizona_haptics { 
 | 
    struct arizona *arizona; 
 | 
    struct input_dev *input_dev; 
 | 
    struct work_struct work; 
 | 
  
 | 
    struct mutex mutex; 
 | 
    u8 intensity; 
 | 
}; 
 | 
  
 | 
static void arizona_haptics_work(struct work_struct *work) 
 | 
{ 
 | 
    struct arizona_haptics *haptics = container_of(work, 
 | 
                               struct arizona_haptics, 
 | 
                               work); 
 | 
    struct arizona *arizona = haptics->arizona; 
 | 
    struct snd_soc_component *component = 
 | 
        snd_soc_dapm_to_component(arizona->dapm); 
 | 
    int ret; 
 | 
  
 | 
    if (!haptics->arizona->dapm) { 
 | 
        dev_err(arizona->dev, "No DAPM context\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (haptics->intensity) { 
 | 
        ret = regmap_update_bits(arizona->regmap, 
 | 
                     ARIZONA_HAPTICS_PHASE_2_INTENSITY, 
 | 
                     ARIZONA_PHASE2_INTENSITY_MASK, 
 | 
                     haptics->intensity); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to set intensity: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        /* This enable sequence will be a noop if already enabled */ 
 | 
        ret = regmap_update_bits(arizona->regmap, 
 | 
                     ARIZONA_HAPTICS_CONTROL_1, 
 | 
                     ARIZONA_HAP_CTRL_MASK, 
 | 
                     1 << ARIZONA_HAP_CTRL_SHIFT); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to start haptics: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        ret = snd_soc_component_enable_pin(component, "HAPTICS"); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to start HAPTICS: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        ret = snd_soc_dapm_sync(arizona->dapm); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
    } else { 
 | 
        /* This disable sequence will be a noop if already enabled */ 
 | 
        ret = snd_soc_component_disable_pin(component, "HAPTICS"); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        ret = snd_soc_dapm_sync(arizona->dapm); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to sync DAPM: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        ret = regmap_update_bits(arizona->regmap, 
 | 
                     ARIZONA_HAPTICS_CONTROL_1, 
 | 
                     ARIZONA_HAP_CTRL_MASK, 0); 
 | 
        if (ret != 0) { 
 | 
            dev_err(arizona->dev, "Failed to stop haptics: %d\n", 
 | 
                ret); 
 | 
            return; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
static int arizona_haptics_play(struct input_dev *input, void *data, 
 | 
                struct ff_effect *effect) 
 | 
{ 
 | 
    struct arizona_haptics *haptics = input_get_drvdata(input); 
 | 
    struct arizona *arizona = haptics->arizona; 
 | 
  
 | 
    if (!arizona->dapm) { 
 | 
        dev_err(arizona->dev, "No DAPM context\n"); 
 | 
        return -EBUSY; 
 | 
    } 
 | 
  
 | 
    if (effect->u.rumble.strong_magnitude) { 
 | 
        /* Scale the magnitude into the range the device supports */ 
 | 
        if (arizona->pdata.hap_act) { 
 | 
            haptics->intensity = 
 | 
                effect->u.rumble.strong_magnitude >> 9; 
 | 
            if (effect->direction < 0x8000) 
 | 
                haptics->intensity += 0x7f; 
 | 
        } else { 
 | 
            haptics->intensity = 
 | 
                effect->u.rumble.strong_magnitude >> 8; 
 | 
        } 
 | 
    } else { 
 | 
        haptics->intensity = 0; 
 | 
    } 
 | 
  
 | 
    schedule_work(&haptics->work); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void arizona_haptics_close(struct input_dev *input) 
 | 
{ 
 | 
    struct arizona_haptics *haptics = input_get_drvdata(input); 
 | 
    struct snd_soc_component *component; 
 | 
  
 | 
    cancel_work_sync(&haptics->work); 
 | 
  
 | 
    if (haptics->arizona->dapm) { 
 | 
        component = snd_soc_dapm_to_component(haptics->arizona->dapm); 
 | 
        snd_soc_component_disable_pin(component, "HAPTICS"); 
 | 
    } 
 | 
} 
 | 
  
 | 
static int arizona_haptics_probe(struct platform_device *pdev) 
 | 
{ 
 | 
    struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); 
 | 
    struct arizona_haptics *haptics; 
 | 
    int ret; 
 | 
  
 | 
    haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL); 
 | 
    if (!haptics) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    haptics->arizona = arizona; 
 | 
  
 | 
    ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, 
 | 
                 ARIZONA_HAP_ACT, arizona->pdata.hap_act); 
 | 
    if (ret != 0) { 
 | 
        dev_err(arizona->dev, "Failed to set haptics actuator: %d\n", 
 | 
            ret); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    INIT_WORK(&haptics->work, arizona_haptics_work); 
 | 
  
 | 
    haptics->input_dev = devm_input_allocate_device(&pdev->dev); 
 | 
    if (!haptics->input_dev) { 
 | 
        dev_err(arizona->dev, "Failed to allocate input device\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
  
 | 
    input_set_drvdata(haptics->input_dev, haptics); 
 | 
  
 | 
    haptics->input_dev->name = "arizona:haptics"; 
 | 
    haptics->input_dev->close = arizona_haptics_close; 
 | 
    __set_bit(FF_RUMBLE, haptics->input_dev->ffbit); 
 | 
  
 | 
    ret = input_ff_create_memless(haptics->input_dev, NULL, 
 | 
                      arizona_haptics_play); 
 | 
    if (ret < 0) { 
 | 
        dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n", 
 | 
            ret); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    ret = input_register_device(haptics->input_dev); 
 | 
    if (ret < 0) { 
 | 
        dev_err(arizona->dev, "couldn't register input device: %d\n", 
 | 
            ret); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static struct platform_driver arizona_haptics_driver = { 
 | 
    .probe        = arizona_haptics_probe, 
 | 
    .driver        = { 
 | 
        .name    = "arizona-haptics", 
 | 
    }, 
 | 
}; 
 | 
module_platform_driver(arizona_haptics_driver); 
 | 
  
 | 
MODULE_ALIAS("platform:arizona-haptics"); 
 | 
MODULE_DESCRIPTION("Arizona haptics driver"); 
 | 
MODULE_LICENSE("GPL"); 
 | 
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 
 |