1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29dd555e2SMark Brown /*
39dd555e2SMark Brown  * Arizona haptics driver
49dd555e2SMark Brown  *
59dd555e2SMark Brown  * Copyright 2012 Wolfson Microelectronics plc
69dd555e2SMark Brown  *
79dd555e2SMark Brown  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
89dd555e2SMark Brown  */
99dd555e2SMark Brown 
109dd555e2SMark Brown #include <linux/module.h>
119dd555e2SMark Brown #include <linux/platform_device.h>
129dd555e2SMark Brown #include <linux/input.h>
139dd555e2SMark Brown #include <linux/slab.h>
149dd555e2SMark Brown 
159dd555e2SMark Brown #include <sound/soc.h>
169dd555e2SMark Brown #include <sound/soc-dapm.h>
179dd555e2SMark Brown 
189dd555e2SMark Brown #include <linux/mfd/arizona/core.h>
199dd555e2SMark Brown #include <linux/mfd/arizona/pdata.h>
209dd555e2SMark Brown #include <linux/mfd/arizona/registers.h>
219dd555e2SMark Brown 
229dd555e2SMark Brown struct arizona_haptics {
239dd555e2SMark Brown 	struct arizona *arizona;
249dd555e2SMark Brown 	struct input_dev *input_dev;
259dd555e2SMark Brown 	struct work_struct work;
269dd555e2SMark Brown 
279dd555e2SMark Brown 	struct mutex mutex;
289dd555e2SMark Brown 	u8 intensity;
299dd555e2SMark Brown };
309dd555e2SMark Brown 
arizona_haptics_work(struct work_struct * work)319dd555e2SMark Brown static void arizona_haptics_work(struct work_struct *work)
329dd555e2SMark Brown {
339dd555e2SMark Brown 	struct arizona_haptics *haptics = container_of(work,
349dd555e2SMark Brown 						       struct arizona_haptics,
359dd555e2SMark Brown 						       work);
369dd555e2SMark Brown 	struct arizona *arizona = haptics->arizona;
37931afc41SRichard Fitzgerald 	struct snd_soc_component *component =
38931afc41SRichard Fitzgerald 		snd_soc_dapm_to_component(arizona->dapm);
399dd555e2SMark Brown 	int ret;
409dd555e2SMark Brown 
419dd555e2SMark Brown 	if (!haptics->arizona->dapm) {
429dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
439dd555e2SMark Brown 		return;
449dd555e2SMark Brown 	}
459dd555e2SMark Brown 
469dd555e2SMark Brown 	if (haptics->intensity) {
479dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
489dd555e2SMark Brown 					 ARIZONA_HAPTICS_PHASE_2_INTENSITY,
499dd555e2SMark Brown 					 ARIZONA_PHASE2_INTENSITY_MASK,
509dd555e2SMark Brown 					 haptics->intensity);
519dd555e2SMark Brown 		if (ret != 0) {
529dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to set intensity: %d\n",
539dd555e2SMark Brown 				ret);
549dd555e2SMark Brown 			return;
559dd555e2SMark Brown 		}
569dd555e2SMark Brown 
579dd555e2SMark Brown 		/* This enable sequence will be a noop if already enabled */
589dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
599dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
609dd555e2SMark Brown 					 ARIZONA_HAP_CTRL_MASK,
619dd555e2SMark Brown 					 1 << ARIZONA_HAP_CTRL_SHIFT);
629dd555e2SMark Brown 		if (ret != 0) {
639dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start haptics: %d\n",
649dd555e2SMark Brown 				ret);
659dd555e2SMark Brown 			return;
669dd555e2SMark Brown 		}
679dd555e2SMark Brown 
68931afc41SRichard Fitzgerald 		ret = snd_soc_component_enable_pin(component, "HAPTICS");
699dd555e2SMark Brown 		if (ret != 0) {
709dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to start HAPTICS: %d\n",
719dd555e2SMark Brown 				ret);
729dd555e2SMark Brown 			return;
739dd555e2SMark Brown 		}
749dd555e2SMark Brown 
759dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
769dd555e2SMark Brown 		if (ret != 0) {
779dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
789dd555e2SMark Brown 				ret);
799dd555e2SMark Brown 			return;
809dd555e2SMark Brown 		}
819dd555e2SMark Brown 	} else {
829dd555e2SMark Brown 		/* This disable sequence will be a noop if already enabled */
83931afc41SRichard Fitzgerald 		ret = snd_soc_component_disable_pin(component, "HAPTICS");
849dd555e2SMark Brown 		if (ret != 0) {
859dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n",
869dd555e2SMark Brown 				ret);
879dd555e2SMark Brown 			return;
889dd555e2SMark Brown 		}
899dd555e2SMark Brown 
909dd555e2SMark Brown 		ret = snd_soc_dapm_sync(arizona->dapm);
919dd555e2SMark Brown 		if (ret != 0) {
929dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
939dd555e2SMark Brown 				ret);
949dd555e2SMark Brown 			return;
959dd555e2SMark Brown 		}
969dd555e2SMark Brown 
979dd555e2SMark Brown 		ret = regmap_update_bits(arizona->regmap,
989dd555e2SMark Brown 					 ARIZONA_HAPTICS_CONTROL_1,
991cf44efaSCharles Keepax 					 ARIZONA_HAP_CTRL_MASK, 0);
1009dd555e2SMark Brown 		if (ret != 0) {
1019dd555e2SMark Brown 			dev_err(arizona->dev, "Failed to stop haptics: %d\n",
1029dd555e2SMark Brown 				ret);
1039dd555e2SMark Brown 			return;
1049dd555e2SMark Brown 		}
1059dd555e2SMark Brown 	}
1069dd555e2SMark Brown }
1079dd555e2SMark Brown 
arizona_haptics_play(struct input_dev * input,void * data,struct ff_effect * effect)1089dd555e2SMark Brown static int arizona_haptics_play(struct input_dev *input, void *data,
1099dd555e2SMark Brown 				struct ff_effect *effect)
1109dd555e2SMark Brown {
1119dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
1129dd555e2SMark Brown 	struct arizona *arizona = haptics->arizona;
1139dd555e2SMark Brown 
1149dd555e2SMark Brown 	if (!arizona->dapm) {
1159dd555e2SMark Brown 		dev_err(arizona->dev, "No DAPM context\n");
1169dd555e2SMark Brown 		return -EBUSY;
1179dd555e2SMark Brown 	}
1189dd555e2SMark Brown 
1199dd555e2SMark Brown 	if (effect->u.rumble.strong_magnitude) {
1209dd555e2SMark Brown 		/* Scale the magnitude into the range the device supports */
1219dd555e2SMark Brown 		if (arizona->pdata.hap_act) {
1229dd555e2SMark Brown 			haptics->intensity =
1239dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 9;
1249dd555e2SMark Brown 			if (effect->direction < 0x8000)
1259dd555e2SMark Brown 				haptics->intensity += 0x7f;
1269dd555e2SMark Brown 		} else {
1279dd555e2SMark Brown 			haptics->intensity =
1289dd555e2SMark Brown 				effect->u.rumble.strong_magnitude >> 8;
1299dd555e2SMark Brown 		}
1309dd555e2SMark Brown 	} else {
1319dd555e2SMark Brown 		haptics->intensity = 0;
1329dd555e2SMark Brown 	}
1339dd555e2SMark Brown 
1349dd555e2SMark Brown 	schedule_work(&haptics->work);
1359dd555e2SMark Brown 
1369dd555e2SMark Brown 	return 0;
1379dd555e2SMark Brown }
1389dd555e2SMark Brown 
arizona_haptics_close(struct input_dev * input)1399dd555e2SMark Brown static void arizona_haptics_close(struct input_dev *input)
1409dd555e2SMark Brown {
1419dd555e2SMark Brown 	struct arizona_haptics *haptics = input_get_drvdata(input);
142931afc41SRichard Fitzgerald 	struct snd_soc_component *component;
1439dd555e2SMark Brown 
1449dd555e2SMark Brown 	cancel_work_sync(&haptics->work);
1459dd555e2SMark Brown 
146931afc41SRichard Fitzgerald 	if (haptics->arizona->dapm) {
147931afc41SRichard Fitzgerald 		component = snd_soc_dapm_to_component(haptics->arizona->dapm);
148931afc41SRichard Fitzgerald 		snd_soc_component_disable_pin(component, "HAPTICS");
149931afc41SRichard Fitzgerald 	}
1509dd555e2SMark Brown }
1519dd555e2SMark Brown 
arizona_haptics_probe(struct platform_device * pdev)1529dd555e2SMark Brown static int arizona_haptics_probe(struct platform_device *pdev)
1539dd555e2SMark Brown {
1549dd555e2SMark Brown 	struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
1559dd555e2SMark Brown 	struct arizona_haptics *haptics;
1569dd555e2SMark Brown 	int ret;
1579dd555e2SMark Brown 
1589dd555e2SMark Brown 	haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL);
1599dd555e2SMark Brown 	if (!haptics)
1609dd555e2SMark Brown 		return -ENOMEM;
1619dd555e2SMark Brown 
1629dd555e2SMark Brown 	haptics->arizona = arizona;
1639dd555e2SMark Brown 
1649dd555e2SMark Brown 	ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1,
1659dd555e2SMark Brown 				 ARIZONA_HAP_ACT, arizona->pdata.hap_act);
1669dd555e2SMark Brown 	if (ret != 0) {
1679dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to set haptics actuator: %d\n",
1689dd555e2SMark Brown 			ret);
1699dd555e2SMark Brown 		return ret;
1709dd555e2SMark Brown 	}
1719dd555e2SMark Brown 
1729dd555e2SMark Brown 	INIT_WORK(&haptics->work, arizona_haptics_work);
1739dd555e2SMark Brown 
174e75ed3c4SDmitry Torokhov 	haptics->input_dev = devm_input_allocate_device(&pdev->dev);
175e75ed3c4SDmitry Torokhov 	if (!haptics->input_dev) {
1769dd555e2SMark Brown 		dev_err(arizona->dev, "Failed to allocate input device\n");
1779dd555e2SMark Brown 		return -ENOMEM;
1789dd555e2SMark Brown 	}
1799dd555e2SMark Brown 
1809dd555e2SMark Brown 	input_set_drvdata(haptics->input_dev, haptics);
1819dd555e2SMark Brown 
1829dd555e2SMark Brown 	haptics->input_dev->name = "arizona:haptics";
1839dd555e2SMark Brown 	haptics->input_dev->close = arizona_haptics_close;
1849dd555e2SMark Brown 	__set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
1859dd555e2SMark Brown 
1869dd555e2SMark Brown 	ret = input_ff_create_memless(haptics->input_dev, NULL,
1879dd555e2SMark Brown 				      arizona_haptics_play);
1889dd555e2SMark Brown 	if (ret < 0) {
1899dd555e2SMark Brown 		dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
1909dd555e2SMark Brown 			ret);
191e75ed3c4SDmitry Torokhov 		return ret;
1929dd555e2SMark Brown 	}
1939dd555e2SMark Brown 
1949dd555e2SMark Brown 	ret = input_register_device(haptics->input_dev);
1959dd555e2SMark Brown 	if (ret < 0) {
1969dd555e2SMark Brown 		dev_err(arizona->dev, "couldn't register input device: %d\n",
1979dd555e2SMark Brown 			ret);
1989dd555e2SMark Brown 		return ret;
1999dd555e2SMark Brown 	}
2009dd555e2SMark Brown 
2019dd555e2SMark Brown 	return 0;
2029dd555e2SMark Brown }
2039dd555e2SMark Brown 
2049dd555e2SMark Brown static struct platform_driver arizona_haptics_driver = {
2059dd555e2SMark Brown 	.probe		= arizona_haptics_probe,
2069dd555e2SMark Brown 	.driver		= {
2079dd555e2SMark Brown 		.name	= "arizona-haptics",
2089dd555e2SMark Brown 	},
2099dd555e2SMark Brown };
2109dd555e2SMark Brown module_platform_driver(arizona_haptics_driver);
2119dd555e2SMark Brown 
2129dd555e2SMark Brown MODULE_ALIAS("platform:arizona-haptics");
2139dd555e2SMark Brown MODULE_DESCRIPTION("Arizona haptics driver");
2149dd555e2SMark Brown MODULE_LICENSE("GPL");
2159dd555e2SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
216