All of lore.kernel.org
 help / color / mirror / Atom feed
From: James Ogletree <james.ogletree@opensource.cirrus.com>
To: unlisted-recipients:; (no To-header on input)
Cc: James Ogletree <james.ogletree@cirrus.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Rob Herring <robh+dt@kernel.org>,
	"Krzysztof Kozlowski" <krzysztof.kozlowski+dt@linaro.org>,
	Lee Jones <lee@kernel.org>, Fred Treven <fred.treven@cirrus.com>,
	Ben Bright <ben.bright@cirrus.com>, <linux-input@vger.kernel.org>,
	<devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Subject: [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver
Date: Wed, 18 Oct 2023 17:57:25 +0000	[thread overview]
Message-ID: <20231018175726.3879955-5-james.ogletree@opensource.cirrus.com> (raw)
In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com>

From: James Ogletree <james.ogletree@cirrus.com>

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The input driver provides the interface for control of
haptic effects through the device.

Signed-off-by: James Ogletree <james.ogletree@cirrus.com>
---
 MAINTAINERS                        |   1 +
 drivers/input/misc/Kconfig         |  10 +
 drivers/input/misc/Makefile        |   1 +
 drivers/input/misc/cs40l50-vibra.c | 353 +++++++++++++++++++++++++++++
 4 files changed, 365 insertions(+)
 create mode 100644 drivers/input/misc/cs40l50-vibra.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 08e1e9695d49..24a00d8e5c1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4971,6 +4971,7 @@ L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
 F:	drivers/input/misc/cirrus*
+F:	drivers/input/misc/cs40l*
 F:	drivers/mfd/cs40l*
 F:	include/linux/input/cirrus*
 F:	include/linux/mfd/cs40l*
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 9f088900f863..938090648126 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -129,6 +129,16 @@ config INPUT_BMA150
 	  To compile this driver as a module, choose M here: the
 	  module will be called bma150.
 
+config INPUT_CS40L50_VIBRA
+	tristate "CS40L50 Haptic Driver support"
+	depends on MFD_CS40L50_CORE
+	help
+	  Say Y here to enable support for Cirrus Logic's CS40L50
+	  haptic driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cs40l50-vibra.
+
 config INPUT_E3X0_BUTTON
 	tristate "NI Ettus Research USRP E3xx Button support."
 	default n
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 6abefc41037b..6b653ed2124f 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_INPUT_CMA3000)		+= cma3000_d0x.o
 obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
 obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
 obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON)	+= cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_CS40L50_VIBRA)	+= cs40l50-vibra.o cirrus_haptics.o
 obj-$(CONFIG_INPUT_DA7280_HAPTICS)	+= da7280.o
 obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
 obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
diff --git a/drivers/input/misc/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c
new file mode 100644
index 000000000000..3b3e4cb10de0
--- /dev/null
+++ b/drivers/input/misc/cs40l50-vibra.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ */
+
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+static int cs40l50_add(struct input_dev *dev,
+		       struct ff_effect *effect,
+		       struct ff_effect *old)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+	u32 len = effect->u.periodic.custom_len;
+
+	if (effect->type != FF_PERIODIC || effect->u.periodic.waveform != FF_CUSTOM) {
+		dev_err(cs40l50->dev, "Type (%#X) or waveform (%#X) unsupported\n",
+			effect->type, effect->u.periodic.waveform);
+		return -EINVAL;
+	}
+
+	memcpy(&cs40l50->haptics.add_effect, effect, sizeof(struct ff_effect));
+
+	cs40l50->haptics.add_effect.u.periodic.custom_data = kcalloc(len,
+								     sizeof(s16),
+								     GFP_KERNEL);
+	if (!cs40l50->haptics.add_effect.u.periodic.custom_data)
+		return -ENOMEM;
+
+	if (copy_from_user(cs40l50->haptics.add_effect.u.periodic.custom_data,
+			   effect->u.periodic.custom_data, sizeof(s16) * len)) {
+		cs40l50->haptics.add_error = -EFAULT;
+		goto out_free;
+	}
+
+	queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.add_work);
+	flush_work(&cs40l50->haptics.add_work);
+
+out_free:
+	kfree(cs40l50->haptics.add_effect.u.periodic.custom_data);
+	cs40l50->haptics.add_effect.u.periodic.custom_data = NULL;
+
+	return cs40l50->haptics.add_error;
+}
+
+static int cs40l50_playback(struct input_dev *dev, int effect_id, int val)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+
+	if (val > 0) {
+		cs40l50->haptics.start_effect = &dev->ff->effects[effect_id];
+		queue_work(cs40l50->haptics.vibe_wq,
+			   &cs40l50->haptics.vibe_start_work);
+	} else {
+		queue_work(cs40l50->haptics.vibe_wq,
+			   &cs40l50->haptics.vibe_stop_work);
+	}
+
+	return 0;
+}
+
+static int cs40l50_erase(struct input_dev *dev, int effect_id)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+
+	cs40l50->haptics.erase_effect = &dev->ff->effects[effect_id];
+
+	queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.erase_work);
+	flush_work(&cs40l50->haptics.erase_work);
+
+	return cs40l50->haptics.erase_error;
+}
+
+static const struct reg_sequence cs40l50_int_vamp_seq[] = {
+	{ CS40L50_BST_LPMODE_SEL,	CS40L50_DCM_LOW_POWER },
+	{ CS40L50_BLOCK_ENABLES2,	CS40L50_OVERTEMP_WARN },
+};
+
+static const struct reg_sequence cs40l50_irq_mask_seq[] = {
+	{ CS40L50_IRQ1_MASK_2,	CS40L50_IRQ_MASK_2_OVERRIDE },
+	{ CS40L50_IRQ1_MASK_20,	CS40L50_IRQ_MASK_20_OVERRIDE },
+};
+
+static int cs40l50_hw_init(struct cs40l50_private *cs40l50)
+{
+	int error;
+
+	error = regmap_multi_reg_write(cs40l50->regmap,
+				       cs40l50_int_vamp_seq,
+				       ARRAY_SIZE(cs40l50_int_vamp_seq));
+	if (error)
+		return error;
+
+	error = cs_hap_pseq_multi_write(&cs40l50->haptics,
+					cs40l50_int_vamp_seq,
+					ARRAY_SIZE(cs40l50_int_vamp_seq),
+					false, PSEQ_OP_WRITE_FULL);
+	if (error)
+		return error;
+
+	error = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_seq,
+				       ARRAY_SIZE(cs40l50_irq_mask_seq));
+	if (error)
+		return error;
+
+	return cs_hap_pseq_multi_write(&cs40l50->haptics, cs40l50_irq_mask_seq,
+				       ARRAY_SIZE(cs40l50_irq_mask_seq), false,
+				       PSEQ_OP_WRITE_FULL);
+}
+
+static const struct cs_dsp_client_ops cs40l50_cs_dsp_client_ops;
+
+static const struct cs_dsp_region cs40l50_dsp_regions[] = {
+	{
+		.type = WMFW_HALO_PM_PACKED,
+		.base = CS40L50_DSP1_PMEM_0
+	},
+	{
+		.type = WMFW_HALO_XM_PACKED,
+		.base = CS40L50_DSP1_XMEM_PACKED_0
+	},
+	{
+		.type = WMFW_HALO_YM_PACKED,
+		.base = CS40L50_DSP1_YMEM_PACKED_0
+	},
+	{
+		.type = WMFW_ADSP2_XM,
+		.base = CS40L50_DSP1_XMEM_UNPACKED24_0
+	},
+	{
+		.type = WMFW_ADSP2_YM,
+		.base = CS40L50_DSP1_YMEM_UNPACKED24_0
+	},
+};
+
+static int cs40l50_cs_dsp_init(struct cs40l50_private *cs40l50)
+{
+	cs40l50->dsp.num = 1;
+	cs40l50->dsp.type = WMFW_HALO;
+	cs40l50->dsp.dev = cs40l50->dev;
+	cs40l50->dsp.regmap = cs40l50->regmap;
+	cs40l50->dsp.base = CS40L50_CORE_BASE;
+	cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID;
+	cs40l50->dsp.mem = cs40l50_dsp_regions;
+	cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions);
+	cs40l50->dsp.no_core_startstop = true;
+	cs40l50->dsp.client_ops = &cs40l50_cs_dsp_client_ops;
+
+	return cs_dsp_halo_init(&cs40l50->dsp);
+}
+
+static struct cs_hap_bank cs40l50_banks[] = {
+	{
+		.bank =		WVFRM_BANK_RAM,
+		.base_index =	CS40L50_RAM_BANK_INDEX_START,
+		.max_index =	CS40L50_RAM_BANK_INDEX_START,
+	},
+	{
+		.bank =		WVFRM_BANK_ROM,
+		.base_index =	CS40L50_ROM_BANK_INDEX_START,
+		.max_index =	CS40L50_ROM_BANK_INDEX_END,
+	},
+	{
+		.bank =		WVFRM_BANK_OWT,
+		.base_index =	CS40L50_RTH_INDEX_START,
+		.max_index =	CS40L50_RTH_INDEX_END,
+	},
+};
+
+static int cs40l50_cs_hap_init(struct cs40l50_private *cs40l50)
+{
+	cs40l50->haptics.regmap = cs40l50->regmap;
+	cs40l50->haptics.dev = cs40l50->dev;
+	cs40l50->haptics.banks = cs40l50_banks;
+	cs40l50->haptics.dsp.gpio_base_reg = CS40L50_GPIO_BASE;
+	cs40l50->haptics.dsp.owt_base_reg = CS40L50_OWT_BASE;
+	cs40l50->haptics.dsp.owt_offset_reg = CS40L50_OWT_NEXT;
+	cs40l50->haptics.dsp.owt_size_reg = CS40L50_OWT_SIZE;
+	cs40l50->haptics.dsp.mailbox_reg = CS40L50_DSP_MBOX;
+	cs40l50->haptics.dsp.pseq_reg = CS40L50_POWER_ON_SEQ;
+	cs40l50->haptics.dsp.push_owt_cmd = CS40L50_OWT_PUSH;
+	cs40l50->haptics.dsp.delete_owt_cmd = CS40L50_OWT_DELETE;
+	cs40l50->haptics.dsp.stop_cmd = CS40L50_STOP_PLAYBACK;
+	cs40l50->haptics.dsp.pseq_size = CS40L50_PSEQ_SIZE;
+	cs40l50->haptics.runtime_pm = true;
+
+	return cs_hap_init(&cs40l50->haptics);
+}
+
+static void cs40l50_upload_wt(const struct firmware *bin, void *context)
+{
+	struct cs40l50_private *cs40l50 = context;
+	u32 nwaves;
+
+	mutex_lock(&cs40l50->lock);
+
+	if (cs40l50->wmfw) {
+		if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL,
+				 CS40L50_CLOCK_DISABLE))
+			goto err_mutex;
+
+		if (regmap_write(cs40l50->regmap, CS40L50_RAM_INIT,
+				 CS40L50_RAM_INIT_FLAG))
+			goto err_mutex;
+
+		if (regmap_write(cs40l50->regmap, CS40L50_PWRMGT_CTL,
+				 CS40L50_MEM_RDY_HW))
+			goto err_mutex;
+	}
+
+	cs_dsp_power_up(&cs40l50->dsp, cs40l50->wmfw, "cs40l50.wmfw",
+			bin, "cs40l50.bin", "cs40l50");
+
+	if (cs40l50->wmfw) {
+		if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL,
+				 CS40L50_CLOCK_ENABLE))
+			goto err_mutex;
+	}
+
+	if (regmap_read(cs40l50->regmap, CS40L50_NUM_OF_WAVES, &nwaves))
+		goto err_mutex;
+
+	cs40l50->haptics.banks[WVFRM_BANK_RAM].max_index += (nwaves - 1);
+
+err_mutex:
+	mutex_unlock(&cs40l50->lock);
+	release_firmware(bin);
+	release_firmware(cs40l50->wmfw);
+}
+
+static void cs40l50_upload_patch(const struct firmware *wmfw, void *context)
+{
+	struct cs40l50_private *cs40l50 = context;
+
+	cs40l50->wmfw = wmfw;
+
+	if (request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT,
+				    cs40l50->dev, GFP_KERNEL,
+				    cs40l50, cs40l50_upload_wt))
+		release_firmware(cs40l50->wmfw);
+}
+
+static int cs40l50_input_init(struct cs40l50_private *cs40l50)
+{
+	int error;
+
+	cs40l50->input = devm_input_allocate_device(cs40l50->dev);
+	if (!cs40l50->input)
+		return -ENOMEM;
+
+	cs40l50->input->id.product = cs40l50->devid & 0xFFFF;
+	cs40l50->input->id.version = cs40l50->revid;
+	cs40l50->input->name = "cs40l50_vibra";
+
+	input_set_drvdata(cs40l50->input, cs40l50);
+	input_set_capability(cs40l50->input, EV_FF, FF_PERIODIC);
+	input_set_capability(cs40l50->input, EV_FF, FF_CUSTOM);
+
+	error = input_ff_create(cs40l50->input, FF_MAX_EFFECTS);
+	if (error)
+		return error;
+
+	cs40l50->input->ff->upload = cs40l50_add;
+	cs40l50->input->ff->playback = cs40l50_playback;
+	cs40l50->input->ff->erase = cs40l50_erase;
+
+	INIT_LIST_HEAD(&cs40l50->haptics.effect_head);
+
+	error = input_register_device(cs40l50->input);
+	if (error)
+		goto err_free_dev;
+
+	return cs40l50_cs_hap_init(cs40l50);
+
+err_free_dev:
+	input_free_device(cs40l50->input);
+	return error;
+}
+static int cs40l50_vibra_probe(struct platform_device *pdev)
+{
+	struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+	int error;
+
+	error = cs40l50_input_init(cs40l50);
+	if (error)
+		return error;
+
+	error = cs40l50_hw_init(cs40l50);
+	if (error)
+		goto err_input;
+
+	error = cs40l50_cs_dsp_init(cs40l50);
+	if (error)
+		goto err_input;
+
+	error = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+					CS40L50_FW, cs40l50->dev,
+					GFP_KERNEL, cs40l50,
+					cs40l50_upload_patch);
+	if (error)
+		goto err_dsp;
+
+	return 0;
+
+err_dsp:
+	cs_dsp_remove(&cs40l50->dsp);
+err_input:
+	input_unregister_device(cs40l50->input);
+	cs_hap_remove(&cs40l50->haptics);
+
+	return error;
+}
+
+static int cs40l50_vibra_remove(struct platform_device *pdev)
+{
+	struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+
+	input_unregister_device(cs40l50->input);
+	cs_hap_remove(&cs40l50->haptics);
+
+	if (cs40l50->dsp.booted)
+		cs_dsp_power_down(&cs40l50->dsp);
+	if (&cs40l50->dsp)
+		cs_dsp_remove(&cs40l50->dsp);
+
+	return 0;
+}
+
+static const struct platform_device_id cs40l50_id_vibra[] = {
+	{"cs40l50-vibra", },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_id_vibra);
+
+static struct platform_driver cs40l50_vibra_driver = {
+	.probe		= cs40l50_vibra_probe,
+	.remove		= cs40l50_vibra_remove,
+	.id_table	= cs40l50_id_vibra,
+	.driver		= {
+		.name	= "cs40l50-vibra",
+	},
+};
+module_platform_driver(cs40l50_vibra_driver);
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
-- 
2.25.1


WARNING: multiple messages have this Message-ID (diff)
From: James Ogletree <james.ogletree@opensource.cirrus.com>
Cc: James Ogletree <james.ogletree@cirrus.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Rob Herring <robh+dt@kernel.org>,
	"Krzysztof Kozlowski" <krzysztof.kozlowski+dt@linaro.org>,
	Lee Jones <lee@kernel.org>, Fred Treven <fred.treven@cirrus.com>,
	Ben Bright <ben.bright@cirrus.com>, <linux-input@vger.kernel.org>,
	<devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Subject: [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver
Date: Wed, 18 Oct 2023 17:57:25 +0000	[thread overview]
Message-ID: <20231018175726.3879955-5-james.ogletree@opensource.cirrus.com> (raw)
In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com>

From: James Ogletree <james.ogletree@cirrus.com>

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The input driver provides the interface for control of
haptic effects through the device.

Signed-off-by: James Ogletree <james.ogletree@cirrus.com>
---
 MAINTAINERS                        |   1 +
 drivers/input/misc/Kconfig         |  10 +
 drivers/input/misc/Makefile        |   1 +
 drivers/input/misc/cs40l50-vibra.c | 353 +++++++++++++++++++++++++++++
 4 files changed, 365 insertions(+)
 create mode 100644 drivers/input/misc/cs40l50-vibra.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 08e1e9695d49..24a00d8e5c1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4971,6 +4971,7 @@ L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
 F:	drivers/input/misc/cirrus*
+F:	drivers/input/misc/cs40l*
 F:	drivers/mfd/cs40l*
 F:	include/linux/input/cirrus*
 F:	include/linux/mfd/cs40l*
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 9f088900f863..938090648126 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -129,6 +129,16 @@ config INPUT_BMA150
 	  To compile this driver as a module, choose M here: the
 	  module will be called bma150.
 
+config INPUT_CS40L50_VIBRA
+	tristate "CS40L50 Haptic Driver support"
+	depends on MFD_CS40L50_CORE
+	help
+	  Say Y here to enable support for Cirrus Logic's CS40L50
+	  haptic driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cs40l50-vibra.
+
 config INPUT_E3X0_BUTTON
 	tristate "NI Ettus Research USRP E3xx Button support."
 	default n
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 6abefc41037b..6b653ed2124f 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_INPUT_CMA3000)		+= cma3000_d0x.o
 obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
 obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
 obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON)	+= cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_CS40L50_VIBRA)	+= cs40l50-vibra.o cirrus_haptics.o
 obj-$(CONFIG_INPUT_DA7280_HAPTICS)	+= da7280.o
 obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
 obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
diff --git a/drivers/input/misc/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c
new file mode 100644
index 000000000000..3b3e4cb10de0
--- /dev/null
+++ b/drivers/input/misc/cs40l50-vibra.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ */
+
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+static int cs40l50_add(struct input_dev *dev,
+		       struct ff_effect *effect,
+		       struct ff_effect *old)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+	u32 len = effect->u.periodic.custom_len;
+
+	if (effect->type != FF_PERIODIC || effect->u.periodic.waveform != FF_CUSTOM) {
+		dev_err(cs40l50->dev, "Type (%#X) or waveform (%#X) unsupported\n",
+			effect->type, effect->u.periodic.waveform);
+		return -EINVAL;
+	}
+
+	memcpy(&cs40l50->haptics.add_effect, effect, sizeof(struct ff_effect));
+
+	cs40l50->haptics.add_effect.u.periodic.custom_data = kcalloc(len,
+								     sizeof(s16),
+								     GFP_KERNEL);
+	if (!cs40l50->haptics.add_effect.u.periodic.custom_data)
+		return -ENOMEM;
+
+	if (copy_from_user(cs40l50->haptics.add_effect.u.periodic.custom_data,
+			   effect->u.periodic.custom_data, sizeof(s16) * len)) {
+		cs40l50->haptics.add_error = -EFAULT;
+		goto out_free;
+	}
+
+	queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.add_work);
+	flush_work(&cs40l50->haptics.add_work);
+
+out_free:
+	kfree(cs40l50->haptics.add_effect.u.periodic.custom_data);
+	cs40l50->haptics.add_effect.u.periodic.custom_data = NULL;
+
+	return cs40l50->haptics.add_error;
+}
+
+static int cs40l50_playback(struct input_dev *dev, int effect_id, int val)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+
+	if (val > 0) {
+		cs40l50->haptics.start_effect = &dev->ff->effects[effect_id];
+		queue_work(cs40l50->haptics.vibe_wq,
+			   &cs40l50->haptics.vibe_start_work);
+	} else {
+		queue_work(cs40l50->haptics.vibe_wq,
+			   &cs40l50->haptics.vibe_stop_work);
+	}
+
+	return 0;
+}
+
+static int cs40l50_erase(struct input_dev *dev, int effect_id)
+{
+	struct cs40l50_private *cs40l50 = input_get_drvdata(dev);
+
+	cs40l50->haptics.erase_effect = &dev->ff->effects[effect_id];
+
+	queue_work(cs40l50->haptics.vibe_wq, &cs40l50->haptics.erase_work);
+	flush_work(&cs40l50->haptics.erase_work);
+
+	return cs40l50->haptics.erase_error;
+}
+
+static const struct reg_sequence cs40l50_int_vamp_seq[] = {
+	{ CS40L50_BST_LPMODE_SEL,	CS40L50_DCM_LOW_POWER },
+	{ CS40L50_BLOCK_ENABLES2,	CS40L50_OVERTEMP_WARN },
+};
+
+static const struct reg_sequence cs40l50_irq_mask_seq[] = {
+	{ CS40L50_IRQ1_MASK_2,	CS40L50_IRQ_MASK_2_OVERRIDE },
+	{ CS40L50_IRQ1_MASK_20,	CS40L50_IRQ_MASK_20_OVERRIDE },
+};
+
+static int cs40l50_hw_init(struct cs40l50_private *cs40l50)
+{
+	int error;
+
+	error = regmap_multi_reg_write(cs40l50->regmap,
+				       cs40l50_int_vamp_seq,
+				       ARRAY_SIZE(cs40l50_int_vamp_seq));
+	if (error)
+		return error;
+
+	error = cs_hap_pseq_multi_write(&cs40l50->haptics,
+					cs40l50_int_vamp_seq,
+					ARRAY_SIZE(cs40l50_int_vamp_seq),
+					false, PSEQ_OP_WRITE_FULL);
+	if (error)
+		return error;
+
+	error = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_seq,
+				       ARRAY_SIZE(cs40l50_irq_mask_seq));
+	if (error)
+		return error;
+
+	return cs_hap_pseq_multi_write(&cs40l50->haptics, cs40l50_irq_mask_seq,
+				       ARRAY_SIZE(cs40l50_irq_mask_seq), false,
+				       PSEQ_OP_WRITE_FULL);
+}
+
+static const struct cs_dsp_client_ops cs40l50_cs_dsp_client_ops;
+
+static const struct cs_dsp_region cs40l50_dsp_regions[] = {
+	{
+		.type = WMFW_HALO_PM_PACKED,
+		.base = CS40L50_DSP1_PMEM_0
+	},
+	{
+		.type = WMFW_HALO_XM_PACKED,
+		.base = CS40L50_DSP1_XMEM_PACKED_0
+	},
+	{
+		.type = WMFW_HALO_YM_PACKED,
+		.base = CS40L50_DSP1_YMEM_PACKED_0
+	},
+	{
+		.type = WMFW_ADSP2_XM,
+		.base = CS40L50_DSP1_XMEM_UNPACKED24_0
+	},
+	{
+		.type = WMFW_ADSP2_YM,
+		.base = CS40L50_DSP1_YMEM_UNPACKED24_0
+	},
+};
+
+static int cs40l50_cs_dsp_init(struct cs40l50_private *cs40l50)
+{
+	cs40l50->dsp.num = 1;
+	cs40l50->dsp.type = WMFW_HALO;
+	cs40l50->dsp.dev = cs40l50->dev;
+	cs40l50->dsp.regmap = cs40l50->regmap;
+	cs40l50->dsp.base = CS40L50_CORE_BASE;
+	cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID;
+	cs40l50->dsp.mem = cs40l50_dsp_regions;
+	cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions);
+	cs40l50->dsp.no_core_startstop = true;
+	cs40l50->dsp.client_ops = &cs40l50_cs_dsp_client_ops;
+
+	return cs_dsp_halo_init(&cs40l50->dsp);
+}
+
+static struct cs_hap_bank cs40l50_banks[] = {
+	{
+		.bank =		WVFRM_BANK_RAM,
+		.base_index =	CS40L50_RAM_BANK_INDEX_START,
+		.max_index =	CS40L50_RAM_BANK_INDEX_START,
+	},
+	{
+		.bank =		WVFRM_BANK_ROM,
+		.base_index =	CS40L50_ROM_BANK_INDEX_START,
+		.max_index =	CS40L50_ROM_BANK_INDEX_END,
+	},
+	{
+		.bank =		WVFRM_BANK_OWT,
+		.base_index =	CS40L50_RTH_INDEX_START,
+		.max_index =	CS40L50_RTH_INDEX_END,
+	},
+};
+
+static int cs40l50_cs_hap_init(struct cs40l50_private *cs40l50)
+{
+	cs40l50->haptics.regmap = cs40l50->regmap;
+	cs40l50->haptics.dev = cs40l50->dev;
+	cs40l50->haptics.banks = cs40l50_banks;
+	cs40l50->haptics.dsp.gpio_base_reg = CS40L50_GPIO_BASE;
+	cs40l50->haptics.dsp.owt_base_reg = CS40L50_OWT_BASE;
+	cs40l50->haptics.dsp.owt_offset_reg = CS40L50_OWT_NEXT;
+	cs40l50->haptics.dsp.owt_size_reg = CS40L50_OWT_SIZE;
+	cs40l50->haptics.dsp.mailbox_reg = CS40L50_DSP_MBOX;
+	cs40l50->haptics.dsp.pseq_reg = CS40L50_POWER_ON_SEQ;
+	cs40l50->haptics.dsp.push_owt_cmd = CS40L50_OWT_PUSH;
+	cs40l50->haptics.dsp.delete_owt_cmd = CS40L50_OWT_DELETE;
+	cs40l50->haptics.dsp.stop_cmd = CS40L50_STOP_PLAYBACK;
+	cs40l50->haptics.dsp.pseq_size = CS40L50_PSEQ_SIZE;
+	cs40l50->haptics.runtime_pm = true;
+
+	return cs_hap_init(&cs40l50->haptics);
+}
+
+static void cs40l50_upload_wt(const struct firmware *bin, void *context)
+{
+	struct cs40l50_private *cs40l50 = context;
+	u32 nwaves;
+
+	mutex_lock(&cs40l50->lock);
+
+	if (cs40l50->wmfw) {
+		if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL,
+				 CS40L50_CLOCK_DISABLE))
+			goto err_mutex;
+
+		if (regmap_write(cs40l50->regmap, CS40L50_RAM_INIT,
+				 CS40L50_RAM_INIT_FLAG))
+			goto err_mutex;
+
+		if (regmap_write(cs40l50->regmap, CS40L50_PWRMGT_CTL,
+				 CS40L50_MEM_RDY_HW))
+			goto err_mutex;
+	}
+
+	cs_dsp_power_up(&cs40l50->dsp, cs40l50->wmfw, "cs40l50.wmfw",
+			bin, "cs40l50.bin", "cs40l50");
+
+	if (cs40l50->wmfw) {
+		if (regmap_write(cs40l50->regmap, CS40L50_CCM_CORE_CONTROL,
+				 CS40L50_CLOCK_ENABLE))
+			goto err_mutex;
+	}
+
+	if (regmap_read(cs40l50->regmap, CS40L50_NUM_OF_WAVES, &nwaves))
+		goto err_mutex;
+
+	cs40l50->haptics.banks[WVFRM_BANK_RAM].max_index += (nwaves - 1);
+
+err_mutex:
+	mutex_unlock(&cs40l50->lock);
+	release_firmware(bin);
+	release_firmware(cs40l50->wmfw);
+}
+
+static void cs40l50_upload_patch(const struct firmware *wmfw, void *context)
+{
+	struct cs40l50_private *cs40l50 = context;
+
+	cs40l50->wmfw = wmfw;
+
+	if (request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT,
+				    cs40l50->dev, GFP_KERNEL,
+				    cs40l50, cs40l50_upload_wt))
+		release_firmware(cs40l50->wmfw);
+}
+
+static int cs40l50_input_init(struct cs40l50_private *cs40l50)
+{
+	int error;
+
+	cs40l50->input = devm_input_allocate_device(cs40l50->dev);
+	if (!cs40l50->input)
+		return -ENOMEM;
+
+	cs40l50->input->id.product = cs40l50->devid & 0xFFFF;
+	cs40l50->input->id.version = cs40l50->revid;
+	cs40l50->input->name = "cs40l50_vibra";
+
+	input_set_drvdata(cs40l50->input, cs40l50);
+	input_set_capability(cs40l50->input, EV_FF, FF_PERIODIC);
+	input_set_capability(cs40l50->input, EV_FF, FF_CUSTOM);
+
+	error = input_ff_create(cs40l50->input, FF_MAX_EFFECTS);
+	if (error)
+		return error;
+
+	cs40l50->input->ff->upload = cs40l50_add;
+	cs40l50->input->ff->playback = cs40l50_playback;
+	cs40l50->input->ff->erase = cs40l50_erase;
+
+	INIT_LIST_HEAD(&cs40l50->haptics.effect_head);
+
+	error = input_register_device(cs40l50->input);
+	if (error)
+		goto err_free_dev;
+
+	return cs40l50_cs_hap_init(cs40l50);
+
+err_free_dev:
+	input_free_device(cs40l50->input);
+	return error;
+}
+static int cs40l50_vibra_probe(struct platform_device *pdev)
+{
+	struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+	int error;
+
+	error = cs40l50_input_init(cs40l50);
+	if (error)
+		return error;
+
+	error = cs40l50_hw_init(cs40l50);
+	if (error)
+		goto err_input;
+
+	error = cs40l50_cs_dsp_init(cs40l50);
+	if (error)
+		goto err_input;
+
+	error = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+					CS40L50_FW, cs40l50->dev,
+					GFP_KERNEL, cs40l50,
+					cs40l50_upload_patch);
+	if (error)
+		goto err_dsp;
+
+	return 0;
+
+err_dsp:
+	cs_dsp_remove(&cs40l50->dsp);
+err_input:
+	input_unregister_device(cs40l50->input);
+	cs_hap_remove(&cs40l50->haptics);
+
+	return error;
+}
+
+static int cs40l50_vibra_remove(struct platform_device *pdev)
+{
+	struct cs40l50_private *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+
+	input_unregister_device(cs40l50->input);
+	cs_hap_remove(&cs40l50->haptics);
+
+	if (cs40l50->dsp.booted)
+		cs_dsp_power_down(&cs40l50->dsp);
+	if (&cs40l50->dsp)
+		cs_dsp_remove(&cs40l50->dsp);
+
+	return 0;
+}
+
+static const struct platform_device_id cs40l50_id_vibra[] = {
+	{"cs40l50-vibra", },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_id_vibra);
+
+static struct platform_driver cs40l50_vibra_driver = {
+	.probe		= cs40l50_vibra_probe,
+	.remove		= cs40l50_vibra_remove,
+	.id_table	= cs40l50_id_vibra,
+	.driver		= {
+		.name	= "cs40l50-vibra",
+	},
+};
+module_platform_driver(cs40l50_vibra_driver);
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
-- 
2.25.1


  parent reply	other threads:[~2023-10-18 17:58 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-18 17:57 [PATCH v4 0/4] Add support for CS40L50 James Ogletree
2023-10-18 17:57 ` James Ogletree
2023-10-18 17:57 ` [PATCH v4 1/4] dt-bindings: input: cirrus,cs40l50: Add initial DT binding James Ogletree
2023-10-18 17:57   ` James Ogletree
2023-10-18 19:21   ` Krzysztof Kozlowski
2023-10-18 21:44     ` James Ogletree
2023-10-18 17:57 ` [PATCH v4 2/4] Input: cs40l50 - Add cirrus haptics base support James Ogletree
2023-10-18 17:57   ` James Ogletree
2023-10-25  2:04   ` Jeff LaBundy
2023-11-01 20:46     ` James Ogletree
2023-11-26  0:52       ` Jeff LaBundy
2023-11-29 22:22         ` James Ogletree
2023-12-14  2:11           ` Jeff LaBundy
2023-10-18 17:57 ` [PATCH v4 3/4] mfd: cs40l50: Add support for CS40L50 core driver James Ogletree
2023-10-18 17:57   ` James Ogletree
2023-10-19 16:23   ` Lee Jones
2023-10-20 15:39     ` James Ogletree
2023-10-23  9:20       ` Lee Jones
2023-10-24  1:08         ` Jeff LaBundy
2023-10-24  1:30           ` James Ogletree
2023-10-24 15:47           ` Lee Jones
2023-10-24  1:14         ` James Ogletree
2023-10-21 14:56   ` kernel test robot
2023-10-25  2:56   ` Jeff LaBundy
2023-11-01 20:47     ` James Ogletree
2023-11-26  1:03       ` Jeff LaBundy
2023-10-25  3:20   ` Jeff LaBundy
2023-10-25  9:26     ` Lee Jones
2023-10-18 17:57 ` James Ogletree [this message]
2023-10-18 17:57   ` [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver James Ogletree
2023-10-20 15:30   ` kernel test robot
2023-10-25  3:03   ` Jeff LaBundy
2023-11-01 20:47     ` James Ogletree
2023-11-26  1:11       ` Jeff LaBundy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231018175726.3879955-5-james.ogletree@opensource.cirrus.com \
    --to=james.ogletree@opensource.cirrus.com \
    --cc=ben.bright@cirrus.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=fred.treven@cirrus.com \
    --cc=james.ogletree@cirrus.com \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=lee@kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.