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;
40931afc41SRichard Fitzgerald 	struct snd_soc_component *component =
41931afc41SRichard Fitzgerald 		snd_soc_dapm_to_component(arizona->dapm);
429dd555e2SMark Brown 	int ret;
439dd555e2SMark Brown 
449dd555e2SMark Brown 	if (!haptics->arizona->dapm) {
459dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
469dd555e2SMark Brown 		return;
479dd555e2SMark Brown 	}
489dd555e2SMark Brown 
499dd555e2SMark Brown 	if (haptics->intensity) {
509dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
519dd555e2SMark Brown 					 ARIZONA_HAPTICS_PHASE_2_INTENSITY,
529dd555e2SMark Brown 					 ARIZONA_PHASE2_INTENSITY_MASK,
539dd555e2SMark Brown 					 haptics->intensity);
549dd555e2SMark Brown 		if (ret != 0) {
559dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to set intensity: %d\n",
569dd555e2SMark Brown 				ret);
579dd555e2SMark Brown 			return;
589dd555e2SMark Brown 		}
599dd555e2SMark Brown 
609dd555e2SMark Brown 		/* This enable sequence will be a noop if already enabled */
619dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
629dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
639dd555e2SMark Brown 					 ARIZONA_HAP_CTRL_MASK,
649dd555e2SMark Brown 					 1 << ARIZONA_HAP_CTRL_SHIFT);
659dd555e2SMark Brown 		if (ret != 0) {
669dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start haptics: %d\n",
679dd555e2SMark Brown 				ret);
689dd555e2SMark Brown 			return;
699dd555e2SMark Brown 		}
709dd555e2SMark Brown 
71931afc41SRichard Fitzgerald 		ret = snd_soc_component_enable_pin(component, "HAPTICS");
729dd555e2SMark Brown 		if (ret != 0) {
739dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start HAPTICS: %d\n",
749dd555e2SMark Brown 				ret);
759dd555e2SMark Brown 			return;
769dd555e2SMark Brown 		}
779dd555e2SMark Brown 
789dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
799dd555e2SMark Brown 		if (ret != 0) {
809dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
819dd555e2SMark Brown 				ret);
829dd555e2SMark Brown 			return;
839dd555e2SMark Brown 		}
849dd555e2SMark Brown 	} else {
859dd555e2SMark Brown 		/* This disable sequence will be a noop if already enabled */
86931afc41SRichard Fitzgerald 		ret = snd_soc_component_disable_pin(component, "HAPTICS");
879dd555e2SMark Brown 		if (ret != 0) {
889dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n",
899dd555e2SMark Brown 				ret);
909dd555e2SMark Brown 			return;
919dd555e2SMark Brown 		}
929dd555e2SMark Brown 
939dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
949dd555e2SMark Brown 		if (ret != 0) {
959dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
969dd555e2SMark Brown 				ret);
979dd555e2SMark Brown 			return;
989dd555e2SMark Brown 		}
999dd555e2SMark Brown 
1009dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
1019dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
1021cf44efaSCharles Keepax 					 ARIZONA_HAP_CTRL_MASK, 0);
1039dd555e2SMark Brown 		if (ret != 0) {
1049dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to stop haptics: %d\n",
1059dd555e2SMark Brown 				ret);
1069dd555e2SMark Brown 			return;
1079dd555e2SMark Brown 		}
1089dd555e2SMark Brown 	}
1099dd555e2SMark Brown }
1109dd555e2SMark Brown 
1119dd555e2SMark Brown static int arizona_haptics_play(struct input_dev *input, void *data,
1129dd555e2SMark Brown 				struct ff_effect *effect)
1139dd555e2SMark Brown {
1149dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
1159dd555e2SMark Brown 	struct arizona *arizona = haptics->arizona;
1169dd555e2SMark Brown 
1179dd555e2SMark Brown 	if (!arizona->dapm) {
1189dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
1199dd555e2SMark Brown 		return -EBUSY;
1209dd555e2SMark Brown 	}
1219dd555e2SMark Brown 
1229dd555e2SMark Brown 	if (effect->u.rumble.strong_magnitude) {
1239dd555e2SMark Brown 		/* Scale the magnitude into the range the device supports */
1249dd555e2SMark Brown 		if (arizona->pdata.hap_act) {
1259dd555e2SMark Brown 			haptics->intensity =
1269dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 9;
1279dd555e2SMark Brown 			if (effect->direction < 0x8000)
1289dd555e2SMark Brown 				haptics->intensity += 0x7f;
1299dd555e2SMark Brown 		} else {
1309dd555e2SMark Brown 			haptics->intensity =
1319dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 8;
1329dd555e2SMark Brown 		}
1339dd555e2SMark Brown 	} else {
1349dd555e2SMark Brown 		haptics->intensity = 0;
1359dd555e2SMark Brown 	}
1369dd555e2SMark Brown 
1379dd555e2SMark Brown 	schedule_work(&haptics->work);
1389dd555e2SMark Brown 
1399dd555e2SMark Brown 	return 0;
1409dd555e2SMark Brown }
1419dd555e2SMark Brown 
1429dd555e2SMark Brown static void arizona_haptics_close(struct input_dev *input)
1439dd555e2SMark Brown {
1449dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
145931afc41SRichard Fitzgerald 	struct snd_soc_component *component;
1469dd555e2SMark Brown 
1479dd555e2SMark Brown 	cancel_work_sync(&haptics->work);
1489dd555e2SMark Brown 
149931afc41SRichard Fitzgerald 	if (haptics->arizona->dapm) {
150931afc41SRichard Fitzgerald 		component = snd_soc_dapm_to_component(haptics->arizona->dapm);
151931afc41SRichard Fitzgerald 		snd_soc_component_disable_pin(component, "HAPTICS");
152931afc41SRichard Fitzgerald 	}
1539dd555e2SMark Brown }
1549dd555e2SMark Brown 
1559dd555e2SMark Brown static int arizona_haptics_probe(struct platform_device *pdev)
1569dd555e2SMark Brown {
1579dd555e2SMark Brown 	struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
1589dd555e2SMark Brown 	struct arizona_haptics *haptics;
1599dd555e2SMark Brown 	int ret;
1609dd555e2SMark Brown 
1619dd555e2SMark Brown 	haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL);
1629dd555e2SMark Brown 	if (!haptics)
1639dd555e2SMark Brown 		return -ENOMEM;
1649dd555e2SMark Brown 
1659dd555e2SMark Brown 	haptics->arizona = arizona;
1669dd555e2SMark Brown 
1679dd555e2SMark Brown 	ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1,
1689dd555e2SMark Brown 				 ARIZONA_HAP_ACT, arizona->pdata.hap_act);
1699dd555e2SMark Brown 	if (ret != 0) {
1709dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to set haptics actuator: %d\n",
1719dd555e2SMark Brown 			ret);
1729dd555e2SMark Brown 		return ret;
1739dd555e2SMark Brown 	}
1749dd555e2SMark Brown 
1759dd555e2SMark Brown 	INIT_WORK(&haptics->work, arizona_haptics_work);
1769dd555e2SMark Brown 
177e75ed3c4SDmitry Torokhov 	haptics->input_dev = devm_input_allocate_device(&pdev->dev);
178e75ed3c4SDmitry Torokhov 	if (!haptics->input_dev) {
1799dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to allocate input device\n");
1809dd555e2SMark Brown 		return -ENOMEM;
1819dd555e2SMark Brown 	}
1829dd555e2SMark Brown 
1839dd555e2SMark Brown 	input_set_drvdata(haptics->input_dev, haptics);
1849dd555e2SMark Brown 
1859dd555e2SMark Brown 	haptics->input_dev->name = "arizona:haptics";
1869dd555e2SMark Brown 	haptics->input_dev->close = arizona_haptics_close;
1879dd555e2SMark Brown 	__set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
1889dd555e2SMark Brown 
1899dd555e2SMark Brown 	ret = input_ff_create_memless(haptics->input_dev, NULL,
1909dd555e2SMark Brown 				      arizona_haptics_play);
1919dd555e2SMark Brown 	if (ret < 0) {
1929dd555e2SMark Brown 		dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
1939dd555e2SMark Brown 			ret);
194e75ed3c4SDmitry Torokhov 		return ret;
1959dd555e2SMark Brown 	}
1969dd555e2SMark Brown 
1979dd555e2SMark Brown 	ret = input_register_device(haptics->input_dev);
1989dd555e2SMark Brown 	if (ret < 0) {
1999dd555e2SMark Brown 		dev_err(arizona->dev, "couldn't register input device: %d\n",
2009dd555e2SMark Brown 			ret);
2019dd555e2SMark Brown 		return ret;
2029dd555e2SMark Brown 	}
2039dd555e2SMark Brown 
204e75ed3c4SDmitry Torokhov 	platform_set_drvdata(pdev, haptics);
2059dd555e2SMark Brown 
2069dd555e2SMark Brown 	return 0;
2079dd555e2SMark Brown }
2089dd555e2SMark Brown 
2099dd555e2SMark Brown static struct platform_driver arizona_haptics_driver = {
2109dd555e2SMark Brown 	.probe		= arizona_haptics_probe,
2119dd555e2SMark Brown 	.driver		= {
2129dd555e2SMark Brown 		.name	= "arizona-haptics",
2139dd555e2SMark Brown 	},
2149dd555e2SMark Brown };
2159dd555e2SMark Brown module_platform_driver(arizona_haptics_driver);
2169dd555e2SMark Brown 
2179dd555e2SMark Brown MODULE_ALIAS("platform:arizona-haptics");
2189dd555e2SMark Brown MODULE_DESCRIPTION("Arizona haptics driver");
2199dd555e2SMark Brown MODULE_LICENSE("GPL");
2209dd555e2SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
221