From patchwork Sat Oct 12 13:48:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Markus Stockhausen X-Patchwork-Id: 13833460 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 64A0FCF2566 for ; Sat, 12 Oct 2024 13:49:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=NlEouhifV2PHNL6DCehCYFFunOSMvLcTz1jj5u8ZmJE=; b=kx+C6+0nTsiYSz SlAZEPMRVBTsaoz4ExAoqoqY6HR10gxIKT+5jstXoLDawPlDVv2mjvgaUGg/toqN4AyX01WUSKk52 na6Bn8jW/3qKCucL5Ed8zV2O4//wr/tobnrqKzrywxkxWuCZEn1H78IXXYHqKgBhs+0JfNcp3mEdb 8UeBvvqW40TTTu7MOFn/SNlaC0+5AZojLWqyqOq+FRkNTFJ1zIICY1dFcpMtFjv0VowT17mNlZmZe ONo5ql5y7XNdWSlAtTD7VIZXUyFGFsMu9diPvCK/hsU3Fi6ByYmLAhgq/6xbl/o/4Eo/mw6pI+xwE OcCVhZxViV9MfdbDJ5hg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98 #2 (Red Hat Linux)) id 1szcUT-00000001I8Y-0VXa; Sat, 12 Oct 2024 13:49:05 +0000 Received: from mout.gmx.net ([212.227.15.18]) by bombadil.infradead.org with esmtps (Exim 4.98 #2 (Red Hat Linux)) id 1szcUQ-00000001I7j-1b7a for linux-phy@lists.infradead.org; Sat, 12 Oct 2024 13:49:04 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.de; s=s31663417; t=1728740918; x=1729345718; i=markus.stockhausen@gmx.de; bh=GYj5Y8dYhOKGETE0dKfWIyajVQoJN4CpNoHfjXbDEw0=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:Message-ID:In-Reply-To: References:MIME-Version:Content-Transfer-Encoding:cc: content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=eSZg2bNWfIOZ/GwPF5WKUmEib+CiafTTHmabaYMmpS9aIXBTt/gMabrwcYgvUFHc vFpllSAbKmzIK2+ZI6JDMzt4tUYGS22kAin5B+RfTxNLJYZCVtU4S/SmiyhYvRN2e i+5rOtZkumtxrH1XyaxFykUm5fNCJmpFBy2q+qFmqqqkU4zg+UofeXYNya1xvdOdf e0OXtd5ZpKxCeLomQKoEWJ7CKB8mlU1gmX98uylbHo5C7W1BPWDUDTd1fBglHECYq yaZl1mQTDvgWv9C6/u9lRalS7FXL5fvoekUvM5tBBKQlmjzL51XMscSaJDQmkuv9f iOxmI1Q2UWLhoAMcbA== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from openwrt ([94.31.70.17]) by mail.gmx.net (mrgmx004 [212.227.17.190]) with ESMTPSA (Nemesis) id 1MJVDW-1tEwRi2yUa-00JZeS; Sat, 12 Oct 2024 15:48:38 +0200 From: Markus Stockhausen To: vkoul@kernel.org, kishon@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, linux-phy@lists.infradead.org, devicetree@vger.kernel.org, chris.packham@alliedtelesis.co.nz Cc: Markus Stockhausen Subject: [PATCH v3 1/2] dt-bindings: phy: add realtek,rtl8380m-serdes Date: Sat, 12 Oct 2024 09:48:33 -0400 Message-ID: <20241012134834.1306992-2-markus.stockhausen@gmx.de> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241012134834.1306992-1-markus.stockhausen@gmx.de> References: <20241012134834.1306992-1-markus.stockhausen@gmx.de> MIME-Version: 1.0 X-Provags-ID: V03:K1:xPlqO6n/Cw0yXYnx4nmw5uYX2LUN0f8/ahCnFjTfIsppaB24i1h o5ilD3MEXm2yBtsXgBfSwdWEv9+bkv9gekGBtKV9ZzYEf2Ne9wUWzdVetxKH4K3v5nPq3lf SoxGl53rtjkweVRzuxWSLOYYXeGZYE41giDd6I5sboIjyO65zFsmcHK7ts+dO7NeWcswPmB dm5rxXEfZzceD8olLzK5w== UI-OutboundReport: notjunk:1;M01:P0:vw5mg7WeZQw=;e9lhV4cdZ7dGzOthZEypc9Rjbpx ZzE2ubHcng7Qi6MiG33eU19D+wqkkHELvUEJi2q1Ob4WMXN57H3+yCzV2GXefRBPh6MFfmjQI oBanb5Wt/dta3cTfMFjYo+2VJHlgF9NIpCWsoqHCv/MFAwDODLzTApqo99hYjYItrExm+7AjA LTh699sVdNp/o2fFiB7Dxzv8uWNCCpH/L7HpyBlRMHOfZoMpFgUsW1LwFFBOPMtHRQkwaB8mR lwqXpsEo+jNIYj+U8QNINRA329zzTUUPorrxZDje8+YsGx9/44+uSOV8BvRY0T4ckIRoiUzJY tQwl99xRfcuF2VPzpmFBgHL551O8VyRa7MnYKc4OdhTI5CPw5zn3yeeu2EAVskohtU8kGJc7Z J7YgosHoBeRBasEywSSQU6CjkOpCWJiPGZhftOl3W16dqbM/8Pfyg5SCM2IxguNNo2fayUJIJ q1ZGa96y/nA2I92MxJALRa23T+dVjpLQuaK2bUQOHutf/w+mkc7wX9EfGSoe6KfL/ae7R2nFG nLG4xouS9yzeJ3UjNC7JmxRc7d3qhoE/M9LL9kAxxedYDu8tf04Wl8hTpOtZL21F++vKM7SK/ pplJpMPAHVl/tyu59uCDwmI/v/nx+ufHsXRdPZ6HgzqWUbs5ZzIoq7Aq4k7NDWXbMZTPeJpgH azu7JlH2alV6EQ60wzaRR8rEzkebgckj3mojGvBbaCOBFzBZZ0W2SjI1EgHBZ2KRX4HfALU3a At5mKperEWp/ItH1tmQmfeGocB10h60UlMvQwFiCEg9QQl990wuSHMydYyzQHPSLJFoy9jFxN /0LSB5He8DrF/uguurlv0UIw== X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20241012_064902_730658_AF38BEDD X-CRM114-Status: GOOD ( 12.13 ) X-BeenThere: linux-phy@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Linux Phy Mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-phy" Errors-To: linux-phy-bounces+linux-phy=archiver.kernel.org@lists.infradead.org Add bindings for the SerDes of the Realtek Otto platform. These are MIPS based network Switch SoCs with up to 52 ports divided into four different model lines. Changes in v3 - renamed to realtek,rtl8380m-serdes.yaml - removed parameter controlled-ports - verified with make dt_binding_check - recipient list according to get_maintainers Changes in v2: - new subject - removed patch command sequences - renamed parameter controlled-ports to realtek,controlled-ports Signed-off-by: Markus Stockhausen --- .../bindings/phy/realtek,rtl8380m-serdes.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/realtek,rtl8380m-serdes.yaml -- 2.46.2 diff --git a/Documentation/devicetree/bindings/phy/realtek,rtl8380m-serdes.yaml b/Documentation/devicetree/bindings/phy/realtek,rtl8380m-serdes.yaml new file mode 100644 index 000000000000..c1deef8ec63c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/realtek,rtl8380m-serdes.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/realtek,rtl8380m-serdes.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek Otto SerDes controller + +maintainers: + - Markus Stockhausen + +description: + The MIPS based Realtek Switch SoCs of the Realtek RTL838x, RTL839x, RTL930x + and RTL931x series have multiple SerDes built in. They are linked to single, + quad or octa PHYs like the RTL8218B, RTL8218D or RTL8214FC and are one of + the integral part of the up-to-52-port switch architecture. Although these + SerDes controllers have common basics they are designed differently in the + SoC families. + +properties: + $nodename: + pattern: "^phy@[0-9a-f]+$" + + compatible: + items: + - enum: + - realtek,rtl8380m-serdes + - realtek,rtl8392m-serdes + - realtek,rtl9302b-serdes + - realtek,rtl9311-serdes + + reg: + items: + description: + The primary register memory location. On RTL83xx devices this is the + address to the I/O register range, on RTL93xx devices this is the + address of the MDIO style command/data registers. + + "#phy-cells": + const: 4 + description: + The first number defines the SerDes to use. The second number a linked + SerDes. E.g. if a octa 1G PHY is attached to two QSGMII SerDes. The third + number is the first switch port this SerDes is working for, the fourth + number is the last switch port the SerDes is working for. + + firmware-name: + maxItems: 1 + description: + An alternative name of the SerDes firmware image file located in the + firmware search path. Set to "" to disable firmware loading. + +required: + - compatible + - reg + - "#phy-cells" + +additionalProperties: false + +examples: + - | + serdes: phy@1b00e780 { + compatible = "realtek,rtl9302b-serdes"; + reg = <0x1b0003b0 0x8>; + firmware-name = "zyxel-xgs1210-12-serdes.fw"; + #phy-cells = <4>; + }; From patchwork Sat Oct 12 13:48:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Markus Stockhausen X-Patchwork-Id: 13833459 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 5A2B7CF2566 for ; Sat, 12 Oct 2024 13:48:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=Ws7fcKA0f7pVJO1kkRIgyWXHDJyMNhcUzr2TFD38IDM=; b=j3b+VsVzPPeugX tpSkR/dBFCgKpvbRQIwAGanRAx3ReZVsJefQSDhT9Bznj4yDeclvBV1uGB+yQH+uMHQriaE7sHVOw Gd32W2cBLrTec5urDC3j5mtRfIKEzaZt6TnW1B+adkD1enQz6zk10glvN5lsKrslUufXyerMKiMRE AR6raOAce0BdkaPLz+Q+zZpYy+LycC6TGqfti9ZjmXr9XQ5SELa694briPeJlbb8VvFnJ0C/9rOIJ YiGXc8eRgUavVSoNCEWgaOVUQlRhcEI3SfXv/IjQysHy7VX2FhNOg5JKYT1NlDge8oab+7H7uZohb bbVQrzU/1BbgOqQVaNdQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98 #2 (Red Hat Linux)) id 1szcUL-00000001I60-44ST; Sat, 12 Oct 2024 13:48:57 +0000 Received: from mout.gmx.net ([212.227.15.18]) by bombadil.infradead.org with esmtps (Exim 4.98 #2 (Red Hat Linux)) id 1szcUE-00000001I2x-3Mv7 for linux-phy@lists.infradead.org; Sat, 12 Oct 2024 13:48:57 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.de; s=s31663417; t=1728740919; x=1729345719; i=markus.stockhausen@gmx.de; bh=brWBVN4pxqTFv86ww/2Ca2YdBDXdplIr/58Vh0ohukU=; h=X-UI-Sender-Class:From:To:Cc:Subject:Date:Message-ID:In-Reply-To: References:MIME-Version:Content-Transfer-Encoding:cc: content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=i0CIP1GlDdtvnwbOwnqxhBCxKkY3lOYw69c5Nzc/inciePUcUZqcHaUDS26HCUBl iJciWqUwl7y2vcYX0JFNYw9oGKOAYf8YeHuePj2XNJ3McJFff4yWsyDnYPk7lWWK/ /cyV/htaGQMcN8MhhtuRwnAIzr/9W3IvAkjhcbZ71DYXRXFdTPlMyew7wQATibobD HhFQI2IZhm91DgkLXnpnNhBKZ+sMUNgkHAd8JfQwP7DFTbaYs3zWuJE10bv2+whFb ErJxFNTMBF/F8KuX05pVBp2RbxUvzCUVyNC30nE6epoz72SAusrcrvcgrky0ujqzO X/ydKU/m/p7gmEkCCg== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from openwrt ([94.31.70.17]) by mail.gmx.net (mrgmx004 [212.227.17.190]) with ESMTPSA (Nemesis) id 1M89Kr-1t3j533rHv-007p8N; Sat, 12 Oct 2024 15:48:39 +0200 From: Markus Stockhausen To: vkoul@kernel.org, kishon@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, linux-phy@lists.infradead.org, devicetree@vger.kernel.org, chris.packham@alliedtelesis.co.nz Cc: Markus Stockhausen Subject: [PATCH v3 2/2] phy: Realtek Otto SerDes driver Date: Sat, 12 Oct 2024 09:48:34 -0400 Message-ID: <20241012134834.1306992-3-markus.stockhausen@gmx.de> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241012134834.1306992-1-markus.stockhausen@gmx.de> References: <20241012134834.1306992-1-markus.stockhausen@gmx.de> MIME-Version: 1.0 X-Provags-ID: V03:K1:6SWb8Kwa8X4fmawkLHHWa8eTIIH5F3AcyoBynfz2lI9dhspKQiy DEfj8bXETOLWAWiJY+vuUngrCtgO5kybK+rUMOl0aGz8cfYpb95PTXU+H7G7caV57ZkIEZc 3ECmjpvC5hDoq11tkWt/q6IiFuAGa0HKmMF0KNCgMMGVIj6J30Hc2NRjldO7YdlgSOPAyzr LM1dHv2MM6X83rdRoWmLA== UI-OutboundReport: notjunk:1;M01:P0:tz0YCAAPvRs=;jJvAMw+zkwt4dzhCFIdsGaYZOgi IWkphQfIDxLqgpurPaE3E/DDm2I4jMducODeJ+5Vm+HGh7izKirs2APdDnHDwubv+21c2G6vD jtfZBbi5NuQBqX1YziWnLr1dMot/QmW/fnx48SZLsrIS6yAbESGTRPJy0zJnnTb2kNUizTvAt yVyQhwaTdQBYz3PbWjZqaL47a3HOOX9yrGJnLoYwzYA5d7gQ20YDrb26+OsBV3eLJ34lqnUX6 iuZPxzlXROirbgcgDyibHfJx7zIlH9SbCdHITASIR3cZ7OFBsbeG3m3ftUENhiRz/5e69Fnd/ 3hpYSZtTjz3lSsxdnM560imj99JyBKQRRELI2sH5pedI/Dq/hclEBOOuBwiJkWLNMDj3ZZLIt VO0Ecc5fA62esNZe1+IKvtOMpUtBwSafWu+Ofn7aFrK2lSai4cu0eWnZ8rnRWAYDoO8+AMI0U +64AKb8j+oTqyC4mE4v9ZEUHdGNdJpfxnDBCjAmXrm6HWeRJ+LWj4HumW3hPaBWR3mb18BSYg eW+LBzFeKAT8sNgYe3C17FvXyvpiowgZQocQD0JWtCwxcYTwN39Bd/OLWnM8ZVfe/tTW0VaTh JCXIdUD4yZ3PXMOySag0Ra++anCTQLcSIqUXUuD05ztkibHMN3rKPR7hYd1fWXLJ23SaFLAEt 7NjWTApZT0bC+c3xiRUpc4LgkXdg17rihOdNSJ1Tf1+/wyJ2NEoN0GVqFYv4tDM2GbEvtc4l4 f6n20SR0w4ZwvHfSojAHrpK9PIra+QHQaanYvjGNTdp7GSpF1DQO1bfv/fxwoBhc1oXKwL4FV ejA1IWhlMHXxzFyPZgmJ6PJA== X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20241012_064852_554552_676960C2 X-CRM114-Status: GOOD ( 31.92 ) X-BeenThere: linux-phy@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Linux Phy Mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-phy" Errors-To: linux-phy-bounces+linux-phy=archiver.kernel.org@lists.infradead.org The Realtek Otto platform is a series of 4 different MIPS32 based network switch SoCs. They consist of: - RTL838x: 500MHz single core, up to 28 ports 20GBps switching capacity - RTL839x: 700MHz single core, up to 52 ports 100GBps switching capacity - RTL930x: 700MHz single core, up to 28 ports 120GBps switching capacity - RTL931x: 1.0GHz dual core, up to 52 ports 170GBps switching capacity The SoCs have 6-14 SerDes that provide the interconnect between several one, quad or octa port attached transceivers like the RTL8214FC. This driver builts on top of several GPL source drops from different switch vendors and harmonizes the different programming models. The common basics are: - A SerDes is controlled through registers that are organized into pages - A page consists of 32x 16 bit registers that cover various functions - Registers are either accessed through I/O addresses or an MDIO style bus - The SerDes operate on different MII variants (mostly QSGMII & XGMII) While some of the pages have meaningful names the registers within a page cannot be identified. Use 2 digit hex notation for a consistent register access in the code and debug interface. The SerDes rely on heavy register modifications with lots of undocumented features. This is even hardware specific (board, transceivers, ...) and developers may not have access to all devices. Provide a debug interface that allows to access the most important internals. With that patching sequences can be developed that can be fed back as firmware files into the driver. Examples of other drivers with similar reset/register interfaces are: - gpu/drm/msm/adreno/a5xx_debugfs.c - gpu/drm/i915/i915_debugfs_params.c - gpu/drm/armada/armada_debugfs.c Changes in v3: REMARK FOR REVIEW! Because of helpful feedback and the problems that can arise from different hardware designs and device configurations this patch version was overhauled in several places. From now on patches can be applied that are loaded from firmware files. For this a lot of locations have been hardened to ensure that hardware is instructed the right way. This allows for easier adaption and bug analysis when moving forward with this driver in the future. So some changes might differ from the feedback for v2. - designed/explained meaningful firmware format - converted patch sequences to be firmware loadable - determine/print chip version to verify DT compatibility - consolidated/simplified reset code paths - verify input in debug interface - added page names in code where known & possible - add multiple helpers for cleaner code - add possibility to modify registers from debug interface - fixed kernel buildbot warnings - changed comments to imperative style - recipient list according to get_maintainers Changes in v2: - switched logic to internal patch sequences - added setup sequences for RTL838x and RTL839x - moved includes from header to source file - added helpers for better readability Signed-off-by: Markus Stockhausen --- drivers/phy/realtek/Kconfig | 10 + drivers/phy/realtek/Makefile | 1 + drivers/phy/realtek/phy-rtk-otto-serdes.c | 1367 +++++++++++++++++++++ drivers/phy/realtek/phy-rtk-otto-serdes.h | 171 +++ 4 files changed, 1549 insertions(+) create mode 100644 drivers/phy/realtek/phy-rtk-otto-serdes.c create mode 100644 drivers/phy/realtek/phy-rtk-otto-serdes.h -- 2.46.2 diff --git a/drivers/phy/realtek/Kconfig b/drivers/phy/realtek/Kconfig index 75ac7e7c31ae..021b4c4e700a 100644 --- a/drivers/phy/realtek/Kconfig +++ b/drivers/phy/realtek/Kconfig @@ -30,3 +30,13 @@ config PHY_RTK_RTD_USB3PHY of the parameters. endif # ARCH_REALTEK || COMPILE_TEST + +config PHY_RTK_OTTO_SERDES + tristate "SerDes driver for the Realtek Otto platform" + depends on OF + select GENERIC_PHY + help + Enable this to support Realtek SerDes in the RTL83xx and + RTL93xx network SoCs. These are based on MIPS32 architecture + and the SerDes connect to one to octa transceivers to build + up switches with up to 52 ports. diff --git a/drivers/phy/realtek/Makefile b/drivers/phy/realtek/Makefile index ed7b47ff8a26..34e607f33961 100644 --- a/drivers/phy/realtek/Makefile +++ b/drivers/phy/realtek/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PHY_RTK_RTD_USB2PHY) += phy-rtk-usb2.o obj-$(CONFIG_PHY_RTK_RTD_USB3PHY) += phy-rtk-usb3.o +obj-$(CONFIG_PHY_RTK_OTTO_SERDES) += phy-rtk-otto-serdes.o diff --git a/drivers/phy/realtek/phy-rtk-otto-serdes.c b/drivers/phy/realtek/phy-rtk-otto-serdes.c new file mode 100644 index 000000000000..22f3e077aa12 --- /dev/null +++ b/drivers/phy/realtek/phy-rtk-otto-serdes.c @@ -0,0 +1,1367 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver + * Copyright (c) 2024 Markus Stockhausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-rtk-otto-serdes.h" + +/* + * A Realtek Otto SerDes is configured/patched by writing specific values into its registers. + * These values are bound to the individual hardware and the transceivers that are connected to + * it. Depending on the model some of this might be integrated into the bootloader. To fully + * support different configurations allow the driver to load firmware files and run patch + * sequences. + * + * A firmware file contains a head, a directory and at the end the raw patch data. See + * structure rtsds_fw_head, rtsds_fw_dir an rtsds_fw_seq for more details. + * + * header + * (u32) magic = 0x83009300, see RTSDS_FW_MAGIC + * (u32) CRC checksum of the following data + * (u32) filesize + * (u32) directory size = number of sequences + * + * directory with n elements consisting of + * (u32) id of the sequence. See RTSDS_FW_EVT_xxx + * (u32) offset of patch data for this directory entry + * + * patch data with x elements consisting of + * (u16) action to process. See RTSDS_FW_OP_xxx + * (u16) mode for which the command is to be executed. See RTSDS_FW_MODE_xxx + * (u16) SerDes ports bitmask for which the command is to be executed + * (u16) page for action + * (u16) register for action + * (u16) value for action + * (u16) mask for write operations + * (u16) future use to avoid structure breakage + */ + +static const char *rtsds_fw_events[RTSDS_FW_EVT_CNT] = { + [RTSDS_FW_EVT_SETUP] = "setup", + [RTSDS_FW_EVT_INIT] = "init", + [RTSDS_FW_EVT_POWER_ON] = "power-on", + [RTSDS_FW_EVT_PRE_SET_MODE] = "pre-set-mode", + [RTSDS_FW_EVT_POST_SET_MODE] = "post-set-mode", + [RTSDS_FW_EVT_PRE_RESET] = "pre-reset", + [RTSDS_FW_EVT_POST_RESET] = "post-reset", + [RTSDS_FW_EVT_PRE_POWER_OFF] = "pre-power-off", + [RTSDS_FW_EVT_POST_POWER_OFF] = "post-power-off", +}; + +static const u8 rtsds_fw_modes[PHY_INTERFACE_MODE_MAX] = { + [PHY_INTERFACE_MODE_NA] = RTSDS_FW_MODE_ALL, + [PHY_INTERFACE_MODE_QSGMII] = RTSDS_FW_MODE_QSGMII, + [PHY_INTERFACE_MODE_XGMII] = RTSDS_FW_MODE_XGMII, + [PHY_INTERFACE_MODE_USXGMII] = RTSDS_FW_MODE_USXGMII, + [PHY_INTERFACE_MODE_1000BASEX] = RTSDS_FW_MODE_1000BASEX, + [PHY_INTERFACE_MODE_2500BASEX] = RTSDS_FW_MODE_2500BASEX, + [PHY_INTERFACE_MODE_10GBASER] = RTSDS_FW_MODE_10GBASER, +}; + +static int rtsds_fw_load(struct rtsds_ctrl *ctrl) +{ + int ret; + struct rtsds_fw_head *h; + u32 checksum; + const char *msg; + const char *fwprop; + char fwname[128]; + + ret = device_property_read_string(ctrl->dev, "firmware-name", &fwprop); + if (ret) + snprintf(fwname, sizeof(fwname), "realtek/%s", ctrl->conf->fwname); + else if (strcmp(fwprop, "")) + snprintf(fwname, sizeof(fwname), "realtek/%s", fwprop); + else { + dev_info(ctrl->dev, "firmware deactivated, patching disabled\n"); + return 0; + } + + ret = firmware_request_nowarn(&ctrl->firmware, fwname, ctrl->dev); + if (ret < 0) { + msg = "not found"; + goto error; + } + + if (ctrl->firmware->size < 16) { + msg = "size to small"; + goto error; + } + + h = (struct rtsds_fw_head *)ctrl->firmware->data; + if (h->magic != RTSDS_FW_MAGIC) { + msg = "magic mismatch"; + goto error; + } + + if (h->filesize != ctrl->firmware->size) { + msg = "size mismatch"; + goto error; + } + + checksum = ~crc32(0xFFFFFFFFU, ctrl->firmware->data + 8, ctrl->firmware->size - 8); + if (h->checksum != checksum) { + msg = "checksum mismatch"; + goto error; + } + + dev_info(ctrl->dev, "firmware %s: loaded with %d bytes, %d sequences\n", + fwname, ctrl->firmware->size, h->dirsize); + + return 0; +error: + dev_err(ctrl->dev, "firmware %s: %s, patching disabled\n", fwname, msg); + ctrl->firmware = NULL; + return -EINVAL; +} + +static struct rtsds_fw_seq *rtsds_fw_get_sequence(struct rtsds_ctrl *ctrl, int evt) +{ + int i; + struct rtsds_fw_head *h; + + if (!ctrl->firmware) + return NULL; + + h = (struct rtsds_fw_head *)ctrl->firmware->data; + for (i = 0; i < h->dirsize; i++) + if (h->dir[i].evtid == evt) + return (struct rtsds_fw_seq *)(ctrl->firmware->data + h->dir[i].offset); + + return NULL; +} + +static int rtsds_fw_run_event(struct rtsds_ctrl *ctrl, u32 sid, int evt) +{ + int ret, step = 1, delay = 0, mode = rtsds_fw_modes[ctrl->sds[sid].mode]; + struct rtsds_fw_seq *seq; + + if (evt >= RTSDS_FW_EVT_CNT || sid >= ctrl->conf->sds_cnt) + return -EINVAL; + + seq = rtsds_fw_get_sequence(ctrl, evt); + if (!seq) + return 0; + + while (seq->action != RTSDS_FW_OP_STOP) { + if (!(seq->ports & BIT(sid)) || + (seq->mode != RTSDS_FW_MODE_ALL && seq->mode != mode)) { + step++; seq++; + continue; + } + + if (seq->action == RTSDS_FW_OP_WAIT) + delay = seq->val; + + if (delay) { + dev_dbg(ctrl->dev, "%s/%03d: SDS %02d WAIT(%d)\n", + rtsds_fw_events[evt], step, sid, delay); + + usleep_range(delay << 10, (delay + 1) << 10); + } + + if (seq->action == RTSDS_FW_OP_MASK) { + dev_dbg(ctrl->dev, + "%s/%03d: SDS %02d MASK(0x%04x, 0x%04x, 0x%04x, 0x%04x, 0x%04x, 0x%04x)\n", + rtsds_fw_events[evt], step, sid, seq->mode, seq->ports, + seq->page, seq->reg, seq->val, seq->mask); + + ret = ctrl->conf->mask(ctrl, sid, seq->page, + seq->reg, seq->val, seq->mask); + if (ret) { + dev_err(ctrl->dev, + "sequence %s failed for SerDes %d at step %d, rc=%d", + rtsds_fw_events[evt], sid, step, ret); + return -EIO; + } + } + + step++; seq++; + } + + return 0; +} + +/* common helpers */ + +static inline bool rtsds_invalid_reg(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + return (sid >= ctrl->conf->sds_cnt || page >= ctrl->conf->page_cnt || reg > 31); +} + +static inline bool rtsds_invalid_sds(struct rtsds_ctrl *ctrl, u32 sid) +{ + return (sid >= ctrl->conf->sds_cnt); +} + +static inline bool rtsds_invalid_mask(u32 val, u32 mask) +{ + return (val & mask) != val; +} + +static int rtsds_hwmode_to_phymode(struct rtsds_ctrl *ctrl, int hwmode) +{ + for (int m = 0; m < PHY_INTERFACE_MODE_MAX; m++) + if (ctrl->conf->mode_map[m] == hwmode) + return m; + + return PHY_INTERFACE_MODE_MAX; +} + +static void rtsds_get_chip(struct rtsds_ctrl *ctrl) +{ + u32 val, shift = 28; + void __iomem __force *reg; + + if (ctrl->conf->family == RTSDS_838X_FAMILY) + reg = RTSDS_838X_MODEL_NAME_INFO; + else if (ctrl->conf->family == RTSDS_839X_FAMILY) + reg = RTSDS_839X_MODEL_NAME_INFO; + else { + reg = RTSDS_93XX_MODEL_NAME_INFO; + shift = 16; + } + + val = ioread32(reg); + ctrl->soc.model_id = val >> 16; + ctrl->soc.model_version = (val >> 11) & 0x1f; + + iomask32(0xf << shift, 0xa << shift, reg + 4); + val = ioread32(reg + 4); + ctrl->soc.chip_id = val & 0xffff; + ctrl->soc.chip_version = (val >> (44 - shift)) & 0x1f; + + snprintf(ctrl->soc.model_name, sizeof(ctrl->soc.model_name), + "RTL%04X%c", ctrl->soc.model_id, + ctrl->soc.model_version ? ctrl->soc.model_version + 64 : 0); + + snprintf(ctrl->soc.chip_name, sizeof(ctrl->soc.chip_name), + "%04X%c", ctrl->soc.chip_id, + ctrl->soc.chip_version ? ctrl->soc.chip_version + 64 : 0); +} + +/* common RTL838x and RTL839x helpers */ + +static inline int rtsds_83xx_sds_5g(u32 sid) +{ + return 0xcff & BIT(sid); +} + +static void rtsds_83xx_rx_reset(struct rtsds_ctrl *ctrl, u32 sid) +{ + u32 page, reg, bits, sds5g = rtsds_83xx_sds_5g(sid); + + if (sds5g) { + /* RTL838x or RTL839x 5G SerDes */ + page = RTSDS_PAGE_SDS_EXT; + reg = 0x09; + bits = RTSDS_BIT_RX_SELF; + } else if (sid == 8 || sid == 12) { + /* RTL839x 10G SerDes */ + page = RTSDS_PAGE_ANA_TG_EXT; + reg = 0x00; + bits = RTSDS_BIT_RX_SELF_10G; + } else + return; + + ctrl->conf->mask(ctrl, sid, page, reg, bits, bits); + usleep_range(100000, 101000); + ctrl->conf->mask(ctrl, sid, page, reg, 0, bits); +} + +static void rtsds_83xx_digital_reset(struct rtsds_ctrl *ctrl, u32 sid) +{ + int bits; + + /* soft reset */ + bits = RTSDS_BIT_SOFT_RST; + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS, 0x03, bits, bits); + usleep_range(100000, 101000); + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS, 0x03, 0, bits); + + /* SerDes RX/TX reset */ + bits = RTSDS_BIT_SDS_EN_RX | RTSDS_BIT_SDS_EN_TX; + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS, 0x00, 0, bits); + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS, 0x00, bits, bits); +} + +static void rtsds_83xx_cmu_reset(struct rtsds_ctrl *ctrl, u32 sid) +{ + int mask, shift, hi = sid | 1, sds5g = rtsds_83xx_sds_5g(sid); + + if (ctrl->conf->family == RTSDS_838X_FAMILY) { + /* 5G SerDes sequence for register with bits CMU_EN, RXEN, PDOWN */ + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS_EXT, 0x00, 0x4040, 0xffff); + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS_EXT, 0x00, 0x4740, 0xffff); + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS_EXT, 0x00, 0x47c0, 0xffff); + ctrl->conf->mask(ctrl, sid, RTSDS_PAGE_SDS_EXT, 0x00, 0x4000, 0xffff); + } else if (sds5g) { + shift = 4 + ((sid & 1) << 2); + mask = 3 << shift; + /* 5G SerDes sequence for undocumented shared CMU register at offset 0x3C0 */ + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_RG_EXT, 0x01, 1 << shift, mask); + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_RG_EXT, 0x01, 3 << shift, mask); + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_RG_EXT, 0x01, 0, mask); + } else { + shift = (sid & 1) << 2; + mask = 3 << shift; + /* 10G SerDes sequence for undocumented shared CMU register at offset 0x3F8 */ + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_TG_EXT, 0x1d, 1 << shift, mask); + usleep_range(500000, 501000); + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_TG_EXT, 0x1d, 3 << shift, mask); + ctrl->conf->mask(ctrl, hi, RTSDS_PAGE_ANA_TG_EXT, 0x1d, 0, mask); + } +} + +static int rtsds_83xx_reset(struct rtsds_ctrl *ctrl, u32 sid) +{ + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + rtsds_83xx_rx_reset(ctrl, sid); + rtsds_83xx_cmu_reset(ctrl, sid); + rtsds_83xx_digital_reset(ctrl, sid); + + return 0; +} + +/* common RTL930x and RTL931x helpers */ + +static inline int rtsds_rt93xx_io(struct rtsds_ctrl *ctrl, int cmd) +{ + int cnt = 100; + + iowrite32(cmd | RTSDS_93XX_SDS_BUSY, ctrl->base); + while (--cnt && (ioread32(ctrl->base) & RTSDS_93XX_SDS_BUSY)) + usleep_range(30, 60); + + return cnt ? 0 : -EIO; +} + +static int rtsds_93xx_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + int cmd = (sid << 2) | (page << 7) | (reg << 13) | RTSDS_93XX_SDS_READ; + + if (rtsds_rt93xx_io(ctrl, cmd)) + return -EIO; + + return ioread32(ctrl->base + 4) & 0xffff; +} + +static int rtsds_93xx_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask) +{ + int cmd = (sid << 2) | (page << 7) | (reg << 13) | RTSDS_93XX_SDS_WRITE; + + if (mask != 0xffff) { + int oldval = rtsds_93xx_read(ctrl, sid, page, reg); + + if (oldval < 0) + return -EIO; + val |= oldval & ~mask; + } + + iowrite32(val, ctrl->base + 4); + return rtsds_rt93xx_io(ctrl, cmd); +} + +static int rtsds_93xx_reset(struct rtsds_ctrl *ctrl, u32 sid) +{ + int ret, hwcur, hwoff, pwr = 0; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + if (ctrl->sds[sid].mode == PHY_INTERFACE_MODE_NA) + return 0; + + hwoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]; + hwcur = ctrl->conf->mode_map[ctrl->sds[sid].mode]; + + if (ctrl->conf->family == RTSDS_931X_FAMILY) { + pwr = ioread32(RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + iowrite32(pwr | BIT(sid), RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + } + + ret = ctrl->conf->set_hwmode(ctrl, sid, hwoff); + if (!ret) + ret = ctrl->conf->set_hwmode(ctrl, sid, hwcur); + + if (ctrl->conf->family == RTSDS_931X_FAMILY) + iowrite32(pwr, RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + + return ret; +} + +/* + * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped directly into + * 32 bit memory addresses. High 16 bits are always empty. A "lower memory block serves pages 0/3 + * a higher one pages 1/2. + */ + +static int rtsds_838x_reg_offset(u32 sid, u32 page, u32 reg) +{ + if (page == 0 || page == 3) + return (sid << 9) + (page << 7) + (reg << 2); + else + return 0xb80 + (sid << 8) + (page << 7) + (reg << 2); +} + +static int rtsds_838x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + int offs; + + if (rtsds_invalid_reg(ctrl, sid, page, reg)) + return -EINVAL; + + offs = rtsds_838x_reg_offset(sid, page, reg); + + /* read twice for link status latch */ + if (page == RTSDS_PAGE_FIB && reg == 0x01) + ioread32(ctrl->base + offs); + + return ioread32(ctrl->base + offs); +} + +static int rtsds_838x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask) +{ + int offs; + + if (rtsds_invalid_reg(ctrl, sid, page, reg) || rtsds_invalid_mask(val, mask)) + return -EINVAL; + + offs = rtsds_838x_reg_offset(sid, page, reg); + + /* read twice for link status latch */ + if (page == RTSDS_PAGE_FIB && reg == 0x01) + ioread32(ctrl->base + offs); + + iomask32(mask, val, ctrl->base + offs); + + return 0; +} + +static int rtsds_838x_set_hwmode(struct rtsds_ctrl *ctrl, u32 sid, int combomode) +{ + int shift, mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode); + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + if (sid >= 4) { + shift = (sid - 4) * 3; + iomask32(0x7 << shift, (submode & 0x7) << shift, RTSDS_838X_INT_MODE_CTRL); + } else if (submode != 0) + return -EINVAL; + + shift = 25 - sid * 5; + iomask32(0x1f << shift, (mode & 0x1f) << shift, RTSDS_838X_SDS_MODE_SEL); + + return 0; +} + +static int rtsds_838x_get_hwmode(struct rtsds_ctrl *ctrl, u32 sid) +{ + int shift, mode, submode = 0; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + if (sid >= 4) { + shift = (sid - 4) * 3; + submode = (ioread32(RTSDS_838X_INT_MODE_CTRL) >> shift) & 0x7; + } + + shift = 25 - sid * 5; + mode = (ioread32(RTSDS_838X_SDS_MODE_SEL) >> shift) & 0x1f; + + return RTSDS_COMBOMODE(mode, submode); +} + +/* + * The RTL839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13 are + * 10GIt. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area. Per 32 bit + * address two registers are stored. The first register is stored in the lower 2 bytes ("on the + * right" due to big endian) and the second register in the upper 2 bytes. We know the following + * register areas: + * + * - XSG0 (4 pages @ offset 0x000): for even SerDes + * - XSG1 (4 pages @ offset 0x100): for odd SerDes + * - TGRX (4 pages @ offset 0x200): for even 10G SerDes + * - ANA_RG (2 pages @ offset 0x300): for even 5G SerDes + * - ANA_RG (2 pages @ offset 0x380): for odd 5G SerDes + * - ANA_TG (2 pages @ offset 0x300): for even 10G SerDes + * - ANA_TG (2 pages @ offset 0x380): for odd 10G SerDes + * + * The most consistent mapping that aligns to the RTL93xx devices is: + * + * even 5G SerDes odd 5G SerDes even 10G SerDes odd 10G SerDes + * Page 0: XSG0/0 XSG1/0 XSG0/0 XSG1/0 + * Page 1: XSG0/1 XSG1/1 XSG0/1 XSG1/1 + * Page 2: XSG0/2 XSG1/2 XSG0/2 XSG1/2 + * Page 3: XSG0/3 XSG1/3 XSG0/3 XSG1/3 + * Page 4: TGRX/0 + * Page 5: TGRX/1 + * Page 6: TGRX/2 + * Page 7: TGRX/3 + * Page 8: ANA_RG ANA_RG + * Page 9: ANA_RG_EXT ANA_RG_EXT + * Page 10: ANA_TG ANA_TG + * Page 11: ANA_TG_EXT ANA_TG_EXT + */ + +static int rtsds_839x_reg_offset(u32 sid, u32 page, u32 reg) +{ + int offs = ((sid & 0xfe) << 9) + ((reg & 0xfe) << 1) + (page << 6); + int sds5g = rtsds_83xx_sds_5g(sid); + + if (page < 4) + return offs + ((sid & 1) << 8); + else if ((page & 4) && (sid == 8 || sid == 12)) + return offs + 0x100; + else if (page >= 8 && page <= 9 && sds5g) + return offs + 0x100 + ((sid & 1) << 7); + else if (page >= 10 && !sds5g) + return offs + 0x80 + ((sid & 1) << 7); + + return -1; /* hole */ +} + +static int rtsds_839x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + int offs, shift = (reg << 4) & 0x10; + + if (rtsds_invalid_reg(ctrl, sid, page, reg)) + return -EINVAL; + + offs = rtsds_839x_reg_offset(sid, page, reg); + if (offs < 0) + return 0; + + /* read twice for link status latch */ + if (page == RTSDS_PAGE_FIB && reg == 0x01) + ioread32(ctrl->base + offs); + + return (ioread32(ctrl->base + offs) >> shift) & 0xffff; +} + +static int rtsds_839x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask) +{ + int oldval, offs; + + if (rtsds_invalid_reg(ctrl, sid, page, reg) || rtsds_invalid_mask(val, mask)) + return -EINVAL; + + offs = rtsds_839x_reg_offset(sid, page, reg); + if (offs < 0) + return 0; + + /* read twice for link status latch */ + if (page == RTSDS_PAGE_FIB && reg == 0x01) + ioread32(ctrl->base + offs); + + oldval = ioread32(ctrl->base + offs); + val = reg & 1 ? (oldval & ~(mask << 16)) | (val << 16) : (oldval & ~mask) | val; + iowrite32(val, ctrl->base + offs); + + return 0; +} + +static int rtsds_839x_set_hwmode(struct rtsds_ctrl *ctrl, u32 sid, int combomode) +{ + int shift = (sid & 7) << 2, offs = (sid >> 1) & ~3; + int mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode); + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + rtsds_839x_mask(ctrl, sid, RTSDS_PAGE_SDS, 0x04, (submode << 12) & 0xf000, 0xf000); + iomask32(0xf << shift, (mode & 0xf) << shift, RTSDS_839X_MAC_SERDES_IF_CTRL + offs); + + return 0; +} + +static int rtsds_839x_get_hwmode(struct rtsds_ctrl *ctrl, u32 sid) +{ + int mode, submode, shift = (sid & 7) << 2, offs = (sid >> 1) & ~3; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + submode = (rtsds_839x_read(ctrl, sid, RTSDS_PAGE_SDS, 0x04) >> 12) & 0xf; + mode = (ioread32(RTSDS_839X_MAC_SERDES_IF_CTRL + offs) >> shift) & 0xf; + + return RTSDS_COMBOMODE(mode, submode); +} + +/* + * The RTL930x family has 12 SerdDes of three types. They are accessed through two IO registers at + * 0xbb0003b0 which simulate commands to an internal MDIO bus: + * + * - SerDes 0-1 exist on the RTL9301 and 9302B and are QSGMII capable + * - SerDes 2-9 are USXGMII capabable with either quad or single configuration + * - SerDes 10-11 are 10GBase-R capable + */ + +static int rtsds_930x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + if (rtsds_invalid_reg(ctrl, sid, page, reg)) + return -EINVAL; + + return rtsds_93xx_read(ctrl, sid, page, reg); +} + +static int rtsds_930x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask) +{ + if (rtsds_invalid_reg(ctrl, sid, page, reg) || rtsds_invalid_mask(val, mask)) + return -EINVAL; + + return rtsds_93xx_mask(ctrl, sid, page, reg, val, mask); +} + +static void rtsds_930x_mode_offset(int sid, + void __iomem __force **modereg, int *modeshift, + void __iomem __force **subreg, int *subshift) +{ + if (sid > 3) { + *subreg = RTSDS_930X_SDS_SUBMODE_CTRL1; + *subshift = (sid - 4) * 5; + } else { + *subreg = RTSDS_930X_SDS_SUBMODE_CTRL0; + *subshift = (sid - 2) * 5; + } + + if (sid < 4) { + *modeshift = sid * 6; + *modereg = RTSDS_930X_SDS_MODE_SEL_0; + } else if (sid < 8) { + *modeshift = (sid - 4) * 6; + *modereg = RTSDS_930X_SDS_MODE_SEL_1; + } else if (sid < 10) { + *modeshift = (sid - 8) * 6; + *modereg = RTSDS_930X_SDS_MODE_SEL_2; + } else { + *modeshift = (sid - 10) * 6; + *modereg = RTSDS_930X_SDS_MODE_SEL_3; + } +} + +static int rtsds_930x_set_hwmode(struct rtsds_ctrl *ctrl, u32 sid, int combomode) +{ + int modeshift, subshift; + int mode = RTSDS_MODE(combomode); + int submode = RTSDS_SUBMODE(combomode); + void __iomem __force *modereg, *subreg; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift); + if (sid >= 2 && sid <= 9) + iomask32(0x1f << subshift, (submode & 0x1f) << subshift, subreg); + else if (submode != 0) + return -EINVAL; + iomask32(0x1f << modeshift, (mode & 0x1f) << modeshift, modereg); + + return 0; +} + +static int rtsds_930x_get_hwmode(struct rtsds_ctrl *ctrl, u32 sid) +{ + int modeshift, subshift, mode, submode = 0; + void __iomem __force *modereg, *subreg; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift); + mode = (ioread32(modereg) >> modeshift) & 0x1f; + if (sid >= 2 && sid <= 9) + submode = (ioread32(subreg) >> subshift) & 0x1f; + + return RTSDS_COMBOMODE(mode, submode); +} + +/* + * The RTL931x family has 14 "frontend" SerDes that are cascaded. All operations (e.g. reset) work + * on this frontend view while their registers are distributed over a total of 32 background + * SerDes. Two types of SerDes exist: + * + * A "even" SerDes with numbers 0, 1, 2, 4, 6, 8, 10, 12 works on two background SerDes. 64 analog + * and 64 XGMII data pages are coming from a first background SerDes while another 64 XGMII pages + * are served from a second SerDes. + * + * The "odd" SerDes with numbers 3, 5, 7, 9, 11 & 13 SerDes consist of a total of 3 background + * SerDes (one analog and two XGMII) each with an own page/register set. + * + * Align this for readability by simulating a total of 576 pages and mix them as follows. + * + * frontend page "even" frontend SerDes "odd" frontend SerDes + * page 0x000-0x03f (analog): page 0x000-0x03f back SDS page 0x000-0x03f back SDS + * page 0x100-0x13f (XGMII1): page 0x000-0x03f back SDS page 0x000-0x03f back SDS+1 + * page 0x200-0x23f (XGMII2): page 0x000-0x03f back SDS+1 page 0x000-0x03f back SDS+2 + */ + +static int rtsds_931x_sds_offset(u32 sid, u32 page) +{ + int map[] = {0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23}; + int backsid = map[sid]; + + if (page & 0xc0) + return -1; /* hole */ + + if ((sid & 1) && (sid != 1)) + backsid += (page >> 8); /* distribute "odd" to 3 background SerDes */ + else if (page >= 512) + backsid += 1; /* distribute "even" to 2 background SerDes */ + + return backsid; +} + +static int rtsds_931x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg) +{ + int backsid; + + if (rtsds_invalid_reg(ctrl, sid, page, reg)) + return -EINVAL; + + backsid = rtsds_931x_sds_offset(sid, page); + + return backsid < 0 ? 0 : rtsds_93xx_read(ctrl, backsid, page & 0x3f, reg); +} + +static int rtsds_931x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask) +{ + int backsid; + + if (rtsds_invalid_reg(ctrl, sid, page, reg) || rtsds_invalid_mask(val, mask)) + return -EINVAL; + + backsid = rtsds_931x_sds_offset(sid, page); + + return backsid < 0 ? 0 : rtsds_93xx_mask(ctrl, backsid, page & 0x3f, reg, val, mask); +} + +static int rtsds_931x_set_hwmode(struct rtsds_ctrl *ctrl, u32 sid, int combomode) +{ + int shift = (sid & 3) << 3, offs = sid & ~3; + int mode = RTSDS_MODE(combomode); + int submode = RTSDS_SUBMODE(combomode); + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + rtsds_931x_mask(ctrl, sid, 0x1f, 0x09, (submode & 0x3f) << 6, 0x0fc0); + iomask32(0xff << shift, ((mode | RTSDS_931X_SDS_FORCE_SETUP) & 0xff) << shift, + RTSDS_931X_SERDES_MODE_CTRL + offs); + + return 0; +} + +static int rtsds_931x_get_hwmode(struct rtsds_ctrl *ctrl, u32 sid) +{ + int mode, submode, shift = (sid & 3) << 3, offs = sid & ~3; + + if (rtsds_invalid_sds(ctrl, sid)) + return -EINVAL; + + submode = (rtsds_931x_read(ctrl, sid, 0x1f, 0x09) >> 6) & 0x3f; + mode = (ioread32(RTSDS_931X_SERDES_MODE_CTRL + offs) >> shift) & 0x1f; + + return RTSDS_COMBOMODE(mode, submode); +} + +/* phy controller functions */ + +static int rtsds_phy_init(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 sid = macro->sid; + int ret; + + dev_dbg(ctrl->dev, "init SerDes %d\n", sid); + + mutex_lock(&ctrl->lock); + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_INIT); + mutex_unlock(&ctrl->lock); + + if (ret) + dev_err(ctrl->dev, "init failed for SerDes %d\n", sid); + + return ret; +} + +static int rtsds_phy_power_on(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 sid = macro->sid; + int ret; + + dev_dbg(ctrl->dev, "power on SerDes %d\n", sid); + + mutex_lock(&ctrl->lock); + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_POWER_ON); + mutex_unlock(&ctrl->lock); + + if (ret) + dev_err(ctrl->dev, "power on failed for SerDes %d\n", sid); + + return ret; +} + +static int rtsds_phy_power_off(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 sid = macro->sid; + int ret; + + dev_dbg(ctrl->dev, "power off SerDes %d\n", sid); + + mutex_lock(&ctrl->lock); + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_PRE_POWER_OFF); + if (!ret) + ret = ctrl->conf->set_hwmode(ctrl, sid, + ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]); + if (!ret) + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_POST_POWER_OFF); + mutex_unlock(&ctrl->lock); + + if (ret) + dev_err(ctrl->dev, "power off failed for SerDes %d\n", sid); + + return ret; +} + +static int rtsds_phy_set_mode_int(struct rtsds_ctrl *ctrl, u32 sid, int phymode, int hwmode) +{ + int ret; + + if (ctrl->conf->get_hwmode(ctrl, sid) == hwmode) + return 0; + + dev_dbg(ctrl->dev, "switch SerDes %d to %s (hw mode %X)\n", + sid, phy_modes(phymode), hwmode); + + mutex_lock(&ctrl->lock); + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_PRE_SET_MODE); + if (!ret) + ret = ctrl->conf->set_hwmode(ctrl, sid, hwmode); + if (!ret) { + ctrl->sds[sid].mode = phymode; + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_POST_SET_MODE); + } + mutex_unlock(&ctrl->lock); + + if (ret) + dev_err(ctrl->dev, "set mode failed for SerDes %d\n", sid); + + return ret; +} + +static int rtsds_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 sid = macro->sid; + + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + return rtsds_phy_set_mode_int(ctrl, sid, submode, ctrl->conf->mode_map[submode]); +} + +static int rtsds_phy_reset_int(struct rtsds_ctrl *ctrl, u32 sid) +{ + int ret; + + dev_dbg(ctrl->dev, "reset SerDes %d\n", sid); + + mutex_lock(&ctrl->lock); + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_PRE_RESET); + if (!ret) + ret = ctrl->conf->reset(ctrl, sid); + if (!ret) + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_POST_RESET); + mutex_unlock(&ctrl->lock); + + if (ret) + dev_err(ctrl->dev, "reset failed for SerDes %d\n", sid); + + return ret; +} + +static int rtsds_phy_reset(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 sid = macro->sid; + + return rtsds_phy_reset_int(ctrl, sid); +} + +static const struct phy_ops rtsds_phy_ops = { + .init = rtsds_phy_init, + .power_on = rtsds_phy_power_on, + .power_off = rtsds_phy_power_off, + .reset = rtsds_phy_reset, + .set_mode = rtsds_phy_set_mode, + .owner = THIS_MODULE, +}; + +/* + * The SerDes offer a lot of magic that sill needs to be uncovered. To help further development + * provide some basic debugging about registers, modes, reset and polarity. All functions are + * run under the global lock to avoid inconsistencies. + */ + +#ifdef CONFIG_DEBUG_FS + +#define RTSDS_PAGE_NAMES 48 + +static const char *rtsds_page_name[RTSDS_PAGE_NAMES] = { + [0] = "SDS", [1] = "SDS_EXT", + [2] = "FIB", [3] = "FIB_EXT", + [4] = "DTE", [5] = "DTE_EXT", + [6] = "TGX", [7] = "TGX_EXT", + [8] = "ANA_RG", [9] = "ANA_RG_EXT", + [10] = "ANA_TG", [11] = "ANA_TG_EXT", + [31] = "ANA_WDIG", + [32] = "ANA_MISC", [33] = "ANA_COM", + [34] = "ANA_SP", [35] = "ANA_SP_EXT", + [36] = "ANA_1G", [37] = "ANA_1G_EXT", + [38] = "ANA_2G", [39] = "ANA_2G_EXT", + [40] = "ANA_3G", [41] = "ANA_3G_EXT", + [42] = "ANA_5G", [43] = "ANA_5G_EXT", + [44] = "ANA_6G", [45] = "ANA_6G_EXT", + [46] = "ANA_10G", [47] = "ANA_10G_EXT", +}; + +static int rtsds_dbg_mode_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int mode, sid = macro->sid; + + mutex_lock(&ctrl->lock); + mode = ctrl->conf->get_hwmode(ctrl, sid); + mutex_unlock(&ctrl->lock); + + seq_printf(seqf, "hw mode: 0x%X\n", mode); + seq_puts(seqf, "phy mode: "); + + if (ctrl->sds[sid].mode == PHY_INTERFACE_MODE_NA) + seq_puts(seqf, "off\n"); + else + seq_printf(seqf, "%s\n", phy_modes(ctrl->sds[sid].mode)); + + return 0; +} + +static ssize_t rtsds_dbg_mode_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *seqf = file->private_data; + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int ret, hwmode, phymode, sid = macro->sid; + + ret = kstrtou32_from_user(userbuf, count, 16, &hwmode); + if (ret) + return ret; + + /* Allow only modes we have under control. */ + phymode = rtsds_hwmode_to_phymode(ctrl, hwmode); + if (phymode == PHY_INTERFACE_MODE_MAX) + return -EINVAL; + + ret = rtsds_phy_set_mode_int(ctrl, sid, phymode, hwmode); + + return ret ? ret : count; +} +DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_mode); + +static int rtsds_dbg_reset_show(struct seq_file *seqf, void *unused) +{ + return 0; +} + +static ssize_t rtsds_dbg_reset_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *seqf = file->private_data; + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int ret, reset, sid = macro->sid; + + ret = kstrtou32_from_user(userbuf, count, 10, &reset); + if (ret || reset != 1) + return -EINVAL; + + ret = rtsds_phy_reset_int(ctrl, sid); + + return ret ? ret : count; +} +DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_reset); + +static int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + u32 page = 0, reg, sid = macro->sid; + + seq_printf(seqf, "%*s", 12, ""); + for (int i = 0; i < 32; i++) + seq_printf(seqf, " %02X", i); + + mutex_lock(&ctrl->lock); + while (page < ctrl->conf->page_cnt) { + if (page < RTSDS_PAGE_NAMES && rtsds_page_name[page]) + seq_printf(seqf, "\n%*s: ", -11, rtsds_page_name[page]); + else if (page == 64 || page == 320) { + page += 192; + seq_printf(seqf, "\n\nXGMII_%d : ", page >> 8); + } else + seq_printf(seqf, "\nPAGE_%03X : ", page); + for (reg = 0; reg < 0x20; reg++) + seq_printf(seqf, "%04X ", ctrl->conf->read(ctrl, sid, page, reg)); + page++; + } + seq_puts(seqf, "\n"); + mutex_unlock(&ctrl->lock); + + return 0; +} + +static ssize_t rtsds_dbg_registers_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *seqf = file->private_data; + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int ret, sid = macro->sid; + u32 reg, page, val, mask; + u64 data; + + /* + * Due to many different devices and limited regional hardware access for developers, + * improve analysis with write access to the SerDes registers. This allows testers to + * build new patch sequences from the command line without creating new firmware files + * and building new images. + */ + + ret = kstrtou64_from_user(userbuf, count, 16, &data); + if (ret) + return ret; + + page = (data >> 40) & 0x3ff; + reg = (data >> 32) & 0xff; + val = (data >> 16) & 0xffff; + mask = data & 0xffff; + + if (rtsds_invalid_reg(ctrl, sid, page, reg) || rtsds_invalid_mask(val, mask)) + return -EINVAL; + + mutex_lock(&ctrl->lock); + ret = ctrl->conf->mask(ctrl, sid, page, reg, val, mask); + mutex_unlock(&ctrl->lock); + + return ret ? ret : count; +} +DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_registers); + +static int rtsds_dbg_polarity_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int val, sid = macro->sid; + + mutex_lock(&ctrl->lock); + val = ctrl->conf->read(ctrl, sid, RTSDS_PAGE_SDS, 0x00); + mutex_unlock(&ctrl->lock); + + if (val < 0) + return -EIO; + + seq_puts(seqf, "tx polarity: "); + seq_puts(seqf, val & RTSDS_BIT_INV_HSO ? "inverse" : "normal"); + seq_puts(seqf, "\nrx polarity: "); + seq_puts(seqf, val & RTSDS_BIT_INV_HSI ? "inverse" : "normal"); + seq_puts(seqf, "\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_polarity); + +static void rtsds_dbg_init(struct rtsds_ctrl *ctrl, u32 sid) +{ + debugfs_create_file("mode", 0600, ctrl->sds[sid].phy->debugfs, + &ctrl->sds[sid].phy->dev, &rtsds_dbg_mode_fops); + + debugfs_create_file("reset", 0200, ctrl->sds[sid].phy->debugfs, + &ctrl->sds[sid].phy->dev, &rtsds_dbg_reset_fops); + + debugfs_create_file("polarity", 0400, ctrl->sds[sid].phy->debugfs, + &ctrl->sds[sid].phy->dev, &rtsds_dbg_polarity_fops); + + debugfs_create_file("registers", 0600, ctrl->sds[sid].phy->debugfs, + &ctrl->sds[sid].phy->dev, &rtsds_dbg_registers_fops); +} +#endif /* CONFIG_DEBUG_FS */ + +static void rtsds_setup(struct rtsds_ctrl *ctrl) +{ + int hwmode, ret; + + for (u32 sid = 0; sid < ctrl->conf->sds_cnt; sid++) { + /* switch off SerDes */ + hwmode = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]; + ret = ctrl->conf->set_hwmode(ctrl, sid, hwmode); + if (!ret) + ret = rtsds_fw_run_event(ctrl, sid, RTSDS_FW_EVT_SETUP); + if (ret) + dev_err(ctrl->dev, "setup failed for SerDes %d\n", sid); + + /* in any case sync back hardware status for consistency */ + hwmode = ctrl->conf->get_hwmode(ctrl, sid); + ctrl->sds[sid].mode = rtsds_hwmode_to_phymode(ctrl, hwmode); + } +} + +static struct phy *rtsds_simple_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct rtsds_ctrl *ctrl = dev_get_drvdata(dev); + int sid, sid2, min_port, max_port; + + /* + * A SerDes is usually connected to Ethernet transceivers (e.g. RTL8218B). Some of them + * make use of multiple links, e.g. 2x QSGMII. Transceivers themselves can have multiple + * ports. To make the driver understand this topology when looked up from controller, use + * following translation for the first port of a transceiver package in the devicetree. + * + * Single port/single link: E.g. Switch port 8 connected to SerDes 1 + * + * port@8 { phys = <&serdes 1 1 8 8>; }; + * port@8 { phys = <&serdes 1 -1 8 8>; }; (alternative notation) + * + * Multiport/single link: E.g. Switch ports 16 to 23 connected to SerDes 2 + * + * port@16 { phys = <&serdes 2 2 16 23>; }; + * port@16 { phys = <&serdes 2 -1 16 23>; }; (alternative notation) + * + * Multiport/multilink: E.g. Switch ports 24 to 31 connected to SerDes 3 & 4 + * + * port@24 { phys = <&serdes 3 4 24 27>; }; + * port@28 { phys = <&serdes 4 3 28 31>; }; + */ + + if (args->args_count != 4) + return ERR_PTR(-EINVAL); + + sid = args->args[0]; + sid2 = args->args[1]; + min_port = args->args[2]; + max_port = args->args[3]; + + if (sid < 0 || sid >= ctrl->conf->sds_cnt || + sid2 < -1 || sid2 >= ctrl->conf->sds_cnt || + min_port < 0 || max_port < min_port) + return ERR_PTR(-EINVAL); + + if (sid2 == sid) + sid2 = -1; + + ctrl->sds[sid].link = sid2; + if (sid2 >= 0) + ctrl->sds[sid2].link = sid; + + ctrl->sds[sid].min_port = min_port; + ctrl->sds[sid].max_port = max_port; + + return ctrl->sds[sid].phy; +} + +static int rtsds_phy_create(struct rtsds_ctrl *ctrl, u32 sid) +{ + struct rtsds_macro *macro; + + ctrl->sds[sid].phy = devm_phy_create(ctrl->dev, NULL, &rtsds_phy_ops); + if (IS_ERR(ctrl->sds[sid].phy)) + return PTR_ERR(ctrl->sds[sid].phy); + + macro = devm_kzalloc(ctrl->dev, sizeof(*macro), GFP_KERNEL); + if (!macro) + return -ENOMEM; + + macro->sid = sid; + macro->ctrl = ctrl; + phy_set_drvdata(ctrl->sds[sid].phy, macro); + + ctrl->sds[sid].link = -1; + ctrl->sds[sid].min_port = -1; + ctrl->sds[sid].max_port = -1; + +#ifdef CONFIG_DEBUG_FS + rtsds_dbg_init(ctrl, sid); +#endif + return 0; +} + +static int rtsds_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct rtsds_ctrl *ctrl; + int ret; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->base)) { + dev_err(dev, "failed to map SerDes memory\n"); + return PTR_ERR(ctrl->base); + } + + ctrl->dev = dev; + ctrl->conf = (struct rtsds_conf *)of_device_get_match_data(dev); + + for (u32 sid = 0; sid < ctrl->conf->sds_cnt; sid++) { + ret = rtsds_phy_create(ctrl, sid); + if (ret) { + dev_err(dev, "failed to create phy for SerDes %d\n", sid); + return ret; + } + } + + mutex_init(&ctrl->lock); + dev_set_drvdata(dev, ctrl); + provider = devm_of_phy_provider_register(dev, rtsds_simple_xlate); + + rtsds_get_chip(ctrl); + rtsds_fw_load(ctrl); + rtsds_setup(ctrl); + + dev_info(dev, "%s (chip %s) initialized. %d SerDes, %d pages, 32 registers.", + ctrl->soc.model_name, ctrl->soc.chip_name, + ctrl->conf->sds_cnt, ctrl->conf->page_cnt); + + return PTR_ERR_OR_ZERO(provider); +} + +static const struct rtsds_conf rtsds_838x_conf = { + .family = RTSDS_838X_FAMILY, + .sds_cnt = RTSDS_838X_SDS_CNT, + .page_cnt = RTSDS_838X_PAGE_CNT, + .mask = rtsds_838x_mask, + .read = rtsds_838x_read, + .reset = rtsds_83xx_reset, + .set_hwmode = rtsds_838x_set_hwmode, + .get_hwmode = rtsds_838x_get_hwmode, + .fwname = "rtl838x-serdes.fw", + .mode_map = { + [PHY_INTERFACE_MODE_NA] = RTSDS_COMBOMODE(0x00, 0x00), + [PHY_INTERFACE_MODE_1000BASEX] = RTSDS_COMBOMODE(0x04, 0x01), + [PHY_INTERFACE_MODE_100BASEX] = RTSDS_COMBOMODE(0x05, 0x01), + [PHY_INTERFACE_MODE_QSGMII] = RTSDS_COMBOMODE(0x06, 0x00), + }, +}; + +static const struct rtsds_conf rtsds_839x_conf = { + .family = RTSDS_839X_FAMILY, + .sds_cnt = RTSDS_839X_SDS_CNT, + .page_cnt = RTSDS_839X_PAGE_CNT, + .mask = rtsds_839x_mask, + .read = rtsds_839x_read, + .reset = rtsds_83xx_reset, + .set_hwmode = rtsds_839x_set_hwmode, + .get_hwmode = rtsds_839x_get_hwmode, + .fwname = "rtl839x-serdes.fw", + .mode_map = { + [PHY_INTERFACE_MODE_NA] = RTSDS_COMBOMODE(0x00, 0x00), + [PHY_INTERFACE_MODE_10GBASER] = RTSDS_COMBOMODE(0x01, 0x00), + [PHY_INTERFACE_MODE_1000BASEX] = RTSDS_COMBOMODE(0x07, 0x00), + [PHY_INTERFACE_MODE_100BASEX] = RTSDS_COMBOMODE(0x08, 0x00), + [PHY_INTERFACE_MODE_QSGMII] = RTSDS_COMBOMODE(0x06, 0x00), + [PHY_INTERFACE_MODE_SGMII] = RTSDS_COMBOMODE(0x07, 0x05), + }, +}; + +static const struct rtsds_conf rtsds_930x_conf = { + .family = RTSDS_930X_FAMILY, + .sds_cnt = RTSDS_930X_SDS_CNT, + .page_cnt = RTSDS_930X_PAGE_CNT, + .mask = rtsds_930x_mask, + .read = rtsds_930x_read, + .reset = rtsds_93xx_reset, + .set_hwmode = rtsds_930x_set_hwmode, + .get_hwmode = rtsds_930x_get_hwmode, + .fwname = "rtl930x-serdes.fw", + .mode_map = { + [PHY_INTERFACE_MODE_NA] = RTSDS_COMBOMODE(0x1f, 0x00), + [PHY_INTERFACE_MODE_10GBASER] = RTSDS_COMBOMODE(0x1a, 0x00), + [PHY_INTERFACE_MODE_2500BASEX] = RTSDS_COMBOMODE(0x16, 0x00), + [PHY_INTERFACE_MODE_1000BASEX] = RTSDS_COMBOMODE(0x04, 0x00), + [PHY_INTERFACE_MODE_USXGMII] = RTSDS_COMBOMODE(0x0d, 0x00), + [PHY_INTERFACE_MODE_QUSGMII] = RTSDS_COMBOMODE(0x0d, 0x02), + [PHY_INTERFACE_MODE_XGMII] = RTSDS_COMBOMODE(0x10, 0x00), + [PHY_INTERFACE_MODE_QSGMII] = RTSDS_COMBOMODE(0x06, 0x00), + }, +}; + +static const struct rtsds_conf rtsds_931x_conf = { + .family = RTSDS_931X_FAMILY, + .sds_cnt = RTSDS_931X_SDS_CNT, + .page_cnt = RTSDS_931X_PAGE_CNT, + .mask = rtsds_931x_mask, + .read = rtsds_931x_read, + .reset = rtsds_93xx_reset, + .set_hwmode = rtsds_931x_set_hwmode, + .get_hwmode = rtsds_931x_get_hwmode, + .fwname = "rtl931x-serdes.fw", + .mode_map = { + [PHY_INTERFACE_MODE_NA] = RTSDS_COMBOMODE(0x1f, 0x3f), + [PHY_INTERFACE_MODE_10GBASER] = RTSDS_COMBOMODE(0x1f, 0x35), + [PHY_INTERFACE_MODE_1000BASEX] = RTSDS_COMBOMODE(0x1f, 0x39), + [PHY_INTERFACE_MODE_USXGMII] = RTSDS_COMBOMODE(0x0d, 0x00), + [PHY_INTERFACE_MODE_XGMII] = RTSDS_COMBOMODE(0x0a, 0x00), + [PHY_INTERFACE_MODE_QSGMII] = RTSDS_COMBOMODE(0x06, 0x00), + }, +}; + +static const struct of_device_id rtsds_compatible_ids[] = { + { .compatible = "realtek,rtl8380m-serdes", .data = &rtsds_838x_conf }, + { .compatible = "realtek,rtl8392m-serdes", .data = &rtsds_839x_conf }, + { .compatible = "realtek,rtl9302b-serdes", .data = &rtsds_930x_conf }, + { .compatible = "realtek,rtl9311-serdes", .data = &rtsds_931x_conf }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtsds_compatible_ids); + +static struct platform_driver rtsds_platform_driver = { + .probe = rtsds_probe, + .driver = { + .name = "realtek,otto-serdes", + .of_match_table = rtsds_compatible_ids, + }, +}; + +module_platform_driver(rtsds_platform_driver); + +MODULE_AUTHOR("Markus Stockhausen "); +MODULE_DESCRIPTION("SerDes driver for Realtek RTL83xx, RTL93xx switch SoCs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/realtek/phy-rtk-otto-serdes.h b/drivers/phy/realtek/phy-rtk-otto-serdes.h new file mode 100644 index 000000000000..ba630705007f --- /dev/null +++ b/drivers/phy/realtek/phy-rtk-otto-serdes.h @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver + * Copyright (c) 2024 Markus Stockhausen + */ + +#ifndef _PHY_RTK_OTTO_SERDES_H +#define _PHY_RTK_OTTO_SERDES_H + +#define RTSDS_SWITCH_ADDR_BASE (0xbb000000) +#define RTSDS_REG(x) ((void __iomem __force *)RTSDS_SWITCH_ADDR_BASE + (x)) +#define iomask32(mask, value, addr) iowrite32((ioread32(addr) & ~(mask)) | (value), addr) + +#define RTSDS_PAGE_SDS 0x00 +#define RTSDS_PAGE_SDS_EXT 0x01 +#define RTSDS_PAGE_FIB 0x02 +#define RTSDS_PAGE_FIB_EXT 0x03 +#define RTSDS_PAGE_ANA_RG_EXT 0x09 +#define RTSDS_PAGE_ANA_TG_EXT 0x0b + +#define RTSDS_BIT_INV_HSO BIT(8) +#define RTSDS_BIT_INV_HSI BIT(9) +#define RTSDS_BIT_SOFT_RST BIT(6) +#define RTSDS_BIT_SDS_EN_RX BIT(1) +#define RTSDS_BIT_SDS_EN_TX BIT(0) +#define RTSDS_BIT_RX_SELF BIT(9) +#define RTSDS_BIT_RX_SELF_10G BIT(15) + +#define RTSDS_838X_FAMILY 0x8380 +#define RTSDS_838X_SDS_CNT 6 +#define RTSDS_838X_PAGE_CNT 4 +#define RTSDS_838X_INT_MODE_CTRL RTSDS_REG(0x005c) +#define RTSDS_838X_MODEL_NAME_INFO RTSDS_REG(0x00d4) +#define RTSDS_838X_SDS_MODE_SEL RTSDS_REG(0x0028) + +#define RTSDS_839X_FAMILY 0x8390 +#define RTSDS_839X_SDS_CNT 14 +#define RTSDS_839X_PAGE_CNT 12 +#define RTSDS_839X_MAC_SERDES_IF_CTRL RTSDS_REG(0x0008) +#define RTSDS_839X_MODEL_NAME_INFO RTSDS_REG(0x0ff0) + +#define RTSDS_930X_FAMILY 0x9300 +#define RTSDS_930X_SDS_CNT 12 +#define RTSDS_930X_PAGE_CNT 64 +#define RTSDS_930X_MODEL_NAME_INFO RTSDS_REG(0x0004) +#define RTSDS_930X_SDS_MODE_SEL_0 RTSDS_REG(0x0194) +#define RTSDS_930X_SDS_MODE_SEL_1 RTSDS_REG(0x02a0) +#define RTSDS_930X_SDS_MODE_SEL_2 RTSDS_REG(0x02a4) +#define RTSDS_930X_SDS_MODE_SEL_3 RTSDS_REG(0x0198) +#define RTSDS_930X_SDS_SUBMODE_CTRL0 RTSDS_REG(0x01cc) +#define RTSDS_930X_SDS_SUBMODE_CTRL1 RTSDS_REG(0x02d8) + +#define RTSDS_931X_FAMILY 0x9310 +#define RTSDS_931X_SDS_CNT 14 +#define RTSDS_931X_PAGE_CNT 576 +#define RTSDS_931X_SERDES_MODE_CTRL RTSDS_REG(0x13cc) +#define RTSDS_931X_PS_SERDES_OFF_MODE_CTRL RTSDS_REG(0x13f4) +#define RTSDS_931X_SDS_FORCE_SETUP 0x80 + +#define RTSDS_93XX_SDS_READ 0 +#define RTSDS_93XX_SDS_WRITE BIT(1) +#define RTSDS_93XX_SDS_BUSY BIT(0) +#define RTSDS_93XX_MODEL_NAME_INFO RTSDS_REG(0x0004) + +#define RTSDS_COMBOMODE(mode, submode) (0x10000 | (mode << 8) | submode) +#define RTSDS_MODE(combomode) ((combomode >> 8) & 0xff) +#define RTSDS_SUBMODE(combomode) (combomode & 0xff) + +/* + * All the following firmware constants are fixed and must not be changed. Otherwise existing + * firmware files in the wild will break. If new events, operations or modes need to be added, + * new id numbers have to be assigned and reordering is strictly forbidden. + */ + +#define RTSDS_FW_MAGIC 0x83009300 + +#define RTSDS_FW_EVT_SETUP 0 +#define RTSDS_FW_EVT_INIT 1 +#define RTSDS_FW_EVT_POWER_ON 2 +#define RTSDS_FW_EVT_PRE_SET_MODE 3 +#define RTSDS_FW_EVT_POST_SET_MODE 4 +#define RTSDS_FW_EVT_PRE_RESET 5 +#define RTSDS_FW_EVT_POST_RESET 6 +#define RTSDS_FW_EVT_PRE_POWER_OFF 7 +#define RTSDS_FW_EVT_POST_POWER_OFF 8 +#define RTSDS_FW_EVT_CNT 9 + +#define RTSDS_FW_OP_STOP 0 +#define RTSDS_FW_OP_MASK 1 +#define RTSDS_FW_OP_WAIT 2 + +#define RTSDS_FW_MODE_ALL 0 +#define RTSDS_FW_MODE_QSGMII 1 +#define RTSDS_FW_MODE_XGMII 2 +#define RTSDS_FW_MODE_USXGMII 3 +#define RTSDS_FW_MODE_1000BASEX 4 +#define RTSDS_FW_MODE_2500BASEX 5 +#define RTSDS_FW_MODE_10GBASER 6 + +struct __packed rtsds_fw_dir { + uint32_t evtid; + uint32_t offset; +}; + +struct __packed rtsds_fw_head { + u32 magic; + u32 checksum; + u32 filesize; + u32 dirsize; + struct rtsds_fw_dir dir[]; +}; + +struct __packed rtsds_fw_seq { + u16 action; + u16 mode; + u16 ports; + u16 page; + u16 reg; + u16 val; + u16 mask; + u16 align; +}; + +struct rtsds_sds { + struct phy *phy; + int mode; + int link; + int min_port; + int max_port; +}; + +struct rtsds_soc { + u32 model_id; + u32 model_version; + char model_name[16]; + u32 chip_id; + u32 chip_version; + char chip_name[16]; +}; + +struct rtsds_ctrl { + struct rtsds_soc soc; + struct device *dev; + void __iomem *base; + struct mutex lock; + struct rtsds_conf *conf; + struct rtsds_sds sds[RTSDS_931X_SDS_CNT]; + const struct firmware *firmware; + char fwname[64]; +}; + +struct rtsds_macro { + struct rtsds_ctrl *ctrl; + u32 sid; +}; + +struct rtsds_conf { + u32 family; + u32 sds_cnt; + u32 page_cnt; + void (*setup)(struct rtsds_ctrl *ctrl); + int (*read)(struct rtsds_ctrl *ctrl, u32 idx, u32 page, u32 reg); + int (*mask)(struct rtsds_ctrl *ctrl, u32 idx, u32 page, u32 reg, u32 val, u32 mask); + int (*reset)(struct rtsds_ctrl *ctrl, u32 idx); + int (*set_hwmode)(struct rtsds_ctrl *ctrl, u32 idx, int mode); + int (*get_hwmode)(struct rtsds_ctrl *ctrl, u32 idx); + int mode_map[PHY_INTERFACE_MODE_MAX]; + const char *fwname; +}; + +#endif /* _PHY_RTK_OTTO_SERDES_H */