From patchwork Fri Mar 8 22:24:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Ogletree X-Patchwork-Id: 13587395 Received: from mx0b-001ae601.pphosted.com (mx0a-001ae601.pphosted.com [67.231.149.25]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 555646026C; Fri, 8 Mar 2024 22:24:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=67.231.149.25 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1709936687; cv=none; b=I5cf1MxDnlq9rbLcPafOJUcXJR/qg7+KlQHstBGKwCkLPCjLur3t2PbIq2/+wM12CWC7LtKPcnHh8JV4zMTbe6yNZZ2Wcc4KxqaIFOg3RFRHZt5bOmRYwMZmGEVpkRKKkKK3/nHLEWaMFqH1Ib7V9cko9brK5wLYDKElYkfj6zg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1709936687; c=relaxed/simple; bh=IDrWEDQ6XzadGtFTN2pfuY13VdSuNTVnm2tWzvSfVY8=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=FwcQ9ru2SIud8+z9dUh8bAAwtzkGY2KDvfcI0EG6Iraj7u1PHOuqnwaBRMpfmcupQel8K0xohv0tKAl32hNY5Rma2IY5fO/440ZrT+5FHDUugS2YccnAisG1YpxQW74PsKl5TkdSnir7tP+EIiZEI3YpzD07qDTaszGfB8sSJkU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=opensource.cirrus.com; spf=pass smtp.mailfrom=opensource.cirrus.com; dkim=pass (2048-bit key) header.d=cirrus.com header.i=@cirrus.com header.b=FIaAxgkY; arc=none smtp.client-ip=67.231.149.25 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=opensource.cirrus.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=opensource.cirrus.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cirrus.com header.i=@cirrus.com header.b="FIaAxgkY" Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.17.1.24/8.17.1.24) with ESMTP id 428FgNWe026388; Fri, 8 Mar 2024 16:24:38 -0600 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=ppMJxemwbOo3GkEve50xf9spM2zL2JaE+HTXw1sEMAQ=; b= FIaAxgkYDqMLfIJqZbBHirnnEN1lHxwJ81EmLXFPdFin0twE4JXJZp77cB4toElv bLtvOyacVzn61u2bcaCmSxNDi+DIMw1VURKLtoAjcIIvySs78h9aEtnG0Bz5HeEd nhPgU0snoCfZXSq2QsyKDrdukc+8Et+Rgee5KAAJLci1/nWA/0LkHRGKOIuDW0lS NAx07lkykpnecifGW0wybCwbdAs+4WJ3ZaLmzy9Q7ari4+DbWZP2Z6pwtebUwf/m sorhNVAix+0AcsHsWk6p73BcuGYH1sPuapClZQTnmxAcwF4Ee/cLjWRVJJ5k2mjf A1At6HmtTNjwpy52uZUegw== Received: from ediex01.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 3wm2d2qu02-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 08 Mar 2024 16:24:38 -0600 (CST) 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.1544.4; Fri, 8 Mar 2024 22:24:35 +0000 Received: from ediswmail9.ad.cirrus.com (198.61.86.93) 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.1544.4 via Frontend Transport; Fri, 8 Mar 2024 22:24:35 +0000 Received: from aus-sw-rshr002.ad.cirrus.com (aus-sw-rshr002.ad.cirrus.com [141.131.145.53]) by ediswmail9.ad.cirrus.com (Postfix) with ESMTP id A8EF782026C; Fri, 8 Mar 2024 22:24:33 +0000 (UTC) From: James Ogletree To: , , , , , , CC: , , , , James Ogletree , Charles Keepax Subject: [PATCH v9 1/5] firmware: cs_dsp: Add write sequencer interface Date: Fri, 8 Mar 2024 22:24:17 +0000 Message-ID: <20240308222421.188858-2-jogletre@opensource.cirrus.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20240308222421.188858-1-jogletre@opensource.cirrus.com> References: <20240308222421.188858-1-jogletre@opensource.cirrus.com> Precedence: bulk X-Mailing-List: linux-sound@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: h6LYcH6vnJCyKKTDgdqHXPgu4sv2X1qH X-Proofpoint-GUID: h6LYcH6vnJCyKKTDgdqHXPgu4sv2X1qH X-Proofpoint-Spam-Reason: safe A write sequencer is a sequence of register addresses and values executed by some Cirrus DSPs following power state transitions. Add support for Cirrus drivers to update or add to a write sequencer present in firmware. Reviewed-by: Charles Keepax Signed-off-by: James Ogletree --- drivers/firmware/cirrus/cs_dsp.c | 263 +++++++++++++++++++++++++ include/linux/firmware/cirrus/cs_dsp.h | 28 +++ 2 files changed, 291 insertions(+) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 79d4254d1f9b..90cd56d70c49 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -275,6 +275,12 @@ #define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff #define HALO_MPU_VIO_ERR_SRC_SHIFT 0 +/* + * Write Sequence + */ +#define WSEQ_OP_MAX_WORDS 3 +#define WSEQ_END_OF_SCRIPT 0xFFFFFF + struct cs_dsp_ops { bool (*validate_version)(struct cs_dsp *dsp, unsigned int version); unsigned int (*parse_sizes)(struct cs_dsp *dsp, @@ -3339,6 +3345,263 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits) } EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP); + +struct cs_dsp_wseq_op { + struct list_head list; + u32 address; + u32 data; + u16 offset; + u8 operation; +}; + +static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq) +{ + struct cs_dsp_wseq_op *op = NULL; + struct cs_dsp_chunk ch; + u8 *words; + int ret; + + if (!wseq->ctl) { + cs_dsp_err(dsp, "No control for write sequence\n"); + return -EINVAL; + } + + words = kzalloc(wseq->ctl->len, GFP_KERNEL); + if (!words) + return -ENOMEM; + + ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len); + if (ret) { + cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret); + goto err_free; + } + + INIT_LIST_HEAD(&wseq->ops); + + ch = cs_dsp_chunk(words, wseq->ctl->len); + + while (!cs_dsp_chunk_end(&ch)) { + op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL); + if (!op) { + ret = -ENOMEM; + goto err_free; + } + + op->offset = cs_dsp_chunk_bytes(&ch); + op->operation = cs_dsp_chunk_read(&ch, 8); + + switch (op->operation) { + case CS_DSP_WSEQ_END: + op->data = WSEQ_END_OF_SCRIPT; + break; + case CS_DSP_WSEQ_UNLOCK: + op->data = cs_dsp_chunk_read(&ch, 16); + break; + case CS_DSP_WSEQ_ADDR8: + op->address = cs_dsp_chunk_read(&ch, 8); + op->data = cs_dsp_chunk_read(&ch, 32); + break; + case CS_DSP_WSEQ_H16: + case CS_DSP_WSEQ_L16: + op->address = cs_dsp_chunk_read(&ch, 24); + op->data = cs_dsp_chunk_read(&ch, 16); + break; + case CS_DSP_WSEQ_FULL: + op->address = cs_dsp_chunk_read(&ch, 32); + op->data = cs_dsp_chunk_read(&ch, 32); + break; + default: + ret = -EINVAL; + cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation); + goto err_free; + } + + list_add_tail(&op->list, &wseq->ops); + + if (op->operation == CS_DSP_WSEQ_END) + break; + } + + if (op && op->operation != CS_DSP_WSEQ_END) { + cs_dsp_err(dsp, "Write sequence missing end terminator\n"); + ret = -ENOENT; + } + +err_free: + kfree(words); + + return ret; +} + +/** + * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware + * @dsp: Pointer to DSP structure + * @wseqs: List of write sequences to initialize + * @num_wseqs: Number of write sequences to initialize + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs) +{ + int i, ret = 0; + + lockdep_assert_held(&dsp->pwr_lock); + + for (i = 0; i < num_wseqs; i++) { + ret = cs_dsp_populate_wseq(dsp, &wseqs[i]); + if (ret) + break; + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP); + +static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code, + struct list_head *wseq_ops) +{ + struct cs_dsp_wseq_op *op; + + list_for_each_entry(op, wseq_ops, list) { + if (op->operation == op_code && op->address == addr) + return op; + } + + return NULL; +} + +/** + * cs_dsp_wseq_write() - Add or update an entry in a write sequence + * @dsp: Pointer to a DSP structure + * @wseq: Write sequence to write to + * @addr: Address of the register to be written to + * @data: Data to be written + * @op_code: The type of operation of the new entry + * @update: If true, searches for the first entry in the write sequence with + * the same address and op_code, and replaces it. If false, creates a new entry + * at the tail. + * + * This function formats register address and value pairs into the format + * required for write sequence entries, and either updates or adds the + * new entry into the write sequence. + * + * If update is set to true and no matching entry is found, it will add a new entry. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, + u32 addr, u32 data, u8 op_code, bool update) +{ + struct cs_dsp_wseq_op *op_end, *op_new = NULL; + u32 words[WSEQ_OP_MAX_WORDS]; + struct cs_dsp_chunk ch; + int new_op_size, ret; + + if (update) + op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops); + + /* If entry to update is not found, treat it as a new operation */ + if (!op_new) { + op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops); + if (!op_end) { + cs_dsp_err(dsp, "Missing write sequence list terminator\n"); + return -EINVAL; + } + + op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL); + if (!op_new) + return -ENOMEM; + + op_new->operation = op_code; + op_new->address = addr; + op_new->offset = op_end->offset; + update = false; + } + + op_new->data = data; + + ch = cs_dsp_chunk(words, sizeof(words)); + cs_dsp_chunk_write(&ch, 8, op_new->operation); + switch (op_code) { + case CS_DSP_WSEQ_FULL: + cs_dsp_chunk_write(&ch, 32, op_new->address); + cs_dsp_chunk_write(&ch, 32, op_new->data); + break; + case CS_DSP_WSEQ_L16: + case CS_DSP_WSEQ_H16: + cs_dsp_chunk_write(&ch, 24, op_new->address); + cs_dsp_chunk_write(&ch, 16, op_new->data); + break; + default: + ret = -EINVAL; + cs_dsp_err(dsp, "Op code not supported: %X\n", op_code); + goto op_new_free; + } + + new_op_size = cs_dsp_chunk_bytes(&ch); + + if (!update) { + if (wseq->ctl->len - op_end->offset < new_op_size) { + cs_dsp_err(dsp, "Not enough memory in write sequence for entry\n"); + ret = -ENOMEM; + goto op_new_free; + } + + op_end->offset += new_op_size; + + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32), + &op_end->data, sizeof(u32)); + if (ret) + goto op_new_free; + + list_add_tail(&op_new->list, &op_end->list); + } + + ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32), + words, new_op_size); + if (ret) + goto op_new_free; + + return 0; + +op_new_free: + devm_kfree(dsp->dev, op_new); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP); + +/** + * cs_dsp_wseq_multi_write() - Add or update multiple entries in the write sequence + * @dsp: Pointer to a DSP structure + * @wseq: Write sequence to write to + * @reg_seq: List of address-data pairs + * @num_regs: Number of address-data pairs + * @op_code: The types of operations of the new entries + * @update: If true, searches for the first entry in the write sequence with the same + * address and op code, and replaces it. If false, creates a new entry at the tail. + * + * This function calls cs_dsp_wseq_write() for multiple address-data pairs. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, + const struct reg_sequence *reg_seq, int num_regs, + u8 op_code, bool update) +{ + int ret, i; + + for (i = 0; i < num_regs; i++) { + ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg, + reg_seq[i].def, update, op_code); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP); + MODULE_DESCRIPTION("Cirrus Logic DSP Support"); MODULE_AUTHOR("Simon Trimmer "); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index 29cd11d5a3cf..cfeab75772f6 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -42,6 +42,16 @@ #define CS_DSP_ACKED_CTL_MIN_VALUE 0 #define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF +/* + * Write sequence operation codes + */ +#define CS_DSP_WSEQ_FULL 0x00 +#define CS_DSP_WSEQ_ADDR8 0x02 +#define CS_DSP_WSEQ_L16 0x04 +#define CS_DSP_WSEQ_H16 0x05 +#define CS_DSP_WSEQ_UNLOCK 0xFD +#define CS_DSP_WSEQ_END 0xFF + /** * struct cs_dsp_region - Describes a logical memory region in DSP address space * @type: Memory region type @@ -255,6 +265,24 @@ struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp, const char *cs_dsp_mem_region_name(unsigned int type); +/** + * struct cs_dsp_wseq - Describes a write sequence + * @name: Name of cs_dsp control + * @ctl: Write sequence cs_dsp control + * @ops: Operations contained within this write sequence + */ +struct cs_dsp_wseq { + struct cs_dsp_coeff_ctl *ctl; + struct list_head ops; +}; + +int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs); +int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data, + u8 op_code, bool update); +int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, + const struct reg_sequence *reg_seq, int num_regs, + u8 op_code, bool update); + /** * struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP * @data: Pointer to underlying buffer memory