From patchwork Wed Oct 18 17:57:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Ogletree X-Patchwork-Id: 736009 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8A940CDB47E for ; Wed, 18 Oct 2023 17:58:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232554AbjJRR6Q (ORCPT ); Wed, 18 Oct 2023 13:58:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42700 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232504AbjJRR6A (ORCPT ); Wed, 18 Oct 2023 13:58:00 -0400 Received: from mx0b-001ae601.pphosted.com (mx0a-001ae601.pphosted.com [67.231.149.25]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1726F1A5; Wed, 18 Oct 2023 10:57:55 -0700 (PDT) Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 39I5YN73000966; Wed, 18 Oct 2023 12:57:45 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding:content-type; s= PODMain02222019; bh=H+80OU+pcRCGUJGQ7e1Oq+LdlTP+ByWSg2j/cCkDTOA=; b= em8qZT+/0ZN01Ykkg4/uAvcYopIy68gtIlo2kwn2BiMXERRqmCPJrovKK66oJ4cV Lv4s4YVx/Vz8m57pG7ppFoH3N5HbQB08uDp5H+zqGO/wG/rP0hM75IB6iZ6HEqsw pizkxzyVd04mWeeRr8vYb2sk8rgstq9zvtrVsb3qLEovveUPWBrwYhM7Sf9SU0Qe e6kt+7OUkQxoeoi9QrHQAvG7crPS33qFKRUYmHsSaarfMapVxoqV4o8OcbWWX7Ae NuDN43SU9ArDlrjlV7XlV7lvxqTPNKYEMNn448kJptwaY26JHfbsLMq/Y2VYfZKk +PdP5HeLCvZdrAW3IEQlbA== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 3tqrcw6n6s-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 18 Oct 2023 12:57:45 -0500 (CDT) Received: from ediex01.ad.cirrus.com (198.61.84.80) by ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.37; Wed, 18 Oct 2023 18:57:42 +0100 Received: from ediswmail.ad.cirrus.com (198.61.86.93) by ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1118.37 via Frontend Transport; Wed, 18 Oct 2023 18:57:42 +0100 Received: from aus-sw-rshr002.ad.cirrus.com (aus-sw-rshr002.ad.cirrus.com [141.131.145.53]) by ediswmail.ad.cirrus.com (Postfix) with ESMTP id 6C23E458; Wed, 18 Oct 2023 17:57:41 +0000 (UTC) From: James Ogletree CC: James Ogletree , Dmitry Torokhov , Rob Herring , "Krzysztof Kozlowski" , Lee Jones , Fred Treven , Ben Bright , , , Subject: [PATCH v4 1/4] dt-bindings: input: cirrus,cs40l50: Add initial DT binding Date: Wed, 18 Oct 2023 17:57:22 +0000 Message-ID: <20231018175726.3879955-2-james.ogletree@opensource.cirrus.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> References: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> MIME-Version: 1.0 X-Proofpoint-GUID: sU0DX1RCCJ29jjm70lmFSYZjVuF3i3t5 X-Proofpoint-ORIG-GUID: sU0DX1RCCJ29jjm70lmFSYZjVuF3i3t5 X-Proofpoint-Spam-Reason: safe To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org From: James Ogletree The CS40L50 is a haptic driver with waveform memory, integrated DSP, and closed-loop algorithms. Add a YAML DT binding document for this device. Signed-off-by: James Ogletree --- .../bindings/input/cirrus,cs40l50.yaml | 70 +++++++++++++++++++ MAINTAINERS | 8 +++ 2 files changed, 78 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml diff --git a/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml new file mode 100644 index 000000000000..6a5bdafed56b --- /dev/null +++ b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS40L50 Advanced Haptic Driver + +maintainers: + - James Ogletree + +description: + CS40L50 is a haptic driver with waveform memory, + integrated DSP, and closed-loop algorithms. + +properties: + compatible: + enum: + - cirrus,cs40l50 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + va-supply: + description: Power supply for internal analog circuits. + + vp-supply: + description: Power supply for always-on circuits. + + vio-supply: + description: Power supply for digital input/output. + + vamp-supply: + description: Power supply for the Class D amplifier. + +required: + - compatible + - reg + - interrupts + - reset-gpios + - vp-supply + - vio-supply + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + haptic-driver@34 { + compatible = "cirrus,cs40l50"; + reg = <0x34>; + interrupt-parent = <&gpio>; + interrupts = <113 IRQ_TYPE_LEVEL_LOW>; + reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>; + vp-supply = <&vreg>; + vio-supply = <&vreg>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 046ff06ff97f..28f0ca9324b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4963,6 +4963,14 @@ F: sound/pci/hda/cs* F: sound/pci/hda/hda_cs_dsp_ctl.* F: sound/soc/codecs/cs* +CIRRUS LOGIC HAPTIC DRIVERS +M: James Ogletree +M: Fred Treven +M: Ben Bright +L: patches@opensource.cirrus.com +S: Supported +F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml + CIRRUS LOGIC DSP FIRMWARE DRIVER M: Simon Trimmer M: Charles Keepax From patchwork Wed Oct 18 17:57:23 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Ogletree X-Patchwork-Id: 736008 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3EC3FC41513 for ; Wed, 18 Oct 2023 17:58:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232672AbjJRR6V (ORCPT ); Wed, 18 Oct 2023 13:58:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56874 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232634AbjJRR6F (ORCPT ); Wed, 18 Oct 2023 13:58:05 -0400 Received: from mx0b-001ae601.pphosted.com (mx0b-001ae601.pphosted.com [67.231.152.168]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 73A4FD51; Wed, 18 Oct 2023 10:58:00 -0700 (PDT) Received: from pps.filterd (m0077474.ppops.net [127.0.0.1]) by mx0b-001ae601.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 39IH92q6014424; Wed, 18 Oct 2023 12:57:48 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding:content-type; s= PODMain02222019; bh=B7VvDX2saUSmkbGwMjzHI+I1JoiQDqH/DET9FBAsyGQ=; b= fIkTbplQE5X3htpzONsCERfP/bUfH58Y4W0EyIP2BAXHCGhOOdgBL1AU69Iohh1/ I4cCepuNGpYn3JyeN7cL7qtY06cxpUWZ/7pvyny46F7rEFIaHUrOvWzLIzyO7xVf LDWfofSPRp3GML5LUgLAn4zyZrp+I4RN6kw2vcmJw2C/XTuqhPEltnYlrXjBwWFL c9KqkcRkCiyETc13m89ejlJS0tlpe+dNGqG/fKZueSKFA2uONnRw3EO/yd5HWStU s2g1AfF7d33SrhOjxGbHlI8/d+VI+ib/3sif0PM11WDL6OUH1qKtxEGS6DApoN99 ajtVVyW8vwHMKRyH6ei6MA== Received: from ediex01.ad.cirrus.com ([84.19.233.68]) by mx0b-001ae601.pphosted.com (PPS) with ESMTPS id 3tqqdh50th-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 18 Oct 2023 12:57:47 -0500 (CDT) Received: from ediex01.ad.cirrus.com (198.61.84.80) by ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.37; Wed, 18 Oct 2023 18:57:46 +0100 Received: from ediswmail.ad.cirrus.com (198.61.86.93) by ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1118.37 via Frontend Transport; Wed, 18 Oct 2023 18:57:46 +0100 Received: from aus-sw-rshr002.ad.cirrus.com (aus-sw-rshr002.ad.cirrus.com [141.131.145.53]) by ediswmail.ad.cirrus.com (Postfix) with ESMTP id AF3CB458; Wed, 18 Oct 2023 17:57:44 +0000 (UTC) From: James Ogletree CC: James Ogletree , Dmitry Torokhov , Rob Herring , "Krzysztof Kozlowski" , Lee Jones , Fred Treven , Ben Bright , , , Subject: [PATCH v4 2/4] Input: cs40l50 - Add cirrus haptics base support Date: Wed, 18 Oct 2023 17:57:23 +0000 Message-ID: <20231018175726.3879955-3-james.ogletree@opensource.cirrus.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> References: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> MIME-Version: 1.0 X-Proofpoint-GUID: g3yf0VngreZ_qEeF9lKrm7e6n_-4MuMi X-Proofpoint-ORIG-GUID: g3yf0VngreZ_qEeF9lKrm7e6n_-4MuMi X-Proofpoint-Spam-Reason: safe To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org From: James Ogletree Introduce the cirrus haptics library which factors out common haptics operations used by Cirrus Logic Input drivers. Signed-off-by: James Ogletree --- 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 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 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 +#include +#include +#include + +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 +#include + +/* 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 From patchwork Wed Oct 18 17:57:24 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Ogletree X-Patchwork-Id: 735337 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9E4ECCDB485 for ; Wed, 18 Oct 2023 17:58:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235063AbjJRR6Y (ORCPT ); Wed, 18 Oct 2023 13:58:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57036 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235133AbjJRR6I (ORCPT ); Wed, 18 Oct 2023 13:58:08 -0400 Received: from mx0b-001ae601.pphosted.com (mx0b-001ae601.pphosted.com [67.231.152.168]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2EC75193; Wed, 18 Oct 2023 10:58:03 -0700 (PDT) Received: from pps.filterd (m0077474.ppops.net [127.0.0.1]) by mx0b-001ae601.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 39IH9Anl014455; Wed, 18 Oct 2023 12:57:51 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding:content-type; s= PODMain02222019; bh=ENZnc8a6fyr5gZfSM6sz7BmGVu3ISk6orlXlbf6HEEs=; b= XSZa7++Yj/j/aIXVzXp1iThfaCgko1oZe3u16tQrYJCtnMh9j9vh6drY6u6M8Pu5 8GJm0MSGVAao/NRlSI7rKz7neTxrYen8oleDS8wM302mq3ALRWsngtiVXjllmAvD +PBIIPWNaMpVzDtEVbxkBYgFtX6QlOU8C1EmvlTB82AXEe/EK7BmYJwSP9HpbmIV 4EdGxAhYqXEdEFo/trQeRwToOEWqpf5yHM64+CtQgwEXPnXrpkLfmcOvhRdqeb0S TWqvgDZ8YSaoXuTirai1TIBY47K3dGLLC5Dedx/8SySfbtc7T3woYLTgU1ex3fT/ uJMERKVaHEAsMh0f63quBw== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0b-001ae601.pphosted.com (PPS) with ESMTPS id 3tqqdh50tn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 18 Oct 2023 12:57:51 -0500 (CDT) Received: from ediex02.ad.cirrus.com (198.61.84.81) by ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.37; Wed, 18 Oct 2023 18:57:49 +0100 Received: from ediswmail.ad.cirrus.com (198.61.86.93) by anon-ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server id 15.2.1118.37 via Frontend Transport; Wed, 18 Oct 2023 18:57:49 +0100 Received: from aus-sw-rshr002.ad.cirrus.com (aus-sw-rshr002.ad.cirrus.com [141.131.145.53]) by ediswmail.ad.cirrus.com (Postfix) with ESMTP id 024EA458; Wed, 18 Oct 2023 17:57:47 +0000 (UTC) From: James Ogletree CC: James Ogletree , Dmitry Torokhov , Rob Herring , "Krzysztof Kozlowski" , Lee Jones , Fred Treven , Ben Bright , , , Subject: [PATCH v4 3/4] mfd: cs40l50: Add support for CS40L50 core driver Date: Wed, 18 Oct 2023 17:57:24 +0000 Message-ID: <20231018175726.3879955-4-james.ogletree@opensource.cirrus.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> References: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> MIME-Version: 1.0 X-Proofpoint-GUID: kbUOxU6E4LFaJOa1Z_Xgs-_HyX8sZ39M X-Proofpoint-ORIG-GUID: kbUOxU6E4LFaJOa1Z_Xgs-_HyX8sZ39M X-Proofpoint-Spam-Reason: safe To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org From: James Ogletree Introduce support for Cirrus Logic Device CS40L50: a haptic driver with waveform memory, integrated DSP, and closed-loop algorithms. The MFD component registers and initializes the device. Signed-off-by: James Ogletree --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 30 +++ drivers/mfd/Makefile | 4 + drivers/mfd/cs40l50-core.c | 443 ++++++++++++++++++++++++++++++++++++ drivers/mfd/cs40l50-i2c.c | 69 ++++++ drivers/mfd/cs40l50-spi.c | 68 ++++++ include/linux/mfd/cs40l50.h | 198 ++++++++++++++++ 7 files changed, 814 insertions(+) create mode 100644 drivers/mfd/cs40l50-core.c create mode 100644 drivers/mfd/cs40l50-i2c.c create mode 100644 drivers/mfd/cs40l50-spi.c create mode 100644 include/linux/mfd/cs40l50.h diff --git a/MAINTAINERS b/MAINTAINERS index 57daf77bf550..08e1e9695d49 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4971,7 +4971,9 @@ L: patches@opensource.cirrus.com S: Supported F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml F: drivers/input/misc/cirrus* +F: drivers/mfd/cs40l* F: include/linux/input/cirrus* +F: include/linux/mfd/cs40l* CIRRUS LOGIC DSP FIRMWARE DRIVER M: Simon Trimmer diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8b93856de432..a133d04a7859 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2187,6 +2187,36 @@ config MCP_UCB1200_TS endmenu +config MFD_CS40L50_CORE + tristate + select MFD_CORE + select CS_DSP + select REGMAP_IRQ + +config MFD_CS40L50_I2C + tristate "Cirrus Logic CS40L50 (I2C)" + select REGMAP_I2C + select MFD_CS40L50_CORE + depends on I2C + help + Select this to support the Cirrus Logic CS40L50 Haptic + Driver over I2C. + + This driver can be built as a module. If built as a module it will be + called "cs40l50-i2c". + +config MFD_CS40L50_SPI + tristate "Cirrus Logic CS40L50 (SPI)" + select REGMAP_SPI + select MFD_CS40L50_CORE + depends on SPI + help + Select this to support the Cirrus Logic CS40L50 Haptic + Driver over SPI. + + This driver can be built as a module. If built as a module it will be + called "cs40l50-spi". + config MFD_VEXPRESS_SYSREG tristate "Versatile Express System Registers" depends on VEXPRESS_CONFIG && GPIOLIB diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 7ed3ef4a698c..3b1a43b3acaf 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -95,6 +95,10 @@ obj-$(CONFIG_MFD_MADERA) += madera.o obj-$(CONFIG_MFD_MADERA_I2C) += madera-i2c.o obj-$(CONFIG_MFD_MADERA_SPI) += madera-spi.o +obj-$(CONFIG_MFD_CS40L50_CORE) += cs40l50-core.o +obj-$(CONFIG_MFD_CS40L50_I2C) += cs40l50-i2c.o +obj-$(CONFIG_MFD_CS40L50_SPI) += cs40l50-spi.o + obj-$(CONFIG_TPS6105X) += tps6105x.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_TPS6507X) += tps6507x.o diff --git a/drivers/mfd/cs40l50-core.c b/drivers/mfd/cs40l50-core.c new file mode 100644 index 000000000000..f1eadd80203a --- /dev/null +++ b/drivers/mfd/cs40l50-core.c @@ -0,0 +1,443 @@ +// 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 +#include +#include +#include +#include + +static const struct mfd_cell cs40l50_devs[] = { + { + .name = "cs40l50-vibra", + }, +}; + +const struct regmap_config cs40l50_regmap = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; +EXPORT_SYMBOL_GPL(cs40l50_regmap); + +static struct regulator_bulk_data cs40l50_supplies[] = { + { + .supply = "vp", + }, + { + .supply = "vio", + }, +}; + +static int cs40l50_handle_f0_est_done(struct cs40l50_private *cs40l50) +{ + u32 f_zero; + int error; + + error = regmap_read(cs40l50->regmap, CS40L50_F0_ESTIMATION, &f_zero); + if (error) + return error; + + return regmap_write(cs40l50->regmap, CS40L50_F0_STORED, f_zero); +} + +static int cs40l50_handle_redc_est_done(struct cs40l50_private *cs40l50) +{ + int error, fractional, integer, stored; + u32 redc; + + error = regmap_read(cs40l50->regmap, CS40L50_RE_EST_STATUS, &redc); + if (error) + return error; + + error = regmap_write(cs40l50->regmap, CS40L50_REDC_ESTIMATION, redc); + if (error) + return error; + + /* Convert from Q8.15 to (Q7.17 * 29/240) */ + integer = ((redc >> 15) & 0xFF) << 17; + fractional = (redc & 0x7FFF) * 4; + stored = (integer | fractional) * 29 / 240; + + return regmap_write(cs40l50->regmap, CS40L50_REDC_STORED, stored); +} + +static int cs40l50_error_release(struct cs40l50_private *cs40l50) +{ + int error; + + error = regmap_write(cs40l50->regmap, CS40L50_ERR_RLS, + CS40L50_GLOBAL_ERR_RLS); + if (error) + return error; + + return regmap_write(cs40l50->regmap, CS40L50_ERR_RLS, 0); +} + +static int cs40l50_mailbox_read_next(struct cs40l50_private *cs40l50, u32 *val) +{ + u32 rd_ptr, wt_ptr; + int error; + + error = regmap_read(cs40l50->regmap, CS40L50_MBOX_QUEUE_WT, &wt_ptr); + if (error) + return error; + + error = regmap_read(cs40l50->regmap, CS40L50_MBOX_QUEUE_RD, &rd_ptr); + if (error) + return error; + + if (wt_ptr == rd_ptr) { + *val = 0; + return 0; + } + + error = regmap_read(cs40l50->regmap, rd_ptr, val); + if (error) + return error; + + rd_ptr += sizeof(u32); + if (rd_ptr > CS40L50_MBOX_QUEUE_END) + rd_ptr = CS40L50_MBOX_QUEUE_BASE; + + return regmap_write(cs40l50->regmap, CS40L50_MBOX_QUEUE_RD, rd_ptr); +} + +static irqreturn_t cs40l50_process_mbox(int irq, void *data) +{ + struct cs40l50_private *cs40l50 = data; + int error = 0; + u32 val; + + mutex_lock(&cs40l50->lock); + + while (!cs40l50_mailbox_read_next(cs40l50, &val)) { + switch (val) { + case 0: + mutex_unlock(&cs40l50->lock); + dev_dbg(cs40l50->dev, "Reached end of queue\n"); + return IRQ_HANDLED; + case CS40L50_MBOX_HAPTIC_TRIGGER_GPIO: + dev_dbg(cs40l50->dev, "Mailbox: TRIGGER_GPIO\n"); + break; + case CS40L50_MBOX_HAPTIC_TRIGGER_MBOX: + dev_dbg(cs40l50->dev, "Mailbox: TRIGGER_MBOX\n"); + break; + case CS40L50_MBOX_HAPTIC_TRIGGER_I2S: + dev_dbg(cs40l50->dev, "Mailbox: TRIGGER_I2S\n"); + break; + case CS40L50_MBOX_HAPTIC_COMPLETE_MBOX: + dev_dbg(cs40l50->dev, "Mailbox: COMPLETE_MBOX\n"); + break; + case CS40L50_MBOX_HAPTIC_COMPLETE_GPIO: + dev_dbg(cs40l50->dev, "Mailbox: COMPLETE_GPIO\n"); + break; + case CS40L50_MBOX_HAPTIC_COMPLETE_I2S: + dev_dbg(cs40l50->dev, "Mailbox: COMPLETE_I2S\n"); + break; + case CS40L50_MBOX_F0_EST_START: + dev_dbg(cs40l50->dev, "Mailbox: F0_EST_START\n"); + break; + case CS40L50_MBOX_F0_EST_DONE: + dev_dbg(cs40l50->dev, "Mailbox: F0_EST_DONE\n"); + error = cs40l50_handle_f0_est_done(cs40l50); + if (error) + goto out_mutex; + break; + case CS40L50_MBOX_REDC_EST_START: + dev_dbg(cs40l50->dev, "Mailbox: REDC_EST_START\n"); + break; + case CS40L50_MBOX_REDC_EST_DONE: + dev_dbg(cs40l50->dev, "Mailbox: REDC_EST_DONE\n"); + error = cs40l50_handle_redc_est_done(cs40l50); + if (error) + goto out_mutex; + break; + case CS40L50_MBOX_LE_EST_START: + dev_dbg(cs40l50->dev, "Mailbox: LE_EST_START\n"); + break; + case CS40L50_MBOX_LE_EST_DONE: + dev_dbg(cs40l50->dev, "Mailbox: LE_EST_DONE\n"); + break; + case CS40L50_MBOX_AWAKE: + dev_dbg(cs40l50->dev, "Mailbox: AWAKE\n"); + break; + case CS40L50_MBOX_INIT: + dev_dbg(cs40l50->dev, "Mailbox: INIT\n"); + break; + case CS40L50_MBOX_ACK: + dev_dbg(cs40l50->dev, "Mailbox: ACK\n"); + break; + case CS40L50_MBOX_ERR_EVENT_UNMAPPED: + dev_err(cs40l50->dev, "Unmapped event\n"); + break; + case CS40L50_MBOX_ERR_EVENT_MODIFY: + dev_err(cs40l50->dev, "Failed to modify event index\n"); + break; + case CS40L50_MBOX_ERR_NULLPTR: + dev_err(cs40l50->dev, "Null pointer\n"); + break; + case CS40L50_MBOX_ERR_BRAKING: + dev_err(cs40l50->dev, "Braking not in progress\n"); + break; + case CS40L50_MBOX_ERR_INVAL_SRC: + dev_err(cs40l50->dev, "Suspend/resume invalid source\n"); + break; + case CS40L50_MBOX_ERR_ENABLE_RANGE: + dev_err(cs40l50->dev, "GPIO enable out of range\n"); + break; + case CS40L50_MBOX_ERR_GPIO_UNMAPPED: + dev_err(cs40l50->dev, "GPIO not mapped\n"); + break; + case CS40L50_MBOX_ERR_ISR_RANGE: + dev_err(cs40l50->dev, "GPIO ISR out of range\n"); + break; + case CS40L50_MBOX_PERMANENT_SHORT: + dev_crit(cs40l50->dev, "Permanent short detected\n"); + break; + case CS40L50_MBOX_RUNTIME_SHORT: + dev_err(cs40l50->dev, "Runtime short detected\n"); + error = cs40l50_error_release(cs40l50); + if (error) + goto out_mutex; + break; + default: + dev_err(cs40l50->dev, "Payload %#X not recognized\n", val); + error = -EINVAL; + goto out_mutex; + } + } + + error = -EIO; + +out_mutex: + mutex_unlock(&cs40l50->lock); + + return IRQ_RETVAL(!error); +} + +static irqreturn_t cs40l50_error(int irq, void *data); + +static const struct cs40l50_irq cs40l50_irqs[] = { + CS40L50_IRQ(AMP_SHORT, "Amp short", error), + CS40L50_IRQ(VIRT2_MBOX, "Mailbox", process_mbox), + CS40L50_IRQ(TEMP_ERR, "Overtemperature", error), + CS40L50_IRQ(BST_UVP, "Boost undervoltage", error), + CS40L50_IRQ(BST_SHORT, "Boost short", error), + CS40L50_IRQ(BST_ILIMIT, "Boost current limit", error), + CS40L50_IRQ(UVLO_VDDBATT, "Boost UVLO", error), + CS40L50_IRQ(GLOBAL_ERROR, "Global", error), +}; + +static irqreturn_t cs40l50_error(int irq, void *data) +{ + struct cs40l50_private *cs40l50 = data; + + dev_err(cs40l50->dev, "%s error\n", cs40l50_irqs[irq].name); + + return IRQ_RETVAL(!cs40l50_error_release(cs40l50)); +} + +static const struct regmap_irq cs40l50_reg_irqs[] = { + CS40L50_REG_IRQ(IRQ1_INT_1, AMP_SHORT), + CS40L50_REG_IRQ(IRQ1_INT_2, VIRT2_MBOX), + CS40L50_REG_IRQ(IRQ1_INT_8, TEMP_ERR), + CS40L50_REG_IRQ(IRQ1_INT_9, BST_UVP), + CS40L50_REG_IRQ(IRQ1_INT_9, BST_SHORT), + CS40L50_REG_IRQ(IRQ1_INT_9, BST_ILIMIT), + CS40L50_REG_IRQ(IRQ1_INT_10, UVLO_VDDBATT), + CS40L50_REG_IRQ(IRQ1_INT_18, GLOBAL_ERROR), +}; + +static struct regmap_irq_chip cs40l50_irq_chip = { + .name = "CS40L50 IRQ Controller", + + .status_base = CS40L50_IRQ1_INT_1, + .mask_base = CS40L50_IRQ1_MASK_1, + .ack_base = CS40L50_IRQ1_INT_1, + .num_regs = 22, + + .irqs = cs40l50_reg_irqs, + .num_irqs = ARRAY_SIZE(cs40l50_reg_irqs), + + .runtime_pm = true, +}; + +static int cs40l50_irq_init(struct cs40l50_private *cs40l50) +{ + struct device *dev = cs40l50->dev; + int error, i, irq; + + error = devm_regmap_add_irq_chip(dev, cs40l50->regmap, cs40l50->irq, + IRQF_ONESHOT | IRQF_SHARED, 0, + &cs40l50_irq_chip, &cs40l50->irq_data); + if (error) + return error; + + for (i = 0; i < ARRAY_SIZE(cs40l50_irqs); i++) { + irq = regmap_irq_get_virq(cs40l50->irq_data, cs40l50_irqs[i].irq); + if (irq < 0) { + dev_err(dev, "Failed getting %s\n", cs40l50_irqs[i].name); + return irq; + } + + error = devm_request_threaded_irq(dev, irq, NULL, + cs40l50_irqs[i].handler, + IRQF_ONESHOT | IRQF_SHARED, + cs40l50_irqs[i].name, cs40l50); + if (error) { + dev_err(dev, "Failed requesting %s\n", cs40l50_irqs[i].name); + return error; + } + } + + return 0; +} + +static int cs40l50_part_num_resolve(struct cs40l50_private *cs40l50) +{ + struct device *dev = cs40l50->dev; + int error; + + error = regmap_read(cs40l50->regmap, CS40L50_DEVID, &cs40l50->devid); + if (error) + return error; + + if (cs40l50->devid != CS40L50_DEVID_A) { + dev_err(dev, "Invalid device ID: %#010X\n", cs40l50->devid); + return -EINVAL; + } + + error = regmap_read(cs40l50->regmap, CS40L50_REVID, &cs40l50->revid); + if (error) + return error; + + if (cs40l50->revid != CS40L50_REVID_B0) { + dev_err(dev, "Invalid revision: %#04X\n", cs40l50->revid); + return -EINVAL; + } + + dev_info(dev, "Cirrus Logic CS40L50 revision %02X\n", cs40l50->revid); + + return 0; +} + +int cs40l50_probe(struct cs40l50_private *cs40l50) +{ + struct device *dev = cs40l50->dev; + int error; + + mutex_init(&cs40l50->lock); + + cs40l50->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(cs40l50->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(cs40l50->reset_gpio), + "Failed getting reset GPIO\n"); + + error = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs40l50_supplies), + cs40l50_supplies); + if (error) + goto err_reset; + + error = regulator_bulk_enable(ARRAY_SIZE(cs40l50_supplies), + cs40l50_supplies); + if (error) + goto err_reset; + + usleep_range(CS40L50_CP_READY_US, CS40L50_CP_READY_US + 100); + + gpiod_set_value_cansleep(cs40l50->reset_gpio, 1); + + usleep_range(CS40L50_CP_READY_US, CS40L50_CP_READY_US + 1000); + + pm_runtime_set_autosuspend_delay(cs40l50->dev, CS40L50_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(cs40l50->dev); + pm_runtime_set_active(cs40l50->dev); + pm_runtime_get_noresume(cs40l50->dev); + devm_pm_runtime_enable(cs40l50->dev); + + error = cs40l50_part_num_resolve(cs40l50); + if (error) + goto err_supplies; + + error = cs40l50_irq_init(cs40l50); + if (error) + goto err_supplies; + + error = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, cs40l50_devs, + ARRAY_SIZE(cs40l50_devs), NULL, 0, NULL); + if (error) + goto err_supplies; + + pm_runtime_mark_last_busy(cs40l50->dev); + pm_runtime_put_autosuspend(cs40l50->dev); + + return 0; + +err_supplies: + regulator_bulk_disable(ARRAY_SIZE(cs40l50_supplies), cs40l50_supplies); +err_reset: + gpiod_set_value_cansleep(cs40l50->reset_gpio, 0); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l50_probe); + +int cs40l50_remove(struct cs40l50_private *cs40l50) +{ + regulator_bulk_disable(ARRAY_SIZE(cs40l50_supplies), cs40l50_supplies); + gpiod_set_value_cansleep(cs40l50->reset_gpio, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l50_remove); + +static int cs40l50_runtime_suspend(struct device *dev) +{ + struct cs40l50_private *cs40l50 = dev_get_drvdata(dev); + + return regmap_write(cs40l50->regmap, CS40L50_DSP_MBOX, CS40L50_ALLOW_HIBER); +} + +static int cs40l50_runtime_resume(struct device *dev) +{ + struct cs40l50_private *cs40l50 = dev_get_drvdata(dev); + int error, i; + u32 val; + + /* Device NAKs when exiting hibernation, so optionally retry here. */ + for (i = 0; i < CS40L50_DSP_TIMEOUT_COUNT; i++) { + error = regmap_write(cs40l50->regmap, CS40L50_DSP_MBOX, + CS40L50_PREVENT_HIBER); + if (!error) + break; + + usleep_range(CS40L50_DSP_POLL_US, CS40L50_DSP_POLL_US + 100); + } + + for (; i < CS40L50_DSP_TIMEOUT_COUNT; i++) { + error = regmap_read(cs40l50->regmap, CS40L50_DSP_MBOX, &val); + if (!error && val == 0) + return 0; + + usleep_range(CS40L50_DSP_POLL_US, CS40L50_DSP_POLL_US + 100); + } + + return error ? error : -ETIMEDOUT; +} + +EXPORT_GPL_DEV_PM_OPS(cs40l50_pm_ops) = { + RUNTIME_PM_OPS(cs40l50_runtime_suspend, cs40l50_runtime_resume, NULL) +}; + +MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver"); +MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cs40l50-i2c.c b/drivers/mfd/cs40l50-i2c.c new file mode 100644 index 000000000000..be1b130eb94b --- /dev/null +++ b/drivers/mfd/cs40l50-i2c.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L50 I2C Driver + * + * Copyright 2023 Cirrus Logic, Inc. + * + */ + +#include +#include + +static int cs40l50_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct cs40l50_private *cs40l50; + + cs40l50 = devm_kzalloc(dev, sizeof(*cs40l50), GFP_KERNEL); + if (!cs40l50) + return -ENOMEM; + + i2c_set_clientdata(client, cs40l50); + + cs40l50->regmap = devm_regmap_init_i2c(client, &cs40l50_regmap); + if (IS_ERR(cs40l50->regmap)) + return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap), + "Failed to initialize register map\n"); + + cs40l50->dev = dev; + cs40l50->irq = client->irq; + + return cs40l50_probe(cs40l50); +} + +static void cs40l50_i2c_remove(struct i2c_client *client) +{ + struct cs40l50_private *cs40l50 = i2c_get_clientdata(client); + + cs40l50_remove(cs40l50); +} + +static const struct i2c_device_id cs40l50_id_i2c[] = { + {"cs40l50", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs40l50_id_i2c); + +static const struct of_device_id cs40l50_of_match[] = { + { .compatible = "cirrus,cs40l50" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l50_of_match); + +static struct i2c_driver cs40l50_i2c_driver = { + .driver = { + .name = "cs40l50", + .of_match_table = cs40l50_of_match, + .pm = pm_ptr(&cs40l50_pm_ops), + }, + .id_table = cs40l50_id_i2c, + .probe = cs40l50_i2c_probe, + .remove = cs40l50_i2c_remove, +}; + +module_i2c_driver(cs40l50_i2c_driver); + +MODULE_DESCRIPTION("CS40L50 I2C Driver"); +MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cs40l50-spi.c b/drivers/mfd/cs40l50-spi.c new file mode 100644 index 000000000000..8311d48efedf --- /dev/null +++ b/drivers/mfd/cs40l50-spi.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L50 SPI Driver + * + * Copyright 2023 Cirrus Logic, Inc. + * + */ + +#include +#include + +static int cs40l50_spi_probe(struct spi_device *spi) +{ + struct cs40l50_private *cs40l50; + struct device *dev = &spi->dev; + + cs40l50 = devm_kzalloc(dev, sizeof(*cs40l50), GFP_KERNEL); + if (!cs40l50) + return -ENOMEM; + + spi_set_drvdata(spi, cs40l50); + + cs40l50->regmap = devm_regmap_init_spi(spi, &cs40l50_regmap); + if (IS_ERR(cs40l50->regmap)) + return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap), + "Failed to initialize register map\n"); + + cs40l50->dev = dev; + cs40l50->irq = spi->irq; + + return cs40l50_probe(cs40l50); +} + +static void cs40l50_spi_remove(struct spi_device *spi) +{ + struct cs40l50_private *cs40l50 = spi_get_drvdata(spi); + + cs40l50_remove(cs40l50); +} + +static const struct spi_device_id cs40l50_id_spi[] = { + {"cs40l50", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, cs40l50_id_spi); + +static const struct of_device_id cs40l50_of_match[] = { + { .compatible = "cirrus,cs40l50" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l50_of_match); + +static struct spi_driver cs40l50_spi_driver = { + .driver = { + .name = "cs40l50", + .of_match_table = cs40l50_of_match, + .pm = pm_ptr(&cs40l50_pm_ops), + }, + .id_table = cs40l50_id_spi, + .probe = cs40l50_spi_probe, + .remove = cs40l50_spi_remove, +}; + +module_spi_driver(cs40l50_spi_driver); + +MODULE_DESCRIPTION("CS40L50 SPI Driver"); +MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/cs40l50.h b/include/linux/mfd/cs40l50.h new file mode 100644 index 000000000000..c625a999a5ae --- /dev/null +++ b/include/linux/mfd/cs40l50.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * CS40L50 Advanced Haptic Driver with waveform memory, + * integrated DSP, and closed-loop algorithms + * + * Copyright 2023 Cirrus Logic, Inc. + * + */ + +#ifndef __CS40L50_H__ +#define __CS40L50_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Power Supply Configuration */ +#define CS40L50_BLOCK_ENABLES2 0x201C +#define CS40L50_ERR_RLS 0x2034 +#define CS40L50_PWRMGT_CTL 0x2900 +#define CS40L50_BST_LPMODE_SEL 0x3810 +#define CS40L50_DCM_LOW_POWER 0x1 +#define CS40L50_OVERTEMP_WARN 0x4000010 + +/* Interrupts */ +#define CS40L50_IRQ1_INT_1 0xE010 +#define CS40L50_IRQ1_INT_2 0xE014 +#define CS40L50_IRQ1_INT_8 0xE02C +#define CS40L50_IRQ1_INT_9 0xE030 +#define CS40L50_IRQ1_INT_10 0xE034 +#define CS40L50_IRQ1_INT_18 0xE054 +#define CS40L50_IRQ1_MASK_1 0xE090 +#define CS40L50_IRQ1_MASK_2 0xE094 +#define CS40L50_IRQ1_MASK_20 0xE0DC +#define CS40L50_IRQ_MASK_2_OVERRIDE 0xFFDF7FFF +#define CS40L50_IRQ_MASK_20_OVERRIDE 0x15C01000 +#define CS40L50_AMP_SHORT_MASK BIT(31) +#define CS40L50_VIRT2_MBOX_MASK BIT(21) +#define CS40L50_TEMP_ERR_MASK BIT(31) +#define CS40L50_BST_UVP_MASK BIT(6) +#define CS40L50_BST_SHORT_MASK BIT(7) +#define CS40L50_BST_ILIMIT_MASK BIT(8) +#define CS40L50_UVLO_VDDBATT_MASK BIT(16) +#define CS40L50_GLOBAL_ERROR_MASK BIT(15) +#define CS40L50_GLOBAL_ERR_RLS BIT(11) +#define CS40L50_IRQ(_irq, _name, _hand) \ + { \ + .irq = CS40L50_ ## _irq ## _IRQ,\ + .name = _name, \ + .handler = cs40l50_ ## _hand, \ + } +#define CS40L50_REG_IRQ(_reg, _irq) \ + [CS40L50_ ## _irq ## _IRQ] = { \ + .reg_offset = (CS40L50_ ## _reg) - CS40L50_IRQ1_INT_1, \ + .mask = CS40L50_ ## _irq ## _MASK \ + } + +/* Mailbox Inbound Commands */ +#define CS40L50_RAM_BANK_INDEX_START 0x1000000 +#define CS40L50_RTH_INDEX_START 0x1400000 +#define CS40L50_RTH_INDEX_END 0x1400001 +#define CS40L50_ROM_BANK_INDEX_START 0x1800000 +#define CS40L50_ROM_BANK_INDEX_END 0x180001A +#define CS40L50_PREVENT_HIBER 0x2000003 +#define CS40L50_ALLOW_HIBER 0x2000004 +#define CS40L50_OWT_PUSH 0x3000008 +#define CS40L50_STOP_PLAYBACK 0x5000000 +#define CS40L50_OWT_DELETE 0xD000000 + +/* Mailbox Outbound Commands */ +#define CS40L50_MBOX_QUEUE_BASE 0x11004 +#define CS40L50_MBOX_QUEUE_END 0x1101C +#define CS40L50_DSP_MBOX 0x11020 +#define CS40L50_MBOX_QUEUE_WT 0x28042C8 +#define CS40L50_MBOX_QUEUE_RD 0x28042CC +#define CS40L50_MBOX_HAPTIC_COMPLETE_MBOX 0x1000000 +#define CS40L50_MBOX_HAPTIC_COMPLETE_GPIO 0x1000001 +#define CS40L50_MBOX_HAPTIC_COMPLETE_I2S 0x1000002 +#define CS40L50_MBOX_HAPTIC_TRIGGER_MBOX 0x1000010 +#define CS40L50_MBOX_HAPTIC_TRIGGER_GPIO 0x1000011 +#define CS40L50_MBOX_HAPTIC_TRIGGER_I2S 0x1000012 +#define CS40L50_MBOX_INIT 0x2000000 +#define CS40L50_MBOX_AWAKE 0x2000002 +#define CS40L50_MBOX_F0_EST_START 0x7000011 +#define CS40L50_MBOX_F0_EST_DONE 0x7000021 +#define CS40L50_MBOX_REDC_EST_START 0x7000012 +#define CS40L50_MBOX_REDC_EST_DONE 0x7000022 +#define CS40L50_MBOX_LE_EST_START 0x7000014 +#define CS40L50_MBOX_LE_EST_DONE 0x7000024 +#define CS40L50_MBOX_ACK 0xA000000 +#define CS40L50_MBOX_PANIC 0xC000000 +#define CS40L50_MBOX_WATERMARK 0xD000000 +#define CS40L50_MBOX_ERR_EVENT_UNMAPPED 0xC0004B3 +#define CS40L50_MBOX_ERR_EVENT_MODIFY 0xC0004B4 +#define CS40L50_MBOX_ERR_NULLPTR 0xC0006A5 +#define CS40L50_MBOX_ERR_BRAKING 0xC0006A8 +#define CS40L50_MBOX_ERR_INVAL_SRC 0xC0006AC +#define CS40L50_MBOX_ERR_ENABLE_RANGE 0xC000836 +#define CS40L50_MBOX_ERR_GPIO_UNMAPPED 0xC000837 +#define CS40L50_MBOX_ERR_ISR_RANGE 0xC000838 +#define CS40L50_MBOX_PERMANENT_SHORT 0xC000C1C +#define CS40L50_MBOX_RUNTIME_SHORT 0xC000C1D + +/* DSP */ +#define CS40L50_DSP1_XMEM_PACKED_0 0x2000000 +#define CS40L50_DSP1_XMEM_UNPACKED32_0 0x2400000 +#define CS40L50_SYS_INFO_ID 0x25E0000 +#define CS40L50_DSP1_XMEM_UNPACKED24_0 0x2800000 +#define CS40L50_RAM_INIT 0x28021DC +#define CS40L50_POWER_ON_SEQ 0x2804320 +#define CS40L50_OWT_BASE 0x2805C34 +#define CS40L50_NUM_OF_WAVES 0x280CB4C +#define CS40L50_CORE_BASE 0x2B80000 +#define CS40L50_CCM_CORE_CONTROL 0x2BC1000 +#define CS40L50_DSP1_YMEM_PACKED_0 0x2C00000 +#define CS40L50_DSP1_YMEM_UNPACKED32_0 0x3000000 +#define CS40L50_DSP1_YMEM_UNPACKED24_0 0x3400000 +#define CS40L50_DSP1_PMEM_0 0x3800000 +#define CS40L50_DSP1_PMEM_5114 0x3804FE8 +#define CS40L50_MEM_RDY_HW 0x2 +#define CS40L50_RAM_INIT_FLAG 0x1 +#define CS40L50_CLOCK_DISABLE 0x80 +#define CS40L50_CLOCK_ENABLE 0x281 +#define CS40L50_DSP_POLL_US 1000 +#define CS40L50_DSP_TIMEOUT_COUNT 100 +#define CS40L50_CP_READY_US 2200 +#define CS40L50_PSEQ_SIZE 200 +#define CS40L50_AUTOSUSPEND_MS 2000 + +/* Firmware */ +#define CS40L50_FW "cs40l50.wmfw" +#define CS40L50_WT "cs40l50.bin" + +/* Calibration */ +#define CS40L50_REDC_ESTIMATION 0x2802F7C +#define CS40L50_F0_ESTIMATION 0x2802F84 +#define CS40L50_F0_STORED 0x2805C00 +#define CS40L50_REDC_STORED 0x2805C04 +#define CS40L50_RE_EST_STATUS 0x3401B40 + +/* Open wavetable */ +#define CS40L50_OWT_SIZE 0x2805C38 +#define CS40L50_OWT_NEXT 0x2805C3C +#define CS40L50_NUM_OF_OWT_WAVES 0x2805C40 + +/* GPIO */ +#define CS40L50_GPIO_BASE 0x2804140 + +/* Device */ +#define CS40L50_DEVID 0x0 +#define CS40L50_REVID 0x4 +#define CS40L50_DEVID_A 0x40A50 +#define CS40L50_REVID_B0 0xB0 + +enum cs40l50_irq_list { + CS40L50_GLOBAL_ERROR_IRQ, + CS40L50_UVLO_VDDBATT_IRQ, + CS40L50_BST_ILIMIT_IRQ, + CS40L50_BST_SHORT_IRQ, + CS40L50_BST_UVP_IRQ, + CS40L50_TEMP_ERR_IRQ, + CS40L50_VIRT2_MBOX_IRQ, + CS40L50_AMP_SHORT_IRQ, +}; + +struct cs40l50_irq { + const char *name; + int irq; + irqreturn_t (*handler)(int irq, void *data); +}; + +struct cs40l50_private { + struct device *dev; + struct regmap *regmap; + struct cs_dsp dsp; + struct mutex lock; + struct gpio_desc *reset_gpio; + struct regmap_irq_chip_data *irq_data; + struct input_dev *input; + const struct firmware *wmfw; + struct cs_hap haptics; + u32 devid; + u32 revid; + int irq; +}; + +int cs40l50_probe(struct cs40l50_private *cs40l50); +int cs40l50_remove(struct cs40l50_private *cs40l50); + +extern const struct regmap_config cs40l50_regmap; +extern const struct dev_pm_ops cs40l50_pm_ops; + +#endif /* __CS40L50_H__ */ From patchwork Wed Oct 18 17:57:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Ogletree X-Patchwork-Id: 735338 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1BCD3CDB47E for ; Wed, 18 Oct 2023 17:58:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232504AbjJRR6U (ORCPT ); Wed, 18 Oct 2023 13:58:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46776 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233137AbjJRR6E (ORCPT ); Wed, 18 Oct 2023 13:58:04 -0400 Received: from mx0b-001ae601.pphosted.com (mx0a-001ae601.pphosted.com [67.231.149.25]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AAB03D52; Wed, 18 Oct 2023 10:57:59 -0700 (PDT) Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 39I6HpUY023598; Wed, 18 Oct 2023 12:57:55 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding:content-type; s= PODMain02222019; bh=wqzo3bJq7VNVz3wf1nhTcWoiQ9cewNW6y6XZXvnNUZg=; b= GAgnEDoiR/78eDx1LggH0kwCga2tib+bRT/yyB9IqVef1wVpyRXLN9UqJn84rroz FujPhXGUyYiWgwelAMkZaX7PhTIasoen5qaPYoTmn/1CV4YeGiTGAN9m4tuWT4P5 5Ksa7Qk19X/CBKB7qv3lRVtwWcIAs+wQxMrhBPU6wU70pvMK034BqHemm9+Ae8Ak 1RTS/QQF0pw3FD0NXK2cVs9HrM17ffyTL+eTzrWtZgI+aAdXQFk3yk7RGBS+YIvb aBe9PsuC8DrUuJHXgcwkXwJTRg5/tiKHoXzxJp8D2OgH8pcsq9Msd41mxPVG5znR eYDvCqs8nCki/VMPxTzcyA== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 3tqrcw6n73-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 18 Oct 2023 12:57:54 -0500 (CDT) Received: from ediex01.ad.cirrus.com (198.61.84.80) by ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.37; Wed, 18 Oct 2023 18:57:52 +0100 Received: from ediswmail.ad.cirrus.com (198.61.86.93) by ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1118.37 via Frontend Transport; Wed, 18 Oct 2023 18:57:52 +0100 Received: from aus-sw-rshr002.ad.cirrus.com (aus-sw-rshr002.ad.cirrus.com [141.131.145.53]) by ediswmail.ad.cirrus.com (Postfix) with ESMTP id 33AC9458; Wed, 18 Oct 2023 17:57:51 +0000 (UTC) From: James Ogletree CC: James Ogletree , Dmitry Torokhov , Rob Herring , "Krzysztof Kozlowski" , Lee Jones , Fred Treven , Ben Bright , , , Subject: [PATCH v4 4/4] Input: cs40l50 - Add support for the CS40L50 haptic driver Date: Wed, 18 Oct 2023 17:57:25 +0000 Message-ID: <20231018175726.3879955-5-james.ogletree@opensource.cirrus.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> References: <20231018175726.3879955-1-james.ogletree@opensource.cirrus.com> MIME-Version: 1.0 X-Proofpoint-GUID: HS4ios6yjPXTA-cXMRGegKvyjDGwX2vR X-Proofpoint-ORIG-GUID: HS4ios6yjPXTA-cXMRGegKvyjDGwX2vR X-Proofpoint-Spam-Reason: safe To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org From: James Ogletree 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 --- 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 +#include +#include +#include + +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. "); +MODULE_LICENSE("GPL");