From patchwork Fri Mar 6 15:05:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alim Akhtar X-Patchwork-Id: 11424181 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 96A77138D for ; Fri, 6 Mar 2020 15:10:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 54E0E21739 for ; Fri, 6 Mar 2020 15:10:28 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="pessfaR2" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727070AbgCFPK2 (ORCPT ); Fri, 6 Mar 2020 10:10:28 -0500 Received: from mailout3.samsung.com ([203.254.224.33]:63819 "EHLO mailout3.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727066AbgCFPK1 (ORCPT ); Fri, 6 Mar 2020 10:10:27 -0500 Received: from epcas5p1.samsung.com (unknown [182.195.41.39]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20200306151024epoutp0371aa664068f7836ec33978df981837f0~5v5zZ60XW2732727327epoutp03K for ; Fri, 6 Mar 2020 15:10:24 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20200306151024epoutp0371aa664068f7836ec33978df981837f0~5v5zZ60XW2732727327epoutp03K DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1583507424; bh=zhyC9h3C6GlA2OmqmBSID9HxHRXQJmJCTLFoLtopECs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pessfaR2nVH4f88teuG2p9RS6/7jWn33w/M3Z9zzF3BH2S7leFIaSuMAsuo9aXvwl r5JBMkFNMd5RixlKcI/OyevDde3sQlasPsk5bY6j246Qxvkkbs8WLI9RAZ03dCnpnD 6WyJ/vQJSdfwdRLD1Hps6CIbIz3DkcnhB68V1LCc= Received: from epsmges5p1new.samsung.com (unknown [182.195.42.73]) by epcas5p1.samsung.com (KnoxPortal) with ESMTP id 20200306151023epcas5p11b86900509505042dc45d799f2d0011c~5v5ywgclE2723827238epcas5p1K; Fri, 6 Mar 2020 15:10:23 +0000 (GMT) Received: from epcas5p3.samsung.com ( [182.195.41.41]) by epsmges5p1new.samsung.com (Symantec Messaging Gateway) with SMTP id 71.D5.19726.FD7626E5; Sat, 7 Mar 2020 00:10:23 +0900 (KST) Received: from epsmtrp2.samsung.com (unknown [182.195.40.14]) by epcas5p4.samsung.com (KnoxPortal) with ESMTPA id 20200306151023epcas5p40179ecb7edaea3357c9d652553075b8d~5v5yYco1g0468404684epcas5p4Y; Fri, 6 Mar 2020 15:10:23 +0000 (GMT) Received: from epsmgms1p1new.samsung.com (unknown [182.195.42.41]) by epsmtrp2.samsung.com (KnoxPortal) with ESMTP id 20200306151023epsmtrp2e58ec3fb9caf3a7e1e7d20eaef239334~5v5yXtAoS0072100721epsmtrp2I; Fri, 6 Mar 2020 15:10:23 +0000 (GMT) X-AuditID: b6c32a49-7c1ff70000014d0e-d5-5e6267dfb482 Received: from epsmtip1.samsung.com ( [182.195.34.30]) by epsmgms1p1new.samsung.com (Symantec Messaging Gateway) with SMTP id CE.22.10238.FD7626E5; Sat, 7 Mar 2020 00:10:23 +0900 (KST) Received: from Jaguar.sa.corp.samsungelectronics.net (unknown [107.108.73.139]) by epsmtip1.samsung.com (KnoxPortal) with ESMTPA id 20200306151021epsmtip1af0e51a514f134218a10eb3ae39c4530~5v5wuqvXA0060400604epsmtip1U; Fri, 6 Mar 2020 15:10:21 +0000 (GMT) From: Alim Akhtar To: robh+dt@kernel.org, devicetree@vger.kernel.org, linux-scsi@vger.kernel.org Cc: krzk@kernel.org, avri.altman@wdc.com, martin.petersen@oracle.com, kwmad.kim@samsung.com, stanley.chu@mediatek.com, cang@codeaurora.org, Alim Akhtar , Kishon Vijay Abraham I Subject: [PATCH 2/5] phy: samsung-ufs: add UFS PHY driver for samsung SoC Date: Fri, 6 Mar 2020 20:35:26 +0530 Message-Id: <20200306150529.3370-3-alim.akhtar@samsung.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200306150529.3370-1-alim.akhtar@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupgleLIzCtJLcpLzFFi42LZdlhTU/d+elKcwbG9ahYP5m1js3j58yqb xaf1y1gt5h85x2px4WkPm8X58xvYLW5uOcpi0X19B5vF8uP/mCxa9x5ht1i69SajA7fH5b5e Jo9NqzrZPFpO7mfx+Pj0FotH35ZVjB7Hb2xn8vi8Sc6j/UA3UwBHFJdNSmpOZllqkb5dAlfG u3tv2AoWTWOsaPn2mbGB8W1tFyMnh4SAicSMFY/Zuxi5OIQEdjNKdLZOYoNwPjFKPLk8lRnC +cYo8fHQR6AyDrCW33OzQbqFBPYySnxe4QZR08IkMWHWNzaQBJuAtsTd6VuYQGwRgQCJS+8P gk1lFrjLKLFr0UewImEBT4lJT+czg9gsAqoS8xe/YAGxeQWsJbZO+8AEcZ+8xOoNB8BqOAVs JH4sugV2kYTAbTaJpv/LmCGKXCR2HOtmhbCFJV4d38IOYUtJfH63lw3i6myJnl3GEOEaiaXz jrFA2PYSB67MYQEpYRbQlFi/Sx8kzCzAJ9H7+wkTRCevREebEES1qkTzu6tQndISE7thlnpI TG35zgQJh35GieV/nrBPYJSdhTB1ASPjKkbJ1ILi3PTUYtMCw7zUcr3ixNzi0rx0veT83E2M 4PSh5bmDcdY5n0OMAhyMSjy8DtZJcUKsiWXFlbmHGCU4mJVEeIVN4+OEeFMSK6tSi/Lji0pz UosPMUpzsCiJ805ivRojJJCeWJKanZpakFoEk2Xi4JRqYGT6s0Hm7oxOc4Mtm8XlC1yPLutt bz66ZcodnZMKP5dM6pj57ut+0fKf86bltyyr4CmXPHzggG1q/e+jP42V/tyVvOKUFJ24rm+P weINj/mvvdasuxkeb5N75eKro6nynff4UgsWLjoRL71oFtMmp4dKLD/iak80N7faJucfmHbY bMcV6aPLWRWUWIozEg21mIuKEwFFV13XGwMAAA== X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFjrBLMWRmVeSWpSXmKPExsWy7bCSnO799KQ4g/4mTosH87axWbz8eZXN 4tP6ZawW84+cY7W48LSHzeL8+Q3sFje3HGWx6L6+g81i+fF/TBate4+wWyzdepPRgdvjcl8v k8emVZ1sHi0n97N4fHx6i8Wjb8sqRo/jN7YzeXzeJOfRfqCbKYAjissmJTUnsyy1SN8ugSvj 3b03bAWLpjFWtHz7zNjA+La2i5GDQ0LAROL33OwuRi4OIYHdjBJXry9m7GLkBIpLS1zfOIEd whaWWPnvOTtEUROTxNcDe1hBEmwC2hJ3p29hArFFBIIk7q1ZywpSxCzwnFFi2uYOsEnCAp4S k57OZwaxWQRUJeYvfsECYvMKWEtsnfaBCWKDvMTqDQfAajgFbCR+LLoFZgsB1ZyftYF9AiPf AkaGVYySqQXFuem5xYYFhnmp5XrFibnFpXnpesn5uZsYweGrpbmD8fKS+EOMAhyMSjy8M2yT 4oRYE8uKK3MPMUpwMCuJ8AqbxscJ8aYkVlalFuXHF5XmpBYfYpTmYFES532adyxSSCA9sSQ1 OzW1ILUIJsvEwSnVwLj25PXJEtx3F8aft9mcvdPIz+N9v6G3Q/+JB9mfTQUmfTQr2shfFJBw 2Oau6nyZU74XPu5QWfMxpFf6mEZa/W/Dx+dmTKlYn1S/cmrZBKN7lWKqdgbfdHlePO8Krn3t 48n3I0Mgbt5vRR/RjuP+R19M/TDHq7A99GrhsZUbRc2CV59aME+vkV2JpTgj0VCLuag4EQAw 1wcRWwIAAA== X-CMS-MailID: 20200306151023epcas5p40179ecb7edaea3357c9d652553075b8d X-Msg-Generator: CA CMS-TYPE: 105P X-CMS-RootMailID: 20200306151023epcas5p40179ecb7edaea3357c9d652553075b8d References: <20200306150529.3370-1-alim.akhtar@samsung.com> Sender: linux-scsi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-scsi@vger.kernel.org This patch introduces Samsung UFS PHY driver. This driver supports to deal with phy calibration and power control according to UFS host driver's behavior. Signed-off-by: Seungwon Jeon Signed-off-by: Alim Akhtar Cc: Kishon Vijay Abraham I Reviewed-by: Kiwoong Kim --- drivers/phy/samsung/Kconfig | 9 + drivers/phy/samsung/Makefile | 1 + drivers/phy/samsung/phy-exynos7-ufs.h | 85 +++++++ drivers/phy/samsung/phy-samsung-ufs.c | 311 ++++++++++++++++++++++++++ drivers/phy/samsung/phy-samsung-ufs.h | 100 +++++++++ include/linux/phy/phy-samsung-ufs.h | 70 ++++++ 6 files changed, 576 insertions(+) create mode 100644 drivers/phy/samsung/phy-exynos7-ufs.h create mode 100644 drivers/phy/samsung/phy-samsung-ufs.c create mode 100644 drivers/phy/samsung/phy-samsung-ufs.h create mode 100644 include/linux/phy/phy-samsung-ufs.h diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig index 9e483d1fdaf2..fc1e3c17f842 100644 --- a/drivers/phy/samsung/Kconfig +++ b/drivers/phy/samsung/Kconfig @@ -29,6 +29,15 @@ config PHY_EXYNOS_PCIE Enable PCIe PHY support for Exynos SoC series. This driver provides PHY interface for Exynos PCIe controller. +config PHY_SAMSUNG_UFS + tristate "SAMSUNG SoC series UFS PHY driver" + depends on OF && (ARCH_EXYNOS || COMPILE_TEST) + select GENERIC_PHY + help + Enable this to support the Samsung UFS PHY driver for + Samsung SoCs. This driver provides the interface for UFS + host controller to do PHY related programming. + config PHY_SAMSUNG_USB2 tristate "Samsung USB 2.0 PHY driver" depends on HAS_IOMEM diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile index db9b1aa0de6e..3959100fe8a2 100644 --- a/drivers/phy/samsung/Makefile +++ b/drivers/phy/samsung/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o +obj-$(CONFIG_PHY_SAMSUNG_UFS) += phy-samsung-ufs.o obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o phy-exynos-usb2-y += phy-samsung-usb2.o phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o diff --git a/drivers/phy/samsung/phy-exynos7-ufs.h b/drivers/phy/samsung/phy-exynos7-ufs.h new file mode 100644 index 000000000000..da981c1ac040 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos7-ufs.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * UFS PHY driver data for Samsung EXYNOS7 SoC + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + */ +#ifndef _PHY_EXYNOS7_UFS_H_ +#define _PHY_EXYNOS7_UFS_H_ + +#include "phy-samsung-ufs.h" + +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL 0x720 +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK 0x1 +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN BIT(0) + +/* Calibration for phy initialization */ +static const struct samsung_ufs_phy_cfg exynos7_pre_init_cfg[] = { + PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x017, 0x84, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x035, 0x58, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x037, 0x40, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x03b, 0x83, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x04c, 0x5b, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x05c, 0x14, PWR_MODE_ANY), + END_UFS_PHY_CFG +}; + +static const struct samsung_ufs_phy_cfg exynos7_post_init_cfg[] = { + END_UFS_PHY_CFG +}; + +/* Calibration for HS mode series A/B */ +static const struct samsung_ufs_phy_cfg exynos7_pre_pwr_hs_cfg[] = { + PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_HS_ANY), + /* Setting order: 1st(0x16, 2nd(0x15) */ + PHY_COMN_REG_CFG(0x016, 0xff, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x015, 0x80, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x017, 0x94, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x037, 0x43, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x038, 0x3f, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x042, 0xbb, PWR_MODE_HS_G2_SER_B), + PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x034, 0x35, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x034, 0x36, PWR_MODE_HS_G2_SER_B), + PHY_TRSV_REG_CFG(0x035, 0x5b, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x035, 0x5c, PWR_MODE_HS_G2_SER_B), + END_UFS_PHY_CFG +}; + +/* Calibration for HS mode series A/B atfer PMC */ +static const struct samsung_ufs_phy_cfg exynos7_post_pwr_hs_cfg[] = { + PHY_COMN_REG_CFG(0x015, 0x00, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_HS_ANY), + END_UFS_PHY_CFG +}; + +static const struct samsung_ufs_phy_cfg *exynos7_ufs_phy_cfgs[CFG_TAG_MAX] = { + [CFG_PRE_INIT] = exynos7_pre_init_cfg, + [CFG_POST_INIT] = exynos7_post_init_cfg, + [CFG_PRE_PWR_HS] = exynos7_pre_pwr_hs_cfg, + [CFG_POST_PWR_HS] = exynos7_post_pwr_hs_cfg, +}; + +static struct samsung_ufs_phy_drvdata exynos7_ufs_phy = { + .cfg = exynos7_ufs_phy_cfgs, + .isol = { + .offset = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL, + .mask = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK, + .en = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN, + }, +}; + +#endif /* _PHY_EXYNOS7_UFS_H_ */ diff --git a/drivers/phy/samsung/phy-samsung-ufs.c b/drivers/phy/samsung/phy-samsung-ufs.c new file mode 100644 index 000000000000..2d5db24de49b --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-ufs.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UFS PHY driver for Samsung SoC + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * Author: Seungwon Jeon + * Author: Alim Akhtar + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-samsung-ufs.h" + +#define for_each_phy_lane(phy, i) \ + for (i = 0; i < (phy)->lane_cnt; i++) +#define for_each_phy_cfg(cfg) \ + for (; (cfg)->id; (cfg)++) + +#define PHY_DEF_LANE_CNT 1 + +static void samsung_ufs_phy_config(struct samsung_ufs_phy *phy, + const struct samsung_ufs_phy_cfg *cfg, u8 lane) +{ + enum {LANE_0, LANE_1}; /* lane index */ + + switch (lane) { + case LANE_0: + writel(cfg->val, (phy)->reg_pma + cfg->off_0); + break; + case LANE_1: + if (cfg->id == PHY_TRSV_BLK) + writel(cfg->val, (phy)->reg_pma + cfg->off_1); + break; + } +} + +int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy) +{ + struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); + const unsigned int timeout_us = 100000; + const unsigned int sleep_us = 10; + u32 val; + int err; + + err = readl_poll_timeout( + ufs_phy->reg_pma + PHY_APB_ADDR(PHY_PLL_LOCK_STATUS), + val, (val & PHY_PLL_LOCK_BIT), sleep_us, timeout_us); + if (err) { + dev_err(ufs_phy->dev, + "failed to get phy pll lock acquisition %d\n", err); + goto out; + } + + err = readl_poll_timeout( + ufs_phy->reg_pma + PHY_APB_ADDR(PHY_CDR_LOCK_STATUS), + val, (val & PHY_CDR_LOCK_BIT), sleep_us, timeout_us); + if (err) { + dev_err(ufs_phy->dev, + "failed to get phy cdr lock acquisition %d\n", err); + goto out; + } + +out: + return err; +} + +int samsung_ufs_phy_calibrate(struct phy *phy) +{ + struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); + struct samsung_ufs_phy_cfg **cfgs = ufs_phy->cfg; + const struct samsung_ufs_phy_cfg *cfg; + int i; + int err = 0; + + if (unlikely(ufs_phy->ufs_phy_state < CFG_PRE_INIT || + ufs_phy->ufs_phy_state >= CFG_TAG_MAX)) { + dev_err(ufs_phy->dev, "invalid phy config index %d\n", + ufs_phy->ufs_phy_state); + return -EINVAL; + } + + if (ufs_phy->is_pre_init) + ufs_phy->is_pre_init = false; + if (ufs_phy->is_post_init) { + ufs_phy->is_post_init = false; + ufs_phy->ufs_phy_state = CFG_POST_INIT; + } + if (ufs_phy->is_pre_pmc) { + ufs_phy->is_pre_pmc = false; + ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS; + } + if (ufs_phy->is_post_pmc) { + ufs_phy->is_post_pmc = false; + ufs_phy->ufs_phy_state = CFG_POST_PWR_HS; + } + + switch (ufs_phy->ufs_phy_state) { + case CFG_PRE_INIT: + ufs_phy->is_post_init = true; + break; + case CFG_POST_INIT: + ufs_phy->is_pre_pmc = true; + break; + case CFG_PRE_PWR_HS: + ufs_phy->is_post_pmc = true; + break; + case CFG_POST_PWR_HS: + break; + default: + dev_err(ufs_phy->dev, "wrong state for phy calibration\n"); + } + + cfg = cfgs[ufs_phy->ufs_phy_state]; + if (!cfg) + goto out; + + for_each_phy_cfg(cfg) { + for_each_phy_lane(ufs_phy, i) { + samsung_ufs_phy_config(ufs_phy, cfg, i); + } + } + + if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS) + err = samsung_ufs_phy_wait_for_lock_acq(phy); +out: + return err; +} + +static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy) +{ + struct clk *child, *parent; + + child = devm_clk_get(phy->dev, "ref_clk"); + if (IS_ERR(child)) + dev_err(phy->dev, "failed to get ref_clk clock\n"); + else + phy->ref_clk = child; + + parent = devm_clk_get(phy->dev, "ref_clk_parent"); + if (IS_ERR(parent)) + dev_err(phy->dev, "failed to get ref_clk_parent clock\n"); + else + phy->ref_clk_parent = parent; + + return clk_set_parent(child, parent); +} + +static int samsung_ufs_phy_init(struct phy *phy) +{ + struct samsung_ufs_phy *_phy = get_samsung_ufs_phy(phy); + + _phy->lane_cnt = phy->attrs.bus_width; + _phy->ufs_phy_state = CFG_PRE_INIT; + + _phy->is_pre_init = true; + _phy->is_post_init = false; + _phy->is_pre_pmc = false; + _phy->is_post_pmc = false; + + samsung_ufs_phy_clks_init(_phy); + + samsung_ufs_phy_calibrate(phy); + + return 0; +} + +static int samsung_ufs_phy_power_on(struct phy *phy) +{ + struct samsung_ufs_phy *_phy = get_samsung_ufs_phy(phy); + int ret; + + ret = clk_prepare_enable(_phy->ref_clk); + if (ret) { + dev_err(_phy->dev, "%s: ref_clk enable failed %d\n", + __func__, ret); + return ret; + } + + samsung_ufs_phy_ctrl_isol(_phy, false); + return 0; +} + +static int samsung_ufs_phy_power_off(struct phy *phy) +{ + struct samsung_ufs_phy *_phy = get_samsung_ufs_phy(phy); + + samsung_ufs_phy_ctrl_isol(_phy, true); + clk_disable_unprepare(_phy->ref_clk); + return 0; +} + +static int samsung_ufs_phy_set_mode(struct phy *generic_phy, + enum phy_mode mode, int submode) +{ + struct samsung_ufs_phy *_phy = get_samsung_ufs_phy(generic_phy); + + _phy->mode = PHY_MODE_INVALID; + + if (mode > 0) + _phy->mode = mode; + + return 0; +} + +static struct phy_ops samsung_ufs_phy_ops = { + .init = samsung_ufs_phy_init, + .power_on = samsung_ufs_phy_power_on, + .power_off = samsung_ufs_phy_power_off, + .calibrate = samsung_ufs_phy_calibrate, + .set_mode = samsung_ufs_phy_set_mode, +} +; +static const struct of_device_id samsung_ufs_phy_match[]; + +static int samsung_ufs_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + const struct of_device_id *match; + struct samsung_ufs_phy *phy; + struct phy *gen_phy; + struct phy_provider *phy_provider; + const struct samsung_ufs_phy_drvdata *drvdata; + int err = 0; + + match = of_match_node(samsung_ufs_phy_match, dev->of_node); + if (!match) { + err = -EINVAL; + dev_err(dev, "failed to get match_node\n"); + goto out; + } + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + err = -ENOMEM; + goto out; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy-pma"); + phy->reg_pma = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->reg_pma)) { + err = PTR_ERR(phy->reg_pma); + goto out; + } + + phy->reg_pmu = syscon_regmap_lookup_by_phandle( + dev->of_node, "samsung,pmu-syscon"); + if (IS_ERR(phy->reg_pmu)) { + err = PTR_ERR(phy->reg_pmu); + dev_err(dev, "failed syscon remap for pmu\n"); + goto out; + } + + gen_phy = devm_phy_create(dev, NULL, &samsung_ufs_phy_ops); + if (IS_ERR(gen_phy)) { + err = PTR_ERR(gen_phy); + dev_err(dev, "failed to create PHY for ufs-phy\n"); + goto out; + } + + drvdata = match->data; + phy->dev = dev; + phy->drvdata = drvdata; + phy->cfg = (struct samsung_ufs_phy_cfg **)drvdata->cfg; + phy->isol = &drvdata->isol; + phy->lane_cnt = PHY_DEF_LANE_CNT; + + phy_set_drvdata(gen_phy, phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + err = PTR_ERR(phy_provider); + dev_err(dev, "failed to register phy-provider\n"); + goto out; + } +out: + return err; +} + +static const struct of_device_id samsung_ufs_phy_match[] = { + { + .compatible = "samsung,exynos7-ufs-phy", + .data = &exynos7_ufs_phy, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match); + +static struct platform_driver samsung_ufs_phy_driver = { + .probe = samsung_ufs_phy_probe, + .driver = { + .name = "samsung-ufs-phy", + .of_match_table = samsung_ufs_phy_match, + }, +}; +module_platform_driver(samsung_ufs_phy_driver); +MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver"); +MODULE_AUTHOR("Seungwon Jeon "); +MODULE_AUTHOR("Alim Akhtar "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-samsung-ufs.h b/drivers/phy/samsung/phy-samsung-ufs.h new file mode 100644 index 000000000000..d0ae2b416b2b --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-ufs.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * UFS PHY driver for Samsung EXYNOS SoC + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * Author: Seungwon Jeon + * Author: Alim Akhtar + * + */ +#ifndef _PHY_SAMSUNG_UFS_ +#define _PHY_SAMSUNG_UFS_ + +#define PHY_COMN_BLK 1 +#define PHY_TRSV_BLK 2 +#define END_UFS_PHY_CFG { 0 } +#define PHY_TRSV_CH_OFFSET 0x30 +#define PHY_APB_ADDR(off) ((off) << 2) + +#define PHY_COMN_REG_CFG(o, v, d) { \ + .off_0 = PHY_APB_ADDR((o)), \ + .off_1 = 0, \ + .val = (v), \ + .desc = (d), \ + .id = PHY_COMN_BLK, \ +} + +#define PHY_TRSV_REG_CFG(o, v, d) { \ + .off_0 = PHY_APB_ADDR((o)), \ + .off_1 = PHY_APB_ADDR((o) + PHY_TRSV_CH_OFFSET), \ + .val = (v), \ + .desc = (d), \ + .id = PHY_TRSV_BLK, \ +} + +/* UFS PHY registers */ +#define PHY_PLL_LOCK_STATUS 0x1e +#define PHY_CDR_LOCK_STATUS 0x5e + +#define PHY_PLL_LOCK_BIT BIT(5) +#define PHY_CDR_LOCK_BIT BIT(4) + +/* PHY calibration point/state */ +enum { + CFG_PRE_INIT, + CFG_POST_INIT, + CFG_PRE_PWR_HS, + CFG_POST_PWR_HS, + CFG_TAG_MAX, +}; + +struct samsung_ufs_phy_cfg { + u32 off_0; + u32 off_1; + u32 val; + u8 desc; + u8 id; +}; + +struct samsung_ufs_phy_drvdata { + const struct samsung_ufs_phy_cfg **cfg; + struct pmu_isol { + u32 offset; + u32 mask; + u32 en; + } isol; +}; + +struct samsung_ufs_phy { + struct device *dev; + void __iomem *reg_pma; + struct regmap *reg_pmu; + struct clk *ref_clk; + struct clk *ref_clk_parent; + const struct samsung_ufs_phy_drvdata *drvdata; + struct samsung_ufs_phy_cfg **cfg; + const struct pmu_isol *isol; + u8 lane_cnt; + int ufs_phy_state; + enum phy_mode mode; + bool is_pre_init; + bool is_post_init; + bool is_pre_pmc; + bool is_post_pmc; +}; + +static inline struct samsung_ufs_phy *get_samsung_ufs_phy(struct phy *phy) +{ + return (struct samsung_ufs_phy *)phy_get_drvdata(phy); +} + +static inline void samsung_ufs_phy_ctrl_isol( + struct samsung_ufs_phy *phy, u32 isol) +{ + regmap_update_bits(phy->reg_pmu, phy->isol->offset, + phy->isol->mask, isol ? 0 : phy->isol->en); +} + +#include "phy-exynos7-ufs.h" + +#endif /* _PHY_SAMSUNG_UFS_ */ diff --git a/include/linux/phy/phy-samsung-ufs.h b/include/linux/phy/phy-samsung-ufs.h new file mode 100644 index 000000000000..1def56207f5d --- /dev/null +++ b/include/linux/phy/phy-samsung-ufs.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * phy-samsung-ufs.h - Header file for the UFS PHY of Samsung SoC + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * Author: Seungwon Jeon + * Author: Alim Akhtar + * + */ + +#ifndef _PHY_EXYNOS_UFS_H_ +#define _PHY_EXYNOS_UFS_H_ + +#include "phy.h" + +/* description for PHY calibration */ +enum { + /* applicable to any */ + PWR_DESC_ANY = 0, + /* mode */ + PWR_DESC_PWM = 1, + PWR_DESC_HS = 2, + /* series */ + PWR_DESC_SER_A = 1, + PWR_DESC_SER_B = 2, + /* gear */ + PWR_DESC_G1 = 1, + PWR_DESC_G2 = 2, + PWR_DESC_G3 = 3, + PWR_DESC_G4 = 4, + PWR_DESC_G5 = 5, + PWR_DESC_G6 = 6, + PWR_DESC_G7 = 7, + /* field mask */ + MD_MASK = 0x3, + SR_MASK = 0x3, + GR_MASK = 0x7, +}; + +#define PWR_MODE(g, s, m) ((((g) & GR_MASK) << 4) |\ + (((s) & SR_MASK) << 2) | ((m) & MD_MASK)) +#define PWR_MODE_HS(g, s) ((((g) & GR_MASK) << 4) |\ + (((s) & SR_MASK) << 2) | PWR_DESC_HS) +#define PWR_MODE_HS_G1_ANY PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_ANY) +#define PWR_MODE_HS_G1_SER_A PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_SER_A) +#define PWR_MODE_HS_G1_SER_B PWR_MODE_HS(PWR_DESC_G1, PWR_DESC_SER_B) +#define PWR_MODE_HS_G2_ANY PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_ANY) +#define PWR_MODE_HS_G2_SER_A PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_SER_A) +#define PWR_MODE_HS_G2_SER_B PWR_MODE_HS(PWR_DESC_G2, PWR_DESC_SER_B) +#define PWR_MODE_HS_G3_ANY PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_ANY) +#define PWR_MODE_HS_G3_SER_A PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_SER_A) +#define PWR_MODE_HS_G3_SER_B PWR_MODE_HS(PWR_DESC_G3, PWR_DESC_SER_B) +#define PWR_MODE_HS_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_HS) +#define PWR_MODE_PWM_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_PWM) +#define PWR_MODE_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_ANY) +#define IS_PWR_MODE_HS(d) (((d) & MD_MASK) == PWR_DESC_HS) +#define IS_PWR_MODE_PWM(d) (((d) & MD_MASK) == PWR_DESC_PWM) +#define IS_PWR_MODE_ANY(d) ((d) == PWR_MODE_ANY) +#define IS_PWR_MODE_HS_ANY(d) ((d) == PWR_MODE_HS_ANY) +#define COMP_PWR_MODE(a, b) ((a) == (b)) +#define COMP_PWR_MODE_GEAR(a, b) ((((a) >> 4) & GR_MASK) == \ + (((b) >> 4) & GR_MASK)) +#define COMP_PWR_MODE_SER(a, b) ((((a) >> 2) & SR_MASK) == \ + (((b) >> 2) & SR_MASK)) +#define COMP_PWR_MODE_MD(a, b) (((a) & MD_MASK) == ((b) & MD_MASK)) + +#endif /* _PHY_EXYNOS_UFS_H_ */