| /* | 
 |  * Arizona haptics driver | 
 |  * | 
 |  * Copyright 2012 Wolfson Microelectronics plc | 
 |  * | 
 |  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 as | 
 |  * published by the Free Software Foundation. | 
 |  */ | 
 |  | 
 | #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>"); |