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 2/4] Input: cs40l50 - Add cirrus haptics base support
Date: Wed, 18 Oct 2023 17:57:23 +0000	[thread overview]
Message-ID: <20231018175726.3879955-3-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 the cirrus haptics library which factors out
common haptics operations used by Cirrus Logic Input
drivers.

Signed-off-by: James Ogletree <james.ogletree@cirrus.com>
---
 MAINTAINERS                          |   2 +
 drivers/input/misc/cirrus_haptics.c  | 586 +++++++++++++++++++++++++++
 include/linux/input/cirrus_haptics.h | 121 ++++++
 3 files changed, 709 insertions(+)
 create mode 100644 drivers/input/misc/cirrus_haptics.c
 create mode 100644 include/linux/input/cirrus_haptics.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 28f0ca9324b3..57daf77bf550 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4970,6 +4970,8 @@ M:	Ben Bright <ben.bright@cirrus.com>
 L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F:	drivers/input/misc/cirrus*
+F:	include/linux/input/cirrus*
 
 CIRRUS LOGIC DSP FIRMWARE DRIVER
 M:	Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/drivers/input/misc/cirrus_haptics.c b/drivers/input/misc/cirrus_haptics.c
new file mode 100644
index 000000000000..7e539cd45167
--- /dev/null
+++ b/drivers/input/misc/cirrus_haptics.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Helper functions for dealing with wavetable
+ * formats and DSP interfaces used by Cirrus
+ * haptic drivers.
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ */
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/input.h>
+#include <linux/input/cirrus_haptics.h>
+#include <linux/pm_runtime.h>
+
+static int cs_hap_pseq_init(struct cs_hap *haptics)
+{
+	struct cs_hap_pseq_op *op;
+	int error, i, num_words;
+	u8 operation;
+	u32 *words;
+
+	if (!haptics->dsp.pseq_size || !haptics->dsp.pseq_reg)
+		return 0;
+
+	INIT_LIST_HEAD(&haptics->pseq_head);
+
+	words = kcalloc(haptics->dsp.pseq_size, sizeof(u32), GFP_KERNEL);
+	if (!words)
+		return -ENOMEM;
+
+	error = regmap_bulk_read(haptics->regmap, haptics->dsp.pseq_reg,
+				 words, haptics->dsp.pseq_size);
+	if (error)
+		goto err_free;
+
+	for (i = 0; i < haptics->dsp.pseq_size; i += num_words) {
+		operation = FIELD_GET(PSEQ_OP_MASK, words[i]);
+		switch (operation) {
+		case PSEQ_OP_END:
+		case PSEQ_OP_WRITE_UNLOCK:
+			num_words = PSEQ_OP_END_WORDS;
+			break;
+		case PSEQ_OP_WRITE_ADDR8:
+		case PSEQ_OP_WRITE_H16:
+		case PSEQ_OP_WRITE_L16:
+			num_words = PSEQ_OP_WRITE_X16_WORDS;
+			break;
+		case PSEQ_OP_WRITE_FULL:
+			num_words = PSEQ_OP_WRITE_FULL_WORDS;
+			break;
+		default:
+			error = -EINVAL;
+			dev_err(haptics->dev, "Unsupported op: %u\n", operation);
+			goto err_free;
+		}
+
+		op = devm_kzalloc(haptics->dev, sizeof(*op), GFP_KERNEL);
+		if (!op) {
+			error = -ENOMEM;
+			goto err_free;
+		}
+
+		op->size = num_words * sizeof(u32);
+		memcpy(op->words, &words[i], op->size);
+		op->offset = i * sizeof(u32);
+		op->operation = operation;
+		list_add(&op->list, &haptics->pseq_head);
+
+		if (operation == PSEQ_OP_END)
+			break;
+	}
+
+	if (operation != PSEQ_OP_END)
+		error = -ENOENT;
+
+err_free:
+	kfree(words);
+
+	return error;
+}
+
+static int cs_hap_pseq_find_end(struct cs_hap *haptics,
+				struct cs_hap_pseq_op **op_end)
+{
+	u8 operation = PSEQ_OP_WRITE_FULL;
+	struct cs_hap_pseq_op *op;
+
+	list_for_each_entry(op, &haptics->pseq_head, list) {
+		operation = op->operation;
+		if (operation == PSEQ_OP_END)
+			break;
+	}
+
+	if (operation != PSEQ_OP_END) {
+		dev_err(haptics->dev, "Missing PSEQ list terminator\n");
+		return -ENOENT;
+	}
+
+	*op_end = op;
+
+	return 0;
+}
+
+static struct cs_hap_pseq_op *cs_hap_pseq_find_op(struct cs_hap_pseq_op *match_op,
+						  struct list_head *pseq_head)
+{
+	struct cs_hap_pseq_op *op;
+
+	list_for_each_entry(op, pseq_head, list) {
+		if (op->operation == PSEQ_OP_END)
+			break;
+		if (op->operation != match_op->operation ||
+		    op->words[0] != match_op->words[0])
+			continue;
+		switch (op->operation) {
+		case PSEQ_OP_WRITE_FULL:
+			if (FIELD_GET(GENMASK(23, 8), op->words[1]) ==
+			    FIELD_GET(GENMASK(23, 8), match_op->words[1]))
+				return op;
+			break;
+		case PSEQ_OP_WRITE_H16:
+		case PSEQ_OP_WRITE_L16:
+			if (FIELD_GET(GENMASK(23, 16), op->words[1]) ==
+			    FIELD_GET(GENMASK(23, 16), match_op->words[1]))
+				return op;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return NULL;
+}
+
+int cs_hap_pseq_write(struct cs_hap *haptics, u32 addr,
+		      u32 data, bool update, u8 op_code)
+{
+	struct cs_hap_pseq_op *op, *op_end, *op_new;
+	struct cs_dsp_chunk ch;
+	u32 pseq_bytes;
+	int error;
+
+	op_new = devm_kzalloc(haptics->dev, sizeof(*op_new), GFP_KERNEL);
+	if (!op_new)
+		return -ENOMEM;
+
+	op_new->operation = op_code;
+
+	ch = cs_dsp_chunk((void *) op_new->words,
+			  PSEQ_OP_WRITE_FULL_WORDS * sizeof(u32));
+	cs_dsp_chunk_write(&ch, 8, op_code);
+	switch (op_code) {
+	case PSEQ_OP_WRITE_FULL:
+		cs_dsp_chunk_write(&ch, 32, addr);
+		cs_dsp_chunk_write(&ch, 32, data);
+		break;
+	case PSEQ_OP_WRITE_L16:
+	case PSEQ_OP_WRITE_H16:
+		cs_dsp_chunk_write(&ch, 24, addr);
+		cs_dsp_chunk_write(&ch, 16, data);
+		break;
+	default:
+		error = -EINVAL;
+		goto op_new_free;
+	}
+
+	op_new->size = cs_dsp_chunk_bytes(&ch);
+
+	if (update) {
+		op = cs_hap_pseq_find_op(op_new, &haptics->pseq_head);
+		if (!op) {
+			error = -EINVAL;
+			goto op_new_free;
+		}
+	}
+
+	error = cs_hap_pseq_find_end(haptics, &op_end);
+	if (error)
+		goto op_new_free;
+
+	pseq_bytes = haptics->dsp.pseq_size * sizeof(u32);
+
+	if (pseq_bytes - op_end->offset < op_new->size) {
+		error = -ENOMEM;
+		goto op_new_free;
+	}
+
+	if (update) {
+		op_new->offset = op->offset;
+	} else {
+		op_new->offset = op_end->offset;
+		op_end->offset += op_new->size;
+	}
+
+	error = regmap_raw_write(haptics->regmap, haptics->dsp.pseq_reg +
+				 op_new->offset, op_new->words, op_new->size);
+	if (error)
+		goto op_new_free;
+
+	if (update) {
+		list_replace(&op->list, &op_new->list);
+	} else {
+		error = regmap_raw_write(haptics->regmap, haptics->dsp.pseq_reg +
+					 op_end->offset, op_end->words,
+					 op_end->size);
+		if (error)
+			goto op_new_free;
+
+		list_add(&op_new->list, &haptics->pseq_head);
+	}
+
+	return 0;
+
+op_new_free:
+	devm_kfree(haptics->dev, op_new);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(cs_hap_pseq_write);
+
+int cs_hap_pseq_multi_write(struct cs_hap *haptics,
+			    const struct reg_sequence *reg_seq,
+			    int num_regs, bool update, u8 op_code)
+{
+	int error, i;
+
+	for (i = 0; i < num_regs; i++) {
+		error = cs_hap_pseq_write(haptics, reg_seq[i].reg,
+					  reg_seq[i].def, update, op_code);
+		if (error)
+			return error;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs_hap_pseq_multi_write);
+
+static struct cs_hap_effect *cs_hap_find_effect(int id,
+						struct list_head *effect_head)
+{
+	struct cs_hap_effect *effect;
+
+	list_for_each_entry(effect, effect_head, list)
+		if (effect->id == id)
+			return effect;
+
+	return NULL;
+}
+
+static int cs_hap_effect_bank_set(struct cs_hap *haptics,
+				  struct cs_hap_effect *effect,
+				  struct ff_periodic_effect add_effect)
+{
+	s16 bank = add_effect.custom_data[0] & 0xffffu;
+	unsigned int len = add_effect.custom_len;
+
+	if (bank >= WVFRM_BANK_NUM) {
+		dev_err(haptics->dev, "Invalid waveform bank: %d\n", bank);
+		return -EINVAL;
+	}
+
+	effect->bank = len > CUSTOM_DATA_SIZE ? WVFRM_BANK_OWT : bank;
+
+	return 0;
+}
+
+static int cs_hap_effect_mapping_set(struct cs_hap *haptics, u16 button,
+				     struct cs_hap_effect *effect)
+{
+	u32 gpio_num, gpio_edge;
+
+	if (button) {
+		gpio_num = FIELD_GET(BTN_NUM_MASK, button);
+		gpio_edge = FIELD_GET(BTN_EDGE_MASK, button);
+		effect->mapping = haptics->dsp.gpio_base_reg +
+				  (gpio_num * 8) - gpio_edge;
+
+		return regmap_write(haptics->regmap, effect->mapping, button);
+	}
+
+	effect->mapping = GPIO_MAPPING_INVALID;
+
+	return 0;
+}
+
+static int cs_hap_effect_index_set(struct cs_hap *haptics,
+				   struct cs_hap_effect *effect,
+				   struct ff_periodic_effect add_effect)
+{
+	struct cs_hap_effect *owt_effect;
+	u32 base_index, max_index;
+
+	base_index = haptics->banks[effect->bank].base_index;
+	max_index = haptics->banks[effect->bank].max_index;
+
+	effect->index = base_index;
+
+	switch (effect->bank) {
+	case WVFRM_BANK_OWT:
+		list_for_each_entry(owt_effect, &haptics->effect_head, list)
+			if (owt_effect->bank == WVFRM_BANK_OWT)
+				effect->index++;
+		break;
+	case WVFRM_BANK_ROM:
+	case WVFRM_BANK_RAM:
+		effect->index += add_effect.custom_data[1] & 0xffffu;
+		break;
+	default:
+		dev_err(haptics->dev, "Bank not supported: %d\n", effect->bank);
+		return -EINVAL;
+	}
+
+	if (effect->index > max_index || effect->index < base_index) {
+		dev_err(haptics->dev, "Index out of bounds: %u\n", effect->index);
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+static int cs_hap_upload_pwle(struct cs_hap *haptics,
+			      struct cs_hap_effect *effect,
+			      struct ff_periodic_effect add_effect)
+{
+	u32 len, wt_offset, wt_size_words;
+	struct cs_hap_pwle_header header;
+	u8 *out_data;
+	int error;
+
+	error = regmap_read(haptics->regmap, haptics->dsp.owt_offset_reg,
+			    &wt_offset);
+	if (error)
+		return error;
+
+	error = regmap_read(haptics->regmap, haptics->dsp.owt_size_reg,
+			    &wt_size_words);
+	if (error)
+		return error;
+
+	len = 2 * add_effect.custom_len;
+
+	if ((wt_size_words * sizeof(u32)) < OWT_HEADER_SIZE + len)
+		return -ENOSPC;
+
+	out_data = kzalloc(OWT_HEADER_SIZE + len, GFP_KERNEL);
+	if (!out_data)
+		return -ENOMEM;
+
+	header.type = add_effect.custom_data[0] == PCM_ID ? OWT_TYPE_PCM :
+							    OWT_TYPE_PWLE;
+	header.offset = OWT_HEADER_SIZE / sizeof(u32);
+	header.data_words = len / sizeof(u32);
+
+	memcpy(out_data, &header, sizeof(header));
+	memcpy(out_data + OWT_HEADER_SIZE, add_effect.custom_data, len);
+
+	error = regmap_bulk_write(haptics->regmap, haptics->dsp.owt_base_reg +
+				  (wt_offset * sizeof(u32)), out_data,
+				  OWT_HEADER_SIZE + len);
+	if (error)
+		goto err_free;
+
+	error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+			     haptics->dsp.push_owt_cmd);
+
+err_free:
+	kfree(out_data);
+
+	return error;
+}
+
+static void cs_hap_add_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      add_work);
+	struct ff_effect add_effect = haptics->add_effect;
+	bool is_new = false;
+	struct cs_hap_effect *effect;
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->add_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	effect = cs_hap_find_effect(add_effect.id, &haptics->effect_head);
+	if (!effect) {
+		effect = kzalloc(sizeof(*effect), GFP_KERNEL);
+		if (!effect) {
+			error = -ENOMEM;
+			goto err_mutex;
+		}
+		effect->id = add_effect.id;
+		is_new = true;
+	}
+
+	error = cs_hap_effect_bank_set(haptics, effect, add_effect.u.periodic);
+	if (error)
+		goto err_free;
+
+	error = cs_hap_effect_index_set(haptics, effect, add_effect.u.periodic);
+	if (error)
+		goto err_free;
+
+	error = cs_hap_effect_mapping_set(haptics, add_effect.trigger.button,
+					  effect);
+	if (error)
+		goto err_free;
+
+	if (effect->bank == WVFRM_BANK_OWT)
+		error = cs_hap_upload_pwle(haptics, effect,
+					   add_effect.u.periodic);
+
+err_free:
+	if (is_new) {
+		if (error)
+			kfree(effect);
+		else
+			list_add(&effect->list, &haptics->effect_head);
+	}
+
+err_mutex:
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->add_error = error;
+}
+
+static void cs_hap_erase_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      erase_work);
+	int error = 0;
+	struct cs_hap_effect *owt_effect, *erase_effect;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->erase_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	erase_effect = cs_hap_find_effect(haptics->erase_effect->id,
+					  &haptics->effect_head);
+	if (!erase_effect) {
+		dev_err(haptics->dev, "Effect to erase does not exist\n");
+		error = -EINVAL;
+		goto err_mutex;
+	}
+
+	if (erase_effect->mapping != GPIO_MAPPING_INVALID) {
+		error = regmap_write(haptics->regmap, erase_effect->mapping,
+				     GPIO_DISABLE);
+		if (error)
+			goto err_mutex;
+	}
+
+	if (erase_effect->bank == WVFRM_BANK_OWT) {
+		error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+				     haptics->dsp.delete_owt_cmd |
+				     erase_effect->index);
+		if (error)
+			goto err_mutex;
+
+		list_for_each_entry(owt_effect, &haptics->effect_head, list)
+			if (owt_effect->bank == WVFRM_BANK_OWT &&
+			    owt_effect->index > erase_effect->index)
+				owt_effect->index--;
+	}
+
+	list_del(&erase_effect->list);
+	kfree(erase_effect);
+
+err_mutex:
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->erase_error = error;
+}
+
+static void cs_hap_vibe_start_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      vibe_start_work);
+	struct cs_hap_effect *effect;
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->start_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	effect = cs_hap_find_effect(haptics->start_effect->id,
+				    &haptics->effect_head);
+	if (effect) {
+		error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+				     effect->index);
+	} else {
+		dev_err(haptics->dev, "Effect to start does not exist\n");
+		error = -EINVAL;
+	}
+
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->start_error = error;
+}
+
+static void cs_hap_vibe_stop_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      vibe_stop_work);
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->stop_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+	error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+			     haptics->dsp.stop_cmd);
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->stop_error = error;
+}
+
+int cs_hap_init(struct cs_hap *haptics)
+{
+	haptics->vibe_wq = alloc_ordered_workqueue("vibe_wq", 0);
+	if (!haptics->vibe_wq)
+		return -ENOMEM;
+
+	mutex_init(&haptics->lock);
+
+	INIT_WORK(&haptics->vibe_start_work, cs_hap_vibe_start_worker);
+	INIT_WORK(&haptics->vibe_stop_work, cs_hap_vibe_stop_worker);
+	INIT_WORK(&haptics->erase_work, cs_hap_erase_worker);
+	INIT_WORK(&haptics->add_work, cs_hap_add_worker);
+
+	return cs_hap_pseq_init(haptics);
+}
+EXPORT_SYMBOL_GPL(cs_hap_init);
+
+void cs_hap_remove(struct cs_hap *haptics)
+{
+	flush_workqueue(haptics->vibe_wq);
+	destroy_workqueue(haptics->vibe_wq);
+}
+EXPORT_SYMBOL_GPL(cs_hap_remove);
+
+MODULE_DESCRIPTION("Cirrus Logic Haptics Support");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/cirrus_haptics.h b/include/linux/input/cirrus_haptics.h
new file mode 100644
index 000000000000..42f6afed7944
--- /dev/null
+++ b/include/linux/input/cirrus_haptics.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Helper functions for dealing with wavetable
+ * formats and DSP interfaces used by Cirrus
+ * haptic drivers.
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ */
+
+#ifndef __CIRRUS_HAPTICS_H
+#define __CIRRUS_HAPTICS_H
+
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Power-on write sequencer */
+#define PSEQ_OP_MASK			GENMASK(23, 16)
+#define PSEQ_OP_SHIFT			16
+#define PSEQ_OP_WRITE_FULL_WORDS	3
+#define PSEQ_OP_WRITE_X16_WORDS		2
+#define PSEQ_OP_END_WORDS		1
+#define PSEQ_OP_WRITE_FULL		0x00
+#define PSEQ_OP_WRITE_ADDR8		0x02
+#define PSEQ_OP_WRITE_L16		0x04
+#define PSEQ_OP_WRITE_H16		0x05
+#define PSEQ_OP_WRITE_UNLOCK		0xFD
+#define PSEQ_OP_END			0xFF
+
+/* Open wavetable */
+#define OWT_HEADER_SIZE		12
+#define OWT_TYPE_PCM		8
+#define OWT_TYPE_PWLE		12
+#define PCM_ID			0x0
+#define CUSTOM_DATA_SIZE	2
+
+/* GPIO */
+#define BTN_NUM_MASK		GENMASK(14, 12)
+#define BTN_EDGE_MASK		BIT(15)
+#define GPIO_MAPPING_INVALID	0
+#define GPIO_DISABLE		0x1FF
+
+enum cs_hap_bank_type {
+	WVFRM_BANK_RAM,
+	WVFRM_BANK_ROM,
+	WVFRM_BANK_OWT,
+	WVFRM_BANK_NUM,
+};
+
+struct cs_hap_pseq_op {
+	struct list_head list;
+	u32 words[3];
+	u16 offset;
+	u8 operation;
+	u8 size;
+};
+
+struct cs_hap_effect {
+	enum cs_hap_bank_type bank;
+	struct list_head list;
+	u32 mapping;
+	u32 index;
+	int id;
+};
+
+struct cs_hap_pwle_header {
+	u32 type;
+	u32 data_words;
+	u32 offset;
+} __packed;
+
+struct cs_hap_bank {
+	enum cs_hap_bank_type bank;
+	u32 base_index;
+	u32 max_index;
+};
+
+struct cs_hap_dsp {
+	u32 gpio_base_reg;
+	u32 owt_offset_reg;
+	u32 owt_size_reg;
+	u32 owt_base_reg;
+	u32 mailbox_reg;
+	u32 pseq_reg;
+	u32 push_owt_cmd;
+	u32 delete_owt_cmd;
+	u32 stop_cmd;
+	u32 pseq_size;
+};
+
+struct cs_hap {
+	struct regmap *regmap;
+	struct mutex lock;
+	struct device *dev;
+	struct list_head pseq_head;
+	struct cs_hap_bank *banks;
+	struct cs_hap_dsp dsp;
+	struct workqueue_struct *vibe_wq;
+	struct work_struct vibe_start_work;
+	struct work_struct vibe_stop_work;
+	struct work_struct erase_work;
+	struct work_struct add_work;
+	struct ff_effect *start_effect;
+	struct ff_effect *erase_effect;
+	struct ff_effect add_effect;
+	struct list_head effect_head;
+	int erase_error;
+	int start_error;
+	int stop_error;
+	int add_error;
+	bool runtime_pm;
+};
+
+int cs_hap_pseq_write(struct cs_hap *haptics, u32 addr,
+		      u32 data, bool update, u8 op_code);
+int cs_hap_pseq_multi_write(struct cs_hap *haptics,
+			    const struct reg_sequence *reg_seq,
+			    int num_regs, bool update, u8 op_code);
+int cs_hap_init(struct cs_hap *haptics);
+void cs_hap_remove(struct cs_hap *haptics);
+
+#endif
-- 
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 2/4] Input: cs40l50 - Add cirrus haptics base support
Date: Wed, 18 Oct 2023 17:57:23 +0000	[thread overview]
Message-ID: <20231018175726.3879955-3-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 the cirrus haptics library which factors out
common haptics operations used by Cirrus Logic Input
drivers.

Signed-off-by: James Ogletree <james.ogletree@cirrus.com>
---
 MAINTAINERS                          |   2 +
 drivers/input/misc/cirrus_haptics.c  | 586 +++++++++++++++++++++++++++
 include/linux/input/cirrus_haptics.h | 121 ++++++
 3 files changed, 709 insertions(+)
 create mode 100644 drivers/input/misc/cirrus_haptics.c
 create mode 100644 include/linux/input/cirrus_haptics.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 28f0ca9324b3..57daf77bf550 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4970,6 +4970,8 @@ M:	Ben Bright <ben.bright@cirrus.com>
 L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F:	drivers/input/misc/cirrus*
+F:	include/linux/input/cirrus*
 
 CIRRUS LOGIC DSP FIRMWARE DRIVER
 M:	Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/drivers/input/misc/cirrus_haptics.c b/drivers/input/misc/cirrus_haptics.c
new file mode 100644
index 000000000000..7e539cd45167
--- /dev/null
+++ b/drivers/input/misc/cirrus_haptics.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Helper functions for dealing with wavetable
+ * formats and DSP interfaces used by Cirrus
+ * haptic drivers.
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ */
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/input.h>
+#include <linux/input/cirrus_haptics.h>
+#include <linux/pm_runtime.h>
+
+static int cs_hap_pseq_init(struct cs_hap *haptics)
+{
+	struct cs_hap_pseq_op *op;
+	int error, i, num_words;
+	u8 operation;
+	u32 *words;
+
+	if (!haptics->dsp.pseq_size || !haptics->dsp.pseq_reg)
+		return 0;
+
+	INIT_LIST_HEAD(&haptics->pseq_head);
+
+	words = kcalloc(haptics->dsp.pseq_size, sizeof(u32), GFP_KERNEL);
+	if (!words)
+		return -ENOMEM;
+
+	error = regmap_bulk_read(haptics->regmap, haptics->dsp.pseq_reg,
+				 words, haptics->dsp.pseq_size);
+	if (error)
+		goto err_free;
+
+	for (i = 0; i < haptics->dsp.pseq_size; i += num_words) {
+		operation = FIELD_GET(PSEQ_OP_MASK, words[i]);
+		switch (operation) {
+		case PSEQ_OP_END:
+		case PSEQ_OP_WRITE_UNLOCK:
+			num_words = PSEQ_OP_END_WORDS;
+			break;
+		case PSEQ_OP_WRITE_ADDR8:
+		case PSEQ_OP_WRITE_H16:
+		case PSEQ_OP_WRITE_L16:
+			num_words = PSEQ_OP_WRITE_X16_WORDS;
+			break;
+		case PSEQ_OP_WRITE_FULL:
+			num_words = PSEQ_OP_WRITE_FULL_WORDS;
+			break;
+		default:
+			error = -EINVAL;
+			dev_err(haptics->dev, "Unsupported op: %u\n", operation);
+			goto err_free;
+		}
+
+		op = devm_kzalloc(haptics->dev, sizeof(*op), GFP_KERNEL);
+		if (!op) {
+			error = -ENOMEM;
+			goto err_free;
+		}
+
+		op->size = num_words * sizeof(u32);
+		memcpy(op->words, &words[i], op->size);
+		op->offset = i * sizeof(u32);
+		op->operation = operation;
+		list_add(&op->list, &haptics->pseq_head);
+
+		if (operation == PSEQ_OP_END)
+			break;
+	}
+
+	if (operation != PSEQ_OP_END)
+		error = -ENOENT;
+
+err_free:
+	kfree(words);
+
+	return error;
+}
+
+static int cs_hap_pseq_find_end(struct cs_hap *haptics,
+				struct cs_hap_pseq_op **op_end)
+{
+	u8 operation = PSEQ_OP_WRITE_FULL;
+	struct cs_hap_pseq_op *op;
+
+	list_for_each_entry(op, &haptics->pseq_head, list) {
+		operation = op->operation;
+		if (operation == PSEQ_OP_END)
+			break;
+	}
+
+	if (operation != PSEQ_OP_END) {
+		dev_err(haptics->dev, "Missing PSEQ list terminator\n");
+		return -ENOENT;
+	}
+
+	*op_end = op;
+
+	return 0;
+}
+
+static struct cs_hap_pseq_op *cs_hap_pseq_find_op(struct cs_hap_pseq_op *match_op,
+						  struct list_head *pseq_head)
+{
+	struct cs_hap_pseq_op *op;
+
+	list_for_each_entry(op, pseq_head, list) {
+		if (op->operation == PSEQ_OP_END)
+			break;
+		if (op->operation != match_op->operation ||
+		    op->words[0] != match_op->words[0])
+			continue;
+		switch (op->operation) {
+		case PSEQ_OP_WRITE_FULL:
+			if (FIELD_GET(GENMASK(23, 8), op->words[1]) ==
+			    FIELD_GET(GENMASK(23, 8), match_op->words[1]))
+				return op;
+			break;
+		case PSEQ_OP_WRITE_H16:
+		case PSEQ_OP_WRITE_L16:
+			if (FIELD_GET(GENMASK(23, 16), op->words[1]) ==
+			    FIELD_GET(GENMASK(23, 16), match_op->words[1]))
+				return op;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return NULL;
+}
+
+int cs_hap_pseq_write(struct cs_hap *haptics, u32 addr,
+		      u32 data, bool update, u8 op_code)
+{
+	struct cs_hap_pseq_op *op, *op_end, *op_new;
+	struct cs_dsp_chunk ch;
+	u32 pseq_bytes;
+	int error;
+
+	op_new = devm_kzalloc(haptics->dev, sizeof(*op_new), GFP_KERNEL);
+	if (!op_new)
+		return -ENOMEM;
+
+	op_new->operation = op_code;
+
+	ch = cs_dsp_chunk((void *) op_new->words,
+			  PSEQ_OP_WRITE_FULL_WORDS * sizeof(u32));
+	cs_dsp_chunk_write(&ch, 8, op_code);
+	switch (op_code) {
+	case PSEQ_OP_WRITE_FULL:
+		cs_dsp_chunk_write(&ch, 32, addr);
+		cs_dsp_chunk_write(&ch, 32, data);
+		break;
+	case PSEQ_OP_WRITE_L16:
+	case PSEQ_OP_WRITE_H16:
+		cs_dsp_chunk_write(&ch, 24, addr);
+		cs_dsp_chunk_write(&ch, 16, data);
+		break;
+	default:
+		error = -EINVAL;
+		goto op_new_free;
+	}
+
+	op_new->size = cs_dsp_chunk_bytes(&ch);
+
+	if (update) {
+		op = cs_hap_pseq_find_op(op_new, &haptics->pseq_head);
+		if (!op) {
+			error = -EINVAL;
+			goto op_new_free;
+		}
+	}
+
+	error = cs_hap_pseq_find_end(haptics, &op_end);
+	if (error)
+		goto op_new_free;
+
+	pseq_bytes = haptics->dsp.pseq_size * sizeof(u32);
+
+	if (pseq_bytes - op_end->offset < op_new->size) {
+		error = -ENOMEM;
+		goto op_new_free;
+	}
+
+	if (update) {
+		op_new->offset = op->offset;
+	} else {
+		op_new->offset = op_end->offset;
+		op_end->offset += op_new->size;
+	}
+
+	error = regmap_raw_write(haptics->regmap, haptics->dsp.pseq_reg +
+				 op_new->offset, op_new->words, op_new->size);
+	if (error)
+		goto op_new_free;
+
+	if (update) {
+		list_replace(&op->list, &op_new->list);
+	} else {
+		error = regmap_raw_write(haptics->regmap, haptics->dsp.pseq_reg +
+					 op_end->offset, op_end->words,
+					 op_end->size);
+		if (error)
+			goto op_new_free;
+
+		list_add(&op_new->list, &haptics->pseq_head);
+	}
+
+	return 0;
+
+op_new_free:
+	devm_kfree(haptics->dev, op_new);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(cs_hap_pseq_write);
+
+int cs_hap_pseq_multi_write(struct cs_hap *haptics,
+			    const struct reg_sequence *reg_seq,
+			    int num_regs, bool update, u8 op_code)
+{
+	int error, i;
+
+	for (i = 0; i < num_regs; i++) {
+		error = cs_hap_pseq_write(haptics, reg_seq[i].reg,
+					  reg_seq[i].def, update, op_code);
+		if (error)
+			return error;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs_hap_pseq_multi_write);
+
+static struct cs_hap_effect *cs_hap_find_effect(int id,
+						struct list_head *effect_head)
+{
+	struct cs_hap_effect *effect;
+
+	list_for_each_entry(effect, effect_head, list)
+		if (effect->id == id)
+			return effect;
+
+	return NULL;
+}
+
+static int cs_hap_effect_bank_set(struct cs_hap *haptics,
+				  struct cs_hap_effect *effect,
+				  struct ff_periodic_effect add_effect)
+{
+	s16 bank = add_effect.custom_data[0] & 0xffffu;
+	unsigned int len = add_effect.custom_len;
+
+	if (bank >= WVFRM_BANK_NUM) {
+		dev_err(haptics->dev, "Invalid waveform bank: %d\n", bank);
+		return -EINVAL;
+	}
+
+	effect->bank = len > CUSTOM_DATA_SIZE ? WVFRM_BANK_OWT : bank;
+
+	return 0;
+}
+
+static int cs_hap_effect_mapping_set(struct cs_hap *haptics, u16 button,
+				     struct cs_hap_effect *effect)
+{
+	u32 gpio_num, gpio_edge;
+
+	if (button) {
+		gpio_num = FIELD_GET(BTN_NUM_MASK, button);
+		gpio_edge = FIELD_GET(BTN_EDGE_MASK, button);
+		effect->mapping = haptics->dsp.gpio_base_reg +
+				  (gpio_num * 8) - gpio_edge;
+
+		return regmap_write(haptics->regmap, effect->mapping, button);
+	}
+
+	effect->mapping = GPIO_MAPPING_INVALID;
+
+	return 0;
+}
+
+static int cs_hap_effect_index_set(struct cs_hap *haptics,
+				   struct cs_hap_effect *effect,
+				   struct ff_periodic_effect add_effect)
+{
+	struct cs_hap_effect *owt_effect;
+	u32 base_index, max_index;
+
+	base_index = haptics->banks[effect->bank].base_index;
+	max_index = haptics->banks[effect->bank].max_index;
+
+	effect->index = base_index;
+
+	switch (effect->bank) {
+	case WVFRM_BANK_OWT:
+		list_for_each_entry(owt_effect, &haptics->effect_head, list)
+			if (owt_effect->bank == WVFRM_BANK_OWT)
+				effect->index++;
+		break;
+	case WVFRM_BANK_ROM:
+	case WVFRM_BANK_RAM:
+		effect->index += add_effect.custom_data[1] & 0xffffu;
+		break;
+	default:
+		dev_err(haptics->dev, "Bank not supported: %d\n", effect->bank);
+		return -EINVAL;
+	}
+
+	if (effect->index > max_index || effect->index < base_index) {
+		dev_err(haptics->dev, "Index out of bounds: %u\n", effect->index);
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+static int cs_hap_upload_pwle(struct cs_hap *haptics,
+			      struct cs_hap_effect *effect,
+			      struct ff_periodic_effect add_effect)
+{
+	u32 len, wt_offset, wt_size_words;
+	struct cs_hap_pwle_header header;
+	u8 *out_data;
+	int error;
+
+	error = regmap_read(haptics->regmap, haptics->dsp.owt_offset_reg,
+			    &wt_offset);
+	if (error)
+		return error;
+
+	error = regmap_read(haptics->regmap, haptics->dsp.owt_size_reg,
+			    &wt_size_words);
+	if (error)
+		return error;
+
+	len = 2 * add_effect.custom_len;
+
+	if ((wt_size_words * sizeof(u32)) < OWT_HEADER_SIZE + len)
+		return -ENOSPC;
+
+	out_data = kzalloc(OWT_HEADER_SIZE + len, GFP_KERNEL);
+	if (!out_data)
+		return -ENOMEM;
+
+	header.type = add_effect.custom_data[0] == PCM_ID ? OWT_TYPE_PCM :
+							    OWT_TYPE_PWLE;
+	header.offset = OWT_HEADER_SIZE / sizeof(u32);
+	header.data_words = len / sizeof(u32);
+
+	memcpy(out_data, &header, sizeof(header));
+	memcpy(out_data + OWT_HEADER_SIZE, add_effect.custom_data, len);
+
+	error = regmap_bulk_write(haptics->regmap, haptics->dsp.owt_base_reg +
+				  (wt_offset * sizeof(u32)), out_data,
+				  OWT_HEADER_SIZE + len);
+	if (error)
+		goto err_free;
+
+	error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+			     haptics->dsp.push_owt_cmd);
+
+err_free:
+	kfree(out_data);
+
+	return error;
+}
+
+static void cs_hap_add_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      add_work);
+	struct ff_effect add_effect = haptics->add_effect;
+	bool is_new = false;
+	struct cs_hap_effect *effect;
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->add_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	effect = cs_hap_find_effect(add_effect.id, &haptics->effect_head);
+	if (!effect) {
+		effect = kzalloc(sizeof(*effect), GFP_KERNEL);
+		if (!effect) {
+			error = -ENOMEM;
+			goto err_mutex;
+		}
+		effect->id = add_effect.id;
+		is_new = true;
+	}
+
+	error = cs_hap_effect_bank_set(haptics, effect, add_effect.u.periodic);
+	if (error)
+		goto err_free;
+
+	error = cs_hap_effect_index_set(haptics, effect, add_effect.u.periodic);
+	if (error)
+		goto err_free;
+
+	error = cs_hap_effect_mapping_set(haptics, add_effect.trigger.button,
+					  effect);
+	if (error)
+		goto err_free;
+
+	if (effect->bank == WVFRM_BANK_OWT)
+		error = cs_hap_upload_pwle(haptics, effect,
+					   add_effect.u.periodic);
+
+err_free:
+	if (is_new) {
+		if (error)
+			kfree(effect);
+		else
+			list_add(&effect->list, &haptics->effect_head);
+	}
+
+err_mutex:
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->add_error = error;
+}
+
+static void cs_hap_erase_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      erase_work);
+	int error = 0;
+	struct cs_hap_effect *owt_effect, *erase_effect;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->erase_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	erase_effect = cs_hap_find_effect(haptics->erase_effect->id,
+					  &haptics->effect_head);
+	if (!erase_effect) {
+		dev_err(haptics->dev, "Effect to erase does not exist\n");
+		error = -EINVAL;
+		goto err_mutex;
+	}
+
+	if (erase_effect->mapping != GPIO_MAPPING_INVALID) {
+		error = regmap_write(haptics->regmap, erase_effect->mapping,
+				     GPIO_DISABLE);
+		if (error)
+			goto err_mutex;
+	}
+
+	if (erase_effect->bank == WVFRM_BANK_OWT) {
+		error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+				     haptics->dsp.delete_owt_cmd |
+				     erase_effect->index);
+		if (error)
+			goto err_mutex;
+
+		list_for_each_entry(owt_effect, &haptics->effect_head, list)
+			if (owt_effect->bank == WVFRM_BANK_OWT &&
+			    owt_effect->index > erase_effect->index)
+				owt_effect->index--;
+	}
+
+	list_del(&erase_effect->list);
+	kfree(erase_effect);
+
+err_mutex:
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->erase_error = error;
+}
+
+static void cs_hap_vibe_start_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      vibe_start_work);
+	struct cs_hap_effect *effect;
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->start_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+
+	effect = cs_hap_find_effect(haptics->start_effect->id,
+				    &haptics->effect_head);
+	if (effect) {
+		error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+				     effect->index);
+	} else {
+		dev_err(haptics->dev, "Effect to start does not exist\n");
+		error = -EINVAL;
+	}
+
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->start_error = error;
+}
+
+static void cs_hap_vibe_stop_worker(struct work_struct *work)
+{
+	struct cs_hap *haptics = container_of(work, struct cs_hap,
+					      vibe_stop_work);
+	int error;
+
+	if (haptics->runtime_pm) {
+		error = pm_runtime_resume_and_get(haptics->dev);
+		if (error < 0) {
+			haptics->stop_error = error;
+			return;
+		}
+	}
+
+	mutex_lock(&haptics->lock);
+	error = regmap_write(haptics->regmap, haptics->dsp.mailbox_reg,
+			     haptics->dsp.stop_cmd);
+	mutex_unlock(&haptics->lock);
+
+	if (haptics->runtime_pm) {
+		pm_runtime_mark_last_busy(haptics->dev);
+		pm_runtime_put_autosuspend(haptics->dev);
+	}
+
+	haptics->stop_error = error;
+}
+
+int cs_hap_init(struct cs_hap *haptics)
+{
+	haptics->vibe_wq = alloc_ordered_workqueue("vibe_wq", 0);
+	if (!haptics->vibe_wq)
+		return -ENOMEM;
+
+	mutex_init(&haptics->lock);
+
+	INIT_WORK(&haptics->vibe_start_work, cs_hap_vibe_start_worker);
+	INIT_WORK(&haptics->vibe_stop_work, cs_hap_vibe_stop_worker);
+	INIT_WORK(&haptics->erase_work, cs_hap_erase_worker);
+	INIT_WORK(&haptics->add_work, cs_hap_add_worker);
+
+	return cs_hap_pseq_init(haptics);
+}
+EXPORT_SYMBOL_GPL(cs_hap_init);
+
+void cs_hap_remove(struct cs_hap *haptics)
+{
+	flush_workqueue(haptics->vibe_wq);
+	destroy_workqueue(haptics->vibe_wq);
+}
+EXPORT_SYMBOL_GPL(cs_hap_remove);
+
+MODULE_DESCRIPTION("Cirrus Logic Haptics Support");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/cirrus_haptics.h b/include/linux/input/cirrus_haptics.h
new file mode 100644
index 000000000000..42f6afed7944
--- /dev/null
+++ b/include/linux/input/cirrus_haptics.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Helper functions for dealing with wavetable
+ * formats and DSP interfaces used by Cirrus
+ * haptic drivers.
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ */
+
+#ifndef __CIRRUS_HAPTICS_H
+#define __CIRRUS_HAPTICS_H
+
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Power-on write sequencer */
+#define PSEQ_OP_MASK			GENMASK(23, 16)
+#define PSEQ_OP_SHIFT			16
+#define PSEQ_OP_WRITE_FULL_WORDS	3
+#define PSEQ_OP_WRITE_X16_WORDS		2
+#define PSEQ_OP_END_WORDS		1
+#define PSEQ_OP_WRITE_FULL		0x00
+#define PSEQ_OP_WRITE_ADDR8		0x02
+#define PSEQ_OP_WRITE_L16		0x04
+#define PSEQ_OP_WRITE_H16		0x05
+#define PSEQ_OP_WRITE_UNLOCK		0xFD
+#define PSEQ_OP_END			0xFF
+
+/* Open wavetable */
+#define OWT_HEADER_SIZE		12
+#define OWT_TYPE_PCM		8
+#define OWT_TYPE_PWLE		12
+#define PCM_ID			0x0
+#define CUSTOM_DATA_SIZE	2
+
+/* GPIO */
+#define BTN_NUM_MASK		GENMASK(14, 12)
+#define BTN_EDGE_MASK		BIT(15)
+#define GPIO_MAPPING_INVALID	0
+#define GPIO_DISABLE		0x1FF
+
+enum cs_hap_bank_type {
+	WVFRM_BANK_RAM,
+	WVFRM_BANK_ROM,
+	WVFRM_BANK_OWT,
+	WVFRM_BANK_NUM,
+};
+
+struct cs_hap_pseq_op {
+	struct list_head list;
+	u32 words[3];
+	u16 offset;
+	u8 operation;
+	u8 size;
+};
+
+struct cs_hap_effect {
+	enum cs_hap_bank_type bank;
+	struct list_head list;
+	u32 mapping;
+	u32 index;
+	int id;
+};
+
+struct cs_hap_pwle_header {
+	u32 type;
+	u32 data_words;
+	u32 offset;
+} __packed;
+
+struct cs_hap_bank {
+	enum cs_hap_bank_type bank;
+	u32 base_index;
+	u32 max_index;
+};
+
+struct cs_hap_dsp {
+	u32 gpio_base_reg;
+	u32 owt_offset_reg;
+	u32 owt_size_reg;
+	u32 owt_base_reg;
+	u32 mailbox_reg;
+	u32 pseq_reg;
+	u32 push_owt_cmd;
+	u32 delete_owt_cmd;
+	u32 stop_cmd;
+	u32 pseq_size;
+};
+
+struct cs_hap {
+	struct regmap *regmap;
+	struct mutex lock;
+	struct device *dev;
+	struct list_head pseq_head;
+	struct cs_hap_bank *banks;
+	struct cs_hap_dsp dsp;
+	struct workqueue_struct *vibe_wq;
+	struct work_struct vibe_start_work;
+	struct work_struct vibe_stop_work;
+	struct work_struct erase_work;
+	struct work_struct add_work;
+	struct ff_effect *start_effect;
+	struct ff_effect *erase_effect;
+	struct ff_effect add_effect;
+	struct list_head effect_head;
+	int erase_error;
+	int start_error;
+	int stop_error;
+	int add_error;
+	bool runtime_pm;
+};
+
+int cs_hap_pseq_write(struct cs_hap *haptics, u32 addr,
+		      u32 data, bool update, u8 op_code);
+int cs_hap_pseq_multi_write(struct cs_hap *haptics,
+			    const struct reg_sequence *reg_seq,
+			    int num_regs, bool update, u8 op_code);
+int cs_hap_init(struct cs_hap *haptics);
+void cs_hap_remove(struct cs_hap *haptics);
+
+#endif
-- 
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 ` James Ogletree [this message]
2023-10-18 17:57   ` [PATCH v4 2/4] Input: cs40l50 - Add cirrus haptics base support 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 ` [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver James Ogletree
2023-10-18 17:57   ` 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-3-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.