From patchwork Tue Feb 4 23:18:30 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fredrik Treven X-Patchwork-Id: 861965 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 63FFE21C17B; Tue, 4 Feb 2025 23:22:03 +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=1738711325; cv=none; b=ki4bO6VcA1bfzjCNFYpr/QOvpNVh9M8i97v3VsJmPK8ciqVJTtf353aWsla2sw9qsJwNPxv3fs2wSjQ0MqeRfWyXmilRthfdc/GUm8iTv1/T8lEh1AAMrDLf7Kf8qfPO3qcl0S02yXGQs+ylHQb0saTWZ787OKPyqPz41GLKJGM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711325; c=relaxed/simple; bh=e2TVzUbRqBCxTVqTlfUdnaIbDJGEbaayIa/i2ReZiNw=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=uSuH88KAzNajJLxUYkXJP+c136JrCAT4OvlbXNOQJWmp2954EzRP/JYfcIqEzC7rFLwQ8XBpydXnrNEoV7bRNaqFdJiOu2MTQQCJ4ywp/3IKQkp5/0IxMQ3LhJJbzSy1rPqlH+UtLazLeFAj98iL9QRuvjro/R6ucAiFBky54ro= 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=qmATbGk+; 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="qmATbGk+" Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 514FmWAQ029190; Tue, 4 Feb 2025 17:19:32 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s= PODMain02222019; bh=iwVGm2zIZxv30QAxGxxXgudWn5/8W/GZ/AyWJMQKILI=; b= qmATbGk+dCT6TqCrWx5EByS1QmaxM3kGEU55NF+y9IWGYnwWQEMQs2tgFUcCs3yv C+tFM2hN5y7iGZ++PJH2W75G5CB15cKTXOfuXAFV6/hL57rFoZEWsA5SyxKusTJc qXbCkjXLgrMhqIcS18sOghm8ybFO9x6fXdENN/m/AgF2x+X2lUOXVScvmXUvW0QT ibznDUU8fwbFUSvNv9mzN+NkEgBxJK3BQnyzl5hukLb5q2BnBbhnMa20+BXmyYRg q5yP4TloRtDWsfYzApAi3H2he0ft+zLZ9lL+ihG3v0wv49b2qqQy75K7/THv4E6G 67XFNc5a8hD6wvXENWUR+A== Received: from ediex01.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 44hhw53pk0-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 04 Feb 2025 17:19:32 -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.14; Tue, 4 Feb 2025 23:19:29 +0000 Received: from ediswmail9.ad.cirrus.com (198.61.86.93) by anon-ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1544.14 via Frontend Transport; Tue, 4 Feb 2025 23:19:24 +0000 Received: from ftrev.crystal.cirrus.com (ftrev.ad.cirrus.com [141.131.145.81]) by ediswmail9.ad.cirrus.com (Postfix) with ESMTP id 7CF3F820248; Tue, 4 Feb 2025 23:19:20 +0000 (UTC) From: Fred Treven To: Lee Jones , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , "Simon Trimmer" , Charles Keepax , Richard Fitzgerald , Dmitry Torokhov , James Ogletree , Ben Bright , Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , David Rhodes , Jeff LaBundy , Heiko Stuebner , Karel Balej , Igor Prusov , Jack Yu , Weidong Wang , Binbin Zhou , Prasad Kumpatla , "Paul Handrigan" , Masahiro Yamada , Nuno Sa , Fred Treven CC: , , , , , Subject: [PATCH RESEND 1/7] firmware: cs_dsp: Fix error checking in wseq_write() Date: Tue, 4 Feb 2025 17:18:30 -0600 Message-ID: <20250204231835.2000457-2-ftreven@opensource.cirrus.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> References: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: LLWwZEzpZPzgeIlQ15q6_d9H5f4dIMCe X-Authority-Analysis: v=2.4 cv=W/3CVQWk c=1 sm=1 tr=0 ts=67a2a084 cx=c_pps a=uGhh+3tQvKmCLpEUO+DX4w==:117 a=uGhh+3tQvKmCLpEUO+DX4w==:17 a=T2h4t0Lz3GQA:10 a=w1d2syhTAAAA:8 a=b0R_zkI-s6yCh7IOb7YA:9 a=YXXWInSmI4Sqt1AkVdoW:22 X-Proofpoint-GUID: LLWwZEzpZPzgeIlQ15q6_d9H5f4dIMCe X-Proofpoint-Spam-Reason: safe cs_dsp_coeff_write_ctrl() may return a non-zero value (1) upon success. Change error checking in the write sequencer code such that it checks for negative errnos rather than any non-zero value when using this function. Signed-off-by: Fred Treven --- drivers/firmware/cirrus/cs_dsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 5365e9a43000..56315b0b5583 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -3702,7 +3702,7 @@ int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32), &op_end->data, sizeof(u32)); - if (ret) + if (ret < 0) goto op_new_free; list_add_tail(&op_new->list, &op_end->list); @@ -3710,7 +3710,7 @@ int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32), words, new_op_size); - if (ret) + if (ret < 0) goto op_new_free; return 0; From patchwork Tue Feb 4 23:18:31 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fredrik Treven X-Patchwork-Id: 861963 Received: from mx0b-001ae601.pphosted.com (mx0b-001ae601.pphosted.com [67.231.152.168]) (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 689932163BA; Tue, 4 Feb 2025 23:27:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=67.231.152.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711634; cv=none; b=j4q8SRJ+oXbCsGeexbFAGVYgcEI4To7Q/GV3RqaxwKU+d9BAUgkiyJxWfr3ApucnaYopaTDBK4n1HvZK/CIvkTeAmHHBpEVRgNZ5/HqfZZJAVm+1l3Y+WF6mqP1VNo3fv/L3o7ICQ5K7Mni7mcYH58nDFtsBdsCwySrBrW+/a70= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711634; c=relaxed/simple; bh=1Ndbk2BaIajsSq6LVjOKCXHTVo6NfBh3Ifl5W7zU9q4=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=UoV8VaXnPplX6tp8nCGnXl/X1LAjAvva+8dfW1FbC6ZQBGDJ6kqeiauRAWyFCxfCuER+T3VDi4FVxQsaIJGZc0XudmDk9LFBgknNrDKO3AKBk8tXyth7chYZiL42NOM4HCRIQ6zKv9FE6w0KvIalfcoeFnmMCj7HEN0nkVd8C04= 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=fXsSkeXF; arc=none smtp.client-ip=67.231.152.168 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="fXsSkeXF" Received: from pps.filterd (m0077474.ppops.net [127.0.0.1]) by mx0b-001ae601.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 514Klioc016339; Tue, 4 Feb 2025 17:19:43 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s= PODMain02222019; bh=4rk84s04nJmPDhNfVK0qQcCyoF1+d6Q1J1vzsSv4K4c=; b= fXsSkeXFyA3zFwkflPotTk/AvJWBN7rwXu9+spJWWZf3DFgttkG+ZQ16iGddrTJ2 c9A5rzMb7mh9d6Fg3Bozyr6W4IZ46E1tIuVgmtNOjfTXKLmBCihoYq0xg77UhqrQ cbL/8BZ0XOimg6VoGFExUUqYfuLjCpthTjksSa7T8+Lr4x39P2EsJlYVrZSDWe6C qhKlBajk1ZgrRO4JBMx40u1aQ4dRhjWXnUQH8tXW6eUugXZL7hWvQqUjQk0+2pup 5/sz7ahhHNe5KvSyXrA02UCrgP2L6El8Tozj3SfdWuKg/dJolP+3a8y+RU8nhVTJ 7Mzx1Yk1lWKPXnnPRLAXzw== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0b-001ae601.pphosted.com (PPS) with ESMTPS id 44hgwm3spw-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 04 Feb 2025 17:19:43 -0600 (CST) 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.1544.14; Tue, 4 Feb 2025 23:19:41 +0000 Received: from ediswmail9.ad.cirrus.com (198.61.86.93) by anon-ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1544.14 via Frontend Transport; Tue, 4 Feb 2025 23:19:36 +0000 Received: from ftrev.crystal.cirrus.com (ftrev.ad.cirrus.com [141.131.145.81]) by ediswmail9.ad.cirrus.com (Postfix) with ESMTP id 5020F820248; Tue, 4 Feb 2025 23:19:32 +0000 (UTC) From: Fred Treven To: Lee Jones , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , "Simon Trimmer" , Charles Keepax , Richard Fitzgerald , Dmitry Torokhov , James Ogletree , Ben Bright , Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , David Rhodes , Jeff LaBundy , Heiko Stuebner , Karel Balej , Igor Prusov , Jack Yu , Weidong Wang , Binbin Zhou , Prasad Kumpatla , "Paul Handrigan" , Masahiro Yamada , Nuno Sa , Fred Treven CC: , , , , , Subject: [PATCH RESEND 2/7] firmware: cs_dsp: Check for valid num_regs in cs_dsp_wseq_multi_write() Date: Tue, 4 Feb 2025 17:18:31 -0600 Message-ID: <20250204231835.2000457-3-ftreven@opensource.cirrus.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> References: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Authority-Analysis: v=2.4 cv=EPv800ZC c=1 sm=1 tr=0 ts=67a2a08f cx=c_pps a=uGhh+3tQvKmCLpEUO+DX4w==:117 a=uGhh+3tQvKmCLpEUO+DX4w==:17 a=T2h4t0Lz3GQA:10 a=w1d2syhTAAAA:8 a=pYzt1GAZzx6qoJfKPqoA:9 a=zZCYzV9kfG8A:10 a=YXXWInSmI4Sqt1AkVdoW:22 X-Proofpoint-ORIG-GUID: QwIhPN2wXpUWh2VjXVyow-eG4EN6tgsG X-Proofpoint-GUID: QwIhPN2wXpUWh2VjXVyow-eG4EN6tgsG X-Proofpoint-Spam-Reason: safe If a value of 0 or below is passed into cs_dsp_wseq_multi_write() the function will never enter its for loop. Verify that num_regs passed into the function is valid and throw a user-visible error if not. Signed-off-by: Fred Treven --- drivers/firmware/cirrus/cs_dsp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 56315b0b5583..aacf6960d1ea 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -3743,6 +3743,11 @@ int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, { int i, ret; + if (num_regs <= 0) { + cs_dsp_err(dsp, "Invalid number of regs: %d\n", num_regs); + return -EINVAL; + } + for (i = 0; i < num_regs; i++) { ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg, reg_seq[i].def, op_code, update); From patchwork Tue Feb 4 23:18:34 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fredrik Treven X-Patchwork-Id: 861966 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 CCC2A21CA17; Tue, 4 Feb 2025 23:20:54 +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=1738711258; cv=none; b=FUtuX1TPLy2S7uhuhkjxu9V8ICbrjSrb5WPwS+1VxDMaMH1q2BuOxYm34rpH1HkLRQ+v5uY0s1DNkKLu1vLDLJ9oOVTlxUzULNOY1YgVOjDJhPQqui1XZBFRCBwiiLzQvgZTDbYT3VzbXm30DIVVF+zDbNegj8gJrfMUcw2TRDw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711258; c=relaxed/simple; bh=HMt93Qvd72e7Vz2VoEDR2dGKT3tO6E4ZXpdIP3TDNc8=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fix1yEC+v+zjEJ+ybdesu8GWdRoJpC5pvFHYo6NR6osEg910ZOrlstxAUWTjpf0be6wqmJp7xeGunlXDweKYte92qqs1hNEcqLRBPVPV7robyTD44npTCljCWOhh2hFg03uyiS/+h0Z4SLwrzHhN6cd2shGwndMrVI6E5neJ20I= 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=EEGmVRt5; 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="EEGmVRt5" Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 514Fmw8M029853; Tue, 4 Feb 2025 17:20:17 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s= PODMain02222019; bh=/f6FTRfiMITsj7S7AFHQkwLB5hayI/diyUQ2xzJa794=; b= EEGmVRt5X9MkMBwMkAPwI4wV06tGRRMZdhTGXWNBAKhVzDEdwR4paPcB41OvFzzw e5in5bHSEUElOYDQ5AjqZcpGG//NOlikMDVUrOJM9prHKuVHlQxdWstZ2hNpaWin fHc3MeBNG2e+yNi+Z7/VGfdZPGsIhnt/ncBFSbjJVN0FFwJaUQR6Oemn1Qe2NysU M+SUVco0ubUvl0uhNi2TuVd1uX+odjiSMZW8GOb/O2vCkE92hdWkzLjpEuTMAN/J TsRqGg+KsiGxyVPYWydmkE0VeZT1rNSiCHOcZRLn1RH2iBFc884OpafLax0zjSrn 9GzgyAQHobFn+S5jT5rr+w== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 44hhw53pkk-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 04 Feb 2025 17:20:16 -0600 (CST) 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.1544.14; Tue, 4 Feb 2025 23:20:13 +0000 Received: from ediswmail9.ad.cirrus.com (198.61.86.93) by anon-ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server id 15.2.1544.14 via Frontend Transport; Tue, 4 Feb 2025 23:20:08 +0000 Received: from ftrev.crystal.cirrus.com (ftrev.ad.cirrus.com [141.131.145.81]) by ediswmail9.ad.cirrus.com (Postfix) with ESMTP id 5DB30820270; Tue, 4 Feb 2025 23:20:04 +0000 (UTC) From: Fred Treven To: Lee Jones , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , "Simon Trimmer" , Charles Keepax , Richard Fitzgerald , Dmitry Torokhov , James Ogletree , Ben Bright , Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , David Rhodes , Jeff LaBundy , Heiko Stuebner , Karel Balej , Igor Prusov , Jack Yu , Weidong Wang , Binbin Zhou , Prasad Kumpatla , "Paul Handrigan" , Masahiro Yamada , Nuno Sa , Fred Treven CC: , , , , , Subject: [PATCH RESEND 5/7] mfd: cs40l26: Add support for CS40L26 core driver Date: Tue, 4 Feb 2025 17:18:34 -0600 Message-ID: <20250204231835.2000457-6-ftreven@opensource.cirrus.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> References: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: SPOiO2LvLNFZ5ZQZ73Ku65ybGAjSEO5O X-Authority-Analysis: v=2.4 cv=W/3CVQWk c=1 sm=1 tr=0 ts=67a2a0b0 cx=c_pps a=uGhh+3tQvKmCLpEUO+DX4w==:117 a=uGhh+3tQvKmCLpEUO+DX4w==:17 a=T2h4t0Lz3GQA:10 a=w1d2syhTAAAA:8 a=kJREcIERN5GkY3D7nBAA:9 a=YXXWInSmI4Sqt1AkVdoW:22 X-Proofpoint-GUID: SPOiO2LvLNFZ5ZQZ73Ku65ybGAjSEO5O X-Proofpoint-Spam-Reason: safe Introduce support for Cirrus Logic Device CS40L26: A boosted haptic driver with integrated DSP and waveform memory with advanced closed loop algorithms and LRA protection. Signed-off-by: Fred Treven --- drivers/mfd/Kconfig | 29 + drivers/mfd/Makefile | 4 + drivers/mfd/cs40l26-core.c | 1412 +++++++++++++++++++++++++++++++++++ drivers/mfd/cs40l26-i2c.c | 63 ++ drivers/mfd/cs40l26-spi.c | 63 ++ include/linux/mfd/cs40l26.h | 341 +++++++++ 6 files changed, 1912 insertions(+) create mode 100644 drivers/mfd/cs40l26-core.c create mode 100644 drivers/mfd/cs40l26-i2c.c create mode 100644 drivers/mfd/cs40l26-spi.c create mode 100644 include/linux/mfd/cs40l26.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6b0682af6e32..93a60fa9551a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2293,6 +2293,35 @@ config MCP_UCB1200_TS endmenu +config MFD_CS40L26_CORE + tristate + select MFD_CORE + select FW_CS_DSP + +config MFD_CS40L26_I2C + tristate "Cirrus Logic CS40L26 (I2C)" + select REGMAP_I2C + select MFD_CS40L26_CORE + depends on I2C + help + Select this to support the Cirrus Logic CS40L26 Haptic + Driver over I2C. + + This driver can be built as a module. If built as a module it will be + called "cs40l26-i2c". + +config MFD_CS40L26_SPI + tristate "Cirrus Logic CS40L26 (SPI)" + select REGMAP_SPI + select MFD_CS40L26_CORE + depends on SPI + help + Select this to support the Cirrus Logic CS40L26 Haptic + Driver over SPI. + + This driver can be built as a module. If built as a module it will be + called "cs40l26-spi". + config MFD_CS40L50_CORE tristate select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9220eaf7cf12..8a245f36d73d 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -90,6 +90,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_CS40L26_CORE) += cs40l26-core.o +obj-$(CONFIG_MFD_CS40L26_I2C) += cs40l26-i2c.o +obj-$(CONFIG_MFD_CS40L26_SPI) += cs40l26-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 diff --git a/drivers/mfd/cs40l26-core.c b/drivers/mfd/cs40l26-core.c new file mode 100644 index 000000000000..b314f820de1e --- /dev/null +++ b/drivers/mfd/cs40l26-core.c @@ -0,0 +1,1412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L26 Advanced Haptic Driver with waveform memory, + * integrated DSP, and closed-loop algorithms + * + * Copyright 2025 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#include +#include +#include +#include +#include + +static const struct mfd_cell cs40l26_devs[] = { + { .name = "cs40l26-codec", }, + { .name = "cs40l26-vibra", }, +}; + +const struct regmap_config cs40l26_regmap = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS40L26_LASTREG, + .cache_type = REGCACHE_NONE, +}; +EXPORT_SYMBOL_GPL(cs40l26_regmap); + +static const char *const cs40l26_supplies[] = { + "va", "vp", +}; + +inline void cs40l26_pm_exit(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} +EXPORT_SYMBOL_GPL(cs40l26_pm_exit); + +static int cs40l26_fw_write_raw(struct cs_dsp *dsp, const char *const name, + const unsigned int algo_id, const u32 offset_words, + const size_t len_words, u32 *buf) +{ + struct cs_dsp_coeff_ctl *ctl; + __be32 *val; + int i, ret; + + ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id); + if (!ctl) { + dev_err(dsp->dev, "Failed to find FW control %s\n", name); + return -EINVAL; + } + + val = kzalloc(len_words * sizeof(u32), GFP_KERNEL); + if (!val) + return -ENOMEM; + + for (i = 0; i < len_words; i++) + val[i] = cpu_to_be32(buf[i]); + + ret = cs_dsp_coeff_write_ctrl(ctl, offset_words, val, len_words * sizeof(u32)); + if (ret < 0) + dev_err(dsp->dev, "Failed to write FW control %s\n", name); + + kfree(val); + + return (ret < 0) ? ret : 0; +} + +inline int cs40l26_fw_write(struct cs_dsp *dsp, const char *const name, const unsigned int algo_id, + u32 val) +{ + return cs40l26_fw_write_raw(dsp, name, algo_id, 0, 1, &val); +} +EXPORT_SYMBOL_GPL(cs40l26_fw_write); + +static int cs40l26_fw_read_raw(struct cs_dsp *dsp, const char *const name, + const unsigned int algo_id, const unsigned int offset_words, + const size_t len_words, u32 *buf) +{ + struct cs_dsp_coeff_ctl *ctl; + int i, ret; + + ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id); + if (!ctl) { + dev_err(dsp->dev, "Failed to find FW control %s\n", name); + return -EINVAL; + } + + ret = cs_dsp_coeff_read_ctrl(ctl, offset_words, buf, len_words * sizeof(u32)); + if (ret) { + dev_err(dsp->dev, "Failed to read FW control %s\n", name); + return ret; + } + + for (i = 0; i < len_words; i++) + buf[i] = be32_to_cpu(buf[i]); + + return 0; +} + +inline int cs40l26_fw_read(struct cs_dsp *dsp, const char *const name, const unsigned int algo_id, + u32 *buf) +{ + return cs40l26_fw_read_raw(dsp, name, algo_id, 0, 1, buf); +} +EXPORT_SYMBOL_GPL(cs40l26_fw_read); + +static struct cs40l26_irq *cs40l26_get_irq(struct cs40l26 *cs40l26, const int num, const int bit); + +static int cs40l26_gpio1_rise_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) + dev_dbg(cs40l26->dev, "GPIO1 Rising Edge Detected\n"); + + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + + return 0; +} + +static int cs40l26_gpio1_fall_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) + dev_dbg(cs40l26->dev, "GPIO1 Falling Edge Detected\n"); + + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + + return 0; +} + +static int cs40l26_wksrc_any_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + u32 last_wksrc, pwrmgt_sts; + int ret; + + guard(mutex)(&cs40l26->dsp.pwr_lock); + + ret = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &pwrmgt_sts); + if (ret) + return ret; + + cs40l26->wksrc_sts = (u8)FIELD_GET(CS40L26_WKSRC_STS_MASK, pwrmgt_sts); + + ret = cs40l26_fw_read(&cs40l26->dsp, "LAST_WAKESRC_CTL", cs40l26->dsp.fw_id, &last_wksrc); + if (ret) + return ret; + + cs40l26->last_wksrc_pol = (u8)(last_wksrc & CS40L26_WKSRC_GPIO_POL_MASK); + + return 0; +} + +static int cs40l26_wksrc_gpio1_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + /* + * The GPIO wakesource and event interrupts are not able to reliably determine + * the GPIO edge that triggered the interrupt (rising/falling). + * + * The driver must therefore perform this logic in order to determine the edge + * of the GPIO event for two cases: + * 1. The GPIO event is waking the device from hibernation. + * 2. The GPIO event occurs when the device is already awake. + */ + + if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) { + dev_dbg(cs40l26->dev, "GPIO1 Falling Edge Detected\n"); + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + } else { + dev_dbg(cs40l26->dev, "GPIO1 rising edge detected\n"); + } + + return 0; +} + +static int cs40l26_error_release(struct cs40l26 *cs40l26, const enum cs40l26_error err) +{ + int ret; + + dev_err(cs40l26->dev, "Device Reported Error: %u\n", (unsigned int)BIT(err)); + + ret = regmap_clear_bits(cs40l26->regmap, CS40L26_ERROR_RELEASE, BIT(err)); + if (ret) + return ret; + + ret = regmap_set_bits(cs40l26->regmap, CS40L26_ERROR_RELEASE, BIT(err)); + if (ret) + return ret; + + return regmap_clear_bits(cs40l26->regmap, CS40L26_ERROR_RELEASE, BIT(err)); +} + +static int cs40l26_bst_ovp_err_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_BST_OVP); +} + +static int cs40l26_bst_dcm_uvp_err_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_BST_DCM_UVP); +} + +static int cs40l26_bst_short_err_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_BST_SHORT); +} + +static int cs40l26_temp_warn_rise_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_TEMP_WARN); +} + +static int cs40l26_temp_err_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_TEMP_ERR); +} + +static int cs40l26_amp_short_err_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + + return cs40l26_error_release(cs40l26, CS40L26_ERROR_AMP_SHORT); +} + +static int cs40l26_dsp_queue_buffer_read(struct cs40l26 *cs40l26, u32 *val) +{ + u32 queue_rd, queue_wt, sts; + int ret; + + guard(mutex)(&cs40l26->dsp.pwr_lock); + + ret = cs40l26_fw_read(&cs40l26->dsp, "QUEUE_WT", CS40L26_DSP_ALGO_ID, &queue_wt); + if (ret) + return ret; + + ret = cs40l26_fw_read(&cs40l26->dsp, "QUEUE_RD", CS40L26_DSP_ALGO_ID, &queue_rd); + if (ret) + return ret; + + if (queue_rd - sizeof(u32) == queue_wt) { + ret = cs40l26_fw_read(&cs40l26->dsp, "STATUS", CS40L26_DSP_ALGO_ID, &sts); + if (ret) + return ret; + + if (sts) { + dev_err(cs40l26->dev, "DSP Queue Buffer is full, message(s) missed\n"); + return -ENOSPC; + } + } + + if (queue_rd == queue_wt) /* DSP Queue is Empty */ + return 1; + + ret = regmap_read(cs40l26->regmap, queue_rd, val); + if (ret) + return ret; + + if (queue_rd == cs40l26->queue_last) + queue_rd = cs40l26->queue_base; + else + queue_rd += sizeof(u32); + + return cs40l26_fw_write(&cs40l26->dsp, "QUEUE_RD", CS40L26_DSP_ALGO_ID, queue_rd); +} + +static int cs40l26_dsp_queue_irq(void *data) +{ + struct cs40l26 *cs40l26 = data; + bool end = false; + int ret; + u32 val; + + ret = cs40l26_dsp_queue_buffer_read(cs40l26, &val); + if (ret == 1) + return 0; + else if (ret) + return ret; + + while (!end) { + if ((val & CS40L26_DSP_CMD_INDEX_MASK) == CS40L26_DSP_PANIC) { + dev_err(cs40l26->dev, "DSP Panic! Error: 0x%06X\n", + (u32)(val & CS40L26_DSP_CMD_PAYLOAD_MASK)); + return -ENOTRECOVERABLE; + } + + switch (val) { + case CS40L26_DSP_COMPLETE_CP: + dev_dbg(cs40l26->dev, "DSP Queue: Control Port Haptics Completed\n"); + break; + case CS40L26_DSP_COMPLETE_I2S: + dev_dbg(cs40l26->dev, "DSP Queue: I2S Stream Completed\n"); + break; + case CS40L26_DSP_TRIGGER_CP: + dev_dbg(cs40l26->dev, "DSP Queue: Control Port Haptics Triggered\n"); + break; + case CS40L26_DSP_TRIGGER_I2S: + dev_dbg(cs40l26->dev, "DSP Queue: I2S Stream Triggered\n"); + break; + case CS40L26_DSP_PM_AWAKE: + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + dev_dbg(cs40l26->dev, "DSP Queue: AWAKE\n"); + break; + case CS40L26_DSP_SYS_ACK: + dev_dbg(cs40l26->dev, "DSP Queue: Inbound PING received\n"); + break; + default: + dev_err(cs40l26->dev, "DSP Queue value (0x%X) unrecognized\n", val); + return -EINVAL; + } + + ret = cs40l26_dsp_queue_buffer_read(cs40l26, &val); + if (ret == 1) + end = true; + else if (ret) + return ret; + } + + return 0; +} + +static struct reg_sequence cs40l26_irq_masks[] = { + REG_SEQ0(CS40L26_IRQ1_MASK_1, CS40L26_IRQ_1_ALL_MASKED), + REG_SEQ0(CS40L26_IRQ1_MASK_2, CS40L26_IRQ_2_ALL_MASKED), +}; + +static void cs40l26_irq_unmask(struct cs40l26 *cs40l26, const int num, const int virq) +{ + struct cs40l26_irq *irq; + + if (num != 1 && num != 2) { + dev_err(cs40l26->dev, "Invalid IRQ number %d\n", num); + return; + } + + irq = cs40l26_get_irq(cs40l26, num, virq); + if (!irq) + return; + + cs40l26->irq_masks[num - 1].def &= ~irq->mask; +} + +static struct cs40l26_irq cs40l26_irqs_1[] = { + CS40L26_IRQ(GPIO1_RISE, "GPIO1 Rise", cs40l26_gpio1_rise_irq), + CS40L26_IRQ(GPIO1_FALL, "GPIO1 Fall", cs40l26_gpio1_fall_irq), + CS40L26_IRQ(WKSRC_STS_ANY, "ANY Wake", cs40l26_wksrc_any_irq), + CS40L26_IRQ(WKSRC_STS_GPIO1, "GPIO1 Wake", cs40l26_wksrc_gpio1_irq), + CS40L26_IRQ(WKSRC_STS_SPI, "SPI Wake", NULL), + CS40L26_IRQ(WKSRC_STS_I2C, "I2C Wake", NULL), + CS40L26_IRQ(BST_OVP_FLAG_RISE, "BST OVP Rise", NULL), + CS40L26_IRQ(BST_OVP_FLAG_FALL, "BST OVP Fall", NULL), + CS40L26_IRQ(BST_OVP_ERR, "BST OVP Error", cs40l26_bst_ovp_err_irq), + CS40L26_IRQ(BST_DCM_UVP_ERR, "BST UVP Error", cs40l26_bst_dcm_uvp_err_irq), + CS40L26_IRQ(BST_SHORT_ERR, "BST Short Error", cs40l26_bst_short_err_irq), + CS40L26_IRQ(BST_IPK_FLAG, "BST IPK Flag", NULL), + CS40L26_IRQ(TEMP_WARN_RISE, "TEMP Warn Rise", cs40l26_temp_warn_rise_irq), + CS40L26_IRQ(TEMP_WARN_FALL, "TEMP Warn Fall", NULL), + CS40L26_IRQ(TEMP_ERR, "TEMP Error", cs40l26_temp_err_irq), + CS40L26_IRQ(AMP_ERR, "AMP Error", cs40l26_amp_short_err_irq), + CS40L26_IRQ(DSP_RX_QUEUE, "DSP Rx", cs40l26_dsp_queue_irq), +}; + +static struct cs40l26_irq cs40l26_irqs_2[] = { + CS40L26_IRQ(REFCLK_PRESENT, "REFCLK Present", NULL), + CS40L26_IRQ(REFCLK_MISSING_FALL, "REFCLK Missing Fall", NULL), + CS40L26_IRQ(REFCLK_MISSING_RISE, "REFCLK Missing Rise", NULL), + CS40L26_IRQ(VPMON_CLIPPED, "VPMON Clipped", NULL), + CS40L26_IRQ(VBSTMON_CLIPPED, "VBSTMON Clipped", NULL), + CS40L26_IRQ(VMON_CLIPPED, "VMON Clipped", NULL), + CS40L26_IRQ(IMON_CLIPPED, "IMON Clipped", NULL), +}; + +static struct cs40l26_irq *cs40l26_get_irq(struct cs40l26 *cs40l26, const int num, const int bit) +{ + int i; + + if (num == 1) { + for (i = 0; i < ARRAY_SIZE(cs40l26_irqs_1); i++) { + if (cs40l26_irqs_1[i].virq == bit) + return &cs40l26_irqs_1[i]; + } + } else if (num == 2) { + for (i = 0; i < ARRAY_SIZE(cs40l26_irqs_2); i++) { + if (cs40l26_irqs_2[i].virq == bit) + return &cs40l26_irqs_2[i]; + } + } else { + dev_err(cs40l26->dev, "Invalid IRQ number %d\n", num); + return NULL; + } + + dev_err(cs40l26->dev, "Failed to find IRQ corresponding to bit in IRQ%d %d\n", bit, num); + + return NULL; +} + +static irqreturn_t cs40l26_irq_handler(int irq, void *data) +{ + struct cs40l26 *cs40l26 = data; + struct cs40l26_irq *irq_s; + unsigned long handle_bits; + u32 eint, mask, sts; + int i, j, ret; + + if (pm_runtime_resume_and_get(cs40l26->dev)) { + dev_err(cs40l26->dev, "Failed to exit hibernate to service interrupt\n"); + return IRQ_NONE; + } + + guard(mutex)(&cs40l26->lock); + + ret = regmap_read(cs40l26->regmap, CS40L26_IRQ1_STATUS, &sts); + if (ret) + goto err_pm; + + if (!(sts & CS40L26_IRQ_STATUS_ASSERT)) { + dev_err(cs40l26->dev, "IRQ1 asserted with no pending interrupts\n"); + ret = -EIO; + goto err_pm; + } + + for (j = 0; j < 2; j++) { + ret = regmap_read(cs40l26->regmap, CS40L26_IRQ1_MASK_1 + j * 4, &mask); + if (ret) + goto err_pm; + + ret = regmap_read(cs40l26->regmap, CS40L26_IRQ1_EINT_1 + j * 4, &eint); + if (ret) + goto err_pm; + + handle_bits = eint & ~mask; + + for_each_set_bit(i, &handle_bits, j ? CS40L26_IRQ_2_NBITS : CS40L26_IRQ_1_NBITS) { + irq_s = cs40l26_get_irq(cs40l26, j + 1, i); + if (!irq_s) + continue; + + dev_dbg(cs40l26->dev, "%s", irq_s->name); + + if (irq_s->handler) { + ret = irq_s->handler(cs40l26); + if (ret) + goto err_pm; + } + + ret = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1 + j * 4, BIT(i)); + if (ret) + goto err_pm; + } + } + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return IRQ_RETVAL(ret); +} + +int cs40l26_dsp_write(struct cs40l26 *cs40l26, const u32 val) +{ + int i, ret; + u32 ack; + + /* Device NAKs if hibernating, so retry if this is the case */ + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + ret = regmap_write(cs40l26->regmap, CS40L26_DSP_QUEUE, val); + if (!ret) + break; + + usleep_range(CS40L26_DSP_POLL_US, CS40L26_DSP_POLL_US + 100); + } + + if (i == CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(cs40l26->dev, "Timed out writing %#X to DSP\n", val); + return -ETIMEDOUT; + } + + ret = regmap_read_poll_timeout(cs40l26->regmap, CS40L26_DSP_QUEUE, ack, !ack, + CS40L26_DSP_POLL_US, + CS40L26_DSP_POLL_US * CS40L26_DSP_TIMEOUT_COUNT); + if (ret) + dev_err(cs40l26->dev, "DSP failed to ACK %#X: %d\n", val, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(cs40l26_dsp_write); + +int cs40l26_dsp_state_get(struct cs40l26 *cs40l26, u32 *state) +{ + u32 dsp_state = CS40L26_DSP_STATE_NONE; + int i, ret; + + if (cs40l26->dsp.running) { + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + ret = cs40l26_fw_read(&cs40l26->dsp, "PM_CUR_STATE", CS40L26_PM_ALGO_ID, + &dsp_state); + if (ret) + return ret; + + if (dsp_state != CS40L26_DSP_STATE_NONE) + break; + + usleep_range(CS40L26_DSP_POLL_US, CS40L26_DSP_POLL_US + 100); + } + + if (i == CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(cs40l26->dev, "Timed out reading PM_CUR_STATE\n"); + return -ETIMEDOUT; + } + } else { + ret = regmap_read_poll_timeout(cs40l26->regmap, + cs40l26->variant->info->pm_cur_state, dsp_state, + dsp_state != CS40L26_DSP_STATE_NONE, + CS40L26_DSP_POLL_US, + CS40L26_DSP_POLL_US * CS40L26_DSP_TIMEOUT_COUNT); + if (ret) { + dev_err(cs40l26->dev, "Failed to read poll for static PM_CUR_STATE\n"); + return ret; + } + } + + *state = dsp_state; + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_dsp_state_get); + +static bool cs40l26_dsp_can_run(struct cs40l26 *cs40l26) +{ + struct regmap *regmap = cs40l26->regmap; + u32 dsp_state, pm_state_locks; + int ret; + + ret = cs40l26_dsp_state_get(cs40l26, &dsp_state); + if (ret) + return false; + + if (dsp_state == CS40L26_DSP_STATE_ACTIVE) + return true; + + if (dsp_state != CS40L26_DSP_STATE_STANDBY) { + dev_err(cs40l26->dev, "DSP in bad state: %u\n", dsp_state); + return false; + } + + if (cs40l26->dsp.running) + ret = cs40l26_fw_read_raw(&cs40l26->dsp, "PM_STATE_LOCKS", CS40L26_PM_ALGO_ID, + CS40L26_DSP_LOCK3_OFFSET_WORDS, 1, &pm_state_locks); + else + ret = regmap_read(regmap, cs40l26->variant->info->pm_state_locks3, &pm_state_locks); + + if (ret) { + dev_err(cs40l26->dev, "Failed to read PM_STATE_LOCKS\n"); + return false; + } + + return pm_state_locks & CS40L26_DSP_LOCK3_MASK; +} + +static int cs40l26_prevent_hiber(struct cs40l26 *cs40l26) +{ + int i, ret; + + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + ret = cs40l26_dsp_write(cs40l26, CS40L26_DSP_CMD_PREVENT_HIBER); + if (ret) + return ret; + + usleep_range(CS40L26_DSP_POLL_US, CS40L26_DSP_POLL_US + 100); + + if (cs40l26_dsp_can_run(cs40l26)) + break; + } + + if (i == CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(cs40l26->dev, "Failed to prevent hibernation\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int cs40l26_lbst_short_test(struct cs40l26 *cs40l26) +{ + u32 err, vbst_ctl_1, vbst_ctl_2; + int ret; + + /* Read initial values to restore after test is complete */ + ret = regmap_read(cs40l26->regmap, CS40L26_VBST_CTL_2, &vbst_ctl_2); + if (ret) + return ret; + + ret = regmap_read(cs40l26->regmap, CS40L26_VBST_CTL_1, &vbst_ctl_1); + if (ret) + return ret; + + ret = regmap_update_bits(cs40l26->regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK, + CS40L26_BST_CTL_SEL_FIXED); + if (ret) + return ret; + + ret = regmap_update_bits(cs40l26->regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK, + CS40L26_BST_CTL_VP); + if (ret) + return ret; + + ret = regmap_set_bits(cs40l26->regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN); + if (ret) + return ret; + + /* Wait for boost converter to power up */ + usleep_range(CS40L26_BST_TIME_US, CS40L26_BST_TIME_US + 100); + + ret = regmap_read(cs40l26->regmap, CS40L26_ERROR_RELEASE, &err); + if (ret) + return ret; + + if (err & BIT(CS40L26_ERROR_BST_SHORT)) { + dev_err(cs40l26->dev, "Boost shorted at startup\n"); + return -ENOTRECOVERABLE; + } + + /* Return to previous state before test */ + ret = regmap_clear_bits(cs40l26->regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN); + if (ret) + return ret; + + ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, vbst_ctl_1); + if (ret) + return ret; + + return regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_2, vbst_ctl_2); +} + +static const struct reg_sequence cs40l26_a1_b1_errata[] = { + { CS40L26_PLL_REFCLK_DETECT_0, CS40L26_PLL_REFCLK_DET_DISABLE }, + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { CS40L26_TEST_LBST, CS40L26_DISABLE_EXPL_MODE }, +}; + +static int cs40l26_a1_b1_handle_errata(struct cs40l26 *cs40l26) +{ + int ret; + + /* + * Boost Exploratory Mode must be disabled on 0xA1/0xB1 devices in order to ensure there is + * no unintentional damage to the boost inductor. Any boost short that occurs after the + * LBST short test at probe will not be detected. + */ + ret = cs40l26_lbst_short_test(cs40l26); + if (ret) + return ret; + + ret = regmap_multi_reg_write(cs40l26->regmap, cs40l26_a1_b1_errata, + ARRAY_SIZE(cs40l26_a1_b1_errata)); + if (ret) + return ret; + + return cs_dsp_wseq_multi_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + cs40l26_a1_b1_errata, ARRAY_SIZE(cs40l26_a1_b1_errata), + CS_DSP_WSEQ_FULL, false); +} + +static const struct cs40l26_variant_info cs40l26_a1_b1_info = { + .pm_cur_state = CS40L26_A1_B1_PM_CUR_STATE, + .pm_state_locks = CS40L26_A1_B1_PM_STATE_LOCKS, + .pm_state_locks3 = CS40L26_A1_B1_PM_STATE_LOCKS3, + .pm_stdby_ticks = CS40L26_A1_B1_PM_STDBY_TICKS, + .pm_active_ticks = CS40L26_A1_B1_PM_ACTIVE_TICKS, + .halo_state = CS40L26_A1_B1_HALO_STATE, + .event_map_1 = CS40L26_A1_B1_EVENT_MAP_1, + .event_map_2 = CS40L26_A1_B1_EVENT_MAP_2, + .fw_min_rev = CS40L26_FW_A1_B1_MIN_REV, + .ram_ext_algo_id = CS40L26_EXT_ALGO_ID, + .vibegen_algo_id = CS40L26_VIBEGEN_ALGO_ID_A1, +}; + +static const struct cs40l26_variant_info cs40l26_b2_info = { + .pm_cur_state = CS40L26_B2_PM_CUR_STATE, + .pm_state_locks = CS40L26_B2_PM_STATE_LOCKS, + .pm_state_locks3 = CS40L26_B2_PM_STATE_LOCKS3, + .pm_stdby_ticks = CS40L26_B2_PM_STDBY_TICKS, + .pm_active_ticks = CS40L26_B2_PM_ACTIVE_TICKS, + .halo_state = CS40L26_B2_HALO_STATE, + .event_map_1 = CS40L26_B2_EVENT_MAP_1, + .event_map_2 = CS40L26_B2_EVENT_MAP_2, + .fw_min_rev = CS40L26_FW_B2_MIN_REV, + .ram_ext_algo_id = CS40L26_FW_ID, + .vibegen_algo_id = CS40L26_VIBEGEN_ALGO_ID_B2, +}; + +static const struct cs40l26_variant cs40l26_a1_b1_variant = { + .info = &cs40l26_a1_b1_info, + .handle_errata = &cs40l26_a1_b1_handle_errata, +}; + +static const struct cs40l26_variant cs40l26_b2_variant = { + .info = &cs40l26_b2_info, + .handle_errata = NULL, +}; + +static inline int cs40l26_pm_timeout_ms_set(struct cs40l26 *cs40l26, const u32 dsp_state, + const u32 timeout_ms) +{ + return regmap_write(cs40l26->regmap, + dsp_state == CS40L26_DSP_STATE_STANDBY ? + cs40l26->variant->info->pm_stdby_ticks : + cs40l26->variant->info->pm_active_ticks, + (timeout_ms * CS40L26_PM_TICKS_PER_SEC) / 1000); +} + +static int cs40l26_pm_timeout_ms_get(struct cs40l26 *cs40l26, const u32 dsp_state, u32 *timeout_ms) +{ + u32 timeout_ticks; + int ret; + + ret = regmap_read(cs40l26->regmap, + dsp_state == CS40L26_DSP_STATE_STANDBY ? + cs40l26->variant->info->pm_stdby_ticks : + cs40l26->variant->info->pm_active_ticks, + &timeout_ticks); + if (ret) + return ret; + + *timeout_ms = DIV_ROUND_UP(timeout_ticks * 1000, CS40L26_PM_TICKS_PER_SEC); + + return 0; +} + +static int cs40l26_pm_runtime_setup(struct device *dev) +{ + int ret; + + pm_runtime_set_autosuspend_delay(dev, CS40L26_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_get_noresume(dev); + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + return devm_pm_runtime_enable(dev); +} + +static int cs40l26_dsp_pre_config(struct cs40l26 *cs40l26) +{ + u32 dsp_state, halo_state, timeout_ms; + int i, ret; + + ret = regmap_read(cs40l26->regmap, cs40l26->variant->info->halo_state, &halo_state); + if (ret) + return ret; + + if (halo_state != CS40L26_DSP_HALO_STATE_RUN) { + dev_err(cs40l26->dev, "Invalid DSP state: %u\n", halo_state); + return -EINVAL; + } + + ret = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_ACTIVE, &timeout_ms); + if (ret) { + dev_err(cs40l26->dev, "Failed to get ACTIVE timeout\n"); + return ret; + } + + for (i = 0; i < CS40L26_DSP_STATE_TIMEOUT_COUNT; i++) { + ret = cs40l26_dsp_state_get(cs40l26, &dsp_state); + if (ret) + return ret; + + if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN && + dsp_state != CS40L26_DSP_STATE_STANDBY) + dev_warn(cs40l26->dev, "DSP core not safe to kill\n"); + else + break; + + usleep_range(timeout_ms * 1000, (timeout_ms * 1000) + 100); + } + + if (i == CS40L26_DSP_STATE_TIMEOUT_COUNT) { + dev_err(cs40l26->dev, "DSP Core could not be shut down\n"); + return -ETIMEDOUT; + } + + return regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL, + CS40L26_DSP_CCM_CORE_KILL); +} + +static const struct cs_dsp_region cs40l26_dsp_regions[] = { + { .type = WMFW_HALO_PM_PACKED, .base = CS40L26_DSP1_PMEM_0 }, + { .type = WMFW_HALO_XM_PACKED, .base = CS40L26_DSP1_XMEM_PACKED_0 }, + { .type = WMFW_HALO_YM_PACKED, .base = CS40L26_DSP1_YMEM_PACKED_0 }, + { .type = WMFW_ADSP2_XM, .base = CS40L26_DSP1_XMEM_UNPACKED24_0 }, + { .type = WMFW_ADSP2_YM, .base = CS40L26_DSP1_YMEM_UNPACKED24_0 }, +}; + +static int cs40l26_get_model(struct cs40l26 *cs40l26) +{ + int ret; + + ret = regmap_read(cs40l26->regmap, CS40L26_DEVID, &cs40l26->devid); + if (ret) + return ret; + + ret = regmap_read(cs40l26->regmap, CS40L26_REVID, &cs40l26->revid); + if (ret) + return ret; + + switch (cs40l26->devid) { + case CS40L26_DEVID_L26: + if (cs40l26->revid != CS40L26_REVID_A1 && cs40l26->revid != CS40L26_REVID_B1) + goto err; + + cs40l26->variant = &cs40l26_a1_b1_variant; + break; + case CS40L26_DEVID_L27: + if (cs40l26->revid != CS40L26_REVID_B2) + goto err; + + cs40l26->variant = &cs40l26_b2_variant; + break; + default: + dev_err(cs40l26->dev, "Invalid device ID 0x%06X\n", cs40l26->devid); + return -EINVAL; + } + + dev_info(cs40l26->dev, "Cirrus Logic CS40L26 ID: 0x%06X, Revision: 0x%02X\n", + cs40l26->devid, cs40l26->revid); + + return 0; + +err: + dev_err(cs40l26->dev, "Invalid revision 0x%02X for device 0x%06X\n", cs40l26->revid, + cs40l26->devid); + return -EINVAL; +} + +int cs40l26_set_pll_loop(struct cs40l26 *cs40l26, const u32 pll_loop) +{ + int i; + + /* Retry in case DSP is hibernating */ + for (i = 0; i < CS40L26_PLL_NUM_SET_ATTEMPTS; i++) { + if (!regmap_update_bits(cs40l26->regmap, CS40L26_REFCLK_INPUT, + CS40L26_PLL_REFCLK_LOOP_MASK, + pll_loop << CS40L26_PLL_REFCLK_LOOP_SHIFT)) + break; + } + + if (i == CS40L26_PLL_NUM_SET_ATTEMPTS) { + dev_err(cs40l26->dev, "Failed to configure PLL\n"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_set_pll_loop); + +static int cs40l26_wseq_init(struct cs40l26 *cs40l26) +{ + struct cs_dsp *dsp = &cs40l26->dsp; + + cs40l26->wseqs[CS40L26_WSEQ_POWER_ON].ctl = + cs_dsp_get_ctl(dsp, "POWER_ON_SEQUENCE", WMFW_ADSP2_XM, CS40L26_PM_ALGO_ID); + if (!cs40l26->wseqs[CS40L26_WSEQ_POWER_ON].ctl) { + dev_err(cs40l26->dev, "POWER_ON write sequence not found\n"); + return -EINVAL; + } + + cs40l26->wseqs[CS40L26_WSEQ_ACTIVE].ctl = + cs_dsp_get_ctl(dsp, "ACTIVE_SEQUENCE", WMFW_ADSP2_XM, CS40L26_PM_ALGO_ID); + if (!cs40l26->wseqs[CS40L26_WSEQ_ACTIVE].ctl) { + dev_err(cs40l26->dev, "ACTIVE write sequence not found\n"); + return -EINVAL; + } + + cs40l26->wseqs[CS40L26_WSEQ_STANDBY].ctl = + cs_dsp_get_ctl(dsp, "STANDBY_SEQUENCE", WMFW_ADSP2_XM, CS40L26_PM_ALGO_ID); + if (!cs40l26->wseqs[CS40L26_WSEQ_STANDBY].ctl) { + dev_err(cs40l26->dev, "STANDBY write sequence not found\n"); + return -EINVAL; + } + + return cs_dsp_wseq_init(dsp, cs40l26->wseqs, CS40L26_NUM_WSEQS); +} + +static int cs40l26_wksrc_config(struct cs40l26 *cs40l26) +{ + u32 wksrc; + int ret; + + if (!strncmp(cs40l26->bus->name, "spi", strlen(cs40l26->bus->name))) { + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_WKSRC_STS_SPI); + wksrc = CS40L26_WKSRC_POL_SPI | CS40L26_WKSRC_EN_SPI; + } else if (!strncmp(cs40l26->bus->name, "i2c", strlen(cs40l26->bus->name))) { + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_WKSRC_STS_I2C); + wksrc = CS40L26_WKSRC_EN_I2C; + } else { + dev_err(cs40l26->dev, "Invalid bus type\n"); + return -EINVAL; + } + + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_WKSRC_STS_GPIO1); + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_WKSRC_STS_ANY); + + ret = regmap_write(cs40l26->regmap, CS40L26_WAKESRC_CTL, wksrc); + if (ret) + return ret; + + return cs_dsp_wseq_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + CS40L26_WAKESRC_CTL, wksrc, CS_DSP_WSEQ_L16, true); +} + +static inline void cs40l26_gpio_config(struct cs40l26 *cs40l26) +{ + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_GPIO1_RISE); + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_GPIO1_FALL); +} + +static int cs40l26_bst_ipk_config(struct cs40l26 *cs40l26) +{ + u32 bst_ipk; + int ret; + + bst_ipk = (clamp_val(cs40l26->bst_ipk_ua, CS40L26_BST_IPK_UA_MIN, CS40L26_BST_IPK_UA_MAX) - + CS40L26_BST_IPK_UA_OFFSET) / CS40L26_BST_IPK_UA_STEP; + + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_BST_IPK_FLAG); + + ret = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, bst_ipk); + if (ret) + return ret; + + return cs_dsp_wseq_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + CS40L26_BST_IPK_CTL, bst_ipk, CS_DSP_WSEQ_L16, true); +} + +static int cs40l26_bst_ctl_config(struct cs40l26 *cs40l26) +{ + u32 bst_ctl, bst_ctl_lim; + int ret; + + ret = regmap_read(cs40l26->regmap, CS40L26_VBST_CTL_2, &bst_ctl_lim); + if (ret) + return ret; + + bst_ctl_lim |= FIELD_PREP(CS40L26_BST_CTL_LIM_EN_MASK, CS40L26_BST_CTL_LIM_EN); + + ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_2, bst_ctl_lim); + if (ret) + return ret; + + ret = cs_dsp_wseq_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + CS40L26_VBST_CTL_2, bst_ctl_lim, CS_DSP_WSEQ_L16, true); + if (ret) + return ret; + + bst_ctl = (clamp_val(cs40l26->vbst_uv, CS40L26_BST_UV_MIN, CS40L26_BST_UV_MAX) - + CS40L26_BST_UV_MIN) / CS40L26_BST_UV_STEP; + + ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, bst_ctl); + if (ret) + return ret; + + return cs_dsp_wseq_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + CS40L26_VBST_CTL_1, bst_ctl, CS_DSP_WSEQ_L16, true); +} + +static int cs40l26_irq_init(struct cs40l26 *cs40l26) +{ + int i, ret; + + /* Unmask relevant warnings and error interrupts */ + for (i = CS40L26_IRQ_BST_OVP_FLAG_RISE; i <= CS40L26_IRQ_AMP_ERR; i++) + cs40l26_irq_unmask(cs40l26, 1, i); + + cs40l26_irq_unmask(cs40l26, 1, CS40L26_IRQ_DSP_RX_QUEUE); + + for (i = CS40L26_IRQ_VPMON_CLIPPED; i <= CS40L26_IRQ_IMON_CLIPPED; i++) + cs40l26_irq_unmask(cs40l26, 2, i); + + for (i = CS40L26_IRQ_REFCLK_PRESENT; i <= CS40L26_IRQ_REFCLK_MISSING_RISE; i++) + cs40l26_irq_unmask(cs40l26, 2, i); + + ret = regmap_multi_reg_write(cs40l26->regmap, cs40l26_irq_masks, 2); + if (ret) + return ret; + + ret = cs_dsp_wseq_multi_write(&cs40l26->dsp, &cs40l26->wseqs[CS40L26_WSEQ_POWER_ON], + cs40l26_irq_masks, 2, CS_DSP_WSEQ_FULL, true); + if (ret) + return ret; + + ret = devm_request_threaded_irq(cs40l26->dev, cs40l26->irq, NULL, cs40l26_irq_handler, + IRQF_ONESHOT, "cs40l26", cs40l26); + if (ret) + dev_err(cs40l26->dev, "Failed to request IRQ\n"); + + return ret; +} + +static int cs40l26_hw_init(struct cs40l26 *cs40l26) +{ + int ret; + + cs40l26->irq_masks = cs40l26_irq_masks; + + ret = cs40l26_wksrc_config(cs40l26); + if (ret) + return ret; + + cs40l26_gpio_config(cs40l26); + + ret = cs40l26_bst_ipk_config(cs40l26); + if (ret) + return ret; + + ret = cs40l26_bst_ctl_config(cs40l26); + if (ret) + return ret; + + return cs40l26_irq_init(cs40l26); +} + +static int cs40l26_cs_dsp_pre_run(struct cs_dsp *dsp) +{ + struct cs40l26 *cs40l26 = container_of(dsp, struct cs40l26, dsp); + int ret; + + ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_STANDBY, 100); + if (ret) { + dev_err(cs40l26->dev, "Failed to set standby timeout\n"); + return ret; + } + + ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_ACTIVE, 250); + if (ret) { + dev_err(cs40l26->dev, "Failed to set active timeout\n"); + return ret; + } + + ret = regmap_set_bits(cs40l26->regmap, CS40L26_PWRMGT_CTL, CS40L26_MEM_RDY); + if (ret) { + dev_err(cs40l26->dev, "Failed to set MEM_RDY\n"); + return ret; + } + + ret = cs40l26_fw_read(dsp, "QUEUE_BASE", CS40L26_DSP_ALGO_ID, &cs40l26->queue_base); + if (ret) + return ret; + + ret = cs40l26_fw_read(dsp, "QUEUE_LEN", CS40L26_DSP_ALGO_ID, &cs40l26->queue_len); + if (ret) + return ret; + + cs40l26->queue_last = cs40l26->queue_base + ((cs40l26->queue_len - 1) * sizeof(u32)); + + ret = cs40l26_fw_write(dsp, "CALL_RAM_INIT", dsp->fw_id, 1); + if (ret) + return ret; + + ret = cs40l26_wseq_init(cs40l26); + if (ret) + return ret; + + if (cs40l26->variant->handle_errata) + return cs40l26->variant->handle_errata(cs40l26); + else + return 0; +} + +static int cs40l26_cs_dsp_post_run(struct cs_dsp *dsp) +{ + struct cs40l26 *cs40l26 = container_of(dsp, struct cs40l26, dsp); + u32 halo_state; + int ret; + + /* + * cs_dsp_halo_start_core() has reset the DSP core at this point. + * Hibernation must be disabled again. + */ + ret = cs40l26_prevent_hiber(cs40l26); + if (ret) + return ret; + + ret = cs40l26_fw_read(dsp, "HALO_STATE", dsp->fw_id, &halo_state); + if (ret) + return ret; + + if (halo_state != CS40L26_DSP_HALO_STATE_RUN) { + dev_err(dsp->dev, "Invalid DSP state: %u\n", halo_state); + return -EINVAL; + } + + ret = cs40l26_hw_init(cs40l26); + if (ret) + return ret; + + dev_dbg(dsp->dev, "CS40L26/L27 DSP started successfully\n"); + + ret = devm_mfd_add_devices(cs40l26->dev, PLATFORM_DEVID_AUTO, cs40l26_devs, + ARRAY_SIZE(cs40l26_devs), NULL, 0, NULL); + if (ret) + dev_err(cs40l26->dev, "Failed to add MFD child devices: %d\n", ret); + + return ret; +} + +static const struct cs_dsp_client_ops cs40l26_cs_dsp_client_ops = { + .pre_run = cs40l26_cs_dsp_pre_run, + .post_run = cs40l26_cs_dsp_post_run, +}; + +static void cs40l26_cs_dsp_remove(void *data) +{ + cs_dsp_remove((struct cs_dsp *)data); +} + +static struct cs_dsp_coeff_desc cs40l26_coeffs[] = { + { .coeff_firmware = NULL, .coeff_filename = "cs40l26.bin" }, + { .coeff_firmware = NULL, .coeff_filename = "cs40l26-svc.bin" }, + { .coeff_firmware = NULL, .coeff_filename = "cs40l26-dvl.bin" }, +}; + +static int cs40l26_cs_dsp_init(struct cs40l26 *cs40l26) +{ + struct cs_dsp *dsp = &cs40l26->dsp; + int ret; + + dsp->num = 1; + dsp->type = WMFW_HALO; + dsp->dev = cs40l26->dev; + dsp->regmap = cs40l26->regmap; + dsp->base = CS40L26_DSP_CTRL_BASE; + dsp->base_sysinfo = CS40L26_DSP1_SYS_INFO_ID; + dsp->mem = cs40l26_dsp_regions; + dsp->num_mems = ARRAY_SIZE(cs40l26_dsp_regions); + dsp->client_ops = &cs40l26_cs_dsp_client_ops; + + ret = cs_dsp_halo_init(dsp); + if (ret) { + dev_err(cs40l26->dev, "Failed to initialize HALO core\n"); + return ret; + } + + return devm_add_action_or_reset(cs40l26->dev, cs40l26_cs_dsp_remove, dsp); +} + +static void cs40l26_dsp_start(struct cs40l26 *cs40l26) +{ + int i, ret; + + ret = cs40l26_dsp_pre_config(cs40l26); + if (ret) { + dev_err(cs40l26->dev, "DSP Pre Config. Failed: %d\n", ret); + goto err_fw_rls; + } + + guard(mutex)(&cs40l26->lock); + + ret = cs_dsp_power_up_multiple(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw", cs40l26_coeffs, + CS40L26_NUM_COEFF_FILES, "cs40l26"); + if (ret) { + dev_err(cs40l26->dev, "Failed to Power Up DSP\n"); + goto err_fw_rls; + } + + if (cs40l26->dsp.fw_id != CS40L26_FW_ID) { + dev_err(cs40l26->dev, "Invalid firmware ID: 0x%X\n", cs40l26->dsp.fw_id); + goto err_fw_rls; + } + + if (cs40l26->dsp.fw_id_version < cs40l26->variant->info->fw_min_rev) { + dev_err(cs40l26->dev, "Invalid firmware revision: 0x%X\n", + cs40l26->dsp.fw_id_version); + goto err_fw_rls; + } + + ret = cs_dsp_run(&cs40l26->dsp); + if (ret) + dev_err(cs40l26->dev, "DSP Failed to run: %d\n", ret); + +err_fw_rls: + for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++) + release_firmware(cs40l26_coeffs[i].coeff_firmware); + + release_firmware(cs40l26->wmfw); +} + +static void cs40l26_fw_upload(const struct firmware *wmfw, void *context) +{ + struct cs40l26 *cs40l26 = (struct cs40l26 *)context; + const struct firmware *coeff; + int i, ret; + + if (!wmfw) { + dev_err(cs40l26->dev, "Failed to request firmware file\n"); + return; + } + + cs40l26->wmfw = wmfw; + + for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++) { + ret = request_firmware(&coeff, cs40l26_coeffs[i].coeff_filename, cs40l26->dev); + if (ret) + continue; + + cs40l26_coeffs[i].coeff_firmware = coeff; + } + + return cs40l26_dsp_start(cs40l26); +} + +static int cs40l26_init(struct cs40l26 *cs40l26) +{ + int ret; + + cs40l26->bst_ipk_ua = CS40L26_BST_IPK_UA_DEFAULT; + cs40l26->vbst_uv = CS40L26_BST_UV_MAX; + /* + * Set the PLL to open-loop and remove default GPI mappings to prevent DSP lockup while + * the driver configures RAM firmware. + * + * The firmware will set the PLL back to closed-loop when the DSP has been started. + */ + ret = cs40l26_set_pll_loop(cs40l26, CS40L26_PLL_OPEN); + if (ret) + return ret; + + ret = regmap_write(cs40l26->regmap, cs40l26->variant->info->event_map_1, + CS40L26_EVENT_MAP_GPI_DISABLE); + if (ret) + return ret; + + ret = regmap_write(cs40l26->regmap, cs40l26->variant->info->event_map_2, + CS40L26_EVENT_MAP_GPI_DISABLE); + if (ret) + return ret; + + /* Set LRA to HI-Z to avoid fault conditions */ + return regmap_set_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG, + CS40L26_SPK_DEFAULT_HIZ); +} + +static int cs40l26_parse_properties(struct cs40l26 *cs40l26) +{ + struct device *dev = cs40l26->dev; + int ret; + + ret = device_property_read_u32(dev, "cirrus,bst-ctl-microvolt", &cs40l26->vbst_uv); + if (ret && ret != -EINVAL) + return ret; + + ret = device_property_read_u32(dev, "cirrus,bst-ipk-microamp", &cs40l26->bst_ipk_ua); + if (ret && ret != -EINVAL) + return ret; + + return 0; +} + +int cs40l26_probe(struct cs40l26 *cs40l26) +{ + int ret; + + mutex_init(&cs40l26->lock); + + cs40l26->reset_gpio = devm_gpiod_get_optional(cs40l26->dev, "reset", GPIOD_OUT_HIGH); + if (!cs40l26->reset_gpio) + return dev_err_probe(cs40l26->dev, -EINVAL, "Failed to get reset GPIO\n"); + + ret = devm_regulator_bulk_get_enable(cs40l26->dev, ARRAY_SIZE(cs40l26_supplies), + cs40l26_supplies); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to get supplies\n"); + + usleep_range(CS40L26_MIN_RESET_PULSE_US, CS40L26_MIN_RESET_PULSE_US + 100); + + gpiod_set_value_cansleep(cs40l26->reset_gpio, 0); + + usleep_range(CS40L26_CP_READY_DELAY_US, CS40L26_CP_READY_DELAY_US + 100); + + ret = cs40l26_get_model(cs40l26); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to get part number\n"); + + ret = cs40l26_prevent_hiber(cs40l26); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to prevent hibernation\n"); + + ret = cs40l26_init(cs40l26); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to initialize device\n"); + + ret = cs40l26_parse_properties(cs40l26); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to parse devicetree\n"); + + ret = cs40l26_cs_dsp_init(cs40l26); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to initialize CS DSP\n"); + + ret = cs40l26_pm_runtime_setup(cs40l26->dev); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to set up PM Runtime\n"); + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, "cs40l26.wmfw", cs40l26->dev, + GFP_KERNEL, cs40l26, cs40l26_fw_upload); + if (ret) + return dev_err_probe(cs40l26->dev, ret, "Failed to load firmware\n"); + + cs40l26_pm_exit(cs40l26->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_probe); + +static int __maybe_unused cs40l26_suspend(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + guard(mutex)(&cs40l26->lock); + + dev_dbg(dev, "%s: Enabling hibernation\n", __func__); + + cs40l26->wksrc_sts = 0x00; + + /* Don't poll DSP since reading for ACK will wake the device again */ + return regmap_write(cs40l26->regmap, CS40L26_DSP_QUEUE, CS40L26_DSP_CMD_ALLOW_HIBER); +} + +static int __maybe_unused cs40l26_sys_suspend(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + dev_dbg(dev, "System suspend, disabling IRQ\n"); + + disable_irq(cs40l26->irq); + + return 0; +} + +static int __maybe_unused cs40l26_sys_suspend_noirq(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + dev_dbg(dev, "Late system suspend, re-enabling IRQ\n"); + + enable_irq(cs40l26->irq); + + return 0; +} + +static int __maybe_unused cs40l26_resume(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + dev_dbg(dev, "%s: Disabling hibernation\n", __func__); + + guard(mutex)(&cs40l26->dsp.pwr_lock); + + return cs40l26_prevent_hiber(cs40l26); +} + +static int __maybe_unused cs40l26_sys_resume(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + dev_dbg(dev, "System resume, re-enabling IRQ\n"); + + enable_irq(cs40l26->irq); + + return 0; +} + +static int __maybe_unused cs40l26_sys_resume_noirq(struct device *dev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(dev); + + dev_dbg(dev, "Early system resume, disabling IRQ\n"); + + disable_irq(cs40l26->irq); + + return 0; +} + +EXPORT_GPL_DEV_PM_OPS(cs40l26_pm_ops) = { + RUNTIME_PM_OPS(cs40l26_suspend, cs40l26_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend, cs40l26_sys_resume) + NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend_noirq, cs40l26_sys_resume_noirq) +}; + +MODULE_DESCRIPTION("CS40L26 Boosted Class D Amplifier for Haptics"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("FW_CS_DSP"); diff --git a/drivers/mfd/cs40l26-i2c.c b/drivers/mfd/cs40l26-i2c.c new file mode 100644 index 000000000000..c6e4118775a2 --- /dev/null +++ b/drivers/mfd/cs40l26-i2c.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L26 Boosted Haptic Driver with Integrated DSP and + * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection + * + * Copyright 2025 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#include +#include + +static int cs40l26_i2c_probe(struct i2c_client *i2c) +{ + struct cs40l26 *cs40l26; + + cs40l26 = devm_kzalloc(&i2c->dev, sizeof(struct cs40l26), GFP_KERNEL); + if (!cs40l26) + return -ENOMEM; + + i2c_set_clientdata(i2c, cs40l26); + + cs40l26->dev = &i2c->dev; + cs40l26->irq = i2c->irq; + cs40l26->bus = &i2c_bus_type; + + cs40l26->regmap = devm_regmap_init_i2c(i2c, &cs40l26_regmap); + if (IS_ERR(cs40l26->regmap)) + return dev_err_probe(cs40l26->dev, PTR_ERR(cs40l26->regmap), + "Failed to allocate register map\n"); + + return cs40l26_probe(cs40l26); +} + +static const struct i2c_device_id cs40l26_id_i2c[] = { + { "cs40l26a", 0 }, + { "cs40l27b", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs40l26_id_i2c); + +static const struct of_device_id cs40l26_of_match[] = { + { .compatible = "cirrus,cs40l26a" }, + { .compatible = "cirrus,cs40l27b" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l26_of_match); + +static struct i2c_driver cs40l26_i2c_driver = { + .driver = { + .name = "cs40l26", + .of_match_table = cs40l26_of_match, + .pm = pm_ptr(&cs40l26_pm_ops), + }, + .id_table = cs40l26_id_i2c, + .probe = cs40l26_i2c_probe, +}; +module_i2c_driver(cs40l26_i2c_driver); + +MODULE_DESCRIPTION("CS40L26 I2C Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cs40l26-spi.c b/drivers/mfd/cs40l26-spi.c new file mode 100644 index 000000000000..57fc92356d9d --- /dev/null +++ b/drivers/mfd/cs40l26-spi.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CS40L26 Boosted Haptic Driver with Integrated DSP and + * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection + * + * Copyright 2025 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#include +#include + +static int cs40l26_spi_probe(struct spi_device *spi) +{ + struct cs40l26 *cs40l26; + + cs40l26 = devm_kzalloc(&spi->dev, sizeof(struct cs40l26), GFP_KERNEL); + if (!cs40l26) + return -ENOMEM; + + spi_set_drvdata(spi, cs40l26); + + cs40l26->dev = &spi->dev; + cs40l26->irq = spi->irq; + cs40l26->bus = &spi_bus_type; + + cs40l26->regmap = devm_regmap_init_spi(spi, &cs40l26_regmap); + if (IS_ERR(cs40l26->regmap)) + return dev_err_probe(cs40l26->dev, PTR_ERR(cs40l26->regmap), + "Failed to allocate register map\n"); + + return cs40l26_probe(cs40l26); +} + +static const struct spi_device_id cs40l26_id_spi[] = { + { "cs40l26a", 0 }, + { "cs40l27b", 1 }, + {} +}; +MODULE_DEVICE_TABLE(spi, cs40l26_id_spi); + +static const struct of_device_id cs40l26_of_match[] = { + { .compatible = "cirrus,cs40l26a" }, + { .compatible = "cirrus,cs40l27b" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l26_of_match); + +static struct spi_driver cs40l26_spi_driver = { + .driver = { + .name = "cs40l26", + .of_match_table = cs40l26_of_match, + .pm = pm_ptr(&cs40l26_pm_ops), + }, + .id_table = cs40l26_id_spi, + .probe = cs40l26_spi_probe, +}; +module_spi_driver(cs40l26_spi_driver); + +MODULE_DESCRIPTION("CS40L26 SPI Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/cs40l26.h b/include/linux/mfd/cs40l26.h new file mode 100644 index 000000000000..c0647c09e24d --- /dev/null +++ b/include/linux/mfd/cs40l26.h @@ -0,0 +1,341 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * CS40L26 Boosted Haptic Driver with Integrated DSP and + * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection + * + * Copyright 2025 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#ifndef __MFD_CS40L26_H__ +#define __MFD_CS40L26_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register Addresses */ +#define CS40L26_LASTREG 0x3C7DFE8 +#define CS40L26_DEVID 0x0 +#define CS40L26_REVID 0x4 +#define CS40L26_GLOBAL_ENABLES 0x2014 +#define CS40L26_ERROR_RELEASE 0x2034 +#define CS40L26_PWRMGT_CTL 0x2900 +#define CS40L26_WAKESRC_CTL 0x2904 +#define CS40L26_PWRMGT_STS 0x290C +#define CS40L26_REFCLK_INPUT 0x2C04 +#define CS40L26_PLL_REFCLK_DETECT_0 0x2C28 +#define CS40L26_VBST_CTL_1 0x3800 +#define CS40L26_VBST_CTL_2 0x3804 +#define CS40L26_BST_IPK_CTL 0x3808 +#define CS40L26_TEST_LBST 0x391C +#define CS40L26_DAC_MSM_CONFIG 0x7400 +#define CS40L26_TST_DAC_MSM_CONFIG 0x7404 +#define CS40L26_IRQ1_STATUS 0x10004 +#define CS40L26_IRQ1_EINT_1 0x10010 +#define CS40L26_IRQ1_EINT_2 0x10014 +#define CS40L26_IRQ1_MASK_1 0x10110 +#define CS40L26_IRQ1_MASK_2 0x10114 +#define CS40L26_DSP_QUEUE 0x13020 +#define CS40L26_DSP1_XMEM_PACKED_0 0x2000000 +#define CS40L26_DSP1_SYS_INFO_ID 0x25E0000 +#define CS40L26_DSP1_XMEM_UNPACKED24_0 0x2800000 +#define CS40L26_DSP1_CCM_CORE_CONTROL 0x2BC1000 +#define CS40L26_DSP1_YMEM_PACKED_0 0x2C00000 +#define CS40L26_DSP1_YMEM_UNPACKED32_0 0x3000000 +#define CS40L26_DSP1_YMEM_UNPACKED24_0 0x3400000 +#define CS40L26_DSP1_PMEM_0 0x3800000 + +/* Device */ +#define CS40L26_DEVID_L26 0x40A260 +#define CS40L26_DEVID_L27 0x40A270 +#define CS40L26_REVID_A1 0xA1 +#define CS40L26_REVID_B1 0xB1 +#define CS40L26_REVID_B2 0xB2 +#define CS40L26_MIN_RESET_PULSE_US 1500 +#define CS40L26_CP_READY_DELAY_US 6000 +#define CS40L26_SPK_DEFAULT_HIZ BIT(28) +#define CS40L26_DSP_CCM_CORE_KILL 0x00000080 +#define CS40L26_MEM_RDY BIT(1) + +/* Errata */ +#define CS40L26_DISABLE_EXPL_MODE 0x0100C080 + +#define CS40L26_PLL_REFCLK_DET_DISABLE 0x0 + +/* Boost Converter Control */ +#define CS40L26_GLOBAL_EN BIT(0) + +#define CS40L26_BST_IPK_UA_MAX 4800000 +#define CS40L26_BST_IPK_UA_DEFAULT 4500000 +#define CS40L26_BST_IPK_UA_MIN 1600000 +#define CS40L26_BST_IPK_UA_STEP 50000 +#define CS40L26_BST_IPK_UA_OFFSET 800000 + +#define CS40L26_BST_UV_MIN 2500000 +#define CS40L26_BST_UV_MAX 11000000 +#define CS40L26_BST_UV_STEP 50000 + +#define CS40L26_BST_CTL_VP 0x00 +#define CS40L26_BST_CTL_MASK GENMASK(7, 0) +#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0) +#define CS40L26_BST_CTL_SEL_FIXED 0x0 +#define CS40L26_BST_CTL_LIM_EN_MASK BIT(2) +#define CS40L26_BST_CTL_LIM_EN 1 + +#define CS40L26_BST_TIME_US 10000 + +/* Phase Locked Loop */ +#define CS40L26_PLL_REFCLK_LOOP_MASK BIT(11) +#define CS40L26_PLL_REFCLK_LOOP_SHIFT 11 +#define CS40L26_PLL_NUM_SET_ATTEMPTS 5 + +/* GPIO */ +#define CS40L26_EVENT_MAP_GPI_DISABLE 0x1FF + +#define CS40L26_A1_B1_EVENT_MAP_1 0x02806FC4 +#define CS40L26_A1_B1_EVENT_MAP_2 0x02806FC8 + +#define CS40L26_B2_EVENT_MAP_1 0x02806FB0 +#define CS40L26_B2_EVENT_MAP_2 0x02806FB4 + +/* Power Management */ +#define CS40L26_PM_STDBY_TICKS_OFFSET 16 +#define CS40L26_PM_ACTIVE_TICKS_OFFSET 24 + +#define CS40L26_A1_B1_PM_CUR_STATE 0x02800370 +#define CS40L26_A1_B1_PM_STATE_LOCKS 0x02800378 +#define CS40L26_A1_B1_PM_STATE_LOCKS3 (CS40L26_A1_B1_PM_STATE_LOCKS + \ + CS40L26_DSP_LOCK3_OFFSET_BYTES) + +#define CS40L26_A1_B1_PM_TIMEOUT_TICKS 0x02800350 +#define CS40L26_A1_B1_PM_STDBY_TICKS (CS40L26_A1_B1_PM_TIMEOUT_TICKS + \ + CS40L26_PM_STDBY_TICKS_OFFSET) +#define CS40L26_A1_B1_PM_ACTIVE_TICKS (CS40L26_A1_B1_PM_TIMEOUT_TICKS + \ + CS40L26_PM_ACTIVE_TICKS_OFFSET) + +#define CS40L26_A1_B1_HALO_STATE 0x02800FA8 + +#define CS40L26_B2_PM_CUR_STATE 0x02801F98 +#define CS40L26_B2_PM_STATE_LOCKS 0x02801FA0 +#define CS40L26_B2_PM_STATE_LOCKS3 (CS40L26_B2_PM_STATE_LOCKS + CS40L26_DSP_LOCK3_OFFSET_BYTES) +#define CS40L26_B2_PM_TIMEOUT_TICKS 0x02801F78 +#define CS40L26_B2_PM_STDBY_TICKS (CS40L26_B2_PM_TIMEOUT_TICKS + \ + CS40L26_PM_STDBY_TICKS_OFFSET) +#define CS40L26_B2_PM_ACTIVE_TICKS (CS40L26_B2_PM_TIMEOUT_TICKS + \ + CS40L26_PM_ACTIVE_TICKS_OFFSET) + +#define CS40L26_B2_HALO_STATE 0x02806AF8 + +#define CS40L26_AUTOSUSPEND_DELAY_MS 2000 +#define CS40L26_PM_TICKS_PER_SEC 32768 + +/* Firmware Handling */ +#define CS40L26_FW_ID 0x1800D4 +#define CS40L26_FW_A1_B1_MIN_REV 0x070247 +#define CS40L26_FW_B2_MIN_REV 0x0A0000 + +#define CS40L26_NUM_COEFF_FILES 3 + +/* Algorithms */ +#define CS40L26_VIBEGEN_ALGO_ID_A1 0x000400BD +#define CS40L26_VIBEGEN_ALGO_ID_B2 0x000A00BD + +#define CS40L26_BUZZGEN_ALGO_ID 0x0004F202 +#define CS40L26_A2H_ALGO_ID 0x00040110 +#define CS40L26_EXT_ALGO_ID 0x0004013C +#define CS40L26_DSP_ALGO_ID 0x0004F203 +#define CS40L26_PM_ALGO_ID 0x0004F206 + +/* DSP */ +#define CS40L26_DSP_LOCK3_OFFSET_BYTES 8 +#define CS40L26_DSP_LOCK3_OFFSET_WORDS (CS40L26_DSP_LOCK3_OFFSET_BYTES / sizeof(u32)) +#define CS40L26_DSP_LOCK3_MASK BIT(1) +#define CS40L26_DSP_HALO_STATE_RUN 2 +#define CS40L26_DSP_CTRL_BASE 0x2B80000 +#define CS40L26_DSP_POLL_US 1000 +#define CS40L26_DSP_TIMEOUT_COUNT 100 +#define CS40L26_PM_LOCKS_TIMEOUT_COUNT 10 +#define CS40L26_DSP_STATE_TIMEOUT_COUNT 10 + +#define CS40L26_DSP_CMD_PREVENT_HIBER 0x02000003 +#define CS40L26_DSP_CMD_ALLOW_HIBER 0x02000004 +#define CS40L26_DSP_CMD_INDEX_MASK GENMASK(28, 24) +#define CS40L26_DSP_CMD_PAYLOAD_MASK GENMASK(23, 0) + +#define CS40L26_DSP_COMPLETE_CP 0x01000000 +#define CS40L26_DSP_COMPLETE_I2S 0x01000002 +#define CS40L26_DSP_TRIGGER_CP 0x01000010 +#define CS40L26_DSP_TRIGGER_I2S 0x01000012 +#define CS40L26_DSP_PM_AWAKE 0x02000002 +#define CS40L26_DSP_SYS_ACK 0x0A000000 +#define CS40L26_DSP_PANIC 0x0C000000 + +/* Wake Sources */ +#define CS40L26_WKSRC_STS_MASK GENMASK(9, 4) +#define CS40L26_WKSRC_STS_SHIFT 4 +#define CS40L26_WKSRC_STS_EN BIT(7) +#define CS40L26_WKSRC_POL_SPI BIT(4) +#define CS40L26_WKSRC_EN_SPI BIT(9) +#define CS40L26_WKSRC_EN_I2C BIT(10) +#define CS40L26_WKSRC_GPIO_POL_MASK GENMASK(3, 0) + +/* Interrupts */ +#define CS40L26_IRQ_GPIO1_RISE 0 +#define CS40L26_IRQ_GPIO1_FALL 1 +#define CS40L26_IRQ_WKSRC_STS_ANY 8 +#define CS40L26_IRQ_WKSRC_STS_GPIO1 9 +#define CS40L26_IRQ_WKSRC_STS_SPI 13 +#define CS40L26_IRQ_WKSRC_STS_I2C 14 +#define CS40L26_IRQ_BST_OVP_FLAG_RISE 18 +#define CS40L26_IRQ_BST_OVP_FLAG_FALL 19 +#define CS40L26_IRQ_BST_OVP_ERR 20 +#define CS40L26_IRQ_BST_DCM_UVP_ERR 21 +#define CS40L26_IRQ_BST_SHORT_ERR 22 +#define CS40L26_IRQ_BST_IPK_FLAG 23 +#define CS40L26_IRQ_TEMP_WARN_RISE 24 +#define CS40L26_IRQ_TEMP_WARN_FALL 25 +#define CS40L26_IRQ_TEMP_ERR 26 +#define CS40L26_IRQ_AMP_ERR 27 +#define CS40L26_IRQ_DSP_RX_QUEUE 31 + +#define CS40L26_IRQ_1_NBITS 32 + +#define CS40L26_IRQ_REFCLK_PRESENT 6 +#define CS40L26_IRQ_REFCLK_MISSING_FALL 7 +#define CS40L26_IRQ_REFCLK_MISSING_RISE 8 +#define CS40L26_IRQ_VPMON_CLIPPED 23 +#define CS40L26_IRQ_VBSTMON_CLIPPED 24 +#define CS40L26_IRQ_VMON_CLIPPED 25 +#define CS40L26_IRQ_IMON_CLIPPED 26 + +#define CS40L26_IRQ_2_NBITS 30 + +#define CS40L26_IRQ_1_ALL_MASKED 0xFFFFFFFF +#define CS40L26_IRQ_2_ALL_MASKED 0x3FFFFFFF + +#define CS40L26_IRQ_STATUS_ASSERT 0x1 + +/* Playback */ +#define CS40L26_STOP_PLAYBACK 0x05000000 + +#define CS40L26_START_I2S 0x03000002 +#define CS40L26_STOP_I2S 0x03000003 + +/* Error Release */ +enum cs40l26_error { + CS40L26_ERROR_NONE, + CS40L26_ERROR_AMP_SHORT, + CS40L26_ERROR_BST_SHORT, + CS40L26_ERROR_BST_OVP, + CS40L26_ERROR_BST_DCM_UVP, + CS40L26_ERROR_TEMP_WARN, + CS40L26_ERROR_TEMP_ERR, +}; + +struct cs40l26_irq { + int virq; + u32 mask; + const char *name; + int (*handler)(void *data); +}; + +#define CS40L26_IRQ(_irq, _name, _hand) \ + { \ + .virq = CS40L26_IRQ_ ## _irq, \ + .mask = BIT(CS40L26_ ## IRQ_ ## _irq), \ + .name = _name, \ + .handler = _hand, \ + } + +enum cs40l26_dsp_state { + CS40L26_DSP_STATE_HIBERNATE, + CS40L26_DSP_STATE_SHUTDOWN, + CS40L26_DSP_STATE_STANDBY, + CS40L26_DSP_STATE_ACTIVE, + CS40L26_DSP_STATE_NONE, +}; + +enum cs40l26_gpio_map { + CS40L26_GPIO_MAP_A_PRESS, + CS40L26_GPIO_MAP_A_RELEASE, + CS40L26_GPIO_MAP_NUM_AVAILABLE, + CS40L26_GPIO_MAP_INVALID, +}; + +enum cs40l26_pll { + CS40L26_PLL_CLOSED, + CS40L26_PLL_OPEN, +}; + +enum cs40l50_wseqs { + CS40L26_WSEQ_POWER_ON, + CS40L26_WSEQ_ACTIVE, + CS40L26_WSEQ_STANDBY, + CS40L26_NUM_WSEQS, +}; + +struct cs40l26_variant_info { + u32 pm_cur_state; + u32 pm_state_locks; + u32 pm_state_locks3; + u32 pm_stdby_ticks; + u32 pm_active_ticks; + u32 halo_state; + u32 event_map_1; + u32 event_map_2; + u32 fw_min_rev; + u32 ram_ext_algo_id; + u32 vibegen_algo_id; +}; + +struct cs40l26_variant; + +struct cs40l26 { + struct device *dev; + struct regmap *regmap; + struct cs_dsp dsp; + int irq; + struct mutex lock; + struct gpio_desc *reset_gpio; + u32 devid; + u32 revid; + const struct cs40l26_variant *variant; + struct cs_dsp_wseq wseqs[CS40L26_NUM_WSEQS]; + u8 wksrc_sts; + u8 last_wksrc_pol; + u32 queue_base; + u32 queue_len; + u32 queue_last; + unsigned int bst_ipk_ua; + unsigned int vbst_uv; + const struct firmware *wmfw; + const struct bus_type *bus; + struct reg_sequence *irq_masks; +}; + +struct cs40l26_variant { + const struct cs40l26_variant_info *info; + int (*handle_errata)(struct cs40l26 *cs40l26); +}; + +inline void cs40l26_pm_exit(struct device *dev); +int cs40l26_probe(struct cs40l26 *cs40l26); +int cs40l26_set_pll_loop(struct cs40l26 *cs40l26, const u32 pll_loop); +int cs40l26_dsp_write(struct cs40l26 *cs40l26, const u32 val); +int cs40l26_dsp_state_get(struct cs40l26 *cs40l26, u32 *state); +inline int cs40l26_fw_write(struct cs_dsp *dsp, const char *const name, + const unsigned int algo_id, u32 val); +inline int cs40l26_fw_read(struct cs_dsp *dsp, const char *const name, + const unsigned int algo_id, u32 *buf); + +extern const struct regmap_config cs40l26_regmap; +extern const struct dev_pm_ops cs40l26_pm_ops; + +#endif /* __CS40L26_H__ */ From patchwork Tue Feb 4 23:18:35 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fredrik Treven X-Patchwork-Id: 861964 Received: from mx0b-001ae601.pphosted.com (mx0b-001ae601.pphosted.com [67.231.152.168]) (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 A0B6621C9EE; Tue, 4 Feb 2025 23:24:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=67.231.152.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711464; cv=none; b=kRs49NAXQKvunPI5PXs2K0Ab/DyH5tlP+bEl+t+f05+otqgLSstjmx9WXit8o65a6wcj7Gxx0Yk26uGeRVsdeKNsBNqrFZvmiqv9+dL3lpq9Sx4bLJ39a23+XORjjEaOzqCc7q8nJ6SwUK1SSIPpNgPyAIypFftNHMjlMCJiGYE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738711464; c=relaxed/simple; bh=sIln//XUWOCbv+jbIU+HIsNsEaNVSCpgYZ+FUr6PS8M=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Hg1t9IzOqyeYLtlXihshKrawWMgvGHalNbml6caktxC3vt4wefl/aUcAz+q77WjH641bx8LhmpZ8Zwd7T+o9KFVVN5OAPdIXD9jnMddPPbkSb8bBwbVnw5E8uspuFbuzFAUqu+42aXpO9hGoZxqhgWVa4Y54Bd4q/nL/oR4L7jY= 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=l+kysFiy; arc=none smtp.client-ip=67.231.152.168 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="l+kysFiy" Received: from pps.filterd (m0077474.ppops.net [127.0.0.1]) by mx0b-001ae601.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 514FmAYv022131; Tue, 4 Feb 2025 17:20:21 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s= PODMain02222019; bh=vbeaKiAV8j9TomN/Mh6JI2DXEEZVRCZpyRAxRLZvhJo=; b= l+kysFiyTFiepxtfC6QC6cvJ8HYwcd0ZrZWifK86VUUIH7Ru0k+PPuHnVFg6Epmr 4Xv1OrwFiv6DuYieJB/Fj45RUVBxPdEkbw9hb88sVkG4Auy+XHkzN7LbrK5FZBpd xcStX6aFgr1Ap96ONvHW/W7POcAbVcL7XBjzArYnecwpxjxSoep7G6egLNvMLU5t 2rQR/SmK90lRNnPTGYi2tsOx/ma+yVDC7ae5VbnctM+vrX8WdMfMPKadZr831IoY eFf98m6B8/zRDNKVONV9jH2avFHyUfB5bdk2tUBBHetNL0jZAX8v7/lYnzoGH6tO 4dXXM91+E3ev12GPN75odw== Received: from ediex02.ad.cirrus.com ([84.19.233.68]) by mx0b-001ae601.pphosted.com (PPS) with ESMTPS id 44hgwm3sqg-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 04 Feb 2025 17:20:21 -0600 (CST) 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.1544.14; Tue, 4 Feb 2025 23:20:19 +0000 Received: from ediswmail9.ad.cirrus.com (198.61.86.93) by anon-ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server id 15.2.1544.14 via Frontend Transport; Tue, 4 Feb 2025 23:20:14 +0000 Received: from ftrev.crystal.cirrus.com (ftrev.ad.cirrus.com [141.131.145.81]) by ediswmail9.ad.cirrus.com (Postfix) with ESMTP id 30309820248; Tue, 4 Feb 2025 23:20:10 +0000 (UTC) From: Fred Treven To: Lee Jones , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , "Simon Trimmer" , Charles Keepax , Richard Fitzgerald , Dmitry Torokhov , James Ogletree , Ben Bright , Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , David Rhodes , Jeff LaBundy , Heiko Stuebner , Karel Balej , Igor Prusov , Jack Yu , Weidong Wang , Binbin Zhou , Prasad Kumpatla , "Paul Handrigan" , Masahiro Yamada , Nuno Sa , Fred Treven CC: , , , , , Subject: [PATCH RESEND 6/7] ASoC: cs40l26: Support I2S streaming to CS40L26 Date: Tue, 4 Feb 2025 17:18:35 -0600 Message-ID: <20250204231835.2000457-7-ftreven@opensource.cirrus.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> References: <20250204231835.2000457-1-ftreven@opensource.cirrus.com> Precedence: bulk X-Mailing-List: linux-input@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Authority-Analysis: v=2.4 cv=EPv800ZC c=1 sm=1 tr=0 ts=67a2a0b5 cx=c_pps a=uGhh+3tQvKmCLpEUO+DX4w==:117 a=uGhh+3tQvKmCLpEUO+DX4w==:17 a=T2h4t0Lz3GQA:10 a=w1d2syhTAAAA:8 a=CNfp1fVX68x8Q3iYfccA:9 a=YXXWInSmI4Sqt1AkVdoW:22 X-Proofpoint-ORIG-GUID: lA0UnrLd6glWyAKyuQOnA1OYayb_B1pb X-Proofpoint-GUID: lA0UnrLd6glWyAKyuQOnA1OYayb_B1pb X-Proofpoint-Spam-Reason: safe Introduce codec support for Cirrus Logic Device CS40L26. The ASoC driver enables I2S streaming to the device. Signed-off-by: Fred Treven --- sound/soc/codecs/Kconfig | 12 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs40l26-codec.c | 523 +++++++++++++++++++++++++++++++ 3 files changed, 537 insertions(+) create mode 100644 sound/soc/codecs/cs40l26-codec.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ee35f3aa5521..850b5fab984c 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -77,6 +77,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_CS35L56_I2C imply SND_SOC_CS35L56_SPI imply SND_SOC_CS35L56_SDW + imply SND_SOC_CS40L26 imply SND_SOC_CS40L50 imply SND_SOC_CS42L42 imply SND_SOC_CS42L42_SDW @@ -875,6 +876,17 @@ config SND_SOC_CS35L56_SDW help Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control +config SND_SOC_CS40L26 + tristate "Cirrus Logic CS40L26 CODEC" + depends on MFD_CS40L26_CORE + help + This option enables support for I2S streaming to Cirrus Logic CS40L26. + + CS40L26 is a boosted haptic driver with integrated DSP and waveform + memory with advanced closed loop algorithms and LRA protection. + + If built as a module, it will be named snd-soc-cs40l26. + config SND_SOC_CS40L50 tristate "Cirrus Logic CS40L50 CODEC" depends on MFD_CS40L50_CORE diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d7ad795603c1..086e18964e60 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -80,6 +80,7 @@ snd-soc-cs35l56-shared-y := cs35l56-shared.o snd-soc-cs35l56-i2c-y := cs35l56-i2c.o snd-soc-cs35l56-spi-y := cs35l56-spi.o snd-soc-cs35l56-sdw-y := cs35l56-sdw.o +snd-soc-cs40l26-y := cs40l26-codec.o snd-soc-cs40l50-y := cs40l50-codec.o snd-soc-cs42l42-y := cs42l42.o snd-soc-cs42l42-i2c-y := cs42l42-i2c.o @@ -497,6 +498,7 @@ obj-$(CONFIG_SND_SOC_CS35L56_SHARED) += snd-soc-cs35l56-shared.o obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o +obj-$(CONFIG_SND_SOC_CS40L26) += snd-soc-cs40l26.o obj-$(CONFIG_SND_SOC_CS40L50) += snd-soc-cs40l50.o obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o diff --git a/sound/soc/codecs/cs40l26-codec.c b/sound/soc/codecs/cs40l26-codec.c new file mode 100644 index 000000000000..5bfaff0683a5 --- /dev/null +++ b/sound/soc/codecs/cs40l26-codec.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// CS40L26 Boosted Haptic Driver with integrated DSP and +// waveform memory with advanced closed loop algorithms and +// LRA protection +// +// Copyright 2025 Cirrus Logic Inc. +// +// Author: Fred Treven + +#include +#include +#include +#include + +#define CS40L26_MONITOR_FILT 0x4008 +#define CS40L26_ASP_ENABLES1 0x4800 +#define CS40L26_ASP_CONTROL2 0x4808 +#define CS40L26_ASP_FRAME_CONTROL5 0x4820 +#define CS40L26_ASP_DATA_CONTROL5 0x4840 +#define CS40L26_DACPCM1_INPUT 0x4C00 +#define CS40L26_ASPTX1_INPUT 0x4C20 + +#define CS40L26_PLL_CLK_SEL_BCLK 0x0 +#define CS40L26_PLL_CLK_SEL_MCLK 0x5 + +#define CS40L26_PLL_CLK_FREQ_MASK GENMASK(31, 0) + +#define CS40L26_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define CS40L26_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +#define CS40L26_ASP_RX_WIDTH_MASK GENMASK(31, 24) +#define CS40L26_ASP_FMT_MASK GENMASK(10, 8) +#define CS40L26_ASP_BCLK_INV_MASK BIT(6) +#define CS40L26_ASP_FSYNC_INV_MASK BIT(2) +#define CS40L26_ASP_FSYNC_INV_SHIFT 2 + +#define CS40L26_ASP_FMT_TDM1_DSPA 0x0 +#define CS40L26_ASP_FMT_I2S 0x2 + +#define CS40L26_PLL_REFCLK_BCLK 0x0 +#define CS40L26_PLL_REFCLK_FSYNC 0x1 +#define CS40L26_PLL_REFCLK_MCLK 0x5 + +#define CS40L26_PLL_REFCLK_SEL_MASK GENMASK(2, 0) +#define CS40L26_PLL_REFCLK_FREQ_MASK GENMASK(10, 5) +#define CS40L26_PLL_REFCLK_FREQ_SHIFT 5 +#define CS40L26_PLL_REFCLK_LOOP_MASK BIT(11) + +#define CS40L26_ASP_RX_WL_MASK GENMASK(5, 0) + +#define CS40L26_DATA_SRC_DSP1TX1 0x32 + +#define CS40L26_DATA_SRC_MASK GENMASK(6, 0) + +#define CS40L26_ASP_TX1_EN_MASK BIT(0) +#define CS40L26_ASP_TX2_EN_MASK BIT(1) +#define CS40L26_ASP_RX1_EN_MASK BIT(16) +#define CS40L26_ASP_RX2_EN_MASK BIT(17) +#define CS40L26_ASP_ENABLE_MASK \ + (CS40L26_ASP_TX1_EN_MASK | CS40L26_ASP_TX2_EN_MASK | CS40L26_ASP_RX1_EN_MASK | \ + CS40L26_ASP_RX2_EN_MASK) + +#define CS40L26_ASP_RX1_SLOT_MASK GENMASK(5, 0) +#define CS40L26_ASP_RX2_SLOT_MASK GENMASK(13, 8) + +#define CS40L26_VIMON_DUAL_RATE_MASK BIT(16) + +struct cs40l26_pll_sysclk_config { + u32 freq; + u8 cfg; +}; + +struct cs40l26_codec { + struct cs40l26 *core; + struct device *dev; + struct regmap *regmap; + unsigned int rate; + u32 daifmt; + int tdm_width; + int tdm_slot[2]; + u32 refclk_input; +}; + +static const struct cs40l26_pll_sysclk_config cs40l26_pll_sysclk[] = { + { 32768, 0x00 }, + { 1536000, 0x1B }, + { 3072000, 0x21 }, + { 6144000, 0x28 }, + { 9600000, 0x30 }, + { 12288000, 0x33 }, +}; + +static int cs40l26_get_clk_config(struct cs40l26_codec *codec, u32 freq, u8 *clk_cfg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs40l26_pll_sysclk); i++) { + if (cs40l26_pll_sysclk[i].freq == freq) { + *clk_cfg = cs40l26_pll_sysclk[i].cfg; + return 0; + } + } + + dev_err(codec->dev, "Invalid clock frequency: %u Hz\n", freq); + + return -EINVAL; +} + +static int cs40l26_swap_ext_clk(struct cs40l26_codec *codec, u8 clk_src) +{ + u8 clk_cfg, clk_sel; + int ret; + + switch (clk_src) { + case CS40L26_PLL_REFCLK_BCLK: + clk_sel = CS40L26_PLL_CLK_SEL_BCLK; + ret = cs40l26_get_clk_config(codec, codec->rate, &clk_cfg); + break; + case CS40L26_PLL_REFCLK_MCLK: + clk_sel = CS40L26_PLL_CLK_SEL_MCLK; + ret = cs40l26_get_clk_config(codec, 32768, &clk_cfg); + break; + case CS40L26_PLL_REFCLK_FSYNC: + ret = -EPERM; + break; + default: + ret = -EINVAL; + } + + if (ret) { + dev_err(codec->dev, "Failed to get clock configuration\n"); + return ret; + } + + ret = cs40l26_set_pll_loop(codec->core, CS40L26_PLL_OPEN); + if (ret) + return ret; + + ret = regmap_update_bits(codec->regmap, CS40L26_REFCLK_INPUT, + CS40L26_PLL_REFCLK_FREQ_MASK | CS40L26_PLL_REFCLK_SEL_MASK, + (clk_cfg << CS40L26_PLL_REFCLK_FREQ_SHIFT) | clk_sel); + if (ret) + return ret; + + return cs40l26_set_pll_loop(codec->core, CS40L26_PLL_CLOSED); +} + +static int cs40l26_clk_en(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + struct cs40l26 *cs40l26 = codec->core; + int ret; + + guard(mutex)(&cs40l26->lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = cs40l26_dsp_write(cs40l26, CS40L26_STOP_PLAYBACK); + if (ret) + return ret; + + ret = regmap_read(codec->regmap, CS40L26_REFCLK_INPUT, &codec->refclk_input); + if (ret) + return ret; + + ret = cs40l26_dsp_write(cs40l26, CS40L26_START_I2S); + if (ret) + return ret; + + ret = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_BCLK); + if (ret) + return ret; + break; + case SND_SOC_DAPM_PRE_PMD: + ret = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_MCLK); + if (ret) + return ret; + + /* Restore PLL Configuration */ + ret = cs40l26_set_pll_loop(cs40l26, (u32)FIELD_GET(CS40L26_PLL_REFCLK_LOOP_MASK, + codec->refclk_input)); + if (ret) + return ret; + break; + default: + dev_err(codec->dev, "Invalid event: %d\n", event); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_dsp_tx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + struct cs40l26 *cs40l26 = codec->core; + int ret; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = cs40l26_fw_write(&cs40l26->dsp, "A2HEN", CS40L26_A2H_ALGO_ID, 1); + break; + case SND_SOC_DAPM_PRE_PMD: + ret = cs40l26_fw_write(&cs40l26->dsp, "A2HEN", CS40L26_A2H_ALGO_ID, 0); + break; + default: + dev_err(codec->dev, "Invalid DSPTX event: %d\n", event); + ret = -EINVAL; + } + + return ret; +} + +static int cs40l26_asp_rx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + struct cs40l26_codec *codec; + struct cs40l26 *cs40l26; + int ret; + + codec = snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + + cs40l26 = codec->core; + + guard(mutex)(&cs40l26->lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = regmap_update_bits(codec->regmap, CS40L26_DACPCM1_INPUT, + CS40L26_DATA_SRC_MASK, CS40L26_DATA_SRC_DSP1TX1); + if (ret) + return ret; + + ret = regmap_update_bits(codec->regmap, CS40L26_ASPTX1_INPUT, CS40L26_DATA_SRC_MASK, + CS40L26_DATA_SRC_DSP1TX1); + if (ret) + return ret; + + ret = regmap_set_bits(codec->regmap, CS40L26_ASP_ENABLES1, CS40L26_ASP_ENABLE_MASK); + if (ret) + return ret; + break; + case SND_SOC_DAPM_PRE_PMD: + ret = cs40l26_dsp_write(cs40l26, CS40L26_STOP_I2S); + if (ret) + return ret; + + ret = regmap_clear_bits(codec->regmap, CS40L26_ASP_ENABLES1, + CS40L26_ASP_ENABLE_MASK); + if (ret) + return ret; + break; + default: + dev_err(codec->dev, "Invalid ASPRX event: %d\n", event); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_component_set_sysclk(struct snd_soc_component *component, int clk_id, int source, + unsigned int freq, int dir) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + u8 clk_cfg; + int ret; + + ret = cs40l26_get_clk_config(codec, (u32)(CS40L26_PLL_CLK_FREQ_MASK & freq), &clk_cfg); + if (ret) + return ret; + + if (clk_id) { + dev_err(codec->dev, "Invalid input clock (ID: %d)\n", clk_id); + return -EINVAL; + } + + codec->rate = CS40L26_PLL_CLK_FREQ_MASK & freq; + + return 0; +} + +static int cs40l26_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(codec_dai->component); + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC) { + dev_err(codec->dev, "Device can not be master\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + codec->daifmt = 0; + break; + case SND_SOC_DAIFMT_NB_IF: + codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + codec->daifmt = CS40L26_ASP_BCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK; + break; + default: + dev_err(codec->dev, "Invalid clock inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + codec->daifmt |= FIELD_PREP(CS40L26_ASP_FMT_MASK, CS40L26_ASP_FMT_TDM1_DSPA); + break; + case SND_SOC_DAIFMT_I2S: + codec->daifmt |= FIELD_PREP(CS40L26_ASP_FMT_MASK, CS40L26_ASP_FMT_I2S); + break; + default: + dev_err(codec->dev, "Invalid DAI format: 0x%X\n", fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(dai->component); + u32 asp_rx_wl, asp_rx_width; + int ret; + + ret = pm_runtime_resume_and_get(codec->core->dev); + if (ret) + return ret; + + switch (params_rate(params)) { + case 48000: + ret = regmap_clear_bits(codec->regmap, CS40L26_MONITOR_FILT, + CS40L26_VIMON_DUAL_RATE_MASK); + break; + case 96000: + ret = regmap_set_bits(codec->regmap, CS40L26_MONITOR_FILT, + CS40L26_VIMON_DUAL_RATE_MASK); + break; + default: + dev_err(codec->dev, "Unsupported sample rate: %d Hz\n", params_rate(params)); + ret = -EINVAL; + } + + if (ret) + goto pm_exit; + + asp_rx_wl = params_width(params); + + ret = regmap_update_bits(codec->regmap, CS40L26_ASP_DATA_CONTROL5, CS40L26_ASP_RX_WL_MASK, + asp_rx_wl); + if (ret) + goto pm_exit; + + + asp_rx_width = codec->tdm_width ? codec->tdm_width : asp_rx_wl; + + codec->daifmt |= FIELD_PREP(CS40L26_ASP_RX_WIDTH_MASK, asp_rx_width); + + ret = regmap_update_bits(codec->regmap, CS40L26_ASP_CONTROL2, + CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK | + CS40L26_ASP_FMT_MASK | CS40L26_ASP_RX_WIDTH_MASK, codec->daifmt); + if (ret) + goto pm_exit; + + ret = regmap_update_bits(codec->regmap, CS40L26_ASP_FRAME_CONTROL5, + CS40L26_ASP_RX1_SLOT_MASK | CS40L26_ASP_RX2_SLOT_MASK, + codec->tdm_slot[0] | + FIELD_PREP(CS40L26_ASP_RX2_SLOT_MASK, codec->tdm_slot[1])); + if (ret) + goto pm_exit; + + dev_dbg(codec->dev, "ASP: %d bits in %d bit slots, slot #s: %d, %d\n", asp_rx_wl, + asp_rx_width, codec->tdm_slot[0], codec->tdm_slot[1]); + +pm_exit: + cs40l26_pm_exit(codec->core->dev); + + return ret; +} + +static int cs40l26_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(dai->component); + + codec->tdm_width = slot_width; + + /* + * Reset slots if TDM is being disabled, and catch the case in which both RX1 and RX2 + * would be set to slot 0 which would cause the hardware to flag an error + */ + if (!slots || rx_mask == 0x1) + rx_mask = 0x3; + + codec->tdm_slot[0] = ffs(rx_mask) - 1; + rx_mask &= ~BIT(codec->tdm_slot[0]); + codec->tdm_slot[1] = ffs(rx_mask) - 1; + + return 0; +} + +static const struct snd_soc_dai_ops cs40l26_dai_ops = { + .set_fmt = cs40l26_set_dai_fmt, + .set_tdm_slot = cs40l26_set_tdm_slot, + .hw_params = cs40l26_pcm_hw_params, +}; + +static struct snd_soc_dai_driver cs40l26_dai[] = { + { + .name = "cs40l26-pcm", + .id = 0, + .playback = { + .stream_name = "ASP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS40L26_RATES, + .formats = CS40L26_FORMATS, + }, + .ops = &cs40l26_dai_ops, + .symmetric_rate = 1, + }, +}; + +static const char *const cs40l26_out_mux_texts[] = { "Off", "ASP", "DSP" }; +static SOC_ENUM_SINGLE_VIRT_DECL(cs40l26_out_mux_enum, cs40l26_out_mux_texts); +static const struct snd_kcontrol_new cs40l26_out_mux = + SOC_DAPM_ENUM("Haptics Source", cs40l26_out_mux_enum); + +static const struct snd_soc_dapm_widget cs40l26_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l26_clk_en, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PGA_E("ASP", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_asp_rx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("DSP", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_dsp_tx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("Haptics Source", SND_SOC_NOPM, 0, 0, &cs40l26_out_mux), + SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static const struct snd_soc_dapm_route cs40l26_dapm_routes[] = { + { "ASP Playback", NULL, "ASP PLL" }, + { "ASPRX1", NULL, "ASP Playback" }, + { "ASPRX2", NULL, "ASP Playback" }, + + { "ASP", NULL, "ASPRX1" }, + { "ASP", NULL, "ASPRX2" }, + { "DSP", NULL, "ASP" }, + + { "Haptics Source", "ASP", "ASP" }, + { "Haptics Source", "DSP", "DSP" }, + { "OUT", NULL, "Haptics Source" }, +}; + +static int cs40l26_codec_probe(struct snd_soc_component *component) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + + /* Default audio SCLK frequency */ + codec->rate = 1536000; + + codec->tdm_slot[0] = 0; + codec->tdm_slot[1] = 1; + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_cs40l26 = { + .probe = cs40l26_codec_probe, + .set_sysclk = cs40l26_component_set_sysclk, + .dapm_widgets = cs40l26_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs40l26_dapm_widgets), + .dapm_routes = cs40l26_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs40l26_dapm_routes), +}; + +static int cs40l26_platform_probe(struct platform_device *pdev) +{ + struct cs40l26 *cs40l26 = dev_get_drvdata(pdev->dev.parent); + struct cs40l26_codec *codec; + + codec = devm_kzalloc(&pdev->dev, sizeof(struct cs40l26_codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + codec->core = cs40l26; + codec->regmap = cs40l26->regmap; + codec->dev = &pdev->dev; + + platform_set_drvdata(pdev, codec); + + return snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l26, cs40l26_dai, + ARRAY_SIZE(cs40l26_dai)); +} + +static const struct platform_device_id cs40l26_id[] = { + { "cs40l26-codec", }, + {} +}; +MODULE_DEVICE_TABLE(platform, cs40l26_id); + +static struct platform_driver cs40l26_codec_driver = { + .probe = cs40l26_platform_probe, + .id_table = cs40l26_id, + .driver = { + .name = "cs40l26-codec", + }, +}; +module_platform_driver(cs40l26_codec_driver); + +MODULE_DESCRIPTION("ASoC CS40L26 driver"); +MODULE_AUTHOR("Fred Treven ftreven@opensource.cirrus.com"); +MODULE_LICENSE("GPL");