19dd555e2SMark Brown /*
29dd555e2SMark Brown  * Arizona haptics driver
39dd555e2SMark Brown  *
49dd555e2SMark Brown  * Copyright 2012 Wolfson Microelectronics plc
59dd555e2SMark Brown  *
69dd555e2SMark Brown  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
79dd555e2SMark Brown  *
89dd555e2SMark Brown  * This program is free software; you can redistribute it and/or modify
99dd555e2SMark Brown  * it under the terms of the GNU General Public License version 2 as
109dd555e2SMark Brown  * published by the Free Software Foundation.
119dd555e2SMark Brown  */
129dd555e2SMark Brown 
139dd555e2SMark Brown #include <linux/module.h>
149dd555e2SMark Brown #include <linux/platform_device.h>
159dd555e2SMark Brown #include <linux/input.h>
169dd555e2SMark Brown #include <linux/slab.h>
179dd555e2SMark Brown 
189dd555e2SMark Brown #include <sound/soc.h>
199dd555e2SMark Brown #include <sound/soc-dapm.h>
209dd555e2SMark Brown 
219dd555e2SMark Brown #include <linux/mfd/arizona/core.h>
229dd555e2SMark Brown #include <linux/mfd/arizona/pdata.h>
239dd555e2SMark Brown #include <linux/mfd/arizona/registers.h>
249dd555e2SMark Brown 
259dd555e2SMark Brown struct arizona_haptics {
269dd555e2SMark Brown 	struct arizona *arizona;
279dd555e2SMark Brown 	struct input_dev *input_dev;
289dd555e2SMark Brown 	struct work_struct work;
299dd555e2SMark Brown 
309dd555e2SMark Brown 	struct mutex mutex;
319dd555e2SMark Brown 	u8 intensity;
329dd555e2SMark Brown };
339dd555e2SMark Brown 
349dd555e2SMark Brown static void arizona_haptics_work(struct work_struct *work)
359dd555e2SMark Brown {
369dd555e2SMark Brown 	struct arizona_haptics *haptics = container_of(work,
379dd555e2SMark Brown 						       struct arizona_haptics,
389dd555e2SMark Brown 						       work);
399dd555e2SMark Brown 	struct arizona *arizona = haptics->arizona;
409dd555e2SMark Brown 	struct mutex *dapm_mutex = &arizona->dapm->card->dapm_mutex;
419dd555e2SMark Brown 	int ret;
429dd555e2SMark Brown 
439dd555e2SMark Brown 	if (!haptics->arizona->dapm) {
449dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
459dd555e2SMark Brown 		return;
469dd555e2SMark Brown 	}
479dd555e2SMark Brown 
489dd555e2SMark Brown 	if (haptics->intensity) {
499dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
509dd555e2SMark Brown 					 ARIZONA_HAPTICS_PHASE_2_INTENSITY,
519dd555e2SMark Brown 					 ARIZONA_PHASE2_INTENSITY_MASK,
529dd555e2SMark Brown 					 haptics->intensity);
539dd555e2SMark Brown 		if (ret != 0) {
549dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to set intensity: %d\n",
559dd555e2SMark Brown 				ret);
569dd555e2SMark Brown 			return;
579dd555e2SMark Brown 		}
589dd555e2SMark Brown 
599dd555e2SMark Brown 		/* This enable sequence will be a noop if already enabled */
609dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
619dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
629dd555e2SMark Brown 					 ARIZONA_HAP_CTRL_MASK,
639dd555e2SMark Brown 					 1 << ARIZONA_HAP_CTRL_SHIFT);
649dd555e2SMark Brown 		if (ret != 0) {
659dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start haptics: %d\n",
669dd555e2SMark Brown 				ret);
679dd555e2SMark Brown 			return;
689dd555e2SMark Brown 		}
699dd555e2SMark Brown 
709dd555e2SMark Brown 		mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
719dd555e2SMark Brown 
729dd555e2SMark Brown 		ret = snd_soc_dapm_enable_pin(arizona->dapm, "HAPTICS");
739dd555e2SMark Brown 		if (ret != 0) {
749dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start HAPTICS: %d\n",
759dd555e2SMark Brown 				ret);
769dd555e2SMark Brown 			mutex_unlock(dapm_mutex);
779dd555e2SMark Brown 			return;
789dd555e2SMark Brown 		}
799dd555e2SMark Brown 
809dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
819dd555e2SMark Brown 		if (ret != 0) {
829dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
839dd555e2SMark Brown 				ret);
849dd555e2SMark Brown 			mutex_unlock(dapm_mutex);
859dd555e2SMark Brown 			return;
869dd555e2SMark Brown 		}
879dd555e2SMark Brown 
889dd555e2SMark Brown 		mutex_unlock(dapm_mutex);
899dd555e2SMark Brown 
909dd555e2SMark Brown 	} else {
919dd555e2SMark Brown 		/* This disable sequence will be a noop if already enabled */
929dd555e2SMark Brown 		mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
939dd555e2SMark Brown 
949dd555e2SMark Brown 		ret = snd_soc_dapm_disable_pin(arizona->dapm, "HAPTICS");
959dd555e2SMark Brown 		if (ret != 0) {
969dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n",
979dd555e2SMark Brown 				ret);
989dd555e2SMark Brown 			mutex_unlock(dapm_mutex);
999dd555e2SMark Brown 			return;
1009dd555e2SMark Brown 		}
1019dd555e2SMark Brown 
1029dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
1039dd555e2SMark Brown 		if (ret != 0) {
1049dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
1059dd555e2SMark Brown 				ret);
1069dd555e2SMark Brown 			mutex_unlock(dapm_mutex);
1079dd555e2SMark Brown 			return;
1089dd555e2SMark Brown 		}
1099dd555e2SMark Brown 
1109dd555e2SMark Brown 		mutex_unlock(dapm_mutex);
1119dd555e2SMark Brown 
1129dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
1139dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
1149dd555e2SMark Brown 					 ARIZONA_HAP_CTRL_MASK,
1159dd555e2SMark Brown 					 1 << ARIZONA_HAP_CTRL_SHIFT);
1169dd555e2SMark Brown 		if (ret != 0) {
1179dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to stop haptics: %d\n",
1189dd555e2SMark Brown 				ret);
1199dd555e2SMark Brown 			return;
1209dd555e2SMark Brown 		}
1219dd555e2SMark Brown 	}
1229dd555e2SMark Brown }
1239dd555e2SMark Brown 
1249dd555e2SMark Brown static int arizona_haptics_play(struct input_dev *input, void *data,
1259dd555e2SMark Brown 				struct ff_effect *effect)
1269dd555e2SMark Brown {
1279dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
1289dd555e2SMark Brown 	struct arizona *arizona = haptics->arizona;
1299dd555e2SMark Brown 
1309dd555e2SMark Brown 	if (!arizona->dapm) {
1319dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
1329dd555e2SMark Brown 		return -EBUSY;
1339dd555e2SMark Brown 	}
1349dd555e2SMark Brown 
1359dd555e2SMark Brown 	if (effect->u.rumble.strong_magnitude) {
1369dd555e2SMark Brown 		/* Scale the magnitude into the range the device supports */
1379dd555e2SMark Brown 		if (arizona->pdata.hap_act) {
1389dd555e2SMark Brown 			haptics->intensity =
1399dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 9;
1409dd555e2SMark Brown 			if (effect->direction < 0x8000)
1419dd555e2SMark Brown 				haptics->intensity += 0x7f;
1429dd555e2SMark Brown 		} else {
1439dd555e2SMark Brown 			haptics->intensity =
1449dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 8;
1459dd555e2SMark Brown 		}
1469dd555e2SMark Brown 	} else {
1479dd555e2SMark Brown 		haptics->intensity = 0;
1489dd555e2SMark Brown 	}
1499dd555e2SMark Brown 
1509dd555e2SMark Brown 	schedule_work(&haptics->work);
1519dd555e2SMark Brown 
1529dd555e2SMark Brown 	return 0;
1539dd555e2SMark Brown }
1549dd555e2SMark Brown 
1559dd555e2SMark Brown static void arizona_haptics_close(struct input_dev *input)
1569dd555e2SMark Brown {
1579dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
1589dd555e2SMark Brown 	struct mutex *dapm_mutex = &haptics->arizona->dapm->card->dapm_mutex;
1599dd555e2SMark Brown 
1609dd555e2SMark Brown 	cancel_work_sync(&haptics->work);
1619dd555e2SMark Brown 
1629dd555e2SMark Brown 	mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
1639dd555e2SMark Brown 
1649dd555e2SMark Brown 	if (haptics->arizona->dapm)
1659dd555e2SMark Brown 		snd_soc_dapm_disable_pin(haptics->arizona->dapm, "HAPTICS");
1669dd555e2SMark Brown 
1679dd555e2SMark Brown 	mutex_unlock(dapm_mutex);
1689dd555e2SMark Brown }
1699dd555e2SMark Brown 
1709dd555e2SMark Brown static int arizona_haptics_probe(struct platform_device *pdev)
1719dd555e2SMark Brown {
1729dd555e2SMark Brown 	struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
1739dd555e2SMark Brown 	struct arizona_haptics *haptics;
1749dd555e2SMark Brown 	int ret;
1759dd555e2SMark Brown 
1769dd555e2SMark Brown 	haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL);
1779dd555e2SMark Brown 	if (!haptics)
1789dd555e2SMark Brown 		return -ENOMEM;
1799dd555e2SMark Brown 
1809dd555e2SMark Brown 	haptics->arizona = arizona;
1819dd555e2SMark Brown 
1829dd555e2SMark Brown 	ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1,
1839dd555e2SMark Brown 				 ARIZONA_HAP_ACT, arizona->pdata.hap_act);
1849dd555e2SMark Brown 	if (ret != 0) {
1859dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to set haptics actuator: %d\n",
1869dd555e2SMark Brown 			ret);
1879dd555e2SMark Brown 		return ret;
1889dd555e2SMark Brown 	}
1899dd555e2SMark Brown 
1909dd555e2SMark Brown 	INIT_WORK(&haptics->work, arizona_haptics_work);
1919dd555e2SMark Brown 
1929dd555e2SMark Brown 	haptics->input_dev = input_allocate_device();
1939dd555e2SMark Brown 	if (haptics->input_dev == NULL) {
1949dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to allocate input device\n");
1959dd555e2SMark Brown 		return -ENOMEM;
1969dd555e2SMark Brown 	}
1979dd555e2SMark Brown 
1989dd555e2SMark Brown 	input_set_drvdata(haptics->input_dev, haptics);
1999dd555e2SMark Brown 
2009dd555e2SMark Brown 	haptics->input_dev->name = "arizona:haptics";
2019dd555e2SMark Brown 	haptics->input_dev->dev.parent = pdev->dev.parent;
2029dd555e2SMark Brown 	haptics->input_dev->close = arizona_haptics_close;
2039dd555e2SMark Brown 	__set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
2049dd555e2SMark Brown 
2059dd555e2SMark Brown 	ret = input_ff_create_memless(haptics->input_dev, NULL,
2069dd555e2SMark Brown 				      arizona_haptics_play);
2079dd555e2SMark Brown 	if (ret < 0) {
2089dd555e2SMark Brown 		dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
2099dd555e2SMark Brown 			ret);
2109dd555e2SMark Brown 		goto err_ialloc;
2119dd555e2SMark Brown 	}
2129dd555e2SMark Brown 
2139dd555e2SMark Brown 	ret = input_register_device(haptics->input_dev);
2149dd555e2SMark Brown 	if (ret < 0) {
2159dd555e2SMark Brown 		dev_err(arizona->dev, "couldn't register input device: %d\n",
2169dd555e2SMark Brown 			ret);
2179dd555e2SMark Brown 		goto err_iff;
2189dd555e2SMark Brown 	}
2199dd555e2SMark Brown 
2209dd555e2SMark Brown 	platform_set_drvdata(pdev, haptics);
2219dd555e2SMark Brown 
2229dd555e2SMark Brown 	return 0;
2239dd555e2SMark Brown 
2249dd555e2SMark Brown err_iff:
2259dd555e2SMark Brown 	if (haptics->input_dev)
2269dd555e2SMark Brown 		input_ff_destroy(haptics->input_dev);
2279dd555e2SMark Brown err_ialloc:
2289dd555e2SMark Brown 	input_free_device(haptics->input_dev);
2299dd555e2SMark Brown 
2309dd555e2SMark Brown 	return ret;
2319dd555e2SMark Brown }
2329dd555e2SMark Brown 
2339dd555e2SMark Brown static int arizona_haptics_remove(struct platform_device *pdev)
2349dd555e2SMark Brown {
2359dd555e2SMark Brown 	struct arizona_haptics *haptics = platform_get_drvdata(pdev);
2369dd555e2SMark Brown 
2379dd555e2SMark Brown 	input_unregister_device(haptics->input_dev);
2389dd555e2SMark Brown 
2399dd555e2SMark Brown 	return 0;
2409dd555e2SMark Brown }
2419dd555e2SMark Brown 
2429dd555e2SMark Brown static struct platform_driver arizona_haptics_driver = {
2439dd555e2SMark Brown 	.probe		= arizona_haptics_probe,
2449dd555e2SMark Brown 	.remove		= arizona_haptics_remove,
2459dd555e2SMark Brown 	.driver		= {
2469dd555e2SMark Brown 		.name	= "arizona-haptics",
2479dd555e2SMark Brown 		.owner	= THIS_MODULE,
2489dd555e2SMark Brown 	},
2499dd555e2SMark Brown };
2509dd555e2SMark Brown module_platform_driver(arizona_haptics_driver);
2519dd555e2SMark Brown 
2529dd555e2SMark Brown MODULE_ALIAS("platform:arizona-haptics");
2539dd555e2SMark Brown MODULE_DESCRIPTION("Arizona haptics driver");
2549dd555e2SMark Brown MODULE_LICENSE("GPL");
2559dd555e2SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
256