From nobody Mon Oct 6 17:06:51 2025 Received: from smtpbguseast1.qq.com (smtpbguseast1.qq.com [54.204.34.129]) (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 CC350215F42; Fri, 18 Jul 2025 12:13:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=54.204.34.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840806; cv=none; b=g6YP7/wn+hR/TNszi/OuKvJ5X4nk6/3YeK9c/etdFPnOKjXUEaxs4n+i+yktZMPuyjOucKCaPCMe6309BNdURZYm4wYpSTbkw8AIzEcN77S2haqaLMPIi9NXbVaWMsgw8GBvVdrYzaFBpraeFynRchholKtTBdTFCVxgJqJx8Qc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840806; c=relaxed/simple; bh=rs0Jrxz0vRepAeCwsVCMO/UiEo/apDCQ/z41u/EWCXc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=VfN4LeV5/WglkouGVsa1nXAMNuzCCRVFM4xaqlirOFwiIlwWx9KpYm1AdwQOxTJYQBvzUjM4TjFUx6ZH7a6UuLQldLnreUIgDY3Lys29aif2gc50mqkeVxG7CCW4YUzMAeoG406ya+WKU0XQNgdkWltanVUodoN7+oZiK2cqho4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com; spf=pass smtp.mailfrom=foursemi.com; arc=none smtp.client-ip=54.204.34.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=foursemi.com X-QQ-mid: esmtpsz21t1752840749tc1baef34 X-QQ-Originating-IP: k+7Z/22s+xsYjJCHqZOVNIROGwloEObo4DIqJ1I/i/8= Received: from localhost.localdomain ( [113.89.232.166]) by bizesmtp.qq.com (ESMTP) with id ; Fri, 18 Jul 2025 20:12:28 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 8113494319884698280 EX-QQ-RecipientCnt: 14 From: Nick To: lgirdwood@gmail.com, broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, perex@perex.cz, tiwai@suse.com, nick.li@foursemi.com Cc: xiaoming.yang@foursemi.com, danyang.zheng@foursemi.com, like.xy@foxmail.com, linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 1/4] dt-bindings: vendor-prefixes: Add Shanghai FourSemi Semiconductor Co.,Ltd Date: Fri, 18 Jul 2025 20:12:18 +0800 Message-Id: <20250718121221.76957-2-nick.li@foursemi.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250718121221.76957-1-nick.li@foursemi.com> References: <20250718121221.76957-1-nick.li@foursemi.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-QQ-SENDSIZE: 520 Feedback-ID: esmtpsz:foursemi.com:qybglogicsvrsz:qybglogicsvrsz3a-0 X-QQ-XMAILINFO: NH+njjzUOij/YnoNbHhfKlFkgfnF8Z1VY07g5FxuSqm9hBHB+EiEN62O NjTdwRjMmwv+wf82aJFeILbO6yeuwEW7fLShFSusrxMWzX4MgHTcCBDwew71bKfTQEmS2Ni E+PxmiBBmIFEXMbisdfSP87tRDJCaDb3dRNF2GDUQ7gXRZ1FW6TjxyFtVuAmgG0gzh2KsFl XlMg0t5sgSH61dJcEp/d/T6A/WP91S9638Hz6IgSfeCtXqum/gvweJtXQBq/wxFheFFo0V1 gTPB8r177SLQbVNEKpVGGayGo+tMzvsFcqQ6gir6EoNaBGNavmtUxUV9QryomBrKKmAdDjM VCWU5z7vsB1CHBNqdgH59HZWWzj8pQPwpyOj02iyvlMt7y1yfb9mq+cVYHCYMWg5MFqQRCb WYhKPV9ZLVT1GV/USo1wiQfO01Hw1/Jv548KbDujYzLMwvTykDZcifuShnws3oePJG2YnKs R/QZtqsjBLBkZ9Kejb4KmQKtRFuwo9CQBZxrtYuaEIlSMKFqd38BzWOyTinczeLR7FVXDPU 28f6m157O0wImw+b5nAKFUkTR2/s3i5Q58x44W3X/8CoFppUig/FxJAwd2Qy4+iUTje7hZ6 CH90gTXcfB2Y3GnFIoSTH2yU4alW741sFA+PSjh/+YJGUNytCevq6aydSkxQy1uLNCTU4R2 iuL526Y4YM/2jB/VLK4iphSG6nQJerPfOTolDwgLdQJCF8/PaCJBBnZBWL+lTL0Gt1IkW5L IQPL+XlVeZKIagkRADUzQm5EtbNbI7efElQrxzfIPW3CaMvl1vZwxsiaixSjdDkUo9LjCLG P8EOQfbPypv1eDSOTnvBeqsHzfTPgIT4C0UIWaPjBcuZ+8HMG5KdhUE9HOn+Lr98cc94mZm 4DBr87VvXixZs8kHFHx5FyXaq5NaAvYoytHVd+HoOCN9HqW0VwxMOn8u0ghyVDdpjrE61Zi CQ7TLcvzl+j65sLl/NMdSB6qMJgCVH7uxT+s2Dn64xtKkAfPkQHAJpRCiA8E1Ax0r66mCub iJzV6LP3xwgbm7GCYGbpQ0Z5lCtvLcWqozB8dd2FXZrX6UAuA1PN450xwTHS6TZph+jwhpY m9EWl4gGoHHMpMGlkFN9vBagDQuVsZTRUkfGIR2v8oqSb6PILQvBe0jMtTgKTD7igRr5hxl q1Xi X-QQ-XMRINFO: MSVp+SPm3vtS1Vd6Y4Mggwc= X-QQ-RECHKSPAM: 0 Content-Type: text/plain; charset="utf-8" From: Nick Li Add vendor prefix for Shanghai FourSemi Semiconductor Co.,Ltd Link: https://en.foursemi.com/ Signed-off-by: Nick Li --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Docum= entation/devicetree/bindings/vendor-prefixes.yaml index 99e86271c..f692e3b29 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -547,6 +547,8 @@ patternProperties: description: FocalTech Systems Co.,Ltd "^forlinx,.*": description: Baoding Forlinx Embedded Technology Co., Ltd. + "^foursemi,.*": + description: Shanghai FourSemi Semiconductor Co.,Ltd. "^freebox,.*": description: Freebox SAS "^freecom,.*": --=20 2.39.5 From nobody Mon Oct 6 17:06:51 2025 Received: from bg1.exmail.qq.com (bg1.exmail.qq.com [114.132.67.179]) (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 62B952D97A6; Fri, 18 Jul 2025 12:13:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=114.132.67.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840817; cv=none; b=JciF3mc5ClMbX1IvyH55+YARXA/6PsWW8FHpHCufs7LzxfFv7r1py7qxRk6wVSCWNRX44mJkPm15alQAkPknkoF9o9KgGSjolin7cdAETqNRWKRTcu7NohxKk7PG8tHMkdqSUHpHtkTqvYaIHUYirm7t8vof5+EPcFhqLtZriNQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840817; c=relaxed/simple; bh=6P7takzY4Q8f6ReFyjrw1HfpKgMBdtdrtHw8aaqhOnI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=LJn2xHpetmiN1uDVyqbs9uO3wahWSOhPoFaJ4aqxCxAPVR0YeaLUgIitBVSRpttMwPd6pFgm7/BNn2+achqhujaQz3VUXeumQoxQCN9bvBAr9A0kNpRX5+lriabz5E2iZUSMZ/Xdg/kUIVDzdRxwShw1n0u1lU6idaxCAhSQiZU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com; spf=pass smtp.mailfrom=foursemi.com; arc=none smtp.client-ip=114.132.67.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=foursemi.com X-QQ-mid: esmtpsz21t1752840751t6cb0562e X-QQ-Originating-IP: 9dRlpqgRyURRDy4xBiYoj8Gt3pAHaU0fiVdyqOcxXOA= Received: from localhost.localdomain ( [113.89.232.166]) by bizesmtp.qq.com (ESMTP) with id ; Fri, 18 Jul 2025 20:12:30 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 2754424817838708234 EX-QQ-RecipientCnt: 14 From: Nick To: lgirdwood@gmail.com, broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, perex@perex.cz, tiwai@suse.com, nick.li@foursemi.com Cc: xiaoming.yang@foursemi.com, danyang.zheng@foursemi.com, like.xy@foxmail.com, linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 2/4] ASoC: dt-bindings: Add FS2104/5S audio amplifiers Date: Fri, 18 Jul 2025 20:12:19 +0800 Message-Id: <20250718121221.76957-3-nick.li@foursemi.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250718121221.76957-1-nick.li@foursemi.com> References: <20250718121221.76957-1-nick.li@foursemi.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-QQ-SENDSIZE: 520 Feedback-ID: esmtpsz:foursemi.com:qybglogicsvrsz:qybglogicsvrsz3a-0 X-QQ-XMAILINFO: N+fE1vV13phxrCGOlXvji/aOxGzgCzxl0lydCNFyUo9FQbASuubjjUKI w1frHfj8rlTU7meq9z7H2ReaxHoux/4NpGtcIUHN264emmHrNvR0XHuQ/OmlZsEm4XhqX5q t9g6iVEDKF9STjdzJ3Wyv0aeDo/4siWXK3B//in/3kbeb4uGxLmR4++qSKYpkKerelm/e/c XjmKUcFSSyZfywPvia/V7ojwi0uTMZdKqAQGf2pLnN4CULiU9vgJV+mX7Yd7LrK7pAoZLgp g67XoBoxmQ+xG4a/Sp/FQ8slF6TpPzfK4DaagU7AHmST0LI6UaIURvyRGWc/IUSauyNRF2U jLHB4qyb+FnjWI8Ae127sJ4amHOCt0PX706R5xK1nI1SFuAZoL6ds505OVwT/GojH3k6Pg3 P1DLF0j7GF6d5KpS09Ltz+sX/zso4EEhuuO8z+dsOJJ1F53AQoLiU5f+GpPt5xJYG3vHIoO 6+LEIVMpZNWWF3H9jaAFnW5UV0OoaRuxmKIWZ11IoPs9AOPJlqj0AL2pgDULr1ctZw8keiN wMGhLzI6l5pORCiTlbk+3gNzGYGiJsjTrLAUtWlMDEz0v+T2GtT1ZwMfVHXEr0IHYznfwfZ Xe9jma/wGVPWATeKhOePgTzE1GJTPDYFO7JkNnxc6kvzNp2maZHCeMjwT//b+trjhNhcCCj dI11QP/C9El5zfoI/Q60MqyF1HDhkekOWBXHIPB1hYqEHBMmbHCy/0ly9hzOhCsW2iVRPak SS/h2UIzjGdHsUEMJAywttgv5mc0Q9Fs5IBPHv6zW+NY17MHnJYj99r8f0LoFozoNznab6p tzCIec/rE7z+J2zc6/UAIX8yJ9V4tFa0XtAATQtLU5kyoIIO56ahd/XhAMmXuETZJ02VsMY 8jgji7GQ5/Ht7Wj72veMOIUbjq2CxB6FDspQ4XWTRpAEBcSoRjikGVHN0OmAhnzAX3EBwks TCQnh0QQxLxNoukzM5wjfO/MK+qk7cMTzDPilH3LyY8PWXP+Nko0kadBIooW1vfCsAURXBI 4EcX3gQoDXoMlmIdlVcNA8hgRuOj6l2lQSR9YNDrw6IGkm6T84BGhqw93IJ9y/7HTmuEev6 aGB1ZDwES8zVHvkS3BMpXUo9X0YGbJK9M38vTiUXBMfhaTFtLsDjaIEhG3d0WnWxg== X-QQ-XMRINFO: NyFYKkN4Ny6FSmKK/uo/jdU= X-QQ-RECHKSPAM: 0 Content-Type: text/plain; charset="utf-8" From: Nick Li Add a DT schema for describing FourSemi FS2104/5S audio amplifiers which support both I2S and I2C interface. Signed-off-by: Nick Li Reviewed-by: Rob Herring (Arm) --- .../bindings/sound/foursemi,fs2105s.yaml | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/foursemi,fs2105= s.yaml diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml = b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml new file mode 100644 index 000000000..7391c404a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/foursemi,fs2105s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: FourSemi FS2104/5S Digital Audio Amplifier + +maintainers: + - Nick Li + +description: + The FS2104 is a 15W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + The FS2105S is a 30W Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifier with Enhanced Signal Processing. + +properties: + compatible: + oneOf: + - items: + - enum: + - foursemi,fs2104 + - const: foursemi,fs2105s + - enum: + - foursemi,fs2105s + + reg: + maxItems: 1 + + clocks: + items: + - description: The clock of I2S BCLK + + clock-names: + items: + - const: bclk + + interrupts: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + pvdd-supply: + description: + Regulator for power supply(PVDD in datasheet). + + dvdd-supply: + description: + Regulator for digital supply(DVDD in datasheet). + + reset-gpios: + maxItems: 1 + description: + It's the SDZ pin in datasheet, the pin is active low, + it will power down and reset the chip to shut down state. + + firmware-name: + maxItems: 1 + description: | + The firmware(*.bin) contains: + a. Register initialization settings + b. DSP effect parameters + c. Multi-scene sound effect configurations(optional) + It's gernerated by FourSemi's tuning tool. + +required: + - compatible + - reg + - '#sound-dai-cells' + - reset-gpios + - firmware-name + +allOf: + - $ref: dai-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + audio-codec@68 { + compatible =3D "foursemi,fs2105s"; + reg =3D <0x68>; + clocks =3D <&clocks 18>; + clock-names =3D "bclk"; + #sound-dai-cells =3D <0>; + pvdd-supply =3D <&pvdd_supply>; + dvdd-supply =3D <&dvdd_supply>; + reset-gpios =3D <&gpio 18 GPIO_ACTIVE_LOW>; + firmware-name =3D "fs2105s-btl-2p0-0s.bin"; + pinctrl-names =3D "default"; + pinctrl-0 =3D <&fs210x_pins_default>; + }; + }; --=20 2.39.5 From nobody Mon Oct 6 17:06:51 2025 Received: from smtpbg151.qq.com (smtpbg151.qq.com [18.169.211.239]) (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 393C9A920; Fri, 18 Jul 2025 12:13:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=18.169.211.239 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840805; cv=none; b=ueKpgHvIxgzq94EEdQWZjKKCDQPoV9iR+2KHWfzx2vRieCC+5Z3liVBKJVFd28JHP3R4vZl6Uax89prYIfcDSCBhOuUXvTpGq5NnW1BS63mJmqpLV0JMb9N05nlGXvFyC0aSx8GdprIoeruu9TEvgYEfV0iMNnS/iAZnz3xkr5w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840805; c=relaxed/simple; bh=9aYmhdKBkoYIXl3s7VlejF7/r+ARoRDQGRosO1s/z+8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DTIP+iis1lK1NnLX1TRMRcY6hLND0Wx1LkM1tWbyzIaNcVpKZ9WBYqEvaxqo7H/EiQ8CWPoToFnJ7uP++xP7pOintEZ1VTm1Au4LWTwvl4s8vOhEXJk0Mnn4xi8nxn9lqRgCICJs/wCNGiN7ii6hweSiehXirJeCKqXsmLzTEyA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com; spf=pass smtp.mailfrom=foursemi.com; arc=none smtp.client-ip=18.169.211.239 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=foursemi.com X-QQ-mid: esmtpsz21t1752840753t751beee9 X-QQ-Originating-IP: N1JajgXQyO6oNjFk97Ss5IwqYS1VcUt8/PSpJz1MrQU= Received: from localhost.localdomain ( [113.89.232.166]) by bizesmtp.qq.com (ESMTP) with id ; Fri, 18 Jul 2025 20:12:32 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 3730004231724635807 EX-QQ-RecipientCnt: 14 From: Nick To: lgirdwood@gmail.com, broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, perex@perex.cz, tiwai@suse.com, nick.li@foursemi.com Cc: xiaoming.yang@foursemi.com, danyang.zheng@foursemi.com, like.xy@foxmail.com, linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 3/4] ASoC: codecs: Add library for FourSemi audio amplifiers Date: Fri, 18 Jul 2025 20:12:20 +0800 Message-Id: <20250718121221.76957-4-nick.li@foursemi.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250718121221.76957-1-nick.li@foursemi.com> References: <20250718121221.76957-1-nick.li@foursemi.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-QQ-SENDSIZE: 520 Feedback-ID: esmtpsz:foursemi.com:qybglogicsvrsz:qybglogicsvrsz3a-0 X-QQ-XMAILINFO: NZm4dNHzd8izXyOxduCInuGuH7Rg0V/KaacqB3DDR4r0oI+YNOf4asAQ 3GIRDrjlmWL4yP8x4c9hScfWQlr5ufhuqCFkHwYZLNIook/rwsI/SbDvyyD2v5jhyQcyfUf gBszu6jpzmXl58AbnXfoz+WgDW3yNu0+fc3gqdLgKqWHga65kAIXOxIHOwBFrcf7j1gZ+Wk oKIKzxqOaVMdYqLGz7QzoZaRm/DIfow3WafJ15nCAWXderpkffg/kDCI8KuFOhrjFMof68R zTLm5f69PW/wpF6weMRZ/HbyE27jzfxzc0DB5SXzrYVAJFekS5lsWZ0uxET0aW+SXeNdjiF m3eT0ugxSaZT/LAsqavGuUEfujoygpmK3dokvYhDVi3jMYTxjdmLKQiYNNeFm6PxkA0uMJ8 xQS+Zb9xdZ4DWs1qjqddXtmUhqNtWP3okmoza2qSM2dfpc24l1e7G1p+Iomzn+IC+i4G/vd qVzwxgH+RHNojvb8uAZjgInMqBP10HdnzkpOX396C+Ft2Kv988WyGLjLau/94idWk0x0EDF nMX6HVm/8q9zca2IjzeIDUiwARVENJOmbhPwxE4bxDXO2Mk+GK/dTHlNl+FFecy2SRpdguR I/QVGzdOFWxq7RKg7ZAFxhL13PLgXEy43TmKLoJKxRjVBBS98ojdoHPTudjg8nqTeAwl2// aRpGanZryiH2sX1pRcyIJGb7tl4yyciU08ylKLNMMC/UtZkREwlfOu7wlRdBjLqvQc6W4mU oaADoFKkBOlVPURExPOqNwhJX8r8NJiMN+rKEz0Q8n1kkDH9vU9WF2YJrBF1x8VNxl2cFlK WS58SSvI6Or5uxiWjKpuZraTsgrDJA03dZVSLM2NYn8fVoDNugd9qfwLz+a78M8Ar1zJ25W vHB04KHIN+sgVBIBhlW2vv8IxR4D88ubly1ahnBXfAeG4bQ79W0BIYXofeQJH3UbrW4+o1Z sYBHAnbKYkQThFGEIh7E65fWmkF6AKdMHxvjSa84KuAG6E2LjoIXy/rlPtmnrfBbHhNsMh0 586EmnhCwbP+ZE5E2QzNVWlyxl2sAFqEjD34TZ7DYEO8+YMJAuovDSlf2gqNho5tAeoXVFJ bo5ELixk1bpVLymHZJDCYpS32ypeF7CgsVnoSaJO+s/d5pLWsSeI/Sve7gtxCecJg== X-QQ-XMRINFO: MSVp+SPm3vtS1Vd6Y4Mggwc= X-QQ-RECHKSPAM: 0 Content-Type: text/plain; charset="utf-8" From: Nick Li This patch adds firmware loading and parsing support for FourSemi audio amplifiers. The library handles firmware file (*.bin) generated by the FourSemi tuning tool, which contains: - Register initialization settings - DSP effect parameters - Multi-scene sound effect switching configurations(optional) The firmware is required for proper initialization and configuration of FourSemi amplifier devices. Signed-off-by: Nick Li --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs-amp-lib.c | 265 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs-amp-lib.h | 150 +++++++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 sound/soc/codecs/fs-amp-lib.c create mode 100644 sound/soc/codecs/fs-amp-lib.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4494e7cd6..6265cc897 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1238,6 +1238,9 @@ config SND_SOC_FRAMER To compile this driver as a module, choose M here: the module will be called snd-soc-framer. =20 +config SND_SOC_FS_AMP_LIB + select CRC16 + tristate =20 config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c497d343d..9afce9aa6 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -137,6 +137,7 @@ snd-soc-es8328-spi-y :=3D es8328-spi.o snd-soc-es8375-y :=3D es8375.o snd-soc-es8389-y :=3D es8389.o snd-soc-framer-y :=3D framer-codec.o +snd-soc-fs-amp-lib-y :=3D fs-amp-lib.o snd-soc-gtm601-y :=3D gtm601.o snd-soc-hdac-hdmi-y :=3D hdac_hdmi.o snd-soc-hdac-hda-y :=3D hdac_hda.o @@ -561,6 +562,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+=3D snd-soc-es8328-spi= .o obj-$(CONFIG_SND_SOC_ES8375) +=3D snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) +=3D snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) +=3D snd-soc-framer.o +obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+=3D snd-soc-fs-amp-lib.o obj-$(CONFIG_SND_SOC_GTM601) +=3D snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) +=3D snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) +=3D snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c new file mode 100644 index 000000000..75d8d5082 --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// fs-amp-lib.c --- Common library for FourSemi Audio Amplifiers +// +// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + +#include +#include +#include +#include +#include + +#include "fs-amp-lib.h" + +static int fs_get_scene_count(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + int count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + table =3D amp_lib->table[FS_INDEX_SCENE]; + if (!table) + return -EFAULT; + + count =3D table->size / sizeof(struct fs_scene_index); + if (count < 1 || count > FS_SCENE_COUNT_MAX) { + dev_err(amp_lib->dev, "Invalid scene count: %d\n", count); + return -ERANGE; + } + + return count; +} + +static void fs_get_fwm_string(struct fs_amp_lib *amp_lib, + int offset, const char **pstr) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !pstr) + return; + + table =3D amp_lib->table[FS_INDEX_STRING]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + *pstr =3D (char *)table + offset; + else + *pstr =3D NULL; +} + +static void fs_get_scene_reg(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table =3D amp_lib->table[FS_INDEX_REG]; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->reg =3D (struct fs_reg_table *)((char *)table + offset); + else + scene->reg =3D NULL; +} + +static void fs_get_scene_model(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table =3D amp_lib->table[FS_INDEX_MODEL]; + ptr =3D (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->model =3D (struct fs_file_table *)(ptr + offset); + else + scene->model =3D NULL; +} + +static void fs_get_scene_effect(struct fs_amp_lib *amp_lib, + int offset, struct fs_amp_scene *scene) +{ + const struct fs_fwm_table *table; + const char *ptr; + + if (!amp_lib || !amp_lib->dev || !scene) + return; + + table =3D amp_lib->table[FS_INDEX_EFFECT]; + ptr =3D (char *)table; + if (table && offset > 0 && offset < table->size + sizeof(*table)) + scene->effect =3D (struct fs_file_table *)(ptr + offset); + else + scene->effect =3D NULL; +} + +static int fs_parse_scene_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_scene_index *scene_index; + const struct fs_fwm_table *table; + struct fs_amp_scene *scene; + int idx, count; + + if (!amp_lib || !amp_lib->dev) + return -EINVAL; + + count =3D fs_get_scene_count(amp_lib); + if (count <=3D 0) + return -EFAULT; + + scene =3D devm_kzalloc(amp_lib->dev, count * sizeof(*scene), GFP_KERNEL); + if (!scene) + return -ENOMEM; + + amp_lib->scene_count =3D count; + amp_lib->scene =3D scene; + + table =3D amp_lib->table[FS_INDEX_SCENE]; + scene_index =3D (struct fs_scene_index *)table->buf; + + for (idx =3D 0; idx < count; idx++) { + fs_get_fwm_string(amp_lib, scene_index->name, &scene->name); + if (!scene->name) + scene->name =3D devm_kasprintf(amp_lib->dev, + GFP_KERNEL, "S%d", idx); + dev_dbg(amp_lib->dev, "scene.%d name: %s\n", idx, scene->name); + fs_get_scene_reg(amp_lib, scene_index->reg, scene); + fs_get_scene_model(amp_lib, scene_index->model, scene); + fs_get_scene_effect(amp_lib, scene_index->effect, scene); + scene++; + scene_index++; + } + + return 0; +} + +static int fs_parse_all_tables(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_table *table; + const struct fs_fwm_index *index; + const char *ptr; + int idx, count; + int ret; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + /* Parse all fwm tables */ + table =3D (struct fs_fwm_table *)amp_lib->hdr->params; + index =3D (struct fs_fwm_index *)table->buf; + count =3D table->size / sizeof(*index); + + for (idx =3D 0; idx < count; idx++, index++) { + if (index->type >=3D FS_INDEX_MAX) + return -ERANGE; + ptr =3D (char *)table + (int)index->offset; + amp_lib->table[index->type] =3D (struct fs_fwm_table *)ptr; + } + + /* Parse all scene tables */ + ret =3D fs_parse_scene_tables(amp_lib); + if (ret) + dev_err(amp_lib->dev, "Failed to parse scene: %d\n", ret); + + return ret; +} + +static int fs_verify_firmware(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + int crcsum; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return -EINVAL; + + hdr =3D amp_lib->hdr; + + /* Verify the crcsum code */ + crcsum =3D crc16(0x0000, (const char *)&hdr->crc_size, hdr->crc_size); + if (crcsum !=3D hdr->crc16) { + dev_err(amp_lib->dev, "Failed to checksum: %x-%x\n", + crcsum, hdr->crc16); + return -EFAULT; + } + + /* Verify the devid(chip_type) */ + if (amp_lib->devid !=3D LO_U16(hdr->chip_type)) { + dev_err(amp_lib->dev, "DEVID dismatch: %04X#%04X\n", + amp_lib->devid, hdr->chip_type); + return -EINVAL; + } + + return 0; +} + +static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) +{ + const struct fs_fwm_header *hdr; + const char *pro_name =3D NULL; + const char *dev_name =3D NULL; + + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) + return; + + hdr =3D amp_lib->hdr; + + fs_get_fwm_string(amp_lib, hdr->project, &pro_name); + fs_get_fwm_string(amp_lib, hdr->device, &dev_name); + + dev_info(amp_lib->dev, "Project: %s Device: %s\n", + pro_name ? pro_name : "null", + dev_name ? dev_name : "null"); + + dev_info(amp_lib->dev, "Date: %04d%02d%02d-%02d%02d\n", + hdr->date.year, hdr->date.month, hdr->date.day, + hdr->date.hour, hdr->date.minute); +} + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) +{ + const struct firmware *cont; + struct fs_fwm_header *hdr; + int ret; + + if (!amp_lib || !amp_lib->dev || !name) + return -EINVAL; + + ret =3D request_firmware(&cont, name, amp_lib->dev); + if (ret) { + dev_err(amp_lib->dev, "Failed to request %s: %d\n", name, ret); + return ret; + } + + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr =3D devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); + release_firmware(cont); + if (!hdr) + return -ENOMEM; + + amp_lib->hdr =3D hdr; + ret =3D fs_verify_firmware(amp_lib); + if (ret) { + amp_lib->hdr =3D NULL; + return ret; + } + + ret =3D fs_parse_all_tables(amp_lib); + if (ret) { + amp_lib->hdr =3D NULL; + return ret; + } + + fs_print_firmware_info(amp_lib); + + return 0; +} +EXPORT_SYMBOL_GPL(fs_amp_load_firmware); + +MODULE_AUTHOR("Nick Li "); +MODULE_DESCRIPTION("FourSemi audio amplifier library"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/fs-amp-lib.h b/sound/soc/codecs/fs-amp-lib.h new file mode 100644 index 000000000..4a77c7b38 --- /dev/null +++ b/sound/soc/codecs/fs-amp-lib.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * fs-amp-lib.h --- Common library for FourSemi Audio Amplifiers + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS_AMP_LIB_H__ +#define __FS_AMP_LIB_H__ + +#define HI_U16(a) (((a) >> 8) & 0xFF) +#define LO_U16(a) ((a) & 0xFF) +#define FS_TABLE_NAME_LEN (4) +#define FS_SCENE_COUNT_MAX (16) +#define FS_CMD_DELAY_MS_MAX (100) /* 100ms */ + +#define FS_CMD_DELAY (0xFF) +#define FS_CMD_BURST (0xFE) +#define FS_CMD_UPDATE (0xFD) + +#define FS_SOC_ENUM_EXT(xname, xhandler_info, xhandler_get, xhandler_put) \ +{ .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, .name =3D xname, \ + .info =3D xhandler_info, \ + .get =3D xhandler_get, .put =3D xhandler_put \ +} + +enum fs_index_type { + FS_INDEX_INFO =3D 0, + FS_INDEX_STCOEF, + FS_INDEX_SCENE, + FS_INDEX_MODEL, + FS_INDEX_REG, + FS_INDEX_EFFECT, + FS_INDEX_STRING, + FS_INDEX_WOOFER, + FS_INDEX_MAX, +}; + +#pragma pack(push, 1) + +struct fs_reg_val { + u8 reg; + u16 val; +}; + +struct fs_reg_bits { + u8 cmd; /* FS_CMD_UPDATE */ + u8 reg; + u16 val; + u16 mask; +}; + +struct fs_cmd_pkg { + union { + u8 cmd; + struct fs_reg_val regv; + struct fs_reg_bits regb; + }; +}; + +struct fs_fwm_index { + /* Index type */ + u16 type; + /* Offset address starting from the end of header */ + u16 offset; +}; + +struct fs_fwm_table { + char name[FS_TABLE_NAME_LEN]; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_scene_index { + /* Offset address(scene name) in string table */ + u16 name; + /* Offset address(scene reg) in register table */ + u16 reg; + /* Offset address(scene model) in model table */ + u16 model; + /* Offset address(scene effect) in effect table */ + u16 effect; +}; + +struct fs_reg_table { + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_file_table { + u16 name; + u16 size; /* size of buf */ + u8 buf[]; +}; + +struct fs_fwm_date { + u32 year:12; + u32 month:4; + u32 day:5; + u32 hour:5; + u32 minute:6; +}; + +struct fs_fwm_header { + u16 version; + u16 project; /* Offset address(project name) in string table */ + u16 device; /* Offset address(device name) in string table */ + struct fs_fwm_date date; + u16 crc16; + u16 crc_size; /* Starting position for CRC checking */ + u16 chip_type; + u16 addr; /* 7-bit i2c address */ + u16 spkid; + u16 rsvd[6]; + u8 params[]; +}; + +#pragma pack(pop) + +struct fs_i2s_srate { + u32 srate; /* Sample rate */ + u16 i2ssr; /* Value of Bit field[I2SSR] */ +}; + +struct fs_pll_div { + unsigned int bclk; /* Rate of bit clock */ + u16 pll1; + u16 pll2; + u16 pll3; +}; + +struct fs_amp_scene { + const char *name; + const struct fs_reg_table *reg; + const struct fs_file_table *model; + const struct fs_file_table *effect; +}; + +struct fs_amp_lib { + const struct fs_fwm_header *hdr; + const struct fs_fwm_table *table[FS_INDEX_MAX]; + struct fs_amp_scene *scene; + struct device *dev; + int scene_count; + u16 devid; +}; + +int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name); + +#endif // __FS_AMP_LIB_H__ --=20 2.39.5 From nobody Mon Oct 6 17:06:51 2025 Received: from smtpbguseast3.qq.com (smtpbguseast3.qq.com [54.243.244.52]) (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 F21DC2D9499; Fri, 18 Jul 2025 12:13:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=54.243.244.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840808; cv=none; b=XjXNXdGHk659EVKPIIVcvylBBiSF2SwUeq6EiVHShy7vXWTGFpbFM6PHn7dv+LbUaPA8jHnWLTTaeFQnWv8E7GjrFiyLQLXAXC/ROXs6uwoS0UqmZg8WNhcG6YyaFaHT2lL8qzc7cAupR+X5L664fLO+Ripa4+H9992S/JEPu0c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752840808; c=relaxed/simple; bh=rSaxBspzp9a7FlKZU3XLG8f6fzxdMXA6tYFtZj2Xfzo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=M4Elbvw2tcdkPslPDsISK6prxGS/qwHBcB7rmTvRdemEBZFAaRoFfyj0fvY+v1Xrlo+KxjuQmoOeo+TOV9P5fTiyHYrwPBW1/DB3AFnvAxS3Jrb/lOYHx/ybtLcrBCjbOezKeuMD3ROON2M2narwZ18gw/Am/KHu6oT7jfhvhYg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com; spf=pass smtp.mailfrom=foursemi.com; arc=none smtp.client-ip=54.243.244.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foursemi.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=foursemi.com X-QQ-mid: esmtpsz21t1752840755t00d3662d X-QQ-Originating-IP: /Qvy6qJBtzKU7hLYN2jC1QcSJHyMcfscoYJfvazAGbw= Received: from localhost.localdomain ( [113.89.232.166]) by bizesmtp.qq.com (ESMTP) with id ; Fri, 18 Jul 2025 20:12:34 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 135644612196188874 EX-QQ-RecipientCnt: 14 From: Nick To: lgirdwood@gmail.com, broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, perex@perex.cz, tiwai@suse.com, nick.li@foursemi.com Cc: xiaoming.yang@foursemi.com, danyang.zheng@foursemi.com, like.xy@foxmail.com, linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 4/4] ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver Date: Fri, 18 Jul 2025 20:12:21 +0800 Message-Id: <20250718121221.76957-5-nick.li@foursemi.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250718121221.76957-1-nick.li@foursemi.com> References: <20250718121221.76957-1-nick.li@foursemi.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-QQ-SENDSIZE: 520 Feedback-ID: esmtpsz:foursemi.com:qybglogicsvrsz:qybglogicsvrsz3a-0 X-QQ-XMAILINFO: OUs+p8z4zt/KacmRm4fQwG5CyQXA/efFYyLcmGOV4f969HB8O8HUItCk NryVBmZmWlx+jzTJz6dQV2Vvulw9qc0mb8Xn/BYsPzH39WsixxRC62/yoBBg5pjDrxB54S/ HX3+1J3s7v4G/QdEznJYnS+VVDrcUgmj9J2W5W++ZzmJz3qw9qmWvYO96u89rmdxXdoyWht u3B2UXoxKV78kh/5TQHmCw/Su0DxDvC2SOQqGh9jwnx5ivkQfSA7CNeGuZPf0V5uWtvckZ8 lmBIzSIZbdtIilwz3LsYtt5zly+K9sqm3hb0nWzzaLW9uUTFxRNcwRJ+TdWkc4alfLOUoBN r0K3qsk+B+lNv5dnxTCwC3ioELwyj6XH0S1kCmqDkpdDM2NNHEyAFDhFw5wmPFdy8R7Mlir /bk1EegoIGuBNEjdF2YvrSU6xoIg/GNhudJvf9J0b/8a45xocPGg4Ks2GBPmmIBJk0ysxZD RlcQXwvOPDzJXwtF657JadeLr6JU+lUYtcY9KBgAV7eKZSVv61FMHuRmjmkUZAJJPlbF4/l InXHfaLFVJdUwqVp1tqbmvycpoOADtgA65zRqjmJ05L5qEsaChHzlrXoGbMEAR4vRQxK1Hg QG6XUskZSO2mcRCXCKxF0Ok1ivLR9OQlchLivEgB4LTd21/sg25346mepQAQH3KPpZCvkpv TOd8XPd6KRfqzLTiU1prPyWcg4AF24eajCV/50qLD7FVKrbIkWFo06bjQAu0OmtW9MJjVIZ Y2/ly6KssK8YWzrdBzO0ZZJ8OiVl+M+ta1UJT4gwSwcEk65p/XNMjkueW8Sfy86tpssuuaY RPRKD0swk+OH0j/+wuzyCi5k1HxLpFtyM/Q3CaCVg13IgZ51Ie+OtfQ3Xrkov7j1bJDWVxA GUl5+ns1LdBI6+fW8KhdLbWKMqdH1RPiX5bFhGQ/IW5aKQF9fSIibsJOARXu6ef4ZtBeBhE Gs/vWziGZA+odmdIeMUj/1CRiGCGWQjdJTBsZlwdlYMzzDIDmU8uRGVcsPPiS4Ey11PqpkL GHXErnHU9fvfm5NWSZt1R/jn5iyymmhUmzjduvbhj8kXbsrhk17RdxFt3Uz3yA3AhnSn6RT DDPF8BqwE4mvQJCBI8o8KWKe/dD1ssS4pkzza+Dpk0FqAUdSspuaeOm+WbUE7B1kAKKN4db wmoV X-QQ-XMRINFO: M/715EihBoGSf6IYSX1iLFg= X-QQ-RECHKSPAM: 0 Content-Type: text/plain; charset="utf-8" From: Nick Li The FS2104/5S are FourSemi digital audio amplifiers with I2C control. They are Inductor-Less, Stereo, Closed-Loop, Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. Signed-off-by: Nick Li --- sound/soc/codecs/Kconfig | 13 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/fs210x.c | 1583 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/fs210x.h | 75 ++ 4 files changed, 1673 insertions(+) create mode 100644 sound/soc/codecs/fs210x.c create mode 100644 sound/soc/codecs/fs210x.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6265cc897..8c85f3dbb 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES7134 imply SND_SOC_ES7241 imply SND_SOC_FRAMER + imply SND_SOC_FS210X imply SND_SOC_GTM601 imply SND_SOC_HDAC_HDMI imply SND_SOC_HDAC_HDA @@ -1242,6 +1243,18 @@ config SND_SOC_FS_AMP_LIB select CRC16 tristate =20 +config SND_SOC_FS210X + tristate 'FourSemi FS2104/5S digital audio amplifier' + depends on I2C + select GPIOLIB + select REGMAP_I2C + select SND_SOC_FS_AMP_LIB + help + Enable support for FourSemi FS2104/5S digital audio amplifier. + The FS2104/5S are Inductor-Less, Stereo, Closed-Loop, + Digital Input Class-D Power Amplifiers with Enhanced Signal Processing. + The amplifiers support I2C and I2S/TDM. + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' =20 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9afce9aa6..967e939a4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -138,6 +138,7 @@ snd-soc-es8375-y :=3D es8375.o snd-soc-es8389-y :=3D es8389.o snd-soc-framer-y :=3D framer-codec.o snd-soc-fs-amp-lib-y :=3D fs-amp-lib.o +snd-soc-fs210x-y :=3D fs210x.o snd-soc-gtm601-y :=3D gtm601.o snd-soc-hdac-hdmi-y :=3D hdac_hdmi.o snd-soc-hdac-hda-y :=3D hdac_hda.o @@ -563,6 +564,7 @@ obj-$(CONFIG_SND_SOC_ES8375) +=3D snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) +=3D snd-soc-es8389.o obj-$(CONFIG_SND_SOC_FRAMER) +=3D snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+=3D snd-soc-fs-amp-lib.o +obj-$(CONFIG_SND_SOC_FS210X) +=3D snd-soc-fs210x.o obj-$(CONFIG_SND_SOC_GTM601) +=3D snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) +=3D snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) +=3D snd-soc-hdac-hda.o diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c new file mode 100644 index 000000000..2cecb05ea --- /dev/null +++ b/sound/soc/codecs/fs210x.c @@ -0,0 +1,1583 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// fs210x.c -- Driver for the FS2104/5S Audio Amplifier +// +// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fs210x.h" +#include "fs-amp-lib.h" + +#define FS210X_DEFAULT_FWM_NAME "fs210x_fwm.bin" +#define FS210X_DEFAULT_DAI_NAME "fs210x-aif" +#define FS2105S_DEVICE_ID 0x20 /* FS2105S */ +#define FS210X_DEVICE_ID 0x45 /* FS2104 */ +#define FS210X_REG_MAX 0xF8 +#define FS210X_INIT_SCENE 0 +#define FS210X_DEFAULT_SCENE 1 +#define FS210X_START_DELAY_MS 5 +#define FS210X_FAULT_CHECK_INTERVAL_MS 2000 +#define FS2105S_RATES (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) +#define FS210X_RATES (SNDRV_PCM_RATE_16000 | FS2105S_RATES) +#define FS210X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define FS210X_NUM_SUPPLIES ARRAY_SIZE(fs210x_supply_names) + +static const char *const fs210x_supply_names[] =3D { + "pvdd", + "dvdd", +}; + +struct fs210x_platform_data { + const char *fwm_name; +}; + +struct fs210x_priv { + struct i2c_client *i2c; + struct device *dev; + struct regmap *regmap; + struct fs210x_platform_data pdata; + struct regulator_bulk_data supplies[FS210X_NUM_SUPPLIES]; + struct gpio_desc *gpio_sdz; + struct delayed_work start_work; + struct delayed_work fault_check_work; + struct fs_amp_lib amp_lib; + const struct fs_amp_scene *cur_scene; + struct clk *clk_bclk; + /* + * @lock: Mutex ensuring exclusive access for critical device operations + * + * This lock serializes access between the following actions: + * - Device initialization procedures(probe) + * - Enable/disable device(DAPM event) + * - Suspend/resume device(PM) + * - Runtime scene switching(control) + * - Scheduling/execution of delayed works items(delayed works) + */ + struct mutex lock; + unsigned int check_interval_ms; + unsigned int bclk; + unsigned int srate; + int scene_id; + u16 devid; + bool is_inited; + bool is_suspended; + bool is_bclk_on; + bool is_playing; +}; + +static const unsigned int fs2105s_rates[] =3D { + 32000, 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list fs2105s_constraints =3D { + .count =3D ARRAY_SIZE(fs2105s_rates), + .list =3D fs2105s_rates, +}; + +static const unsigned int fs210x_rates[] =3D { + 16000, 32000, 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list fs210x_constraints =3D { + .count =3D ARRAY_SIZE(fs210x_rates), + .list =3D fs210x_rates, +}; + +static const struct fs_pll_div fs210x_pll_div[] =3D { + /* bclk, pll1, pll2, pll3 */ + { 512000, 0x006C, 0x0120, 0x0001 }, + { 768000, 0x016C, 0x00C0, 0x0001 }, + { 1024000, 0x016C, 0x0090, 0x0001 }, + { 1536000, 0x016C, 0x0060, 0x0001 }, + { 2048000, 0x016C, 0x0090, 0x0002 }, + { 2304000, 0x016C, 0x0080, 0x0002 }, + { 3072000, 0x016C, 0x0090, 0x0003 }, + { 4096000, 0x016C, 0x0090, 0x0004 }, + { 4608000, 0x016C, 0x0080, 0x0004 }, + { 6144000, 0x016C, 0x0090, 0x0006 }, + { 8192000, 0x016C, 0x0090, 0x0008 }, + { 9216000, 0x016C, 0x0090, 0x0009 }, + { 12288000, 0x016C, 0x0090, 0x000C }, + { 16384000, 0x016C, 0x0090, 0x0010 }, + { 18432000, 0x016C, 0x0090, 0x0012 }, + { 24576000, 0x016C, 0x0090, 0x0018 }, + { 1411200, 0x016C, 0x0060, 0x0001 }, + { 2116800, 0x016C, 0x0080, 0x0002 }, + { 2822400, 0x016C, 0x0090, 0x0003 }, + { 4233600, 0x016C, 0x0080, 0x0004 }, + { 5644800, 0x016C, 0x0090, 0x0006 }, + { 8467200, 0x016C, 0x0090, 0x0009 }, + { 11289600, 0x016C, 0x0090, 0x000C }, + { 16934400, 0x016C, 0x0090, 0x0012 }, + { 22579200, 0x016C, 0x0090, 0x0018 }, + { 2000000, 0x017C, 0x0093, 0x0002 }, +}; + +static int fs210x_bclk_set(struct fs210x_priv *fs210x, bool on) +{ + int ret =3D 0; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if ((fs210x->is_bclk_on ^ on) =3D=3D 0) + return 0; + + if (on) { + clk_set_rate(fs210x->clk_bclk, fs210x->bclk); + ret =3D clk_prepare_enable(fs210x->clk_bclk); + fs210x->is_bclk_on =3D true; + fsleep(2000); /* >=3D 2ms */ + } else { + clk_disable_unprepare(fs210x->clk_bclk); + fs210x->is_bclk_on =3D false; + } + + return ret; +} + +static int fs210x_reg_write(struct fs210x_priv *fs210x, + u8 reg, u16 val) +{ + int ret; + + ret =3D regmap_write(fs210x->regmap, reg, val); + if (ret) { + dev_err(fs210x->dev, "Failed to write %02Xh: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int fs210x_reg_read(struct fs210x_priv *fs210x, + u8 reg, u16 *pval) +{ + unsigned int val; + int ret; + + ret =3D regmap_read(fs210x->regmap, reg, &val); + if (ret) { + dev_err(fs210x->dev, "Failed to read %02Xh: %d\n", reg, ret); + return ret; + } + + *pval =3D (u16)val; + + return 0; +} + +static int fs210x_reg_update_bits(struct fs210x_priv *fs210x, + u8 reg, u16 mask, u16 val) +{ + int ret; + + ret =3D regmap_update_bits(fs210x->regmap, reg, mask, val); + if (ret) { + dev_err(fs210x->dev, "Failed to update %02Xh: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int fs210x_reg_bulk_write(struct fs210x_priv *fs210x, + u8 reg, const void *val, u32 size) +{ + int ret; + + ret =3D regmap_bulk_write(fs210x->regmap, reg, val, size / 2); + if (ret) { + dev_err(fs210x->dev, "Failed to bulk write %02Xh: %d\n", + reg, ret); + return ret; + } + + return 0; +} + +static inline int fs210x_write_reg_val(struct fs210x_priv *fs210x, + const struct fs_reg_val *regv) +{ + return fs210x_reg_write(fs210x, regv->reg, regv->val); +} + +static inline int fs210x_write_reg_bits(struct fs210x_priv *fs210x, + const struct fs_reg_bits *regu) +{ + return fs210x_reg_update_bits(fs210x, + regu->reg, + regu->mask, + regu->val); +} + +static inline int fs210x_set_cmd_pkg(struct fs210x_priv *fs210x, + const struct fs_cmd_pkg *pkg, + unsigned int *offset) +{ + int delay_us; + + if (pkg->cmd >=3D 0x00 && pkg->cmd <=3D FS210X_REG_MAX) { + *offset =3D sizeof(pkg->regv); + return fs210x_write_reg_val(fs210x, &pkg->regv); + } else if (pkg->cmd =3D=3D FS_CMD_UPDATE) { + *offset =3D sizeof(pkg->regb); + return fs210x_write_reg_bits(fs210x, &pkg->regb); + } else if (pkg->cmd =3D=3D FS_CMD_DELAY) { + if (pkg->regv.val > FS_CMD_DELAY_MS_MAX) + return -EOPNOTSUPP; + delay_us =3D pkg->regv.val * 1000; /* ms -> us */ + fsleep(delay_us); + *offset =3D sizeof(pkg->regv); + return 0; + } + + dev_err(fs210x->dev, "Invalid pkg cmd: %d\n", pkg->cmd); + + return -EOPNOTSUPP; +} + +static int fs210x_reg_write_table(struct fs210x_priv *fs210x, + const struct fs_reg_table *reg) +{ + const struct fs_cmd_pkg *pkg; + unsigned int index, offset; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if (!reg || reg->size =3D=3D 0) + return -EFAULT; + + for (index =3D 0; index < reg->size; index +=3D offset) { + pkg =3D (struct fs_cmd_pkg *)(reg->buf + index); + ret =3D fs210x_set_cmd_pkg(fs210x, pkg, &offset); + if (ret) { + dev_err(fs210x->dev, "Failed to set cmd pkg: %02X-%d\n", + pkg->cmd, ret); + return ret; + } + } + + if (index !=3D reg->size) { + dev_err(fs210x->dev, "Invalid reg table size: %d-%d\n", + index, reg->size); + return -EFAULT; + } + + return 0; +} + +static int fs210x_dev_play(struct fs210x_priv *fs210x) +{ + int ret; + + if (!fs210x->is_inited) + return -EFAULT; + + if (fs210x->is_playing) + return 0; + + ret =3D fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PLAY); + if (!ret) + fs210x->is_playing =3D true; + + fsleep(10000); /* >=3D 10ms */ + + return ret; +} + +static int fs210x_dev_stop(struct fs210x_priv *fs210x) +{ + int ret; + + if (!fs210x->is_inited) + return -EFAULT; + + if (!fs210x->is_playing) + return 0; + + ret =3D fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + fs210x->is_playing =3D false; + + fsleep(30000); /* >=3D 30ms */ + + return ret; +} + +static int fs210x_set_reg_table(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene) +{ + const struct fs_amp_scene *cur_scene; + const struct fs_reg_table *reg; + + if (!fs210x || !fs210x->dev || !scene) + return -EINVAL; + + cur_scene =3D fs210x->cur_scene; + if (!scene->reg || cur_scene =3D=3D scene) { + dev_dbg(fs210x->dev, "Skip writing reg table\n"); + return 0; + } + + reg =3D scene->reg; + dev_dbg(fs210x->dev, "reg table size: %d\n", reg->size); + + return fs210x_reg_write_table(fs210x, reg); +} + +static int fs210x_set_woofer_table(struct fs210x_priv *fs210x) +{ + const struct fs_file_table *woofer; + const struct fs_fwm_table *table; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + /* NOTE: fs2105s has woofer ram only */ + if (fs210x->devid !=3D FS2105S_DEVICE_ID) + return 0; + + table =3D fs210x->amp_lib.table[FS_INDEX_WOOFER]; + if (!table) { + dev_dbg(fs210x->dev, "Skip writing woofer table\n"); + return 0; + } + + woofer =3D (struct fs_file_table *)table->buf; + dev_dbg(fs210x->dev, "woofer table size: %d\n", woofer->size); + /* Unit of woofer data is u32(4 bytes) */ + if (woofer->size =3D=3D 0 || (woofer->size & 0x3)) { + dev_err(fs210x->dev, "Invalid woofer size: %d\n", + woofer->size); + return -EINVAL; + } + + ret =3D fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS2105S_46H_CAM_BURST_W); + ret |=3D fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + woofer->buf, woofer->size); + + return ret; +} + +static int fs210x_set_effect_table(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene) +{ + const struct fs_amp_scene *cur_scene; + const struct fs_file_table *effect; + int half_size; + int ret; + + if (!fs210x || !fs210x->dev || !scene) + return -EINVAL; + + cur_scene =3D fs210x->cur_scene; + if (!scene->effect || cur_scene =3D=3D scene) { + dev_dbg(fs210x->dev, "Skip writing effect table\n"); + return 0; + } + + effect =3D scene->effect; + dev_dbg(fs210x->dev, "effect table size: %d\n", effect->size); + + /* Unit of effect data is u32(4 bytes), 2 channels */ + if (effect->size =3D=3D 0 || (effect->size & 0x7)) { + dev_err(fs210x->dev, "Invalid effect size: %d\n", + effect->size); + return -EINVAL; + } + + half_size =3D effect->size / 2; + + /* Left channel */ + ret =3D fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_BURST_L); + ret |=3D fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + effect->buf, half_size); + if (ret) + return ret; + + /* Right channel */ + ret =3D fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_BURST_R); + ret |=3D fs210x_reg_bulk_write(fs210x, FS210X_42H_DACEQWL, + effect->buf + half_size, half_size); + + return ret; +} + +static int fs210x_access_dsp_ram(struct fs210x_priv *fs210x, bool enable) +{ + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + if (enable) { + ret =3D fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_HIZ); + ret |=3D fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY, + FS210X_0BH_ACCKEY_ON); + } else { + ret =3D fs210x_reg_write(fs210x, FS210X_0BH_ACCKEY, + FS210X_0BH_ACCKEY_OFF); + ret |=3D fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + } + + fsleep(10000); /* >=3D 10ms */ + + return ret; +} + +static int fs210x_write_dsp_effect(struct fs210x_priv *fs210x, + const struct fs_amp_scene *scene, + int scene_id) +{ + int ret; + + if (!fs210x || !scene) + return -EINVAL; + + ret =3D fs210x_access_dsp_ram(fs210x, true); + if (ret) { + dev_err(fs210x->dev, "Failed to access dsp: %d\n", ret); + goto tag_exit; + } + + ret =3D fs210x_set_effect_table(fs210x, scene); + if (ret) { + dev_err(fs210x->dev, "Failed to set effect: %d\n", ret); + goto tag_exit; + } + + if (scene_id =3D=3D FS210X_INIT_SCENE) + ret =3D fs210x_set_woofer_table(fs210x); + +tag_exit: + fs210x_reg_write(fs210x, FS210X_46H_DACEQA, + FS210X_46H_CAM_CLEAR); + fs210x_access_dsp_ram(fs210x, false); + + return ret; +} + +static int fs210x_check_scene(struct fs210x_priv *fs210x, + int scene_id, bool *skip_set) +{ + struct fs_amp_lib *amp_lib; + + if (!fs210x || !skip_set) + return -EINVAL; + + amp_lib =3D &fs210x->amp_lib; + if (amp_lib->scene_count =3D=3D 0 || !amp_lib->scene) { + dev_err(fs210x->dev, "There's no scene data\n"); + return -EINVAL; + } + + if (scene_id < 0 || scene_id >=3D amp_lib->scene_count) { + dev_err(fs210x->dev, "Invalid scene_id: %d\n", scene_id); + return -EINVAL; + } + + if (fs210x->scene_id =3D=3D scene_id) { + dev_dbg(fs210x->dev, "Skip to set same scene\n"); + return 0; + } + + *skip_set =3D false; + + return 0; +} + +static int fs210x_set_scene(struct fs210x_priv *fs210x, int scene_id) +{ + const struct fs_amp_scene *scene; + bool skip_set =3D true; + bool is_playing; + int ret; + + if (!fs210x || !fs210x->dev) + return -EINVAL; + + ret =3D fs210x_check_scene(fs210x, scene_id, &skip_set); + if (ret || skip_set) + return ret; + + scene =3D fs210x->amp_lib.scene + scene_id; + dev_info(fs210x->dev, "Switch scene.%d: %s\n", + scene_id, scene->name); + + is_playing =3D fs210x->is_playing; + if (is_playing) + fs210x_dev_stop(fs210x); + + ret =3D fs210x_set_reg_table(fs210x, scene); + if (ret) { + dev_err(fs210x->dev, "Failed to set reg: %d\n", ret); + return ret; + } + + ret =3D fs210x_write_dsp_effect(fs210x, scene, scene_id); + if (ret) { + dev_err(fs210x->dev, "Failed to write ram: %d\n", ret); + return ret; + } + + fs210x->cur_scene =3D scene; + fs210x->scene_id =3D scene_id; + + if (is_playing) + fs210x_dev_play(fs210x); + + return 0; +} + +static int fs210x_init_chip(struct fs210x_priv *fs210x) +{ + int scene_id; + int ret; + + regcache_cache_bypass(fs210x->regmap, true); + + if (!fs210x->gpio_sdz) { + /* Gpio is not found, i2c reset */ + ret =3D fs210x_reg_write(fs210x, FS210X_10H_PWRCTRL, + FS210X_10H_I2C_RESET); + if (ret) + goto tag_power_down; + } else { + /* gpio reset, deactivate */ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); + } + + fsleep(10000); /* >=3D 10ms */ + + /* Backup scene id */ + scene_id =3D fs210x->scene_id; + fs210x->scene_id =3D -1; + + /* Init registers/RAM by init scene */ + ret =3D fs210x_set_scene(fs210x, FS210X_INIT_SCENE); + if (ret) + goto tag_power_down; + + /* + * If the firmware has effect scene(s), + * we load effect scene by default scene or scene_id + */ + if (fs210x->amp_lib.scene_count > 1) { + if (scene_id < FS210X_DEFAULT_SCENE) + scene_id =3D FS210X_DEFAULT_SCENE; + ret =3D fs210x_set_scene(fs210x, scene_id); + if (ret) + goto tag_power_down; + } + +tag_power_down: + /* Power down the device */ + ret |=3D fs210x_reg_write(fs210x, FS210X_11H_SYSCTRL, + FS210X_11H_DPS_PWDN); + fsleep(10000); /* >=3D 10ms */ + + regcache_cache_bypass(fs210x->regmap, false); + if (!ret) { + regcache_mark_dirty(fs210x->regmap); + regcache_sync(fs210x->regmap); + fs210x->is_inited =3D true; + } + + return ret; +} + +static int fs210x_set_i2s_params(struct fs210x_priv *fs210x) +{ + const struct fs_i2s_srate params[] =3D { + { 16000, 0x3 }, + { 32000, 0x7 }, + { 44100, 0x8 }, + { 48000, 0x9 }, + { 88200, 0xA }, + { 96000, 0xB }, + }; + u16 val; + int i, ret; + + for (i =3D 0; i < ARRAY_SIZE(params); i++) { + if (params[i].srate !=3D fs210x->srate) + continue; + val =3D params[i].i2ssr << FS210X_17H_I2SSR_SHIFT; + ret =3D fs210x_reg_update_bits(fs210x, + FS210X_17H_I2SCTRL, + FS210X_17H_I2SSR_MASK, + val); + return ret; + } + + dev_err(fs210x->dev, "Invalid sample rate: %d\n", fs210x->srate); + + return -EINVAL; +} + +static int fs210x_get_pll_div(struct fs210x_priv *fs210x, + const struct fs_pll_div **pll_div) +{ + int i; + + if (!fs210x || !pll_div) + return -EINVAL; + + for (i =3D 0; i < ARRAY_SIZE(fs210x_pll_div); i++) { + if (fs210x_pll_div[i].bclk !=3D fs210x->bclk) + continue; + *pll_div =3D fs210x_pll_div + i; + return 0; + } + + dev_err(fs210x->dev, "No PLL table for bclk: %d\n", fs210x->bclk); + + return -EFAULT; +} + +static int fs210x_set_hw_params(struct fs210x_priv *fs210x) +{ + const struct fs_pll_div *pll_div; + int ret; + + ret =3D fs210x_set_i2s_params(fs210x); + if (ret) { + dev_err(fs210x->dev, "Failed to set i2s params: %d\n", ret); + return ret; + } + + /* Set pll params */ + ret =3D fs210x_get_pll_div(fs210x, &pll_div); + if (ret) + return ret; + + ret =3D fs210x_reg_write(fs210x, FS210X_A1H_PLLCTRL1, pll_div->pll1); + ret |=3D fs210x_reg_write(fs210x, FS210X_A2H_PLLCTRL2, pll_div->pll2); + ret |=3D fs210x_reg_write(fs210x, FS210X_A3H_PLLCTRL3, pll_div->pll3); + + return ret; +} + +static int fs210x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + const struct snd_pcm_hw_constraint_list *list; + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D snd_soc_component_get_drvdata(dai->component); + if (!fs210x) { + pr_err("dai_startup: fs210x is null\n"); + return -EINVAL; + } + + if (!substream->runtime) + return 0; + + ret =3D snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + FS210X_FORMATS); + if (ret < 0) { + dev_err(fs210x->dev, + "Failed to set hw param format: %d\n", ret); + return ret; + } + + if (fs210x->devid =3D=3D FS2105S_DEVICE_ID) + list =3D &fs2105s_constraints; + else + list =3D &fs210x_constraints; + + ret =3D snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + list); + if (ret < 0) { + dev_err(fs210x->dev, + "Failed to set hw param rate: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fs210x_priv *fs210x; + + fs210x =3D snd_soc_component_get_drvdata(dai->component); + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + /* Only supports consumer mode */ + break; + default: + dev_err(fs210x->dev, "Only supports consumer mode\n"); + return -EINVAL; + } + + return 0; +} + +static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fs210x_priv *fs210x; + int chn_num; + int ret; + + if (substream->stream !=3D SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x =3D snd_soc_component_get_drvdata(dai->component); + + fs210x->srate =3D params_rate(params); + fs210x->bclk =3D snd_soc_params_to_bclk(params); + chn_num =3D params_channels(params); + if (chn_num =3D=3D 1) /* mono */ + fs210x->bclk *=3D 2; /* I2S bus has 2 channels */ + + /* The FS2105S can't support 16kHz sample rate. */ + if (fs210x->devid =3D=3D FS2105S_DEVICE_ID && fs210x->srate =3D=3D 16000) + return -EOPNOTSUPP; + + mutex_lock(&fs210x->lock); + ret =3D fs210x_set_hw_params(fs210x); + mutex_unlock(&fs210x->lock); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + + return ret; +} + +static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct fs210x_priv *fs210x; + unsigned long delay; + + if (stream !=3D SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + fs210x =3D snd_soc_component_get_drvdata(dai->component); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended) { + mutex_unlock(&fs210x->lock); + return 0; + } + + mutex_unlock(&fs210x->lock); + + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); + } else { + delay =3D msecs_to_jiffies(fs210x->check_interval_ms); + schedule_delayed_work(&fs210x->fault_check_work, delay); + } + + return 0; +} + +static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct fs210x_priv *fs210x; + + fs210x =3D snd_soc_component_get_drvdata(dai->component); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { + mutex_unlock(&fs210x->lock); + return 0; + } + + mutex_unlock(&fs210x->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * According to the power up/down sequence of FS210x, + * it requests the I2S clock has been present + * and stable(>=3D 2ms) before playing. + */ + schedule_delayed_work(&fs210x->start_work, + msecs_to_jiffies(FS210X_START_DELAY_MS)); + break; + + default: + break; + } + + return 0; +} + +static void fs210x_start_work(struct work_struct *work) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D container_of(work, struct fs210x_priv, start_work.work); + + mutex_lock(&fs210x->lock); + + ret =3D fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); + + mutex_unlock(&fs210x->lock); +} + +static void fs210x_fault_check_work(struct work_struct *work) +{ + struct fs210x_priv *fs210x; + u16 status; + int ret; + + fs210x =3D container_of(work, struct fs210x_priv, fault_check_work.work); + + mutex_lock(&fs210x->lock); + + if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { + mutex_unlock(&fs210x->lock); + return; + } + + ret =3D fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + mutex_unlock(&fs210x->lock); + if (ret) + return; + + if (!(status & FS210X_05H_PVDD_MASK)) + dev_err(fs210x->dev, "PVDD fault\n"); + if (status & FS210X_05H_OCDL_MASK) + dev_err(fs210x->dev, "OC detected\n"); + if (status & FS210X_05H_UVDL_MASK) + dev_err(fs210x->dev, "UV detected\n"); + if (status & FS210X_05H_OVDL_MASK) + dev_err(fs210x->dev, "OV detected\n"); + if (status & FS210X_05H_OTPDL_MASK) + dev_err(fs210x->dev, "OT detected\n"); + if (status & FS210X_05H_OCRDL_MASK) + dev_err(fs210x->dev, "OCR detected\n"); + if (status & FS210X_05H_OCLDL_MASK) + dev_err(fs210x->dev, "OCL detected\n"); + if (status & FS210X_05H_DCRDL_MASK) + dev_err(fs210x->dev, "DCR detected\n"); + if (status & FS210X_05H_DCLDL_MASK) + dev_err(fs210x->dev, "DCL detected\n"); + if (status & FS210X_05H_SRDL_MASK) + dev_err(fs210x->dev, "SR detected\n"); + if (status & FS210X_05H_OTWDL_MASK) + dev_err(fs210x->dev, "OTW detected\n"); + if (!(status & FS210X_05H_AMPS_MASK)) + dev_dbg(fs210x->dev, "Amplifier unready\n"); + if (!(status & FS210X_05H_PLLS_MASK)) + dev_err(fs210x->dev, "PLL unlock\n"); + if (!(status & FS210X_05H_ANAS_MASK)) + dev_err(fs210x->dev, "Analog power fault\n"); + + schedule_delayed_work(&fs210x->fault_check_work, + msecs_to_jiffies(fs210x->check_interval_ms)); +} + +static int fs210x_get_drvdata_from_kctrl(struct snd_kcontrol *kctrl, + struct fs210x_priv **fs210x) +{ + struct snd_soc_component *cmpnt; + + if (!kctrl) { + pr_err("fs210x: kcontrol is null\n"); + return -EINVAL; + } + + cmpnt =3D snd_soc_kcontrol_component(kctrl); + if (!cmpnt) { + pr_err("fs210x: component is null\n"); + return -EINVAL; + } + + *fs210x =3D snd_soc_component_get_drvdata(cmpnt); + + return 0; +} + +static int fs210x_effect_scene_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const struct fs_amp_scene *scene; + struct fs210x_priv *fs210x; + const char *name =3D "N/A"; + int idx, count; + int ret; + + ret =3D fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_info: fs210x is null\n"); + return -EINVAL; + } + + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count =3D 1; + + count =3D fs210x->amp_lib.scene_count - 1; /* Skip init scene */ + if (count < 1) { + uinfo->value.enumerated.items =3D 0; + return 0; + } + + uinfo->value.enumerated.items =3D count; + if (uinfo->value.enumerated.item >=3D count) + uinfo->value.enumerated.item =3D count - 1; + + idx =3D uinfo->value.enumerated.item; + scene =3D fs210x->amp_lib.scene + idx + 1; + if (scene->name) + name =3D scene->name; + + strscpy(uinfo->value.enumerated.name, name, strlen(name) + 1); + + return 0; +} + +static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int index; + int ret; + + ret =3D fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_get: fs210x is null\n"); + return -EINVAL; + } + + /* The id of effect scene is from 1 to N. */ + if (fs210x->scene_id < 1) + return -EINVAL; + + mutex_lock(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id =3D 0 + * effect scene(s): id =3D 1~N (optional) + * effect_index =3D scene_id - 1 + */ + index =3D fs210x->scene_id - 1; + ucontrol->value.integer.value[0] =3D index; + mutex_unlock(&fs210x->lock); + + return 0; +} + +static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct fs210x_priv *fs210x; + int scene_id, scene_count; + bool is_changed =3D false; + int ret; + + ret =3D fs210x_get_drvdata_from_kctrl(kcontrol, &fs210x); + if (ret || !fs210x->dev) { + pr_err("scene_effect_put: fs210x is null\n"); + return -EINVAL; + } + + mutex_lock(&fs210x->lock); + + /* + * FS210x has scene(s) as below: + * init scene: id =3D 0 (It's set in fs210x_init_chip() only) + * effect scene(s): id =3D 1~N (optional) + * scene_id =3D effect_index + 1. + */ + scene_id =3D ucontrol->value.integer.value[0] + 1; + scene_count =3D fs210x->amp_lib.scene_count - 1; /* Skip init scene */ + if (scene_id < 1 || scene_id > scene_count) { + mutex_unlock(&fs210x->lock); + return -ERANGE; + } + + if (scene_id !=3D fs210x->scene_id) + is_changed =3D true; + + if (fs210x->is_suspended) { + fs210x->scene_id =3D scene_id; + mutex_unlock(&fs210x->lock); + return is_changed; + } + + ret =3D fs210x_set_scene(fs210x, scene_id); + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + + mutex_unlock(&fs210x->lock); + + if (!ret && is_changed) + return 1; + + return ret; +} + +static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct snd_soc_component *cmpnt =3D snd_soc_dapm_to_component(w->dapm); + struct fs210x_priv *fs210x =3D snd_soc_component_get_drvdata(cmpnt); + int ret; + + mutex_lock(&fs210x->lock); + + if (fs210x->is_suspended) { + mutex_unlock(&fs210x->lock); + return 0; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* + * If there is no bclk for us to set the clock output, + * we will enable the device(start_work) in dai trigger. + */ + if (!fs210x->clk_bclk) + break; + fs210x_bclk_set(fs210x, true); + ret =3D fs210x_dev_play(fs210x); + break; + case SND_SOC_DAPM_POST_PMD: + ret =3D fs210x_dev_stop(fs210x); + fs210x_bclk_set(fs210x, false); + break; + default: + break; + } + + mutex_unlock(&fs210x->lock); + + return ret; +} + +static const struct snd_soc_dai_ops fs210x_dai_ops =3D { + .startup =3D fs210x_dai_startup, + .set_fmt =3D fs210x_dai_set_fmt, + .hw_params =3D fs210x_dai_hw_params, + .mute_stream =3D fs210x_dai_mute, + .trigger =3D fs210x_dai_trigger, +}; + +static const struct snd_soc_dai_driver fs210x_dai =3D { + .name =3D FS210X_DEFAULT_DAI_NAME, + .playback =3D { + .stream_name =3D "Playback", + .channels_min =3D 1, + .channels_max =3D 2, + .rates =3D FS210X_RATES, + .formats =3D FS210X_FORMATS, + }, + .capture =3D { + .stream_name =3D "Capture", + .channels_min =3D 1, + .channels_max =3D 2, + .rates =3D FS210X_RATES, + .formats =3D FS210X_FORMATS, + }, + .ops =3D &fs210x_dai_ops, + .symmetric_rate =3D 1, + .symmetric_sample_bits =3D 1, +}; + +static const DECLARE_TLV_DB_SCALE(fs2105s_vol_tlv, -9709, 19, 1); +static const DECLARE_TLV_DB_SCALE(fs210x_vol_tlv, -13357, 19, 1); + +static const struct snd_kcontrol_new fs2105s_vol_control[] =3D { + SOC_DOUBLE_R_TLV("PCM Playback Volume", + FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL, + 7, 0x1FF, 0, fs2105s_vol_tlv), +}; + +static const struct snd_kcontrol_new fs210x_vol_control[] =3D { + SOC_DOUBLE_R_TLV("PCM Playback Volume", + FS210X_39H_LVOLCTRL, FS210X_3AH_RVOLCTRL, + 6, 0x2BF, 0, fs210x_vol_tlv), +}; + +static const struct snd_kcontrol_new fs210x_controls[] =3D { + SOC_DOUBLE("DAC Mute Switch", FS210X_30H_DACCTRL, 4, 8, 1, 0), + SOC_DOUBLE("DAC Fade Switch", FS210X_30H_DACCTRL, 5, 9, 1, 0), +}; + +static const struct snd_kcontrol_new fs210x_scene_control[] =3D { + FS_SOC_ENUM_EXT("Effect Scene", + fs210x_effect_scene_info, + fs210x_effect_scene_get, + fs210x_effect_scene_put), +}; + +static const struct snd_soc_dapm_widget fs210x_dapm_widgets[] =3D { + SND_SOC_DAPM_AIF_IN_E("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0, + fs210x_playback_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("AIF OUT", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), + SND_SOC_DAPM_INPUT("SDO"), +}; + +static const struct snd_soc_dapm_route fs210x_dapm_routes[] =3D { + { "OUTL", NULL, "AIF IN" }, + { "OUTR", NULL, "AIF IN" }, + { "AIF OUT", NULL, "SDO" }, +}; + +static int fs210x_add_mixer_controls(struct fs210x_priv *fs210x, + struct snd_soc_component *cmpnt) +{ + const struct snd_kcontrol_new *kctrl; + int count; + int ret; + + if (!fs210x || !cmpnt) + return -EINVAL; + + if (fs210x->devid =3D=3D FS2105S_DEVICE_ID) { + kctrl =3D fs2105s_vol_control; + count =3D ARRAY_SIZE(fs2105s_vol_control); + } else { + kctrl =3D fs210x_vol_control; + count =3D ARRAY_SIZE(fs210x_vol_control); + } + + ret =3D snd_soc_add_component_controls(cmpnt, kctrl, count); + if (ret) + return ret; + + /* + * If the firmware has no scene or only init scene, + * we skip adding this mixer control. + */ + if (fs210x->amp_lib.scene_count < 2) + return 0; + + kctrl =3D fs210x_scene_control; + count =3D ARRAY_SIZE(fs210x_scene_control); + + return snd_soc_add_component_controls(cmpnt, kctrl, count); +} + +static int fs210x_probe(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + fs210x->amp_lib.dev =3D fs210x->dev; + fs210x->amp_lib.devid =3D fs210x->devid; + + ret =3D fs_amp_load_firmware(&fs210x->amp_lib, fs210x->pdata.fwm_name); + if (ret) + return ret; + + ret =3D fs210x_add_mixer_controls(fs210x, cmpnt); + if (ret) + return ret; + + mutex_lock(&fs210x->lock); + ret =3D fs210x_init_chip(fs210x); + mutex_unlock(&fs210x->lock); + + return ret; +} + +static void fs210x_remove(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + + fs210x =3D snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return; + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +} + +#ifdef CONFIG_PM +static int fs210x_suspend(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + regcache_cache_only(fs210x->regmap, true); + + mutex_lock(&fs210x->lock); + fs210x->cur_scene =3D NULL; + fs210x->is_inited =3D false; + fs210x->is_playing =3D false; + fs210x->is_suspended =3D true; + + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ + fsleep(30000); /* >=3D 30ms */ + mutex_unlock(&fs210x->lock); + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); + + ret =3D regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to suspend: %d\n", ret); + return ret; + } + + return 0; +} + +static int fs210x_resume(struct snd_soc_component *cmpnt) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D snd_soc_component_get_drvdata(cmpnt); + if (!fs210x || !fs210x->dev) + return -EINVAL; + + ret =3D regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) { + dev_err(fs210x->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + mutex_lock(&fs210x->lock); + + fs210x->is_suspended =3D false; + ret =3D fs210x_init_chip(fs210x); + + mutex_unlock(&fs210x->lock); + + return ret; +} +#else +#define fs210x_suspend NULL +#define fs210x_resume NULL +#endif // CONFIG_PM + +static bool fs210x_volatile_registers(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FS210X_00H_STATUS ... FS210X_0FH_I2CADDR: + case FS210X_ABH_INTSTAT: + case FS210X_ACH_INTSTATR: + return true; + default: + return false; + } +} + +static const struct snd_soc_component_driver fs210x_soc_component_dev =3D { + .probe =3D fs210x_probe, + .remove =3D fs210x_remove, + .suspend =3D fs210x_suspend, + .resume =3D fs210x_resume, + .controls =3D fs210x_controls, + .num_controls =3D ARRAY_SIZE(fs210x_controls), + .dapm_widgets =3D fs210x_dapm_widgets, + .num_dapm_widgets =3D ARRAY_SIZE(fs210x_dapm_widgets), + .dapm_routes =3D fs210x_dapm_routes, + .num_dapm_routes =3D ARRAY_SIZE(fs210x_dapm_routes), +}; + +static const struct regmap_config fs210x_regmap =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + .max_register =3D FS210X_REG_MAX, + .val_format_endian =3D REGMAP_ENDIAN_BIG, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D fs210x_volatile_registers, +}; + +static int fs210x_detect_device(struct fs210x_priv *fs210x) +{ + u16 devid; + int ret; + + ret =3D fs210x_reg_read(fs210x, FS210X_03H_DEVID, &devid); + if (ret) + return ret; + + fs210x->devid =3D HI_U16(devid); + + switch (fs210x->devid) { + case FS210X_DEVICE_ID: + dev_info(fs210x->dev, "FS2104 detected\n"); + break; + case FS2105S_DEVICE_ID: + dev_info(fs210x->dev, "FS2105S detected\n"); + break; + default: + dev_err(fs210x->dev, "DEVID: 0x%04X dismatch\n", devid); + return -ENODEV; + } + + return 0; +} + +static int fs210x_parse_dts(struct fs210x_priv *fs210x, + struct fs210x_platform_data *pdata) +{ + struct device_node *node =3D fs210x->dev->of_node; + int i, ret; + + if (!node) + return 0; + + ret =3D of_property_read_string(node, "firmware-name", &pdata->fwm_name); + if (ret) + pdata->fwm_name =3D FS210X_DEFAULT_FWM_NAME; + + fs210x->gpio_sdz =3D devm_gpiod_get_optional(fs210x->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(fs210x->gpio_sdz)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->gpio_sdz), + "Failed to get reset-gpios\n"); + + for (i =3D 0; i < FS210X_NUM_SUPPLIES; i++) + fs210x->supplies[i].supply =3D fs210x_supply_names[i]; + + ret =3D devm_regulator_bulk_get(fs210x->dev, + ARRAY_SIZE(fs210x->supplies), + fs210x->supplies); + if (ret) + return dev_err_probe(fs210x->dev, ret, + "Failed to get supplies\n"); + + return 0; +} + +static void fs210x_deinit(struct fs210x_priv *fs210x) +{ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ + fsleep(10000); /* >=3D 10ms */ + + regulator_bulk_disable(FS210X_NUM_SUPPLIES, fs210x->supplies); +} + +static int fs210x_init(struct fs210x_priv *fs210x) +{ + int ret; + + ret =3D fs210x_parse_dts(fs210x, &fs210x->pdata); + if (ret) + return ret; + + fs210x->clk_bclk =3D devm_clk_get_optional(fs210x->dev, "bclk"); + if (IS_ERR(fs210x->clk_bclk)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->clk_bclk), + "Failed to get bclk\n"); + + ret =3D regulator_bulk_enable(FS210X_NUM_SUPPLIES, fs210x->supplies); + if (ret) + return dev_err_probe(fs210x->dev, ret, + "Failed to enable supplies\n"); + + /* Make sure the SDZ pin is pulled down enough time. */ + fsleep(10000); /* >=3D 10ms */ + gpiod_set_value_cansleep(fs210x->gpio_sdz, 0); /* Deactivate */ + fsleep(10000); /* >=3D 10ms */ + + ret =3D fs210x_detect_device(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return ret; + } + + fs210x->scene_id =3D -1; /* Invalid scene */ + fs210x->cur_scene =3D NULL; + fs210x->is_playing =3D false; + fs210x->is_inited =3D false; + fs210x->is_suspended =3D false; + fs210x->check_interval_ms =3D FS210X_FAULT_CHECK_INTERVAL_MS; + + INIT_DELAYED_WORK(&fs210x->fault_check_work, fs210x_fault_check_work); + INIT_DELAYED_WORK(&fs210x->start_work, fs210x_start_work); + mutex_init(&fs210x->lock); + + return 0; +} + +static int fs210x_register_snd_component(struct fs210x_priv *fs210x) +{ + struct snd_soc_dai_driver *dai_drv; + static int instance_id; + int ret; + + dai_drv =3D devm_kmemdup(fs210x->dev, &fs210x_dai, + sizeof(fs210x_dai), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + dai_drv->name =3D devm_kasprintf(fs210x->dev, + GFP_KERNEL, "%s-%d", + dai_drv->name, instance_id); + instance_id++; + + if (fs210x->devid =3D=3D FS2105S_DEVICE_ID) { + dai_drv->playback.rates =3D FS2105S_RATES; + dai_drv->capture.rates =3D FS2105S_RATES; + } + + ret =3D snd_soc_register_component(fs210x->dev, + &fs210x_soc_component_dev, + dai_drv, 1); + return ret; +} + +static ssize_t check_interval_ms_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct fs210x_priv *fs210x =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", fs210x->check_interval_ms); +} + +static ssize_t check_interval_ms_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct fs210x_priv *fs210x =3D dev_get_drvdata(dev); + int ret; + + ret =3D kstrtouint(buf, 10, &fs210x->check_interval_ms); + if (ret) + return -EINVAL; + + return (ssize_t)count; +} + +static DEVICE_ATTR_RW(check_interval_ms); + +static struct attribute *fs210x_attrs[] =3D { + &dev_attr_check_interval_ms.attr, + NULL, +}; + +static struct attribute_group fs210x_attr_group =3D { + .attrs =3D fs210x_attrs, +}; + +static int fs210x_i2c_probe(struct i2c_client *client) +{ + struct fs210x_priv *fs210x; + int ret; + + fs210x =3D devm_kzalloc(&client->dev, sizeof(*fs210x), GFP_KERNEL); + if (!fs210x) + return -ENOMEM; + + fs210x->i2c =3D client; + fs210x->dev =3D &client->dev; + i2c_set_clientdata(client, fs210x); + + fs210x->regmap =3D devm_regmap_init_i2c(client, &fs210x_regmap); + if (IS_ERR(fs210x->regmap)) + return dev_err_probe(fs210x->dev, PTR_ERR(fs210x->regmap), + "Failed to get regmap\n"); + + ret =3D fs210x_init(fs210x); + if (ret) + return ret; + + ret =3D devm_device_add_group(fs210x->dev, &fs210x_attr_group); + if (ret) { + fs210x_deinit(fs210x); + return dev_err_probe(fs210x->dev, ret, + "Failed to create sysfs group\n"); + } + + ret =3D fs210x_register_snd_component(fs210x); + if (ret) { + fs210x_deinit(fs210x); + return dev_err_probe(fs210x->dev, ret, + "Failed to register component\n"); + } + + return 0; +} + +static void fs210x_i2c_remove(struct i2c_client *client) +{ + struct fs210x_priv *fs210x =3D i2c_get_clientdata(client); + + snd_soc_unregister_component(fs210x->dev); + fs210x_deinit(fs210x); +} + +static const struct i2c_device_id fs210x_i2c_id[] =3D { + { "fs2104" }, + { "fs2105s" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, fs210x_i2c_id); + +static const struct of_device_id fs210x_of_match[] =3D { + { .compatible =3D "foursemi,fs2105s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fs210x_of_match); + +static struct i2c_driver fs210x_i2c_driver =3D { + .driver =3D { + .name =3D "fs210x", + .of_match_table =3D of_match_ptr(fs210x_of_match), + }, + .id_table =3D fs210x_i2c_id, + .probe =3D fs210x_i2c_probe, + .remove =3D fs210x_i2c_remove, +}; + +module_i2c_driver(fs210x_i2c_driver); + +MODULE_AUTHOR("Nick Li "); +MODULE_DESCRIPTION("FS2104/5S Audio Amplifier Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/fs210x.h b/sound/soc/codecs/fs210x.h new file mode 100644 index 000000000..78e176033 --- /dev/null +++ b/sound/soc/codecs/fs210x.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * fs210x.h -- Driver for the FS2104/5S Audio Amplifier + * + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + */ + +#ifndef __FS210X_H__ +#define __FS210X_H__ + +#define FS210X_00H_STATUS 0x00 +#define FS210X_03H_DEVID 0x03 +#define FS210X_05H_ANASTAT 0x05 +#define FS210X_06H_DIGSTAT 0x06 +#define FS210X_0BH_ACCKEY 0x0B +#define FS210X_0FH_I2CADDR 0x0F +#define FS210X_10H_PWRCTRL 0x10 +#define FS210X_11H_SYSCTRL 0x11 +#define FS210X_17H_I2SCTRL 0x17 +#define FS210X_30H_DACCTRL 0x30 +#define FS210X_39H_LVOLCTRL 0x39 +#define FS210X_3AH_RVOLCTRL 0x3A +#define FS210X_42H_DACEQWL 0x42 +#define FS210X_46H_DACEQA 0x46 +#define FS210X_A1H_PLLCTRL1 0xA1 +#define FS210X_A2H_PLLCTRL2 0xA2 +#define FS210X_A3H_PLLCTRL3 0xA3 +#define FS210X_ABH_INTSTAT 0xAB +#define FS210X_ACH_INTSTATR 0xAC + +#define FS210X_05H_PVDD_SHIFT 14 +#define FS210X_05H_PVDD_MASK BIT(14) +#define FS210X_05H_OCDL_SHIFT 13 +#define FS210X_05H_OCDL_MASK BIT(13) +#define FS210X_05H_UVDL_SHIFT 12 +#define FS210X_05H_UVDL_MASK BIT(12) +#define FS210X_05H_OVDL_SHIFT 11 +#define FS210X_05H_OVDL_MASK BIT(11) +#define FS210X_05H_OTPDL_SHIFT 10 +#define FS210X_05H_OTPDL_MASK BIT(10) +#define FS210X_05H_OCRDL_SHIFT 9 +#define FS210X_05H_OCRDL_MASK BIT(9) +#define FS210X_05H_OCLDL_SHIFT 8 +#define FS210X_05H_OCLDL_MASK BIT(8) +#define FS210X_05H_DCRDL_SHIFT 7 +#define FS210X_05H_DCRDL_MASK BIT(7) +#define FS210X_05H_DCLDL_SHIFT 6 +#define FS210X_05H_DCLDL_MASK BIT(6) +#define FS210X_05H_SRDL_SHIFT 5 +#define FS210X_05H_SRDL_MASK BIT(5) +#define FS210X_05H_OTWDL_SHIFT 4 +#define FS210X_05H_OTWDL_MASK BIT(4) +#define FS210X_05H_AMPS_SHIFT 3 +#define FS210X_05H_AMPS_MASK BIT(3) +#define FS210X_05H_PLLS_SHIFT 1 +#define FS210X_05H_PLLS_MASK BIT(1) +#define FS210X_05H_ANAS_SHIFT 0 +#define FS210X_05H_ANAS_MASK BIT(0) +#define FS210X_17H_I2SSR_SHIFT 12 +#define FS210X_17H_I2SSR_MASK GENMASK(15, 12) +#define FS210X_30H_RMUTE_SHIFT 8 +#define FS210X_30H_LMUTE_SHIFT 4 + +#define FS210X_0BH_ACCKEY_ON 0x0091 +#define FS210X_0BH_ACCKEY_OFF 0x0000 +#define FS210X_10H_I2C_RESET 0x0002 +#define FS210X_11H_DPS_HIZ 0x0100 +#define FS210X_11H_DPS_PWDN 0x0000 +#define FS210X_11H_DPS_PLAY 0x0300 +#define FS210X_46H_CAM_BURST_L 0x8000 +#define FS210X_46H_CAM_BURST_R 0x8200 +#define FS2105S_46H_CAM_BURST_W 0x8400 +#define FS210X_46H_CAM_CLEAR 0x0000 + +#endif /* __FS210X_H__ */ --=20 2.39.5