From patchwork Sat Mar 28 00:32:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Blumenstingl X-Patchwork-Id: 11463351 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CF10A92A for ; Sat, 28 Mar 2020 00:33:32 +0000 (UTC) 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 mail.kernel.org (Postfix) with ESMTPS id AB10B20787 for ; Sat, 28 Mar 2020 00:33:32 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="KDdmi0hr"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=googlemail.com header.i=@googlemail.com header.b="I3aO5wPJ" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org AB10B20787 Authentication-Results: mail.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=googlemail.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=EUmQVRFbUQy6euxEVTPgSRuYTwxBUExDzSv60NJc/EA=; b=KDdmi0hrqQgo05 adfvumBznADKnZ2EGfQCNpddJa/1q/KskRB7brEuVciD0ULJiwmfMs12foG/ZQf2I3G26UgmvxWqg rJ2ivn6PXua2WigO3EnhS5i8zih/CNjfl6uyzyO83+qb6PhrurSHyDlcDuovnmdzVG91SycGVsWw3 zoOro0X0nMTDNPxUWw2s0AdTwYBRjPy4ly3gl93Zf7URh4icAHoEtbyf/99diKRlvndI4f019VzcZ vDIGf5cXWZZsIyxKvTWGLquCPKaPKalYiQZ2XZ33EJGvSQdKzMw0FKB8nr597qPzkdkjWodw4kJaK 0Fk//VBWyyBV+9xcZHGw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzPn-0005aO-Os; Sat, 28 Mar 2020 00:33:31 +0000 Received: from mail-wm1-x342.google.com ([2a00:1450:4864:20::342]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzPa-0005NX-6q; Sat, 28 Mar 2020 00:33:19 +0000 Received: by mail-wm1-x342.google.com with SMTP id a81so14690826wmf.5; Fri, 27 Mar 2020 17:33:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=665fZkiQeQxQz0e6yTC3YNhkYs0sFtr287HV50LsYZA=; b=I3aO5wPJy2zoEsoipA7CXpUpUlMIDwnkQXDCJyxOls6zsQe2X8tSNwEnMa4RCFoolh ctnAqA9d2fr8Zsrr5tkN0tmRB529sy0KU7zqQLzTmKDY2pruW5JI3nVtCHiCekgPckDN +13PmIo07UVlDtwr4Kd8vd4edKIJpoeYZW2OERLitVxcUvJ5XVOD+hdO8gwhf+2avQhT d8l0rAPtz1+Q51g/et8K12OMKytuXstIFxMEMoJlICjelrlhUSi1JFIeTejvxNlMGU8S lb6vzxAZxw8uAAINOGsMGnvMrNqpvWXgTHlv3iB4bajJPlqscxLIjzAigYiACObIrETb 2wyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=665fZkiQeQxQz0e6yTC3YNhkYs0sFtr287HV50LsYZA=; b=XEnFuZ22DtgnpEpSE1D19VOufVoJI7Lc0xa2cMjypPLinSnMd/uCPSaWvRyLKFQiOo sg4yK/pxonR29touiDGQKKNgQtLDtdhenhhlD+pf4lwX6Hz54kUrRJNgQKzO2TKGjtp2 DaEHkCNcyfg/43J5rUT+YwsFX3PR+ydoFXQ0nvZxupG0SRPy3MWS1D2HTycrRyAwpsaL PJCdBJnrdlvofgujNXaxnkMwdDlPTx2w5pLy8K0xygjsVNoayHZptTWcs6+kPbwLb9VA AxoXa/AKQ4il8fgZHfaTD4TKB6KkGFg+vzll1I+Ua4WR/NwXwl0cLHF09ZnQz4ythSW0 FSkw== X-Gm-Message-State: ANhLgQ17HuSkM9I5ahgBfsuGMW13B5TEO8GsQSHM/0CrGxSeu5//BIKL HHhcWTCZ5RGfVA3wpNcBtuMo4peA X-Google-Smtp-Source: ADFU+vs6pei8+YtfMKOYbZ8goQWS4GvVXwvzmF1A32j7V+nrAIhek6nMb1+trxkKv6LVMaYK09PA7w== X-Received: by 2002:a7b:c388:: with SMTP id s8mr1357582wmj.168.1585355595562; Fri, 27 Mar 2020 17:33:15 -0700 (PDT) Received: from localhost.localdomain (p200300F13710ED00428D5CFFFEB99DB8.dip0.t-ipconnect.de. [2003:f1:3710:ed00:428d:5cff:feb9:9db8]) by smtp.googlemail.com with ESMTPSA id f1sm10346246wrv.37.2020.03.27.17.33.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Mar 2020 17:33:15 -0700 (PDT) From: Martin Blumenstingl To: linux-amlogic@lists.infradead.org, devicetree@vger.kernel.org, linux-mmc@vger.kernel.org, ulf.hansson@linaro.org, robh+dt@kernel.org Subject: [PATCH v5 1/3] dt-bindings: mmc: Document the Amlogic Meson SDHC MMC host controller Date: Sat, 28 Mar 2020 01:32:47 +0100 Message-Id: <20200328003249.1248978-2-martin.blumenstingl@googlemail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> References: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200327_173318_247600_CFC37254 X-CRM114-Status: GOOD ( 11.43 ) X-Spam-Score: -0.2 (/) X-Spam-Report: SpamAssassin version 3.4.4 on bombadil.infradead.org summary: Content analysis details: (-0.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:342 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [martin.blumenstingl[at]googlemail.com] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mark.rutland@arm.com, jianxin.pan@amlogic.com, Martin Blumenstingl , linux-kernel@vger.kernel.org, yinxin_1989@aliyun.com, linux-arm-kernel@lists.infradead.org, lnykww@gmail.com Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org This documents the devicetree bindings for the SDHC MMC host controller found in Meson6, Meson8, Meson8b and Meson8m2 SoCs. It can use a bus-width of 1/4/8-bit and it supports eMMC spec 4.4x/4.5x including HS200 mode (up to 100MHz clock). It embeds an internal clock controller which outputs four clocks (mod_clk, sd_clk, tx_clk and rx_clk) and is fed by four external input clocks (clkin[0-3]). "pclk" is the module register clock, it has to be enabled to access the registers. Signed-off-by: Martin Blumenstingl Reviewed-by: Rob Herring --- .../bindings/mmc/amlogic,meson-mx-sdhc.yaml | 83 +++++++++++++++++++ .../dt-bindings/clock/meson-mx-sdhc-clkc.h | 8 ++ 2 files changed, 91 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdhc.yaml create mode 100644 include/dt-bindings/clock/meson-mx-sdhc-clkc.h diff --git a/Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdhc.yaml b/Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdhc.yaml new file mode 100644 index 000000000000..1ca44529f622 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdhc.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/amlogic,meson-mx-sdhc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic Meson SDHC controller Device Tree Bindings + +allOf: + - $ref: "mmc-controller.yaml" + +maintainers: + - Martin Blumenstingl + +description: | + The SDHC MMC host controller on Amlogic SoCs provides an eMMC and MMC + card interface with 1/4/8-bit bus width. + It supports eMMC spec 4.4x/4.5x including HS200 (up to 100MHz clock). + +properties: + compatible: + items: + - enum: + - amlogic,meson8-sdhc + - amlogic,meson8b-sdhc + - amlogic,meson8m2-sdhc + - const: amlogic,meson-mx-sdhc + + reg: + minItems: 1 + + interrupts: + minItems: 1 + + "#clock-cells": + const: 1 + + clocks: + minItems: 9 + + clock-names: + items: + - const: clkin0 + - const: clkin1 + - const: clkin2 + - const: clkin3 + - const: pclk + - const: mod_clk + - const: sd_clk + - const: tx_clk + - const: rx_clk + +required: + - compatible + - reg + - interrupts + - "#clock-cells" + - clocks + - clock-names + +examples: + - | + #include + #include + #include + + sdhc: mmc@8e00 { + compatible = "amlogic,meson8-sdhc", "amlogic,meson-mx-sdhc"; + reg = <0x8e00 0x42>; + interrupts = ; + #clock-cells = <1>; + clocks = <&xtal>, + <&fclk_div4>, + <&fclk_div3>, + <&fclk_div5>, + <&sdhc_pclk>, + <&sdhc SDHC_CLKID_MOD_CLK>, + <&sdhc SDHC_CLKID_SD_CLK>, + <&sdhc SDHC_CLKID_TX_CLK>, + <&sdhc SDHC_CLKID_RX_CLK>; + clock-names = "clkin0", "clkin1", "clkin2", "clkin3", "pclk", + "mod_clk", "sd_clk", "tx_clk", "rx_clk"; + }; diff --git a/include/dt-bindings/clock/meson-mx-sdhc-clkc.h b/include/dt-bindings/clock/meson-mx-sdhc-clkc.h new file mode 100644 index 000000000000..ad9f6e4dc426 --- /dev/null +++ b/include/dt-bindings/clock/meson-mx-sdhc-clkc.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define SDHC_CLKID_SRC_SEL 0 +#define SDHC_CLKID_DIV 1 +#define SDHC_CLKID_MOD_CLK 2 +#define SDHC_CLKID_SD_CLK 3 +#define SDHC_CLKID_TX_CLK 4 +#define SDHC_CLKID_RX_CLK 5 From patchwork Sat Mar 28 00:32:48 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Blumenstingl X-Patchwork-Id: 11463357 X-Patchwork-Delegate: neil.armstrong@linaro.org Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 4537D15AB for ; Sat, 28 Mar 2020 00:34:00 +0000 (UTC) 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 mail.kernel.org (Postfix) with ESMTPS id 04E8E2071B for ; Sat, 28 Mar 2020 00:34:00 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="EtQSSxTa"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=googlemail.com header.i=@googlemail.com header.b="s+GHEby7" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 04E8E2071B Authentication-Results: mail.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=googlemail.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=+MOp2GZYzR21phmhtNnlJpDp4OMSHBBOtPyysFCIwJU=; b=EtQSSxTaZnLhE0 mGlQKESny64k6a4zF4CRRYreCStdDrKw5jmHn1XYjHb5YkzjlSe4mNvq5krUbYCo0Md2WnuTeKcht Pmfl3UV30KrzAWSOwJ8eWNlZ+69z6K8iPgHlfdGPeibzkb4VEwQBkMnMlky17SF1li0RBFXXZB/30 ic2iEVM4FWe4lL1dQJFGhomt6jhhoMpK6D+7GkxNzPb+Nr8v1WHh5EUMgmvclDet9tlUHU3LC5JMd tUVedjIonopj5CE7R0IqWRq7PhdjfMyr4y7b1/+gb3deWZeOwm08UJ7Sr5D0rT/02Xdw7WTz1PbR/ qX7VeDnxaqxbtVrZZebQ==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzQF-0005xW-I3; Sat, 28 Mar 2020 00:33:59 +0000 Received: from mail-wr1-x443.google.com ([2a00:1450:4864:20::443]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzPa-0005NY-7o; Sat, 28 Mar 2020 00:33:21 +0000 Received: by mail-wr1-x443.google.com with SMTP id u10so13904500wro.7; Fri, 27 Mar 2020 17:33:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=C7EQctRThIFFyXRH4Fh32z2WVvIN3mPncKYCt9/X2y8=; b=s+GHEby7LTqLS7380ErUmduhWu9d3Ou1gV1MSANVspwan3FfNlzG7Ehd3UWxdWcItM TI1/A1/jE5N3zycpdl8i7e43obV9oQUNafBp9s7tXUiIEdsxfIScJw4x2/L50vpXSsNz t68t4nEhKn/FpshDzMcshHwYC7F8PakwOr/uCK27fA6El3R8OvRwyRatNCy4Jcg1OZHa Onl0oSmdLQFrnaBUf2QK6s/iibK4+9iY4ikGDBhusZf6z8mULuHGG3L0JZ3LOmG2R3qM kjwNC5JkBy74PvyPstEekHkiLpQnZH6Y0zsFwoGGJuDFVrPikZ+fd9G8CTkztq1tD4/a EVog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=C7EQctRThIFFyXRH4Fh32z2WVvIN3mPncKYCt9/X2y8=; b=qs024rFj43F2pQc9fsyBGQU2WQjn3sB1L486iK4NUdKzZVDhWtvVxDMl/9pbBt29k2 gIPauISMCaJ1ReVrN+OU64iDoPbXE13nBi/nVgCeHOoJMbx/vp6q3pIav9kl276Nzt6z 0tPcGPAzA/cUntFQCn1Ab0dzuoZLfWMRQCPLFauUPNmNBH52GecjMxvp5aNHcIYowchp T2W7GV9s1cgVHjd17yr//9tvRId5U0BoCSygpLbtNiBLMENAEn3ZvYxFw4jPS7JNK7fY eK0tF5AFYTMXEHXEm8tWKe4t7qqeaLo7jqJ2jO5XYwarCCXcC3HpeV7TtLnlSmdWpUVH OEhw== X-Gm-Message-State: ANhLgQ3fYV4/KKCuysVrVGnBQ4GQz3ZMBTpD+YX6Ate603p9O9ebngn4 Xwe+X+kt5iucFJMnX394rWUQIKDs X-Google-Smtp-Source: ADFU+vvGwtVg9LdcwXl/svwVW06W01mMmomxWIjwY17qb0A4KFPjAr1AgaLg18PhEjpY9F9A7q3Sng== X-Received: by 2002:adf:aa92:: with SMTP id h18mr2235480wrc.139.1585355596658; Fri, 27 Mar 2020 17:33:16 -0700 (PDT) Received: from localhost.localdomain (p200300F13710ED00428D5CFFFEB99DB8.dip0.t-ipconnect.de. [2003:f1:3710:ed00:428d:5cff:feb9:9db8]) by smtp.googlemail.com with ESMTPSA id f1sm10346246wrv.37.2020.03.27.17.33.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Mar 2020 17:33:16 -0700 (PDT) From: Martin Blumenstingl To: linux-amlogic@lists.infradead.org, devicetree@vger.kernel.org, linux-mmc@vger.kernel.org, ulf.hansson@linaro.org, robh+dt@kernel.org Subject: [PATCH v5 2/3] clk: meson: add a driver for the Meson8/8b/8m2 SDHC clock controller Date: Sat, 28 Mar 2020 01:32:48 +0100 Message-Id: <20200328003249.1248978-3-martin.blumenstingl@googlemail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> References: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200327_173318_276902_8AA70D8B X-CRM114-Status: GOOD ( 17.02 ) X-Spam-Score: -0.2 (/) X-Spam-Report: SpamAssassin version 3.4.4 on bombadil.infradead.org summary: Content analysis details: (-0.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:443 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [martin.blumenstingl[at]googlemail.com] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mark.rutland@arm.com, jianxin.pan@amlogic.com, Martin Blumenstingl , linux-kernel@vger.kernel.org, yinxin_1989@aliyun.com, linux-arm-kernel@lists.infradead.org, lnykww@gmail.com Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org The SDHC (MMC) controller embeds a clock controller inside one of the SDHC registers. The outputs of thisclock controller are dedicated to the SDHC controller. Implement a dedicated clock controller driver so we can keep all the clock specific logic outside of the MMC controller driver. There is no dedicated clock controller OF node because the hardware is a big SDHC IP block with an embedded clock controller (so the .dts doesn't need a separate clock controller node). Instead this driver re-uses the regmap as registered by the (platform_device) parent. Signed-off-by: Martin Blumenstingl --- drivers/clk/meson/Kconfig | 9 ++ drivers/clk/meson/Makefile | 1 + drivers/clk/meson/meson-mx-sdhc.c | 212 ++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 drivers/clk/meson/meson-mx-sdhc.c diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index dabeb435d067..8769335d2d46 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -53,6 +53,15 @@ config COMMON_CLK_MESON8B S805 (Meson8b) and S812 (Meson8m2) devices. Say Y if you want peripherals and CPU frequency scaling to work. +config COMMON_CLK_MESON_MX_SDHC + tristate "Meson MX SDHC MMC Clock Controller Driver" + depends on ARCH_MESON + select COMMON_CLK_MESON_REGMAP + help + Support for the SDHC clock controller on Amlogic Meson8/8b/8m2 SoCs + devices. Say Y or M if you want to use the SDHC MMC controller. + Otherwise say N. + config COMMON_CLK_GXBB bool depends on ARCH_MESON diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 6eca2a406ee3..b71c7ae78dbd 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o obj-$(CONFIG_COMMON_CLK_G12A) += g12a.o g12a-aoclk.o obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o meson8-ddr.o +obj-$(CONFIG_COMMON_CLK_MESON_MX_SDHC) += meson-mx-sdhc.o diff --git a/drivers/clk/meson/meson-mx-sdhc.c b/drivers/clk/meson/meson-mx-sdhc.c new file mode 100644 index 000000000000..b98a35d99f65 --- /dev/null +++ b/drivers/clk/meson/meson-mx-sdhc.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Amlogic Meson SDHC clock controller + * + * Copyright (C) 2019 Martin Blumenstingl + */ + +#include + +#include +#include +#include +#include + +#include "clk-regmap.h" +#include "clk-pll.h" + +#define MESON_SDHC_CLKC 0x10 + +static const struct clk_regmap meson_mx_sdhc_src_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = MESON_SDHC_CLKC, + .mask = 0x3, + .shift = 16, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_src_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data[]) { + { .fw_name = "clkin0", .index = -1, }, + { .fw_name = "clkin1", .index = -1, }, + { .fw_name = "clkin2", .index = -1, }, + { .fw_name = "clkin3", .index = -1, }, + }, + .num_parents = 4, + }, +}; + +static const struct clk_div_table meson_mx_sdhc_div_table[] = { + { .div = 6, .val = 5, }, + { .div = 8, .val = 7, }, + { .div = 9, .val = 8, }, + { .div = 10, .val = 9, }, + { .div = 12, .val = 11, }, + { .div = 16, .val = 15, }, + { .div = 18, .val = 17, }, + { .div = 34, .val = 33, }, + { .div = 142, .val = 141, }, + { .div = 850, .val = 849, }, + { .div = 2126, .val = 2125, }, + { .div = 4096, .val = 4095, }, + { /* sentinel */ } +}; + +static const struct clk_regmap meson_mx_sdhc_div = { + .data = &(struct clk_regmap_div_data){ + .offset = MESON_SDHC_CLKC, + .shift = 0, + .width = 12, + .table = meson_mx_sdhc_div_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_div", + .ops = &clk_regmap_divider_ops, + .parent_data = (const struct clk_parent_data[]) { + { .name = "sdhc_src_sel", .index = -1, }, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_regmap meson_mx_sdhc_mod_clk_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = MESON_SDHC_CLKC, + .bit_idx = 15, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_mod_clk_on", + .ops = &clk_regmap_gate_ops, + .parent_data = (const struct clk_parent_data[]) { + { .name = "sdhc_div", .index = -1, }, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_GATE, + }, +}; + +static const struct clk_regmap meson_mx_sdhc_tx_clk_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = MESON_SDHC_CLKC, + .bit_idx = 14, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_tx_clk_on", + .ops = &clk_regmap_gate_ops, + .parent_data = (const struct clk_parent_data[]) { + { .name = "sdhc_div", .index = -1, }, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_GATE, + }, +}; + +static const struct clk_regmap meson_mx_sdhc_rx_clk_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = MESON_SDHC_CLKC, + .bit_idx = 13, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_rx_clk_on", + .ops = &clk_regmap_gate_ops, + .parent_data = (const struct clk_parent_data[]) { + { .name = "sdhc_div", .index = -1, }, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, + }, +}; + +static const struct clk_regmap meson_mx_sdhc_sd_clk_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = MESON_SDHC_CLKC, + .bit_idx = 12, + }, + .hw.init = &(struct clk_init_data){ + .name = "sdhc_sd_clk_on", + .ops = &clk_regmap_gate_ops, + .parent_data = (const struct clk_parent_data[]) { + { .name = "sdhc_div", .index = -1, }, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, + }, +}; + +static const struct clk_regmap *meson_mx_sdhc_clk_regmaps[] = { + [SDHC_CLKID_SRC_SEL] = &meson_mx_sdhc_src_sel, + [SDHC_CLKID_DIV] = &meson_mx_sdhc_div, + [SDHC_CLKID_MOD_CLK] = &meson_mx_sdhc_mod_clk_en, + [SDHC_CLKID_SD_CLK] = &meson_mx_sdhc_sd_clk_en, + [SDHC_CLKID_TX_CLK] = &meson_mx_sdhc_tx_clk_en, + [SDHC_CLKID_RX_CLK] = &meson_mx_sdhc_rx_clk_en, +}; + +#define MESON_MX_SDHC_NUM_CLKS ARRAY_SIZE(meson_mx_sdhc_clk_regmaps) + +static int meson_mx_sdhc_clkc_probe(struct platform_device *pdev) +{ + struct device *parent = pdev->dev.parent; + struct clk_hw_onecell_data *onecell_data; + struct clk_regmap *clk_regmap; + struct regmap *regmap; + int i, ret; + + regmap = dev_get_regmap(parent, NULL); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + clk_regmap = devm_kcalloc(parent, sizeof(*clk_regmap), + MESON_MX_SDHC_NUM_CLKS, GFP_KERNEL); + if (!clk_regmap) + return -ENOMEM; + + onecell_data = devm_kzalloc(parent, + struct_size(onecell_data, hws, + MESON_MX_SDHC_NUM_CLKS), + GFP_KERNEL); + if (!onecell_data) + return -ENOMEM; + + for (i = 0; i < MESON_MX_SDHC_NUM_CLKS; i++) { + memcpy(&clk_regmap[i], meson_mx_sdhc_clk_regmaps[i], + sizeof(*clk_regmap)); + + clk_regmap[i].map = regmap; + onecell_data->hws[i] = &clk_regmap[i].hw; + + ret = devm_clk_hw_register(parent, onecell_data->hws[i]); + if (ret) { + dev_err(parent, + "Registration of SDHC clock %d failed\n", i); + return ret; + } + } + + onecell_data->num = MESON_MX_SDHC_NUM_CLKS; + + return devm_of_clk_add_hw_provider(parent, of_clk_hw_onecell_get, + onecell_data); +} + +static const struct platform_device_id meson_mx_sdhc_clkc_ids[] = { + { "meson8-sdhc-clkc" }, + { "meson8b-sdhc-clkc" }, + { "meson8m2-sdhc-clkc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, meson_mx_sdhc_clkc_ids); + +static struct platform_driver meson_mx_sdhc_clkc_driver = { + .id_table = meson_mx_sdhc_clkc_ids, + .probe = meson_mx_sdhc_clkc_probe, + .driver = { + .name = "meson-mx-sdhc-clkc", + }, +}; +module_platform_driver(meson_mx_sdhc_clkc_driver); + +MODULE_DESCRIPTION("Amlogic Meson8/8b/8m2 SDHC clock controller driver"); +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_LICENSE("GPL v2"); From patchwork Sat Mar 28 00:32:49 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Blumenstingl X-Patchwork-Id: 11463361 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6A1B015AB for ; Sat, 28 Mar 2020 00:34:17 +0000 (UTC) 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 mail.kernel.org (Postfix) with ESMTPS id 2C9F42071B for ; Sat, 28 Mar 2020 00:34:17 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="h3LGLzJQ"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=googlemail.com header.i=@googlemail.com header.b="iUAIGxOQ" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 2C9F42071B Authentication-Results: mail.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=googlemail.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=s3TLkxsqXESf091nHsr5Kc+w2Q5pAHGhN+us6xBB0SU=; b=h3LGLzJQ/98CH/ oNgqnQeBvOIVg0y2hYB+ynYHNPo4zQ7wJnxbQvf1yMMETYq1YgnVtz/kdS9azJuM3hXESl+78md9F LsY472i+rrq/28WoYJibop4dNwUeSi09M6tId7grtMNkrLXfSBoiSQbMA61VWmmLx4a66m0ErHaTR zMnZChCUTtKsIMTHpF6wRRjUiyrU+09w7t+Rz2RsdjD5z2zoKb7cWvfY4a8U2bF0r4HMgrybrpMvj TjfcCwD9K8nfpDYDu6S3kFh/jxpnIQWeihwBjhNYowogmzTggzGURX15kklFfyFBsegI4xkoKJQy7 zqmXd8bN+2sz3awrv2/Q==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzQV-0006Em-Ek; Sat, 28 Mar 2020 00:34:15 +0000 Received: from mail-wm1-x343.google.com ([2a00:1450:4864:20::343]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1jHzPc-0005OI-AC; Sat, 28 Mar 2020 00:33:23 +0000 Received: by mail-wm1-x343.google.com with SMTP id f6so8108139wmj.3; Fri, 27 Mar 2020 17:33:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=ZtXjlfU7hqQi59hM3vasd6FSHMXP5xNNNi3cHrkWnWg=; b=iUAIGxOQVRnGT3RXTcbpRSfE46PWmoiOU0UNRUObanJ+mlyJt6o5ddPqBT2dCVBFTu wYpmLHlZL4bS/su0CmD/lM+EW9kiaq2z/hzqPS/65WJQrJqbqGxw5xAxIqthQO+IsEli LlFAglxWKmgePO2dtobo+5U6nLVoA76s7TlWK03Bnit5+6/PUdIQs/5C04A9NvVaFExT MqjXW6NvAID0pdv3vDDhQxTMtbCj9bA3xV28R37dXf4e2uIYv84orSbs/FRQ+5DgcuwO R5djk+zeOs6u9mCJT6a3CQh2zvxuewCkdIyi0BpuywKyKYcbNi/76KXd0jYz9kh/iwHn 5UbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=ZtXjlfU7hqQi59hM3vasd6FSHMXP5xNNNi3cHrkWnWg=; b=Xm/35XfAghTxUVBxLrh6q37doTTZhWaP0kuB/ESOHiVEdaUx/1NaQoh5omDlNE9uZg +DizLluV9xVfC1VZfBkfeSIg78Iaq1boQGqmMnJLOe+qFXqKf932KjrZUjE3kEeidmvC AUyMwGFJGtQlEQ+9BkC5FcnHdKI0//N5LZAZd4GQarb0U2V8UOkq51E7ZVzVYMzXY+wi 3gga0AqUlhg+FSm4FzyjE7ntn1fNvYVpY4PmDp5F+uzypuqItoWIkvEcO8kYlhYHQZEV xbL4j/98ii6pOT+w6iupnXWJ9H2SXSHgGZnG89RrdZcqi1/GfncDjzHYv46vgt5F1n/A JPtA== X-Gm-Message-State: ANhLgQ1rtwQxoZARqlRkSoOC0ynLn2z8GTCA0qkTYy5fX/bn5T/2mVP+ T3axwb9xLcfm+HKbfs+6I2zkjaZY X-Google-Smtp-Source: ADFU+vv6y0Xtic61aYI7sxqWhqvkfgGJZNO4frx+SgnqBKu+RTW+M67gZ2AzrKiBo3VL3T/74r/79A== X-Received: by 2002:a7b:c450:: with SMTP id l16mr1268455wmi.9.1585355597805; Fri, 27 Mar 2020 17:33:17 -0700 (PDT) Received: from localhost.localdomain (p200300F13710ED00428D5CFFFEB99DB8.dip0.t-ipconnect.de. [2003:f1:3710:ed00:428d:5cff:feb9:9db8]) by smtp.googlemail.com with ESMTPSA id f1sm10346246wrv.37.2020.03.27.17.33.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Mar 2020 17:33:17 -0700 (PDT) From: Martin Blumenstingl To: linux-amlogic@lists.infradead.org, devicetree@vger.kernel.org, linux-mmc@vger.kernel.org, ulf.hansson@linaro.org, robh+dt@kernel.org Subject: [PATCH v5 3/3] mmc: host: meson-mx-sdhc: new driver for the Amlogic Meson SDHC host Date: Sat, 28 Mar 2020 01:32:49 +0100 Message-Id: <20200328003249.1248978-4-martin.blumenstingl@googlemail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> References: <20200328003249.1248978-1-martin.blumenstingl@googlemail.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200327_173320_368303_F7FE0B43 X-CRM114-Status: GOOD ( 16.12 ) X-Spam-Score: -0.2 (/) X-Spam-Report: SpamAssassin version 3.4.4 on bombadil.infradead.org summary: Content analysis details: (-0.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:343 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [martin.blumenstingl[at]googlemail.com] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mark.rutland@arm.com, jianxin.pan@amlogic.com, Martin Blumenstingl , linux-kernel@vger.kernel.org, yinxin_1989@aliyun.com, linux-arm-kernel@lists.infradead.org, lnykww@gmail.com Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org The SDHC MMC host controller on Amlogic SoCs provides an eMMC and MMC card interface with 1/4/8-bit bus width. It supports eMMC spec 4.4x/4.5x including HS200 (up to 100MHz clock). The public S805 datasheet [0] contains a short documentation about the registers. Unfortunately it does not describe how to use the registers to make the hardware work. Thus this driver is based on reading (and understanding) the Amlogic 3.10 GPL kernel code. Some hardware details are not easy to see. Jianxin Pan was kind enough to answer my questions: The hardware has built-in busy timeout support. The maximum timeout is 30 seconds. This is only documented in Amlogic's internal documentation. The controller only works with very specific clock configurations. The details are not part of the public datasheet. In my own words the supported configurations are: - 399.812kHz: clkin = 850MHz div = 2126 sd_rx_phase = 63 - 1MHz: clkin = 850MHz div = 850 sd_rx_phase = 55 - 5.986MHz: clkin = 850MHz div = 142 sd_rx_phase = 24 - 25MHz: clkin = 850MHz div = 34 sd_rx_phase = 15 - 47.222MHz: clkin = 850MHz div = 18 sd_rx_phase = 11/15 (SDR50/HS) - 53.125MHz: clkin = 850MHz div = 16 sd_rx_phase = (tuning) - 70.833MHz: clkin = 850MHz div = 12 sd_rx_phase = (tuning) - 85MHz: clkin = 850MHz div = 10 sd_rx_phase = (tuning) - 94.44MHz: clkin = 850MHz div = 9 sd_rx_phase = (tuning) - 106.25MHz: clkin = 850MHz div = 8 sd_rx_phase = (tuning) - 127.5MHz: clkin = 1275MHz div = 10 sd_rx_phase = (tuning) - 141.667MHz: clkin = 850MHz div = 6 sd_rx_phase = (tuning) - 159.375MHz: clkin = 1275MHz div = 8 sd_rx_phase = (tuning) - 212.5MHz: clkin = 1275MHz div = 6 sd_rx_phase = (tuning) - (sd_tx_phase is always 1, 94.44MHz is not listed in the datasheet but this is what the 3.10 BSP kernel on Odroid-C1 actually uses) NOTE: CMD23 support is disabled for now because it results in command timeouts and thus decreases read performance. Tested-by: Wei Wang Tested-by: Xin Yin Reviewed-by: Xin Yin Signed-off-by: Martin Blumenstingl Tested-by: Anand Moon --- drivers/mmc/host/Kconfig | 14 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/meson-mx-sdhc.c | 1064 ++++++++++++++++++++++++++++++ 3 files changed, 1079 insertions(+) create mode 100644 drivers/mmc/host/meson-mx-sdhc.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 3a5089f0332c..2c8361aa7eed 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -405,6 +405,20 @@ config MMC_MESON_GX If you have a controller with this interface, say Y here. +config MMC_MESON_MX_SDHC + tristate "Amlogic Meson SDHC Host Controller support" + depends on (ARM && ARCH_MESON) || COMPILE_TEST + depends on OF + imply COMMON_CLK_MESON_MX_SDHC + help + This selects support for the SDHC Host Controller on + Amlogic Meson6, Meson8, Meson8b and Meson8m2 SoCs. + The controller supports the SD/SDIO Spec 3.x and eMMC Spec 4.5x + with 1, 4, and 8 bit bus widths. + + If you have a controller with this interface, say Y or M here. + If unsure, say N. + config MMC_MESON_MX_SDIO tristate "Amlogic Meson6/Meson8/Meson8b SD/MMC Host Controller support" depends on ARCH_MESON || COMPILE_TEST diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 21d9089e5eda..9a0c22a8139a 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o +obj-$(CONFIG_MMC_MESON_MX_SDHC) += meson-mx-sdhc.o obj-$(CONFIG_MMC_MESON_MX_SDIO) += meson-mx-sdio.o obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o diff --git a/drivers/mmc/host/meson-mx-sdhc.c b/drivers/mmc/host/meson-mx-sdhc.c new file mode 100644 index 000000000000..b92400ee488b --- /dev/null +++ b/drivers/mmc/host/meson-mx-sdhc.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Amlogic Meson6/Meson8/Meson8b/Meson8m2 SDHC MMC host controller driver. + * + * Copyright (C) 2019 Martin Blumenstingl + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MESON_SDHC_ARGU 0x00 + +#define MESON_SDHC_SEND 0x04 + #define MESON_SDHC_SEND_CMD_INDEX GENMASK(5, 0) + #define MESON_SDHC_SEND_CMD_HAS_RESP BIT(6) + #define MESON_SDHC_SEND_CMD_HAS_DATA BIT(7) + #define MESON_SDHC_SEND_RESP_LEN BIT(8) + #define MESON_SDHC_SEND_RESP_NO_CRC BIT(9) + #define MESON_SDHC_SEND_DATA_DIR BIT(10) + #define MESON_SDHC_SEND_DATA_STOP BIT(11) + #define MESON_SDHC_SEND_R1B BIT(12) + #define MESON_SDHC_SEND_TOTAL_PACK GENMASK(31, 16) + +#define MESON_SDHC_CTRL 0x08 + #define MESON_SDHC_CTRL_DAT_TYPE GENMASK(1, 0) + #define MESON_SDHC_CTRL_DDR_MODE BIT(2) + #define MESON_SDHC_CTRL_TX_CRC_NOCHECK BIT(3) + #define MESON_SDHC_CTRL_PACK_LEN GENMASK(12, 4) + #define MESON_SDHC_CTRL_RX_TIMEOUT GENMASK(19, 13) + #define MESON_SDHC_CTRL_RX_PERIOD GENMASK(23, 20) + #define MESON_SDHC_CTRL_RX_ENDIAN GENMASK(26, 24) + #define MESON_SDHC_CTRL_SDIO_IRQ_MODE BIT(27) + #define MESON_SDHC_CTRL_DAT0_IRQ_SEL BIT(28) + #define MESON_SDHC_CTRL_TX_ENDIAN GENMASK(31, 29) + +#define MESON_SDHC_STAT 0x0c + #define MESON_SDHC_STAT_CMD_BUSY BIT(0) + #define MESON_SDHC_STAT_DAT3_0 GENMASK(4, 1) + #define MESON_SDHC_STAT_CMD BIT(5) + #define MESON_SDHC_STAT_RXFIFO_CNT GENMASK(12, 6) + #define MESON_SDHC_STAT_TXFIFO_CNT GENMASK(19, 13) + #define MESON_SDHC_STAT_DAT7_4 GENMASK(23, 20) + +#define MESON_SDHC_CLKC 0x10 + #define MESON_SDHC_CLKC_CLK_DIV GENMASK(11, 0) + #define MESON_SDHC_CLKC_CLK_JIC BIT(24) + #define MESON_SDHC_CLKC_MEM_PWR_OFF GENMASK(26, 25) + +#define MESON_SDHC_ADDR 0x14 + +#define MESON_SDHC_PDMA 0x18 + #define MESON_SDHC_PDMA_DMA_MODE BIT(0) + #define MESON_SDHC_PDMA_PIO_RDRESP GENMASK(3, 1) + #define MESON_SDHC_PDMA_DMA_URGENT BIT(4) + #define MESON_SDHC_PDMA_WR_BURST GENMASK(9, 5) + #define MESON_SDHC_PDMA_RD_BURST GENMASK(14, 10) + #define MESON_SDHC_PDMA_RXFIFO_TH GENMASK(21, 15) + #define MESON_SDHC_PDMA_TXFIFO_TH GENMASK(28, 22) + #define MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH GENMASK(30, 29) + #define MESON_SDHC_PDMA_TXFIFO_FILL BIT(31) + +#define MESON_SDHC_MISC 0x1c + #define MESON_SDHC_MISC_WCRC_ERR_PATT GENMASK(6, 4) + #define MESON_SDHC_MISC_WCRC_OK_PATT GENMASK(9, 7) + #define MESON_SDHC_MISC_BURST_NUM GENMASK(21, 16) + #define MESON_SDHC_MISC_THREAD_ID GENMASK(27, 22) + #define MESON_SDHC_MISC_MANUAL_STOP BIT(28) + #define MESON_SDHC_MISC_TXSTART_THRES GENMASK(31, 29) + +#define MESON_SDHC_DATA 0x20 + +#define MESON_SDHC_ICTL 0x24 + #define MESON_SDHC_ICTL_RESP_OK BIT(0) + #define MESON_SDHC_ICTL_RESP_TIMEOUT BIT(1) + #define MESON_SDHC_ICTL_RESP_ERR_CRC BIT(2) + #define MESON_SDHC_ICTL_RESP_OK_NOCLEAR BIT(3) + #define MESON_SDHC_ICTL_DATA_1PACK_OK BIT(4) + #define MESON_SDHC_ICTL_DATA_TIMEOUT BIT(5) + #define MESON_SDHC_ICTL_DATA_ERR_CRC BIT(6) + #define MESON_SDHC_ICTL_DATA_XFER_OK BIT(7) + #define MESON_SDHC_ICTL_RX_HIGHER BIT(8) + #define MESON_SDHC_ICTL_RX_LOWER BIT(9) + #define MESON_SDHC_ICTL_DAT1_IRQ BIT(10) + #define MESON_SDHC_ICTL_DMA_DONE BIT(11) + #define MESON_SDHC_ICTL_RXFIFO_FULL BIT(12) + #define MESON_SDHC_ICTL_TXFIFO_EMPTY BIT(13) + #define MESON_SDHC_ICTL_ADDI_DAT1_IRQ BIT(14) + #define MESON_SDHC_ICTL_ALL_IRQS GENMASK(14, 0) + #define MESON_SDHC_ICTL_DAT1_IRQ_DELAY GENMASK(17, 16) + +#define MESON_SDHC_ISTA 0x28 + #define MESON_SDHC_ISTA_RESP_OK BIT(0) + #define MESON_SDHC_ISTA_RESP_TIMEOUT BIT(1) + #define MESON_SDHC_ISTA_RESP_ERR_CRC BIT(2) + #define MESON_SDHC_ISTA_RESP_OK_NOCLEAR BIT(3) + #define MESON_SDHC_ISTA_DATA_1PACK_OK BIT(4) + #define MESON_SDHC_ISTA_DATA_TIMEOUT BIT(5) + #define MESON_SDHC_ISTA_DATA_ERR_CRC BIT(6) + #define MESON_SDHC_ISTA_DATA_XFER_OK BIT(7) + #define MESON_SDHC_ISTA_RX_HIGHER BIT(8) + #define MESON_SDHC_ISTA_RX_LOWER BIT(9) + #define MESON_SDHC_ISTA_DAT1_IRQ BIT(10) + #define MESON_SDHC_ISTA_DMA_DONE BIT(11) + #define MESON_SDHC_ISTA_RXFIFO_FULL BIT(12) + #define MESON_SDHC_ISTA_TXFIFO_EMPTY BIT(13) + #define MESON_SDHC_ISTA_ADDI_DAT1_IRQ BIT(14) + #define MESON_SDHC_ISTA_ALL_IRQS GENMASK(14, 0) + +#define MESON_SDHC_SRST 0x2c + #define MESON_SDHC_SRST_MAIN_CTRL BIT(0) + #define MESON_SDHC_SRST_RXFIFO BIT(1) + #define MESON_SDHC_SRST_TXFIFO BIT(2) + #define MESON_SDHC_SRST_DPHY_RX BIT(3) + #define MESON_SDHC_SRST_DPHY_TX BIT(4) + #define MESON_SDHC_SRST_DMA_IF BIT(5) + +#define MESON_SDHC_ESTA 0x30 + #define MESON_SDHC_ESTA_11_13 GENMASK(13, 11) + +#define MESON_SDHC_ENHC 0x34 + #define MESON_SDHC_ENHC_MESON8M2_WRRSP_MODE BIT(0) + #define MESON_SDHC_ENHC_MESON8M2_CHK_WRRSP BIT(1) + #define MESON_SDHC_ENHC_MESON8M2_CHK_DMA BIT(2) + #define MESON_SDHC_ENHC_MESON8M2_DEBUG GENMASK(5, 3) + #define MESON_SDHC_ENHC_MESON6_RX_TIMEOUT GENMASK(7, 0) + #define MESON_SDHC_ENHC_MESON6_DMA_RD_RESP BIT(16) + #define MESON_SDHC_ENHC_MESON6_DMA_WR_RESP BIT(17) + #define MESON_SDHC_ENHC_SDIO_IRQ_PERIOD GENMASK(15, 8) + #define MESON_SDHC_ENHC_RXFIFO_TH GENMASK(24, 18) + #define MESON_SDHC_ENHC_TXFIFO_TH GENMASK(31, 25) + +#define MESON_SDHC_CLK2 0x38 + #define MESON_SDHC_CLK2_RX_CLK_PHASE GENMASK(11, 0) + #define MESON_SDHC_CLK2_SD_CLK_PHASE GENMASK(23, 12) + +#define MESON_SDHC_NUM_BULK_CLKS 4 +#define MESON_SDHC_MAX_BLK_SIZE 512 +#define MESON_SDHC_NUM_TUNING_TRIES 10 + +struct meson_mx_sdhc_data { + void (*init_hw)(struct mmc_host *mmc); + void (*set_pdma)(struct mmc_host *mmc); + void (*wait_before_send)(struct mmc_host *mmc); + bool hardware_flush_all_cmds; + const char *clkc_name; +}; + +struct meson_mx_sdhc_host { + struct mmc_host *mmc; + + struct mmc_request *mrq; + struct mmc_command *cmd; + int error; + + struct regmap *regmap; + + struct platform_device *clkc_pdev; + struct clk *pclk; + struct clk *sd_clk; + struct clk_bulk_data bulk_clks[MESON_SDHC_NUM_BULK_CLKS]; + bool bulk_clks_enabled; + + const struct meson_mx_sdhc_data *platform; +}; + +static const struct regmap_config meson_mx_sdhc_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MESON_SDHC_CLK2, +}; + +static void meson_mx_sdhc_hw_reset(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + regmap_write(host->regmap, MESON_SDHC_SRST, MESON_SDHC_SRST_MAIN_CTRL | + MESON_SDHC_SRST_RXFIFO | MESON_SDHC_SRST_TXFIFO | + MESON_SDHC_SRST_DPHY_RX | MESON_SDHC_SRST_DPHY_TX | + MESON_SDHC_SRST_DMA_IF); + usleep_range(10, 100); + + regmap_write(host->regmap, MESON_SDHC_SRST, 0); + usleep_range(10, 100); +} + +static void meson_mx_sdhc_clear_fifo(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 stat; + + regmap_read(host->regmap, MESON_SDHC_STAT, &stat); + if (!FIELD_GET(MESON_SDHC_STAT_RXFIFO_CNT, stat) && + !FIELD_GET(MESON_SDHC_STAT_TXFIFO_CNT, stat)) + return; + + regmap_write(host->regmap, MESON_SDHC_SRST, MESON_SDHC_SRST_RXFIFO | + MESON_SDHC_SRST_TXFIFO | MESON_SDHC_SRST_MAIN_CTRL); + udelay(5); + + regmap_read(host->regmap, MESON_SDHC_STAT, &stat); + if (FIELD_GET(MESON_SDHC_STAT_RXFIFO_CNT, stat) || + FIELD_GET(MESON_SDHC_STAT_TXFIFO_CNT, stat)) + dev_warn(mmc_dev(host->mmc), + "Failed to clear FIFOs, RX: %lu, TX: %lu\n", + FIELD_GET(MESON_SDHC_STAT_RXFIFO_CNT, stat), + FIELD_GET(MESON_SDHC_STAT_TXFIFO_CNT, stat)); +} + +static void meson_mx_sdhc_wait_cmd_ready(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 stat, esta; + int ret; + + ret = regmap_read_poll_timeout(host->regmap, MESON_SDHC_STAT, stat, + !(stat & MESON_SDHC_STAT_CMD_BUSY), 1, + 100000); + if (ret) { + dev_warn(mmc_dev(mmc), + "Failed to poll for CMD_BUSY while processing CMD%d\n", + host->cmd->opcode); + meson_mx_sdhc_hw_reset(mmc); + } + + ret = regmap_read_poll_timeout(host->regmap, MESON_SDHC_ESTA, esta, + !(esta & MESON_SDHC_ESTA_11_13), 1, + 100000); + if (ret) { + dev_warn(mmc_dev(mmc), + "Failed to poll for ESTA[13:11] while processing CMD%d\n", + host->cmd->opcode); + meson_mx_sdhc_hw_reset(mmc); + } +} + +static void meson_mx_sdhc_start_cmd(struct mmc_host *mmc, + struct mmc_command *cmd) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 ictl, send; + int pack_len; + + host->cmd = cmd; + + ictl = MESON_SDHC_ICTL_DATA_TIMEOUT | MESON_SDHC_ICTL_DATA_ERR_CRC | + MESON_SDHC_ICTL_RXFIFO_FULL | MESON_SDHC_ICTL_TXFIFO_EMPTY | + MESON_SDHC_ICTL_RESP_TIMEOUT | MESON_SDHC_ICTL_RESP_ERR_CRC; + + send = FIELD_PREP(MESON_SDHC_SEND_CMD_INDEX, cmd->opcode); + + if (cmd->data) { + send |= MESON_SDHC_SEND_CMD_HAS_DATA; + send |= FIELD_PREP(MESON_SDHC_SEND_TOTAL_PACK, + cmd->data->blocks - 1); + + if (cmd->data->blksz < MESON_SDHC_MAX_BLK_SIZE) + pack_len = cmd->data->blksz; + else + pack_len = 0; + + if (cmd->data->flags & MMC_DATA_WRITE) + send |= MESON_SDHC_SEND_DATA_DIR; + + /* + * If command with no data, just wait response done + * interrupt(int[0]), and if command with data transfer, just + * wait dma done interrupt(int[11]), don't need care about + * dat0 busy or not. + */ + if (host->platform->hardware_flush_all_cmds || + cmd->data->flags & MMC_DATA_WRITE) + /* hardware flush: */ + ictl |= MESON_SDHC_ICTL_DMA_DONE; + else + /* software flush: */ + ictl |= MESON_SDHC_ICTL_DATA_XFER_OK; + } else { + pack_len = 0; + + ictl |= MESON_SDHC_ICTL_RESP_OK; + } + + if (cmd->opcode == MMC_STOP_TRANSMISSION) + send |= MESON_SDHC_SEND_DATA_STOP; + + if (cmd->flags & MMC_RSP_PRESENT) + send |= MESON_SDHC_SEND_CMD_HAS_RESP; + + if (cmd->flags & MMC_RSP_136) { + send |= MESON_SDHC_SEND_RESP_LEN; + send |= MESON_SDHC_SEND_RESP_NO_CRC; + } + + if (!(cmd->flags & MMC_RSP_CRC)) + send |= MESON_SDHC_SEND_RESP_NO_CRC; + + if (cmd->flags & MMC_RSP_BUSY) + send |= MESON_SDHC_SEND_R1B; + + /* enable the new IRQs and mask all pending ones */ + regmap_write(host->regmap, MESON_SDHC_ICTL, ictl); + regmap_write(host->regmap, MESON_SDHC_ISTA, MESON_SDHC_ISTA_ALL_IRQS); + + regmap_write(host->regmap, MESON_SDHC_ARGU, cmd->arg); + + regmap_update_bits(host->regmap, MESON_SDHC_CTRL, + MESON_SDHC_CTRL_PACK_LEN, + FIELD_PREP(MESON_SDHC_CTRL_PACK_LEN, pack_len)); + + if (cmd->data) + regmap_write(host->regmap, MESON_SDHC_ADDR, + sg_dma_address(cmd->data->sg)); + + meson_mx_sdhc_wait_cmd_ready(mmc); + + if (cmd->data) + host->platform->set_pdma(mmc); + + if (host->platform->wait_before_send) + host->platform->wait_before_send(mmc); + + regmap_write(host->regmap, MESON_SDHC_SEND, send); +} + +static void meson_mx_sdhc_disable_clks(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + if (!host->bulk_clks_enabled) + return; + + clk_bulk_disable_unprepare(MESON_SDHC_NUM_BULK_CLKS, host->bulk_clks); + + host->bulk_clks_enabled = false; +} + +static int meson_mx_sdhc_enable_clks(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + int ret; + + if (host->bulk_clks_enabled) + return 0; + + ret = clk_bulk_prepare_enable(MESON_SDHC_NUM_BULK_CLKS, + host->bulk_clks); + if (ret) + return ret; + + host->bulk_clks_enabled = true; + + return 0; +} + +static int meson_mx_sdhc_set_clk(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 rx_clk_phase; + int ret; + + meson_mx_sdhc_disable_clks(mmc); + + if (ios->clock) { + ret = clk_set_rate(host->sd_clk, ios->clock); + if (ret) { + dev_warn(mmc_dev(mmc), + "Failed to set MMC clock to %uHz: %d\n", + ios->clock, host->error); + return ret; + } + + ret = meson_mx_sdhc_enable_clks(mmc); + if (ret) + return ret; + + mmc->actual_clock = clk_get_rate(host->sd_clk); + + /* + * according to Amlogic the following latching points are + * selected with empirical values, there is no (known) formula + * to calculate these. + */ + if (mmc->actual_clock > 100000000) { + rx_clk_phase = 1; + } else if (mmc->actual_clock > 45000000) { + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) + rx_clk_phase = 15; + else + rx_clk_phase = 11; + } else if (mmc->actual_clock >= 25000000) { + rx_clk_phase = 15; + } else if (mmc->actual_clock > 5000000) { + rx_clk_phase = 23; + } else if (mmc->actual_clock > 1000000) { + rx_clk_phase = 55; + } else { + rx_clk_phase = 1061; + } + + regmap_update_bits(host->regmap, MESON_SDHC_CLK2, + MESON_SDHC_CLK2_RX_CLK_PHASE, + FIELD_PREP(MESON_SDHC_CLK2_RX_CLK_PHASE, + rx_clk_phase)); + } else { + mmc->actual_clock = 0; + } + + return 0; +} + +static void meson_mx_sdhc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + unsigned short vdd = ios->vdd; + + switch (ios->power_mode) { + case MMC_POWER_OFF: + vdd = 0; + /* fall through */ + + case MMC_POWER_UP: + if (!IS_ERR(mmc->supply.vmmc)) { + host->error = mmc_regulator_set_ocr(mmc, + mmc->supply.vmmc, + vdd); + if (host->error) + return; + } + + break; + + case MMC_POWER_ON: + break; + } + + host->error = meson_mx_sdhc_set_clk(mmc, ios); + if (host->error) + return; + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + regmap_update_bits(host->regmap, MESON_SDHC_CTRL, + MESON_SDHC_CTRL_DAT_TYPE, + FIELD_PREP(MESON_SDHC_CTRL_DAT_TYPE, 0)); + break; + + case MMC_BUS_WIDTH_4: + regmap_update_bits(host->regmap, MESON_SDHC_CTRL, + MESON_SDHC_CTRL_DAT_TYPE, + FIELD_PREP(MESON_SDHC_CTRL_DAT_TYPE, 1)); + break; + + case MMC_BUS_WIDTH_8: + regmap_update_bits(host->regmap, MESON_SDHC_CTRL, + MESON_SDHC_CTRL_DAT_TYPE, + FIELD_PREP(MESON_SDHC_CTRL_DAT_TYPE, 2)); + break; + + default: + dev_err(mmc_dev(mmc), "unsupported bus width: %d\n", + ios->bus_width); + host->error = -EINVAL; + return; + } +} + +static int meson_mx_sdhc_map_dma(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + int dma_len; + + if (!data) + return 0; + + dma_len = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (dma_len <= 0) { + dev_err(mmc_dev(mmc), "dma_map_sg failed\n"); + return -ENOMEM; + } + + return 0; +} + +static void meson_mx_sdhc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + + if (!host->error) + host->error = meson_mx_sdhc_map_dma(mmc, mrq); + + if (host->error) { + cmd->error = host->error; + mmc_request_done(mmc, mrq); + return; + } + + host->mrq = mrq; + + meson_mx_sdhc_start_cmd(mmc, mrq->cmd); +} + +static int meson_mx_sdhc_card_busy(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 stat; + + regmap_read(host->regmap, MESON_SDHC_STAT, &stat); + return FIELD_GET(MESON_SDHC_STAT_DAT3_0, stat) == 0; +} + +static bool meson_mx_sdhc_tuning_point_matches(struct mmc_host *mmc, + u32 opcode) +{ + unsigned int i, num_matches = 0; + int ret; + + for (i = 0; i < MESON_SDHC_NUM_TUNING_TRIES; i++) { + ret = mmc_send_tuning(mmc, opcode, NULL); + if (!ret) + num_matches++; + } + + return num_matches == MESON_SDHC_NUM_TUNING_TRIES; +} + +static int meson_mx_sdhc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + int div, start, len, best_start, best_len; + int curr_phase, old_phase, new_phase; + u32 val; + + len = 0; + start = 0; + best_len = 0; + + regmap_read(host->regmap, MESON_SDHC_CLK2, &val); + old_phase = FIELD_GET(MESON_SDHC_CLK2_RX_CLK_PHASE, val); + + regmap_read(host->regmap, MESON_SDHC_CLKC, &val); + div = FIELD_GET(MESON_SDHC_CLKC_CLK_DIV, val); + + for (curr_phase = 0; curr_phase <= div; curr_phase++) { + regmap_update_bits(host->regmap, MESON_SDHC_CLK2, + MESON_SDHC_CLK2_RX_CLK_PHASE, + FIELD_PREP(MESON_SDHC_CLK2_RX_CLK_PHASE, + curr_phase)); + + if (meson_mx_sdhc_tuning_point_matches(mmc, opcode)) { + if (!len) { + start = curr_phase; + + dev_dbg(mmc_dev(mmc), + "New RX phase window starts at %u\n", + start); + } + + len++; + } else { + if (len > best_len) { + best_start = start; + best_len = len; + + dev_dbg(mmc_dev(mmc), + "New best RX phase window: %u - %u\n", + best_start, best_start + best_len); + } + + /* reset the current window */ + len = 0; + } + } + + if (len > best_len) + /* the last window is the best (or possibly only) window */ + new_phase = start + (len / 2); + else if (best_len) + /* there was a better window than the last */ + new_phase = best_start + (best_len / 2); + else + /* no window was found at all, reset to the original phase */ + new_phase = old_phase; + + regmap_update_bits(host->regmap, MESON_SDHC_CLK2, + MESON_SDHC_CLK2_RX_CLK_PHASE, + FIELD_PREP(MESON_SDHC_CLK2_RX_CLK_PHASE, + new_phase)); + + if (!len && !best_len) + return -EIO; + + dev_dbg(mmc_dev(mmc), "Tuned RX clock phase to %u\n", new_phase); + + return 0; +} + +static const struct mmc_host_ops meson_mx_sdhc_ops = { + .hw_reset = meson_mx_sdhc_hw_reset, + .request = meson_mx_sdhc_request, + .set_ios = meson_mx_sdhc_set_ios, + .card_busy = meson_mx_sdhc_card_busy, + .execute_tuning = meson_mx_sdhc_execute_tuning, + .get_cd = mmc_gpio_get_cd, + .get_ro = mmc_gpio_get_ro, +}; + +static void meson_mx_sdhc_request_done(struct meson_mx_sdhc_host *host) +{ + struct mmc_request *mrq = host->mrq; + struct mmc_host *mmc = host->mmc; + + /* disable interrupts and mask all pending ones */ + regmap_update_bits(host->regmap, MESON_SDHC_ICTL, + MESON_SDHC_ICTL_ALL_IRQS, 0); + regmap_update_bits(host->regmap, MESON_SDHC_ISTA, + MESON_SDHC_ISTA_ALL_IRQS, MESON_SDHC_ISTA_ALL_IRQS); + + host->mrq = NULL; + host->cmd = NULL; + + mmc_request_done(mmc, mrq); +} + +static u32 meson_mx_sdhc_read_response(struct meson_mx_sdhc_host *host, u8 idx) +{ + u32 val; + + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_DMA_MODE, 0); + + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_PIO_RDRESP, + FIELD_PREP(MESON_SDHC_PDMA_PIO_RDRESP, idx)); + + regmap_read(host->regmap, MESON_SDHC_ARGU, &val); + + return val; +} + +static irqreturn_t meson_mx_sdhc_irq(int irq, void *data) +{ + struct meson_mx_sdhc_host *host = data; + struct mmc_command *cmd = host->cmd; + u32 ictl, ista; + + regmap_read(host->regmap, MESON_SDHC_ICTL, &ictl); + regmap_read(host->regmap, MESON_SDHC_ISTA, &ista); + + if (!(ictl & ista)) + return IRQ_NONE; + + if (ista & MESON_SDHC_ISTA_RXFIFO_FULL || + ista & MESON_SDHC_ISTA_TXFIFO_EMPTY) + cmd->error = -EIO; + else if (ista & MESON_SDHC_ISTA_RESP_ERR_CRC) + cmd->error = -EILSEQ; + else if (ista & MESON_SDHC_ISTA_RESP_TIMEOUT) + cmd->error = -ETIMEDOUT; + + if (cmd->data) { + if (ista & MESON_SDHC_ISTA_DATA_ERR_CRC) + cmd->data->error = -EILSEQ; + else if (ista & MESON_SDHC_ISTA_DATA_TIMEOUT) + cmd->data->error = -ETIMEDOUT; + } + + if (cmd->error || (cmd->data && cmd->data->error)) + dev_dbg(mmc_dev(host->mmc), "CMD%d error, ISTA: 0x%08x\n", + cmd->opcode, ista); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t meson_mx_sdhc_irq_thread(int irq, void *irq_data) +{ + struct meson_mx_sdhc_host *host = irq_data; + struct mmc_command *cmd; + u32 val; + + cmd = host->cmd; + if (WARN_ON(!cmd)) + return IRQ_HANDLED; + + if (cmd->data && !cmd->data->error) { + if (!host->platform->hardware_flush_all_cmds && + cmd->data->flags & MMC_DATA_READ) { + meson_mx_sdhc_wait_cmd_ready(host->mmc); + + val = FIELD_PREP(MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH, + 2); + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH, + val); + } + + dma_unmap_sg(mmc_dev(host->mmc), cmd->data->sg, + cmd->data->sg_len, mmc_get_dma_dir(cmd->data)); + + cmd->data->bytes_xfered = cmd->data->blksz * cmd->data->blocks; + } + + meson_mx_sdhc_wait_cmd_ready(host->mmc); + + if (cmd->flags & MMC_RSP_136) { + cmd->resp[0] = meson_mx_sdhc_read_response(host, 4); + cmd->resp[1] = meson_mx_sdhc_read_response(host, 3); + cmd->resp[2] = meson_mx_sdhc_read_response(host, 2); + cmd->resp[3] = meson_mx_sdhc_read_response(host, 1); + } else { + cmd->resp[0] = meson_mx_sdhc_read_response(host, 0); + } + + if (cmd->error == -EIO || cmd->error == -ETIMEDOUT) + meson_mx_sdhc_hw_reset(host->mmc); + else if (cmd->data) + /* + * Clear the FIFOs after completing data transfers to prevent + * corrupting data on write access. It's not clear why this is + * needed (for reads and writes), but it mimics what the BSP + * kernel did. + */ + meson_mx_sdhc_clear_fifo(host->mmc); + + meson_mx_sdhc_request_done(host); + + return IRQ_HANDLED; +} + +static int meson_mx_sdhc_register_clkc(struct meson_mx_sdhc_host *host) +{ + struct device *dev = mmc_dev(host->mmc); + int ret; + + host->clkc_pdev = platform_device_alloc(host->platform->clkc_name, + PLATFORM_DEVID_NONE); + if (!host->clkc_pdev) { + dev_err(dev, "Failed to allocate %s clock controller device\n", + host->platform->clkc_name); + return -ENOMEM; + } + + host->clkc_pdev->dev.parent = dev; + + ret = platform_device_add(host->clkc_pdev); + if (ret) { + dev_err(dev, "Failed to register %s clock controller device\n", + host->platform->clkc_name); + platform_device_put(host->clkc_pdev); + return ret; + } + + return 0; +} + +static void meson_mx_sdhc_init_hw_meson8(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + regmap_write(host->regmap, MESON_SDHC_MISC, + FIELD_PREP(MESON_SDHC_MISC_TXSTART_THRES, 7) | + FIELD_PREP(MESON_SDHC_MISC_WCRC_ERR_PATT, 5) | + FIELD_PREP(MESON_SDHC_MISC_WCRC_OK_PATT, 2)); + + regmap_write(host->regmap, MESON_SDHC_ENHC, + FIELD_PREP(MESON_SDHC_ENHC_RXFIFO_TH, 63) | + MESON_SDHC_ENHC_MESON6_DMA_WR_RESP | + FIELD_PREP(MESON_SDHC_ENHC_MESON6_RX_TIMEOUT, 255) | + FIELD_PREP(MESON_SDHC_ENHC_SDIO_IRQ_PERIOD, 12)); +}; + +static void meson_mx_sdhc_set_pdma_meson8(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + if (host->cmd->data->flags & MMC_DATA_WRITE) + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_DMA_MODE | + MESON_SDHC_PDMA_RD_BURST | + MESON_SDHC_PDMA_TXFIFO_FILL, + MESON_SDHC_PDMA_DMA_MODE | + FIELD_PREP(MESON_SDHC_PDMA_RD_BURST, 31) | + MESON_SDHC_PDMA_TXFIFO_FILL); + else + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_DMA_MODE | + MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH, + MESON_SDHC_PDMA_DMA_MODE | + FIELD_PREP(MESON_SDHC_PDMA_RXFIFO_MANUAL_FLUSH, + 1)); + + if (host->cmd->data->flags & MMC_DATA_WRITE) + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_RD_BURST, + FIELD_PREP(MESON_SDHC_PDMA_RD_BURST, 15)); +} + +static void meson_mx_sdhc_wait_before_send_meson8(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + u32 val; + int ret; + + ret = regmap_read_poll_timeout(host->regmap, MESON_SDHC_ESTA, val, + val == 0, 1, 200); + if (ret) + dev_warn(mmc_dev(mmc), + "Failed to wait for ESTA to clear: 0x%08x\n", val); + + if (host->cmd->data && host->cmd->data->flags & MMC_DATA_WRITE) { + ret = regmap_read_poll_timeout(host->regmap, MESON_SDHC_STAT, + val, + val & MESON_SDHC_STAT_TXFIFO_CNT, + 1, 200); + if (ret) + dev_warn(mmc_dev(mmc), + "Failed to wait for TX FIFO to fill\n"); + } +} + +static void meson_mx_sdhc_init_hw_meson8m2(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + regmap_write(host->regmap, MESON_SDHC_MISC, + FIELD_PREP(MESON_SDHC_MISC_TXSTART_THRES, 6) | + FIELD_PREP(MESON_SDHC_MISC_WCRC_ERR_PATT, 5) | + FIELD_PREP(MESON_SDHC_MISC_WCRC_OK_PATT, 2)); + + regmap_write(host->regmap, MESON_SDHC_ENHC, + FIELD_PREP(MESON_SDHC_ENHC_RXFIFO_TH, 64) | + FIELD_PREP(MESON_SDHC_ENHC_MESON8M2_DEBUG, 1) | + MESON_SDHC_ENHC_MESON8M2_WRRSP_MODE | + FIELD_PREP(MESON_SDHC_ENHC_SDIO_IRQ_PERIOD, 12)); +} + +static void meson_mx_sdhc_set_pdma_meson8m2(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + regmap_update_bits(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_DMA_MODE, MESON_SDHC_PDMA_DMA_MODE); +} + +static void meson_mx_sdhc_init_hw(struct mmc_host *mmc) +{ + struct meson_mx_sdhc_host *host = mmc_priv(mmc); + + meson_mx_sdhc_hw_reset(mmc); + + regmap_write(host->regmap, MESON_SDHC_CTRL, + FIELD_PREP(MESON_SDHC_CTRL_RX_PERIOD, 0xf) | + FIELD_PREP(MESON_SDHC_CTRL_RX_TIMEOUT, 0x7f) | + FIELD_PREP(MESON_SDHC_CTRL_RX_ENDIAN, 0x7) | + FIELD_PREP(MESON_SDHC_CTRL_TX_ENDIAN, 0x7)); + + /* + * start with a valid divider and enable the memory (un-setting + * MESON_SDHC_CLKC_MEM_PWR_OFF). + */ + regmap_write(host->regmap, MESON_SDHC_CLKC, MESON_SDHC_CLKC_CLK_DIV); + + regmap_write(host->regmap, MESON_SDHC_CLK2, + FIELD_PREP(MESON_SDHC_CLK2_SD_CLK_PHASE, 1)); + + regmap_write(host->regmap, MESON_SDHC_PDMA, + MESON_SDHC_PDMA_DMA_URGENT | + FIELD_PREP(MESON_SDHC_PDMA_WR_BURST, 7) | + FIELD_PREP(MESON_SDHC_PDMA_TXFIFO_TH, 49) | + FIELD_PREP(MESON_SDHC_PDMA_RD_BURST, 15) | + FIELD_PREP(MESON_SDHC_PDMA_RXFIFO_TH, 7)); + + /* some initialization bits depend on the SoC: */ + host->platform->init_hw(mmc); + + /* disable and mask all interrupts: */ + regmap_write(host->regmap, MESON_SDHC_ICTL, 0); + regmap_write(host->regmap, MESON_SDHC_ISTA, MESON_SDHC_ISTA_ALL_IRQS); +} + +static int meson_mx_sdhc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct meson_mx_sdhc_host *host; + struct mmc_host *mmc; + void __iomem *base; + int ret, irq; + + mmc = mmc_alloc_host(sizeof(*host), dev); + if (!mmc) + return -ENOMEM; + + ret = devm_add_action_or_reset(dev, (void(*)(void *))mmc_free_host, + mmc); + if (ret) { + dev_err(dev, "Failed to register mmc_free_host action\n"); + return ret; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + + platform_set_drvdata(pdev, host); + + host->platform = device_get_match_data(dev); + if (!host->platform) + return -EINVAL; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + host->regmap = devm_regmap_init_mmio(dev, base, + &meson_mx_sdhc_regmap_config); + if (IS_ERR(host->regmap)) + return PTR_ERR(host->regmap); + + host->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(host->pclk)) + return PTR_ERR(host->pclk); + + /* accessing any register requires the module clock to be enabled: */ + ret = clk_prepare_enable(host->pclk); + if (ret) { + dev_err(dev, "Failed to enable 'pclk' clock\n"); + return ret; + } + + meson_mx_sdhc_init_hw(mmc); + + ret = meson_mx_sdhc_register_clkc(host); + if (ret) + goto err_disable_pclk; + + host->bulk_clks[0].id = "mod_clk"; + host->bulk_clks[1].id = "sd_clk"; + host->bulk_clks[2].id = "tx_clk"; + host->bulk_clks[3].id = "rx_clk"; + ret = devm_clk_bulk_get(dev, MESON_SDHC_NUM_BULK_CLKS, + host->bulk_clks); + if (ret) + goto err_clkc_pdev_unregister; + + host->sd_clk = host->bulk_clks[1].clk; + + /* Get regulators and the supported OCR mask */ + ret = mmc_regulator_get_supply(mmc); + if (ret) + goto err_clkc_pdev_unregister; + + mmc->max_req_size = SZ_128K; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_count = FIELD_GET(MESON_SDHC_SEND_TOTAL_PACK, ~0); + mmc->max_blk_size = MESON_SDHC_MAX_BLK_SIZE; + mmc->max_busy_timeout = 30 * MSEC_PER_SEC; + mmc->f_min = clk_round_rate(host->sd_clk, 1); + mmc->f_max = clk_round_rate(host->sd_clk, ULONG_MAX); + mmc->max_current_180 = 300; + mmc->max_current_330 = 300; + mmc->caps |= MMC_CAP_ERASE | MMC_CAP_HW_RESET; + mmc->ops = &meson_mx_sdhc_ops; + + ret = mmc_of_parse(mmc); + if (ret) + goto err_clkc_pdev_unregister; + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(dev, irq, meson_mx_sdhc_irq, + meson_mx_sdhc_irq_thread, IRQF_ONESHOT, + NULL, host); + if (ret) + goto err_clkc_pdev_unregister; + + ret = mmc_add_host(mmc); + if (ret) + goto err_clkc_pdev_unregister; + + return 0; + +err_clkc_pdev_unregister: + platform_device_unregister(host->clkc_pdev); +err_disable_pclk: + clk_disable_unprepare(host->pclk); + return ret; +} + +static int meson_mx_sdhc_remove(struct platform_device *pdev) +{ + struct meson_mx_sdhc_host *host = platform_get_drvdata(pdev); + + mmc_remove_host(host->mmc); + + meson_mx_sdhc_disable_clks(host->mmc); + + platform_device_unregister(host->clkc_pdev); + + clk_disable_unprepare(host->pclk); + + return 0; +} + +static const struct meson_mx_sdhc_data meson_mx_sdhc_data_meson8 = { + .init_hw = meson_mx_sdhc_init_hw_meson8, + .set_pdma = meson_mx_sdhc_set_pdma_meson8, + .wait_before_send = meson_mx_sdhc_wait_before_send_meson8, + .hardware_flush_all_cmds = false, + .clkc_name = "meson8-sdhc-clkc", +}; + +static const struct meson_mx_sdhc_data meson_mx_sdhc_data_meson8m2 = { + .init_hw = meson_mx_sdhc_init_hw_meson8m2, + .set_pdma = meson_mx_sdhc_set_pdma_meson8m2, + .hardware_flush_all_cmds = true, + .clkc_name = "meson8m2-sdhc-clkc", +}; + +static const struct of_device_id meson_mx_sdhc_of_match[] = { + { + .compatible = "amlogic,meson8-sdhc", + .data = &meson_mx_sdhc_data_meson8 + }, + { + .compatible = "amlogic,meson8b-sdhc", + .data = &meson_mx_sdhc_data_meson8 + }, + { + .compatible = "amlogic,meson8m2-sdhc", + .data = &meson_mx_sdhc_data_meson8m2 + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_mx_sdhc_of_match); + +static struct platform_driver meson_mx_sdhc_driver = { + .probe = meson_mx_sdhc_probe, + .remove = meson_mx_sdhc_remove, + .driver = { + .name = "meson-mx-sdhc", + .of_match_table = of_match_ptr(meson_mx_sdhc_of_match), + }, +}; + +module_platform_driver(meson_mx_sdhc_driver); + +MODULE_DESCRIPTION("Meson6, Meson8, Meson8b and Meson8m2 SDHC Host Driver"); +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_LICENSE("GPL v2");