2015-11-09 05:26:07

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 00/11] exynos-ufs: add support for Exynos

This patch-set introduces UFS (Universal Flash Storage) host support
for Samsung Exynos SoC. Mostly, it consists of UFS PHY and host specific driver.
And it also contains some quirks handling for Exynos.

NOTE: ** This series has a dependency on [4]. **

-Changes since v4:
* Removed platform specific PHY ops as suggested by Kishon
* Rebased on the top of Yaniv Gardi's work [4]
* make use of newly introduce ufshcd_{get,set}_variant
* other small changes and improvements.
* rebased on the top of linux next-20151109

-Changes since v3:
* Fixed compilation warrings as reported by "Kbuild Test Robot"[5].
* Restructure the driver to make it as a platform driver, rebased on top of [4].
* Addressed review comments from Arnd Bergmann[5].
* Other misc changes and improvements.

-Changes since v2:
* Addressed review comments from Kishon[1] and Rob Herring [2]
* Splited ufs dt binding documetation from ufs driver patch

-Changes since v1:
* Addressed review comments from Alexey[3] and various review comments from Amit.
* Updated email id of Seungwon as his samsung id is void now.
* Added ufs platform data

[1]-> https://lkml.org/lkml/2015/9/18/29
[2]-> https://lkml.org/lkml/2015/9/21/668
[3]-> https://lkml.org/lkml/2015/8/23/124
[4]-> https://lkml.org/lkml/2015/10/28/271
[5]-> https://lkml.org/lkml/2015/10/1/402

This patch set is tested on exynos7-espresso board.


Alim Akhtar (1):
Documentation: samsung-phy: Add dt bindings for UFS

Seungwon Jeon (10):
phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC
scsi: ufs: add quirk to contain unconformable utrd field
scsi: ufs: add quirk to fix mishandling utrlclr/utmrlclr
scsi: ufs: add quirk not to allow reset of interrupt aggregation
scsi: ufs: add quirk to enable host controller without hce
scsi: ufs: add specific callback for nexus type
scsi: ufs: add add specific callback for hibern8
scsi: ufs: make ufshcd_config_pwr_mode of non-static func
Documentation: devicetree: ufs: Add DT bindings for exynos UFS host
controller
scsi: ufs-exynos: add UFS host support for Exynos SoCs

.../devicetree/bindings/phy/samsung-phy.txt | 22 +
.../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++
drivers/phy/Kconfig | 7 +
drivers/phy/Makefile | 1 +
drivers/phy/phy-exynos-ufs.c | 241 ++++
drivers/phy/phy-exynos-ufs.h | 85 ++
drivers/phy/phy-exynos7-ufs.h | 89 ++
drivers/scsi/ufs/Kconfig | 12 +
drivers/scsi/ufs/Makefile | 1 +
drivers/scsi/ufs/ufs-exynos-hw.c | 131 ++
drivers/scsi/ufs/ufs-exynos-hw.h | 43 +
drivers/scsi/ufs/ufs-exynos.c | 1304 ++++++++++++++++++++
drivers/scsi/ufs/ufs-exynos.h | 247 ++++
drivers/scsi/ufs/ufshcd.c | 168 ++-
drivers/scsi/ufs/ufshcd.h | 54 +
drivers/scsi/ufs/ufshci.h | 26 +-
drivers/scsi/ufs/unipro.h | 47 +
include/linux/phy/phy-exynos-ufs.h | 85 ++
18 files changed, 2647 insertions(+), 20 deletions(-)
create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt
create mode 100644 drivers/phy/phy-exynos-ufs.c
create mode 100644 drivers/phy/phy-exynos-ufs.h
create mode 100644 drivers/phy/phy-exynos7-ufs.h
create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.c
create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.h
create mode 100644 drivers/scsi/ufs/ufs-exynos.c
create mode 100644 drivers/scsi/ufs/ufs-exynos.h
create mode 100644 include/linux/phy/phy-exynos-ufs.h

--
1.7.10.4


2015-11-09 05:26:31

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 01/11] Documentation: samsung-phy: Add dt bindings for UFS

Adds exynos UFS PHY device tree bindings information.

Signed-off-by: Alim Akhtar <[email protected]>
---
.../devicetree/bindings/phy/samsung-phy.txt | 22 ++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/samsung-phy.txt b/Documentation/devicetree/bindings/phy/samsung-phy.txt
index 0289d3b07853..565200d72e91 100644
--- a/Documentation/devicetree/bindings/phy/samsung-phy.txt
+++ b/Documentation/devicetree/bindings/phy/samsung-phy.txt
@@ -177,3 +177,25 @@ Example:
usbdrdphy0 = &usb3_phy0;
usbdrdphy1 = &usb3_phy1;
};
+
+Samsung Exynos7 soc series UFS PHY Controller
+---------------------------------------------
+
+UFS PHY nodes are defined to describe on-chip UFS Physical layer controllers.
+Each UFS PHY controller should have its own node.
+
+Required properties:
+- compatible : compatible should be set to "samsung,exynos7-ufs-phy"
+- reg : offset and length of the UFS PHY register set
+- reg-names : reg name(s) must be 'phy-pma'
+- #phy-cells : must be zero
+- samsung,pmu-syscon : a phandle to the PMU system controller, no arguments
+
+Example:
+ ufs_phy: ufs-phy@0x15571800 {
+ compatible = "samsung,exynos7-ufs-phy";
+ reg = <0x15571800 0x240>;
+ reg-names = "phy-pma";
+ samsung,pmu-syscon = <&pmu_system_controller>;
+ #phy-cells = <0>;
+ };
--
1.7.10.4

2015-11-09 05:27:06

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

From: Seungwon Jeon <[email protected]>

This patch introduces Exynos 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 <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
Cc: Kishon Vijay Abraham I <[email protected]>
---
drivers/phy/Kconfig | 7 ++
drivers/phy/Makefile | 1 +
drivers/phy/phy-exynos-ufs.c | 241 ++++++++++++++++++++++++++++++++++++
drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
6 files changed, 508 insertions(+)
create mode 100644 drivers/phy/phy-exynos-ufs.c
create mode 100644 drivers/phy/phy-exynos-ufs.h
create mode 100644 drivers/phy/phy-exynos7-ufs.h
create mode 100644 include/linux/phy/phy-exynos-ufs.h

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 7eb5859dd035..7d38a92e0297 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
Enable this to support the Broadcom Cygnus PCIe PHY.
If unsure, say N.

+config PHY_EXYNOS_UFS
+ tristate "EXYNOS SoC series UFS PHY driver"
+ depends on OF && ARCH_EXYNOS || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Support for UFS PHY on Samsung EXYNOS chipsets.
+
endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 075db1a81aa5..9bec4d1a89e1 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o
obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
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_UFS) += phy-exynos-ufs.o
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
diff --git a/drivers/phy/phy-exynos-ufs.c b/drivers/phy/phy-exynos-ufs.c
new file mode 100644
index 000000000000..cb1aeaa3d4eb
--- /dev/null
+++ b/drivers/phy/phy-exynos-ufs.c
@@ -0,0 +1,241 @@
+/*
+ * UFS PHY driver for Samsung EXYNOS SoC
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-exynos-ufs.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
+ const struct exynos_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;
+ }
+}
+
+static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
+{
+ if (IS_PWR_MODE_ANY(desc))
+ return true;
+
+ if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
+ return true;
+
+ if (COMP_PWR_MODE(required_pwr, desc))
+ return true;
+
+ if (COMP_PWR_MODE_MD(required_pwr, desc) &&
+ COMP_PWR_MODE_GEAR(required_pwr, desc) &&
+ COMP_PWR_MODE_SER(required_pwr, desc))
+ return true;
+
+ return false;
+}
+
+int exynos_ufs_phy_calibrate(struct phy *phy,
+ enum phy_cfg_tag tag, u8 pwr)
+{
+ struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
+ struct exynos_ufs_phy_cfg **cfgs = ufs_phy->cfg;
+ const struct exynos_ufs_phy_cfg *cfg;
+ int i;
+
+ if (unlikely(tag < CFG_PRE_INIT || tag >= CFG_TAG_MAX)) {
+ dev_err(ufs_phy->dev, "invalid phy config index %d\n", tag);
+ return -EINVAL;
+ }
+
+ cfg = cfgs[tag];
+ if (!cfg)
+ goto out;
+
+ for_each_phy_cfg(cfg) {
+ for_each_phy_lane(ufs_phy, i) {
+ if (match_cfg_to_pwr_mode(cfg->desc, pwr))
+ exynos_ufs_phy_config(ufs_phy, cfg, i);
+ }
+ }
+
+out:
+ return 0;
+}
+
+void exynos_ufs_phy_set_lane_cnt(struct phy *phy, u8 lane_cnt)
+{
+ struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
+
+ ufs_phy->lane_cnt = lane_cnt;
+}
+
+int exynos_ufs_phy_wait_for_lock_acq(struct phy *phy)
+{
+ struct exynos_ufs_phy *ufs_phy = get_exynos_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;
+}
+
+static int exynos_ufs_phy_power_on(struct phy *phy)
+{
+ struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
+
+ exynos_ufs_phy_ctrl_isol(_phy, false);
+ return 0;
+}
+
+static int exynos_ufs_phy_power_off(struct phy *phy)
+{
+ struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
+
+ exynos_ufs_phy_ctrl_isol(_phy, true);
+ return 0;
+}
+
+static struct phy_ops exynos_ufs_phy_ops = {
+ .power_on = exynos_ufs_phy_power_on,
+ .power_off = exynos_ufs_phy_power_off,
+}
+;
+static const struct of_device_id exynos_ufs_phy_match[];
+
+static int exynos_ufs_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ const struct of_device_id *match;
+ struct exynos_ufs_phy *phy;
+ struct phy *gen_phy;
+ struct phy_provider *phy_provider;
+ const struct exynos_ufs_phy_drvdata *drvdata;
+ int err = 0;
+
+ match = of_match_node(exynos_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, &exynos_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 exynos_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 exynos_ufs_phy_match[] = {
+ {
+ .compatible = "samsung,exynos7-ufs-phy",
+ .data = &exynos7_ufs_phy,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_ufs_phy_match);
+
+static struct platform_driver exynos_ufs_phy_driver = {
+ .probe = exynos_ufs_phy_probe,
+ .driver = {
+ .name = "exynos-ufs-phy",
+ .of_match_table = exynos_ufs_phy_match,
+ },
+};
+module_platform_driver(exynos_ufs_phy_driver);
+MODULE_DESCRIPTION("EXYNOS SoC UFS PHY Driver");
+MODULE_AUTHOR("Seungwon Jeon <[email protected]>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-exynos-ufs.h b/drivers/phy/phy-exynos-ufs.h
new file mode 100644
index 000000000000..820d879f393c
--- /dev/null
+++ b/drivers/phy/phy-exynos-ufs.h
@@ -0,0 +1,85 @@
+/*
+ * UFS PHY driver for Samsung EXYNOS SoC
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef _PHY_EXYNOS_UFS_
+#define _PHY_EXYNOS_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)
+
+struct exynos_ufs_phy_cfg {
+ u32 off_0;
+ u32 off_1;
+ u32 val;
+ u8 desc;
+ u8 id;
+};
+
+struct exynos_ufs_phy_drvdata {
+ const struct exynos_ufs_phy_cfg **cfg;
+ struct pmu_isol {
+ u32 offset;
+ u32 mask;
+ u32 en;
+ } isol;
+};
+
+struct exynos_ufs_phy {
+ struct device *dev;
+ void __iomem *reg_pma;
+ struct regmap *reg_pmu;
+ const struct exynos_ufs_phy_drvdata *drvdata;
+ struct exynos_ufs_phy_cfg **cfg;
+ const struct pmu_isol *isol;
+ u8 lane_cnt;
+};
+
+static inline struct exynos_ufs_phy *get_exynos_ufs_phy(struct phy *phy)
+{
+ return (struct exynos_ufs_phy *)phy_get_drvdata(phy);
+}
+
+static inline void exynos_ufs_phy_ctrl_isol(
+ struct exynos_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_EXYNOS_UFS_ */
diff --git a/drivers/phy/phy-exynos7-ufs.h b/drivers/phy/phy-exynos7-ufs.h
new file mode 100644
index 000000000000..6cd29d7fb200
--- /dev/null
+++ b/drivers/phy/phy-exynos7-ufs.h
@@ -0,0 +1,89 @@
+/*
+ * UFS PHY driver for Samsung EXYNOS SoC
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef _PHY_EXYNOS7_UFS_H_
+#define _PHY_EXYNOS7_UFS_H_
+
+#include "phy-exynos-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 exynos_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 exynos_ufs_phy_cfg exynos7_post_init_cfg[] = {
+ END_UFS_PHY_CFG
+};
+
+/* Calibration for HS mode series A/B */
+static const struct exynos_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 exynos_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 exynos_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 exynos_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/include/linux/phy/phy-exynos-ufs.h b/include/linux/phy/phy-exynos-ufs.h
new file mode 100644
index 000000000000..304df68fb9be
--- /dev/null
+++ b/include/linux/phy/phy-exynos-ufs.h
@@ -0,0 +1,85 @@
+/*
+ * phy-exynos-ufs.h - Header file for the UFS PHY of Exynos SoC
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _PHY_EXYNOS_UFS_H_
+#define _PHY_EXYNOS_UFS_H_
+
+#include "phy.h"
+
+/* PHY calibration point */
+enum phy_cfg_tag {
+ CFG_PRE_INIT = 0,
+ CFG_POST_INIT = 1,
+ CFG_PRE_PWR_HS = 2,
+ CFG_POST_PWR_HS = 3,
+ CFG_TAG_MAX,
+};
+
+/* 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))
+
+int exynos_ufs_phy_calibrate(struct phy *phy, enum phy_cfg_tag tag, u8 pwr);
+void exynos_ufs_phy_set_lane_cnt(struct phy *phy, u8 lane_cnt);
+int exynos_ufs_phy_wait_for_lock_acq(struct phy *phy);
+
+#endif /* _PHY_EXYNOS_UFS_H_ */
--
1.7.10.4

2015-11-09 05:27:43

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 03/11] scsi: ufs: add quirk to contain unconformable utrd field

From: Seungwon Jeon <[email protected]>

UTRD(UTP Transfer Request Descriptor)'s field such as offset/length,
especially response's has DWORD expression. This quirk can be specified
for host controller not to conform standard.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 28 +++++++++++++++++++++-------
drivers/scsi/ufs/ufshcd.h | 7 +++++++
2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 131c72038bf8..587a9c8fbfe9 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -1009,7 +1009,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
*
* Returns 0 in case of success, non-zero value in case of failure
*/
-static int ufshcd_map_sg(struct ufshcd_lrb *lrbp)
+static int ufshcd_map_sg(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
{
struct ufshcd_sg_entry *prd_table;
struct scatterlist *sg;
@@ -1023,8 +1023,13 @@ static int ufshcd_map_sg(struct ufshcd_lrb *lrbp)
return sg_segments;

if (sg_segments) {
- lrbp->utr_descriptor_ptr->prd_table_length =
- cpu_to_le16((u16) (sg_segments));
+ if (hba->quirks & UFSHCI_QUIRK_BYTE_ALIGN_UTRD)
+ lrbp->utr_descriptor_ptr->prd_table_length =
+ cpu_to_le16((u16)(sg_segments *
+ sizeof(struct ufshcd_sg_entry)));
+ else
+ lrbp->utr_descriptor_ptr->prd_table_length =
+ cpu_to_le16((u16) (sg_segments));

prd_table = (struct ufshcd_sg_entry *)lrbp->ucd_prdt_ptr;

@@ -1347,7 +1352,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)

/* form UPIU before issuing the command */
ufshcd_compose_upiu(hba, lrbp);
- err = ufshcd_map_sg(lrbp);
+ err = ufshcd_map_sg(hba, lrbp);
if (err) {
lrbp->cmd = NULL;
clear_bit_unlock(tag, &hba->lrb_in_use);
@@ -2034,13 +2039,22 @@ static void ufshcd_host_memory_configure(struct ufs_hba *hba)
utrdlp[i].command_desc_base_addr_hi =
cpu_to_le32(upper_32_bits(cmd_desc_element_addr));

+ if (hba->quirks & UFSHCI_QUIRK_BYTE_ALIGN_UTRD) {
+ utrdlp[i].response_upiu_offset =
+ cpu_to_le16(response_offset);
+ utrdlp[i].prd_table_offset =
+ cpu_to_le16(prdt_offset);
+ utrdlp[i].response_upiu_length =
+ cpu_to_le16(ALIGNED_UPIU_SIZE);
+ } else {
/* Response upiu and prdt offset should be in double words */
- utrdlp[i].response_upiu_offset =
+ utrdlp[i].response_upiu_offset =
cpu_to_le16((response_offset >> 2));
- utrdlp[i].prd_table_offset =
+ utrdlp[i].prd_table_offset =
cpu_to_le16((prdt_offset >> 2));
- utrdlp[i].response_upiu_length =
+ utrdlp[i].response_upiu_length =
cpu_to_le16(ALIGNED_UPIU_SIZE >> 2);
+ }

hba->lrb[i].utr_descriptor_ptr = (utrdlp + i);
hba->lrb[i].ucd_req_ptr =
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 2570d9477b37..6cd542a803d5 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -467,6 +467,13 @@ struct ufs_hba {
*/
#define UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION UFS_BIT(5)

+ /*
+ * This quirk needs to be enabled if host controller doesn't conform
+ * with UTRD. Some fields such as offset/length might not be in double
+ * word, but in byte.
+ */
+ #define UFSHCI_QUIRK_BYTE_ALIGN_UTRD UFS_BIT(6)
+
unsigned int quirks; /* Deviations from standard UFSHCI spec. */

wait_queue_head_t tm_wq;
--
1.7.10.4

2015-11-09 05:28:20

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 04/11] scsi: ufs: add quirk to fix mishandling utrlclr/utmrlclr

From: Seungwon Jeon <[email protected]>

In the right behavior, setting the bit to '0' indicates clear and
'1' indicates no change. If host contoller handles this the other way,
UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR can be used.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 21 +++++++++++++++++++--
drivers/scsi/ufs/ufshcd.h | 5 +++++
2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 587a9c8fbfe9..2b16eb363203 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -356,7 +356,24 @@ static inline void ufshcd_put_tm_slot(struct ufs_hba *hba, int slot)
*/
static inline void ufshcd_utrl_clear(struct ufs_hba *hba, u32 pos)
{
- ufshcd_writel(hba, ~(1 << pos), REG_UTP_TRANSFER_REQ_LIST_CLEAR);
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
+ ufshcd_writel(hba, (1 << pos), REG_UTP_TRANSFER_REQ_LIST_CLEAR);
+ else
+ ufshcd_writel(hba, ~(1 << pos),
+ REG_UTP_TRANSFER_REQ_LIST_CLEAR);
+}
+
+/**
+ * ufshcd_utmrl_clear - Clear a bit in UTRMLCLR register
+ * @hba: per adapter instance
+ * @pos: position of the bit to be cleared
+ */
+static inline void ufshcd_utmrl_clear(struct ufs_hba *hba, u32 pos)
+{
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
+ ufshcd_writel(hba, (1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
+ else
+ ufshcd_writel(hba, ~(1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
}

/**
@@ -3685,7 +3702,7 @@ static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag)
goto out;

spin_lock_irqsave(hba->host->host_lock, flags);
- ufshcd_writel(hba, ~(1 << tag), REG_UTP_TASK_REQ_LIST_CLEAR);
+ ufshcd_utmrl_clear(hba, tag);
spin_unlock_irqrestore(hba->host->host_lock, flags);

/* poll for max. 1 sec to clear door bell register by h/w */
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 6cd542a803d5..d625d01110b0 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -474,6 +474,11 @@ struct ufs_hba {
*/
#define UFSHCI_QUIRK_BYTE_ALIGN_UTRD UFS_BIT(6)

+ /*
+ * Cleaer handling for transfer/task request list is just opposite.
+ */
+ #define UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR UFS_BIT(7)
+
unsigned int quirks; /* Deviations from standard UFSHCI spec. */

wait_queue_head_t tm_wq;
--
1.7.10.4

2015-11-09 05:28:51

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 05/11] scsi: ufs: add quirk not to allow reset of interrupt aggregation

From: Seungwon Jeon <[email protected]>

Some host controller supports interrupt aggregation, but doesn't
allow to reset counter and timer by s/w.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 3 ++-
drivers/scsi/ufs/ufshcd.h | 6 ++++++
2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 2b16eb363203..ca7483cd899e 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -3199,7 +3199,8 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba)
* false interrupt if device completes another request after resetting
* aggregation and before reading the DB.
*/
- if (ufshcd_is_intr_aggr_allowed(hba))
+ if (ufshcd_is_intr_aggr_allowed(hba) &&
+ !(hba->quirks & UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR))
ufshcd_reset_intr_aggr(hba);

tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index d625d01110b0..4ae32e9316de 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -479,6 +479,12 @@ struct ufs_hba {
*/
#define UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR UFS_BIT(7)

+ /*
+ * This quirk needs to be enabled if host controller doesn't allow
+ * that the interrupt aggregation timer and counter are reset by s/w.
+ */
+ #define UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR UFS_BIT(8)
+
unsigned int quirks; /* Deviations from standard UFSHCI spec. */

wait_queue_head_t tm_wq;
--
1.7.10.4

2015-11-09 05:29:32

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 06/11] scsi: ufs: add quirk to enable host controller without hce

From: Seungwon Jeon <[email protected]>

Some host controller doesn't support host controller enable via HCE.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 75 +++++++++++++++++++++++++++++++++++++++++++--
drivers/scsi/ufs/ufshcd.h | 5 +++
2 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index ca7483cd899e..e8b96ec65987 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -2107,6 +2107,52 @@ static int ufshcd_dme_link_startup(struct ufs_hba *hba)
"dme-link-startup: error code %d\n", ret);
return ret;
}
+/**
+ * ufshcd_dme_reset - UIC command for DME_RESET
+ * @hba: per adapter instance
+ *
+ * DME_RESET command is issued in order to reset UniPro stack.
+ * This function now deal with cold reset.
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+static int ufshcd_dme_reset(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {0};
+ int ret;
+
+ uic_cmd.command = UIC_CMD_DME_RESET;
+
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_err(hba->dev,
+ "dme-reset: error code %d\n", ret);
+
+ return ret;
+}
+
+/**
+ * ufshcd_dme_enable - UIC command for DME_ENABLE
+ * @hba: per adapter instance
+ *
+ * DME_ENABLE command is issued in order to enable UniPro stack.
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+static int ufshcd_dme_enable(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {0};
+ int ret;
+
+ uic_cmd.command = UIC_CMD_DME_ENABLE;
+
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_err(hba->dev,
+ "dme-reset: error code %d\n", ret);
+
+ return ret;
+}

static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba)
{
@@ -2642,7 +2688,7 @@ out:
}

/**
- * ufshcd_hba_enable - initialize the controller
+ * ufshcd_hba_execute_hce - initialize the controller
* @hba: per adapter instance
*
* The controller resets itself and controller firmware initialization
@@ -2651,7 +2697,7 @@ out:
*
* Returns 0 on success, non-zero value on failure
*/
-static int ufshcd_hba_enable(struct ufs_hba *hba)
+static int ufshcd_hba_execute_hce(struct ufs_hba *hba)
{
int retry;

@@ -2715,6 +2761,31 @@ static int ufshcd_hba_enable(struct ufs_hba *hba)
return 0;
}

+static int ufshcd_hba_enable(struct ufs_hba *hba)
+{
+ int ret;
+
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_HCE) {
+ ufshcd_set_link_off(hba);
+ ufshcd_vops_hce_enable_notify(hba, PRE_CHANGE);
+
+ /* enable UIC related interrupts */
+ ufshcd_enable_intr(hba, UFSHCD_UIC_MASK);
+ ret = ufshcd_dme_reset(hba);
+ if (!ret) {
+ ret = ufshcd_dme_enable(hba);
+ if (!ret)
+ ufshcd_vops_hce_enable_notify(hba, POST_CHANGE);
+ if (ret)
+ dev_err(hba->dev,
+ "Host controller enable failed with non-hce\n");
+ }
+ } else {
+ ret = ufshcd_hba_execute_hce(hba);
+ }
+
+ return ret;
+}
static int ufshcd_disable_tx_lcc(struct ufs_hba *hba, bool peer)
{
int tx_lanes, i, err = 0;
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 4ae32e9316de..500e137bf68a 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -485,6 +485,11 @@ struct ufs_hba {
*/
#define UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR UFS_BIT(8)

+ /*
+ * This quirks needs to be enabled if host controller cannot be
+ * enabled via HCE register.
+ */
+ #define UFSHCI_QUIRK_BROKEN_HCE UFS_BIT(9)
unsigned int quirks; /* Deviations from standard UFSHCI spec. */

wait_queue_head_t tm_wq;
--
1.7.10.4

2015-11-09 05:30:00

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 07/11] scsi: ufs: add specific callback for nexus type

From: Seungwon Jeon <[email protected]>

Some host controller needs nexus type information for handling
command. This change adds specific callback function to support
vendor's implementation.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 3 +++
drivers/scsi/ufs/ufshcd.h | 19 +++++++++++++++++++
2 files changed, 22 insertions(+)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index e8b96ec65987..eeb7835c52ab 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -1378,6 +1378,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)

/* issue command to the controller */
spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_vops_specify_nexus_t_xfer_req(hba, tag, lrbp);
ufshcd_send_command(hba, tag);
out_unlock:
spin_unlock_irqrestore(hba->host->host_lock, flags);
@@ -1578,6 +1579,7 @@ static int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
hba->dev_cmd.complete = &wait;

spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_vops_specify_nexus_t_xfer_req(hba, tag, lrbp);
ufshcd_send_command(hba, tag);
spin_unlock_irqrestore(hba->host->host_lock, flags);

@@ -3842,6 +3844,7 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,
task_req_upiup->input_param2 = cpu_to_be32(task_id);

/* send command to the controller */
+ ufshcd_vops_specify_nexus_t_tm_req(hba, free_slot, tm_function);
__set_bit(free_slot, &hba->outstanding_tasks);
ufshcd_writel(hba, 1 << free_slot, REG_UTP_TASK_REQ_DOOR_BELL);

diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 500e137bf68a..b3dd08420100 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -259,6 +259,9 @@ struct ufs_pwr_mode_info {
* @pwr_change_notify: called before and after a power mode change
* is carried out to allow vendor spesific capabilities
* to be set.
+ * @specify_nexus_t_xfer_req:
+ * @specify_nexus_t_tm_req: called before command is issued to allow vendor
+ * specific handling to be set for nexus type.
* @suspend: called during host controller PM callback
* @resume: called during host controller PM callback
* @dbg_register_dump: used to dump controller debug information
@@ -280,6 +283,9 @@ struct ufs_hba_variant_ops {
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *,
struct ufs_pa_layer_attr *);
+ void (*specify_nexus_t_xfer_req)(struct ufs_hba *,
+ int, struct scsi_cmnd *);
+ void (*specify_nexus_t_tm_req)(struct ufs_hba *, int, u8);
int (*suspend)(struct ufs_hba *, enum ufs_pm_op);
int (*resume)(struct ufs_hba *, enum ufs_pm_op);
void (*dbg_register_dump)(struct ufs_hba *hba);
@@ -811,4 +817,17 @@ static inline void ufshcd_vops_dbg_register_dump(struct ufs_hba *hba)
hba->vops->dbg_register_dump(hba);
}

+static inline void ufshcd_vops_specify_nexus_t_xfer_req(struct ufs_hba *hba,
+ int tag, struct ufshcd_lrb *lrbp)
+{
+ if (hba->vops && hba->vops->specify_nexus_t_xfer_req)
+ hba->vops->specify_nexus_t_xfer_req(hba, tag, lrbp->cmd);
+}
+
+static inline void ufshcd_vops_specify_nexus_t_tm_req(struct ufs_hba *hba,
+ int free_slot, u8 tm_function)
+{
+ if (hba->vops && hba->vops->specify_nexus_t_tm_req)
+ hba->vops->specify_nexus_t_tm_req(hba, free_slot, tm_function);
+}
#endif /* End of Header */
--
1.7.10.4

2015-11-09 05:31:21

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 08/11] scsi: ufs: add add specific callback for hibern8

From: Seungwon Jeon <[email protected]>

Some host controller needs specific handling before/after
(un)hibernation, This change adds specific callback function
to support vendor's implementation.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 33 +++++++++++++++++++++++++++++----
drivers/scsi/ufs/ufshcd.h | 10 ++++++++++
2 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index eeb7835c52ab..075b7bf13080 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -181,8 +181,7 @@ static int ufshcd_probe_hba(struct ufs_hba *hba);
static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on,
bool skip_ref_clk);
static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on);
-static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba);
-static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba);
+static int ufshcd_uic_hibern8_ctrl(struct ufs_hba *hba, bool en);
static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
@@ -215,6 +214,16 @@ static inline void ufshcd_disable_irq(struct ufs_hba *hba)
}
}

+static inline int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
+{
+ return ufshcd_uic_hibern8_ctrl(hba, true);
+}
+
+static inline int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
+{
+ return ufshcd_uic_hibern8_ctrl(hba, false);
+}
+
/*
* ufshcd_wait_for_register - wait for register value to change
* @hba - per-adapter interface
@@ -2394,7 +2403,7 @@ out:
return ret;
}

-static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
+static int __ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
{
struct uic_command uic_cmd = {0};

@@ -2403,7 +2412,7 @@ static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
return ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
}

-static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
+static int __ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
{
struct uic_command uic_cmd = {0};
int ret;
@@ -2418,6 +2427,22 @@ static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
return ret;
}

+static int ufshcd_uic_hibern8_ctrl(struct ufs_hba *hba, bool en)
+{
+ int ret;
+
+ ufshcd_vops_hibern8_notify(hba, en, PRE_CHANGE);
+ ret = en ? __ufshcd_uic_hibern8_enter(hba) :
+ __ufshcd_uic_hibern8_exit(hba);
+ if (ret)
+ goto out;
+
+ ufshcd_vops_hibern8_notify(hba, en, POST_CHANGE);
+
+out:
+ return ret;
+}
+
/**
* ufshcd_init_pwr_info - setting the POR (power on reset)
* values in hba power info
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index b3dd08420100..9c69dd2f3672 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -262,6 +262,8 @@ struct ufs_pwr_mode_info {
* @specify_nexus_t_xfer_req:
* @specify_nexus_t_tm_req: called before command is issued to allow vendor
* specific handling to be set for nexus type.
+ * @hibern8_notify: called before and after hibernate/unhibernate is carried out
+ * to allow vendor spesific implementation.
* @suspend: called during host controller PM callback
* @resume: called during host controller PM callback
* @dbg_register_dump: used to dump controller debug information
@@ -283,6 +285,7 @@ struct ufs_hba_variant_ops {
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *,
struct ufs_pa_layer_attr *);
+ void (*hibern8_notify)(struct ufs_hba *, bool, bool);
void (*specify_nexus_t_xfer_req)(struct ufs_hba *,
int, struct scsi_cmnd *);
void (*specify_nexus_t_tm_req)(struct ufs_hba *, int, u8);
@@ -830,4 +833,11 @@ static inline void ufshcd_vops_specify_nexus_t_tm_req(struct ufs_hba *hba,
if (hba->vops && hba->vops->specify_nexus_t_tm_req)
hba->vops->specify_nexus_t_tm_req(hba, free_slot, tm_function);
}
+
+static inline void ufshcd_vops_hibern8_notify(struct ufs_hba *hba,
+ bool en, enum ufs_notify_change_status status)
+{
+ if (hba->vops && hba->vops->hibern8_notify)
+ hba->vops->hibern8_notify(hba, en, status);
+}
#endif /* End of Header */
--
1.7.10.4

2015-11-09 05:30:51

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 09/11] scsi: ufs: make ufshcd_config_pwr_mode of non-static func

From: Seungwon Jeon <[email protected]>

This makes ufshcd_config_pwr_mode non-static so that other vendors
like exynos can use the same.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/ufshcd.c | 5 ++---
drivers/scsi/ufs/ufshcd.h | 2 ++
2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 075b7bf13080..358d9114a1a5 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -185,8 +185,6 @@ static int ufshcd_uic_hibern8_ctrl(struct ufs_hba *hba, bool en);
static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
-static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *desired_pwr_mode);
static int ufshcd_change_power_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *pwr_mode);

@@ -2592,7 +2590,7 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
* @hba: per-adapter instance
* @desired_pwr_mode: desired power configuration
*/
-static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
+int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired_pwr_mode)
{
struct ufs_pa_layer_attr final_params = { 0 };
@@ -2608,6 +2606,7 @@ static int ufshcd_config_pwr_mode(struct ufs_hba *hba,

return ret;
}
+EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);

/**
* ufshcd_complete_dev_init() - checks device readiness
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 9c69dd2f3672..8cad52c072d4 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -666,6 +666,8 @@ extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
u8 attr_set, u32 mib_val, u8 peer);
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
u32 *mib_val, u8 peer);
+extern int ufshcd_config_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *desired_pwr_mode);

/* UIC command interfaces for DME primitives */
#define DME_LOCAL 0
--
1.7.10.4

2015-11-09 05:31:18

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 10/11] Documentation: devicetree: ufs: Add DT bindings for exynos UFS host controller

From: Seungwon Jeon <[email protected]>

This adds Exynos Universal Flash Storage (UFS) Host Controller DT bindings.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
.../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt

diff --git a/Documentation/devicetree/bindings/ufs/ufs-exynos.txt b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
new file mode 100644
index 000000000000..08e2d1497b1b
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
@@ -0,0 +1,104 @@
+* Exynos Universal Flash Storage (UFS) Host Controller
+
+UFSHC nodes are defined to describe on-chip UFS host controllers.
+Each UFS controller instance should have its own node.
+
+Required properties:
+- compatible : compatible name, contains "samsung,exynos7-ufs"
+- interrupts : <interrupt mapping for UFS host controller IRQ>
+- reg : Should contain HCI, vendor specific, UNIPRO and
+ UFS protector address space
+- reg-names : "hci", "vs_hci", "unipro", "ufsp";
+
+Optional properties:
+- vdd-hba-supply : phandle to UFS host controller supply regulator node
+- vcc-supply : phandle to VCC supply regulator node
+- vccq-supply : phandle to VCCQ supply regulator node
+- vccq2-supply : phandle to VCCQ2 supply regulator node
+- vcc-supply-1p8 : For embedded UFS devices, valid VCC range is 1.7-1.95V
+ or 2.7-3.6V. This boolean property when set, specifies
+ to use low voltage range of 1.7-1.95V. Note for external
+ UFS cards this property is invalid and valid VCC range is
+ always 2.7-3.6V.
+- vcc-max-microamp : specifies max. load that can be drawn from vcc supply
+- vccq-max-microamp : specifies max. load that can be drawn from vccq supply
+- vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply
+- <name>-fixed-regulator : boolean property specifying that <name>-supply is a fixed regulator
+
+- clocks : List of phandle and clock specifier pairs
+- clock-names : List of clock input name strings sorted in the same
+ order as the clocks property.
+ "core", "sclk_unipro_main", "ref" and ref_parent
+
+- freq-table-hz : Array of <min max> operating frequencies stored in the same
+ order as the clocks property. If this property is not
+ defined or a value in the array is "0" then it is assumed
+ that the frequency is set by the parent clock or a
+ fixed rate clock source.
+- pclk-freq-avail-range : specifies available frequency range(min/max) for APB clock
+- ufs,pwr-attr-mode : specifies mode value for power mode change, possible values are
+ "FAST", "SLOW", "FAST_auto" and "SLOW_auto"
+- ufs,pwr-attr-lane : specifies lane count value for power mode change
+ allowed values are 1 or 2
+- ufs,pwr-attr-gear : specifies gear count value for power mode change
+ allowed values are 1 or 2
+- ufs,pwr-attr-hs-series : specifies HS rate series for power mode change
+ can be one of "HS_rate_b" or "HS_rate_a"
+- ufs,pwr-local-l2-timer : specifies array of local UNIPRO L2 timer values
+ 3 timers supported
+ <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
+- ufs,pwr-remote-l2-timer : specifies array of remote UNIPRO L2 timer values
+ 3 timers supported
+ <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
+- ufs-rx-adv-fine-gran-sup_en : specifies support of fine granularity of MPHY,
+ this is a boolean property.
+- ufs-rx-adv-fine-gran-step : specifies granularity steps of MPHY,
+ allowed step size is 0 to 3
+- ufs-rx-adv-min-activate-time-cap : specifies rx advanced minimum activate time of MPHY
+ range is 1 to 9
+- ufs-pa-granularity : specifies Granularity for PA_TActivate and PA_Hibern8Time
+- ufs-pa-tacctivate : specifies time to wake-up remote M-RX
+- ufs-pa-hibern8time : specifies minimum time to wait in HIBERN8 state
+
+Note: If above properties are not defined it can be assumed that the supply
+regulators or clocks are always on.
+
+Example:
+ ufshc@0x15570000 {
+ compatible = "samsung,exynos7-ufs";
+ reg = <0x15570000 0x100>,
+ <0x15570100 0x100>,
+ <0x15571000 0x200>,
+ <0x15572000 0x300>;
+ reg-names = "hci", "vs_hci", "unipro", "ufsp";
+ interrupts = <0 200 0>;
+
+ vdd-hba-supply = <&xxx_reg0>;
+ vdd-hba-fixed-regulator;
+ vcc-supply = <&xxx_reg1>;
+ vcc-supply-1p8;
+ vccq-supply = <&xxx_reg2>;
+ vccq2-supply = <&xxx_reg3>;
+ vcc-max-microamp = 500000;
+ vccq-max-microamp = 200000;
+ vccq2-max-microamp = 200000;
+
+ clocks = <&core 0>, <&ref 0>, <&iface 0>;
+ clock-names = "core", "sclk_unipro_main", "ref", "ref_parent";
+ freq-table-hz = <100000000 200000000>, <0 0>, <0 0>, <0 0>;
+
+ pclk-freq-avail-range = <70000000 133000000>;
+
+ ufs,pwr-attr-mode = "FAST";
+ ufs,pwr-attr-lane = <2>;
+ ufs,pwr-attr-gear = <2>;
+ ufs,pwr-attr-hs-series = "HS_rate_b";
+ ufs,pwr-local-l2-timer = <8000 28000 20000>;
+ ufs,pwr-remote-l2-timer = <12000 32000 16000>;
+ ufs-rx-adv-fine-gran-sup_en = <1>;
+ ufs-rx-adv-fine-gran-step = <3>;
+ ufs-rx-adv-min-activate-time-cap = <9>;
+ ufs-pa-granularity = <6>;
+ ufs-pa-tacctivate = <6>;
+ ufs-pa-hibern8time = <20>;
+ };
--
1.7.10.4

2015-11-09 05:31:12

by Alim Akhtar

[permalink] [raw]
Subject: [PATCH v5 11/11] scsi: ufs-exynos: add UFS host support for Exynos SoCs

From: Seungwon Jeon <[email protected]>

This patch introduces Exynos UFS host controller driver,
which mainly handles vendor-specific operations including
link startup, power mode change and hibernation/unhibernation.

Signed-off-by: Seungwon Jeon <[email protected]>
Signed-off-by: Alim Akhtar <[email protected]>
---
drivers/scsi/ufs/Kconfig | 12 +
drivers/scsi/ufs/Makefile | 1 +
drivers/scsi/ufs/ufs-exynos-hw.c | 131 ++++
drivers/scsi/ufs/ufs-exynos-hw.h | 43 ++
drivers/scsi/ufs/ufs-exynos.c | 1304 ++++++++++++++++++++++++++++++++++++++
drivers/scsi/ufs/ufs-exynos.h | 247 ++++++++
drivers/scsi/ufs/ufshci.h | 26 +-
drivers/scsi/ufs/unipro.h | 47 ++
8 files changed, 1810 insertions(+), 1 deletion(-)
create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.c
create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.h
create mode 100644 drivers/scsi/ufs/ufs-exynos.c
create mode 100644 drivers/scsi/ufs/ufs-exynos.h

diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index 5f4530744e0a..bc602be94458 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -83,3 +83,15 @@ config SCSI_UFS_QCOM

Select this if you have UFS controller on QCOM chipset.
If unsure, say N.
+
+config SCSI_UFS_EXYNOS
+ bool "EXYNOS specific hooks to UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && ARCH_EXYNOS || COMPILE_TEST
+ select PHY_EXYNOS_UFS
+ help
+ This selects the EXYNOS specific additions to UFSHCD platform driver.
+ UFS host on EXYNOS includes HCI and UNIPRO layer, and associates with
+ UFS-PHY driver.
+
+ Select this if you have UFS host controller on EXYNOS chipset.
+ If unsure, say N.
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 8303bcce7a23..2accf1e628b3 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -1,5 +1,6 @@
# UFSHCD makefile
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
+obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o ufs-exynos-hw.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
diff --git a/drivers/scsi/ufs/ufs-exynos-hw.c b/drivers/scsi/ufs/ufs-exynos-hw.c
new file mode 100644
index 000000000000..be6c61541a8f
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos-hw.c
@@ -0,0 +1,131 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "ufshcd.h"
+#include "unipro.h"
+
+#include "ufs-exynos.h"
+#include "ufs-exynos-hw.h"
+
+static int exynos7_ufs_drv_init(struct device *dev, struct exynos_ufs *ufs)
+{
+ struct clk *child, *parent;
+
+ child = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(child)) {
+ dev_err(dev, "failed to get ref_clk clock\n");
+ return -EINVAL;
+ }
+
+ parent = devm_clk_get(dev, "ref_clk_parent");
+ if (IS_ERR(parent)) {
+ dev_err(dev, "failed to get ref_clk_parent clock\n");
+ return -EINVAL;
+ }
+ return clk_set_parent(child, parent);
+}
+
+static int exynos7_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ u32 val = ufs->drv_data->uic_attr->pa_dbg_option_suite;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+ for_each_ufs_tx_lane(ufs, i)
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x297, i), 0x17);
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x362, i), 0xff);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x363, i), 0x00);
+ }
+ exynos_ufs_disable_ov_tm(hba);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE_DYN), 0xf);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE_DYN), 0xf);
+ for_each_ufs_tx_lane(ufs, i)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(TX_HIBERN8_CONTROL, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_TXPHY_CFGUPDT), 0x1);
+ udelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE), val | (1 << 12));
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_RESET_PHY), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_LINE_RESET), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_LINE_RESET_REQ), 0x1);
+ udelay(1600);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE), val);
+
+ return 0;
+}
+
+static int exynos7_ufs_post_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x28b, i), 0x83);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x29a, i), 0x07);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x277, i),
+ TX_LINERESET_N(exynos_ufs_calc_time_cntr(ufs, 200000)));
+ }
+ exynos_ufs_disable_ov_tm(hba);
+
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SAVECONFIGTIME), 0xbb8);
+ exynos_ufs_disable_dbg_mode(hba);
+
+ return 0;
+}
+
+static int exynos7_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct uic_pwr_mode *pwr)
+{
+ unipro_writel(ufs, 0x22, UNIPRO_DBG_FORCE_DME_CTRL_STATE);
+
+ return 0;
+}
+
+static int exynos7_ufs_post_pwr_change(struct exynos_ufs *ufs,
+ struct uic_pwr_mode *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_RXPHY_CFGUPDT), 0x1);
+
+ if (pwr->lane == 1) {
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), 0x1);
+ exynos_ufs_disable_dbg_mode(hba);
+ }
+
+ return 0;
+}
+
+struct exynos_ufs_drv_data exynos_ufs_drvs[] = {
+{
+ .compatible = "samsung,exynos7-ufs",
+ .uic_attr = &exynos7_uic_attr,
+ .quirks = UFSHCI_QUIRK_BYTE_ALIGN_UTRD |
+ UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR |
+ UFSHCI_QUIRK_BROKEN_HCE |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR,
+ .opts = EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL |
+ EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
+ EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX,
+ .drv_init = exynos7_ufs_drv_init,
+ .pre_link = exynos7_ufs_pre_link,
+ .post_link = exynos7_ufs_post_link,
+ .pre_pwr_change = exynos7_ufs_pre_pwr_change,
+ .post_pwr_change = exynos7_ufs_post_pwr_change,
+}, {
+}, };
diff --git a/drivers/scsi/ufs/ufs-exynos-hw.h b/drivers/scsi/ufs/ufs-exynos-hw.h
new file mode 100644
index 000000000000..8473ec976b0c
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos-hw.h
@@ -0,0 +1,43 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _UFS_EXYNOS_HW_H_
+#define _UFS_EXYNOS_HW_H_
+
+#include "ufs-exynos.h"
+#include "unipro.h"
+
+struct exynos_ufs_uic_attr exynos7_uic_attr = {
+ .tx_trailingclks = 0x10,
+ .tx_dif_p_nsec = 3000000, /* unit: ns */
+ .tx_dif_n_nsec = 1000000, /* unit: ns */
+ .tx_high_z_cnt_nsec = 20000, /* unit: ns */
+ .tx_base_unit_nsec = 100000, /* unit: ns */
+ .tx_gran_unit_nsec = 4000, /* unit: ns */
+ .tx_sleep_cnt = 1000, /* unit: ns */
+ .tx_min_activatetime = 0xa,
+ .rx_filler_enable = 0x2,
+ .rx_dif_p_nsec = 1000000, /* unit: ns */
+ .rx_hibern8_wait_nsec = 4000000, /* unit: ns */
+ .rx_base_unit_nsec = 100000, /* unit: ns */
+ .rx_gran_unit_nsec = 4000, /* unit: ns */
+ .rx_sleep_cnt = 1280, /* unit: ns */
+ .rx_stall_cnt = 320, /* unit: ns */
+ .rx_hs_g1_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g2_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g3_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g1_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g2_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g3_prep_sync_len_cap = PREP_LEN(0xf),
+ .pa_dbg_option_suite = 0x30103,
+};
+
+#endif /* _UFS_EXYNOS_HW_H_ */
diff --git a/drivers/scsi/ufs/ufs-exynos.c b/drivers/scsi/ufs/ufs-exynos.c
new file mode 100644
index 000000000000..45cbf0d9b04c
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos.c
@@ -0,0 +1,1304 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy-exynos-ufs.h>
+#include <linux/platform_device.h>
+
+#include "ufshcd.h"
+#include "ufshcd-pltfrm.h"
+#include "ufshci.h"
+#include "unipro.h"
+
+#include "ufs-exynos.h"
+
+/*
+ * Exynos's Vendor specific registers for UFSHCI
+ */
+#define HCI_TXPRDT_ENTRY_SIZE 0x00
+#define PRDT_PREFECT_EN BIT(31)
+#define PRDT_SET_SIZE(x) ((x) & 0x1F)
+#define HCI_RXPRDT_ENTRY_SIZE 0x04
+#define HCI_1US_TO_CNT_VAL 0x0C
+#define CNT_VAL_1US_MASK 0x3FF
+#define HCI_UTRL_NEXUS_TYPE 0x40
+#define HCI_UTMRL_NEXUS_TYPE 0x44
+#define HCI_SW_RST 0x50
+#define UFS_LINK_SW_RST BIT(0)
+#define UFS_UNIPRO_SW_RST BIT(1)
+#define UFS_SW_RST_MASK (UFS_UNIPRO_SW_RST | UFS_LINK_SW_RST)
+#define HCI_DATA_REORDER 0x60
+#define HCI_UNIPRO_APB_CLK_CTRL 0x68
+#define UNIPRO_APB_CLK(v, x) (((v) & ~0xF) | ((x) & 0xF))
+#define HCI_AXIDMA_RWDATA_BURST_LEN 0x6C
+#define HCI_GPIO_OUT 0x70
+#define HCI_ERR_EN_PA_LAYER 0x78
+#define HCI_ERR_EN_DL_LAYER 0x7C
+#define HCI_ERR_EN_N_LAYER 0x80
+#define HCI_ERR_EN_T_LAYER 0x84
+#define HCI_ERR_EN_DME_LAYER 0x88
+#define HCI_CLKSTOP_CTRL 0xB0
+#define REFCLK_STOP BIT(2)
+#define UNIPRO_MCLK_STOP BIT(1)
+#define UNIPRO_PCLK_STOP BIT(0)
+#define CLK_STOP_MASK (REFCLK_STOP |\
+ UNIPRO_MCLK_STOP |\
+ UNIPRO_PCLK_STOP)
+#define HCI_MISC 0xB4
+#define REFCLK_CTRL_EN BIT(7)
+#define UNIPRO_PCLK_CTRL_EN BIT(6)
+#define UNIPRO_MCLK_CTRL_EN BIT(5)
+#define HCI_CORECLK_CTRL_EN BIT(4)
+#define CLK_CTRL_EN_MASK (REFCLK_CTRL_EN |\
+ UNIPRO_PCLK_CTRL_EN |\
+ UNIPRO_MCLK_CTRL_EN)
+/* Device fatal error */
+#define DFES_ERR_EN BIT(31)
+#define DFES_DEF_L2_ERRS (UIC_DATA_LINK_LAYER_ERROR_RX_BUF_OVERFLOW |\
+ UIC_DATA_LINK_LAYER_ERROR_PA_INIT)
+#define DFES_DEF_L3_ERRS (UIC_NETWORK_LAYER_ERROR_UNSUPPORTED_HEADER_TYPE |\
+ UIC_NETWORK_LAYER_ERROR_BAD_DEVICEID_ENC |\
+ UIC_NETWORK_LAYER_ERROR_LHDR_TRAP_PACKET_DROPPING)
+#define DFES_DEF_L4_ERRS (UIC_TRANSPORT_LAYER_ERROR_UNSUPPORTED_HEADER_TYPE |\
+ UIC_TRANSPORT_LAYER_ERROR_UNKNOWN_CPORTID |\
+ UIC_TRANSPORT_LAYER_ERROR_NO_CONNECTION_RX |\
+ UIC_TRANSPORT_LAYER_ERROR_BAD_TC)
+
+enum {
+ UNIPRO_L1_5 = 0,/* PHY Adapter */
+ UNIPRO_L2, /* Data Link */
+ UNIPRO_L3, /* Network */
+ UNIPRO_L4, /* Transport */
+ UNIPRO_DME, /* DME */
+};
+
+/*
+ * UNIPRO registers
+ */
+#define UNIPRO_COMP_VERSION 0x000
+#define UNIPRO_DME_PWR_REQ 0x090
+#define UNIPRO_DME_PWR_REQ_POWERMODE 0x094
+#define UNIPRO_DME_PWR_REQ_LOCALL2TIMER0 0x098
+#define UNIPRO_DME_PWR_REQ_LOCALL2TIMER1 0x09C
+#define UNIPRO_DME_PWR_REQ_LOCALL2TIMER2 0x0A0
+#define UNIPRO_DME_PWR_REQ_REMOTEL2TIMER0 0x0A4
+#define UNIPRO_DME_PWR_REQ_REMOTEL2TIMER1 0x0A8
+#define UNIPRO_DME_PWR_REQ_REMOTEL2TIMER2 0x0AC
+
+/*
+ * UFS Protector registers
+ */
+#define UFSPRSECURITY 0x010
+#define NSSMU BIT(14)
+#define UFSPSBEGIN0 0x200
+#define UFSPSEND0 0x204
+#define UFSPSLUN0 0x208
+#define UFSPSCTRL0 0x20C
+
+static void exynos_ufs_auto_ctrl_hcc(struct exynos_ufs *ufs, bool en);
+static void exynos_ufs_ctrl_clkstop(struct exynos_ufs *ufs, bool en);
+
+static inline void exynos_ufs_enable_auto_ctrl_hcc(struct exynos_ufs *ufs)
+{
+ exynos_ufs_auto_ctrl_hcc(ufs, true);
+}
+
+static inline void exynos_ufs_disable_auto_ctrl_hcc(struct exynos_ufs *ufs)
+{
+ exynos_ufs_auto_ctrl_hcc(ufs, false);
+}
+
+static inline void exynos_ufs_disable_auto_ctrl_hcc_save(
+ struct exynos_ufs *ufs, u32 *val)
+{
+ *val = hci_readl(ufs, HCI_MISC);
+ exynos_ufs_auto_ctrl_hcc(ufs, false);
+}
+
+static inline void exynos_ufs_auto_ctrl_hcc_restore(
+ struct exynos_ufs *ufs, u32 *val)
+{
+ hci_writel(ufs, *val, HCI_MISC);
+}
+
+static inline void exynos_ufs_gate_clks(struct exynos_ufs *ufs)
+{
+ exynos_ufs_ctrl_clkstop(ufs, true);
+}
+
+static inline void exynos_ufs_ungate_clks(struct exynos_ufs *ufs)
+{
+ exynos_ufs_ctrl_clkstop(ufs, false);
+}
+
+/**
+ * exynos_ufs_auto_ctrl_hcc - HCI core clock control by h/w
+ * Control should be disabled in the below cases
+ * - Before host controller S/W reset
+ * - Access to UFS protector's register
+ */
+static void exynos_ufs_auto_ctrl_hcc(struct exynos_ufs *ufs, bool en)
+{
+ u32 misc = hci_readl(ufs, HCI_MISC);
+
+ if (en)
+ hci_writel(ufs, misc | HCI_CORECLK_CTRL_EN, HCI_MISC);
+ else
+ hci_writel(ufs, misc & ~HCI_CORECLK_CTRL_EN, HCI_MISC);
+}
+
+static void exynos_ufs_ctrl_clkstop(struct exynos_ufs *ufs, bool en)
+{
+ u32 ctrl = hci_readl(ufs, HCI_CLKSTOP_CTRL);
+ u32 misc = hci_readl(ufs, HCI_MISC);
+
+ if (en) {
+ hci_writel(ufs, misc | CLK_CTRL_EN_MASK, HCI_MISC);
+ hci_writel(ufs, ctrl | CLK_STOP_MASK, HCI_CLKSTOP_CTRL);
+ } else {
+ hci_writel(ufs, ctrl & ~CLK_STOP_MASK, HCI_CLKSTOP_CTRL);
+ hci_writel(ufs, misc & ~CLK_CTRL_EN_MASK, HCI_MISC);
+ }
+}
+
+static int exynos_ufs_get_clk_info(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct list_head *head = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+ u32 pclk_rate;
+ u32 f_min, f_max;
+ u8 div = 0;
+ int ret = 0;
+
+ if (!head || list_empty(head))
+ goto out;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR(clki->clk)) {
+ if (!strcmp(clki->name, "core_clk"))
+ ufs->clk_hci_core = clki->clk;
+ else if (!strcmp(clki->name, "sclk_unipro_main"))
+ ufs->clk_unipro_main = clki->clk;
+ }
+ }
+
+ if (!ufs->clk_hci_core || !ufs->clk_unipro_main) {
+ dev_err(hba->dev, "failed to get clk info\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ufs->mclk_rate = clk_get_rate(ufs->clk_unipro_main);
+ pclk_rate = clk_get_rate(ufs->clk_hci_core);
+ f_min = ufs->pclk_avail_min;
+ f_max = ufs->pclk_avail_max;
+
+ if (ufs->opts & EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL) {
+ do {
+ pclk_rate /= (div + 1);
+
+ if (pclk_rate <= f_max)
+ break;
+ div++;
+ } while (pclk_rate >= f_min);
+ }
+
+ if (unlikely(pclk_rate < f_min || pclk_rate > f_max)) {
+ dev_err(hba->dev, "not available pclk range %d\n", pclk_rate);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ufs->pclk_rate = pclk_rate;
+ ufs->pclk_div = div;
+
+out:
+ return ret;
+}
+
+static void exynos_ufs_set_unipro_pclk_div(struct exynos_ufs *ufs)
+{
+ if (ufs->opts & EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL) {
+ u32 val;
+
+ val = hci_readl(ufs, HCI_UNIPRO_APB_CLK_CTRL);
+ hci_writel(ufs, UNIPRO_APB_CLK(val, ufs->pclk_div),
+ HCI_UNIPRO_APB_CLK_CTRL);
+ }
+}
+
+static void exynos_ufs_set_pwm_clk_div(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(CMN_PWM_CLK_CTRL), attr->cmn_pwm_clk_ctrl);
+}
+
+static void exynos_ufs_calc_pwm_clk_div(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ const unsigned int div = 30, mult = 20;
+ const unsigned long pwm_min = 3 * 1000 * 1000;
+ const unsigned long pwm_max = 9 * 1000 * 1000;
+ const int divs[] = {32, 16, 8, 4};
+ unsigned long clk = 0, _clk, clk_period;
+ int i = 0, clk_idx = -1;
+
+ clk_period = UNIPRO_PCLK_PERIOD(ufs);
+ for (i = 0; i < ARRAY_SIZE(divs); i++) {
+ _clk = NSEC_PER_SEC * mult / (clk_period * divs[i] * div);
+ if (_clk >= pwm_min && _clk <= pwm_max) {
+ if (_clk > clk) {
+ clk_idx = i;
+ clk = _clk;
+ }
+ }
+ }
+
+ if (clk_idx == -1) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(CMN_PWM_CLK_CTRL), &clk_idx);
+ dev_err(hba->dev,
+ "failed to decide pwm clock divider, will not change\n");
+ }
+
+ attr->cmn_pwm_clk_ctrl = clk_idx & PWM_CLK_CTRL_MASK;
+}
+
+long exynos_ufs_calc_time_cntr(struct exynos_ufs *ufs, long period)
+{
+ const int precise = 10;
+ long pclk_rate = ufs->pclk_rate;
+ long clk_period, fraction;
+
+ clk_period = UNIPRO_PCLK_PERIOD(ufs);
+ fraction = ((NSEC_PER_SEC % pclk_rate) * precise) / pclk_rate;
+
+ return (period * precise) / ((clk_period * precise) + fraction);
+}
+
+static void exynos_ufs_specify_phy_time_attr(struct exynos_ufs *ufs)
+{
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ struct ufs_phy_time_cfg *t_cfg = &ufs->t_cfg;
+
+ t_cfg->tx_linereset_p =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_dif_p_nsec);
+ t_cfg->tx_linereset_n =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_dif_n_nsec);
+ t_cfg->tx_high_z_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_high_z_cnt_nsec);
+ t_cfg->tx_base_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_base_unit_nsec);
+ t_cfg->tx_gran_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_gran_unit_nsec);
+ t_cfg->tx_sleep_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_sleep_cnt);
+
+ t_cfg->rx_linereset =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_dif_p_nsec);
+ t_cfg->rx_hibern8_wait =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_hibern8_wait_nsec);
+ t_cfg->rx_base_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_base_unit_nsec);
+ t_cfg->rx_gran_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_gran_unit_nsec);
+ t_cfg->rx_sleep_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_sleep_cnt);
+ t_cfg->rx_stall_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_stall_cnt);
+}
+
+static void exynos_ufs_config_phy_time_attr(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct ufs_phy_time_cfg *t_cfg = &ufs->t_cfg;
+ int i;
+
+ exynos_ufs_set_pwm_clk_div(ufs);
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_FILLER_ENABLE, i),
+ ufs->drv_data->uic_attr->rx_filler_enable);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_LINERESET_VAL, i),
+ RX_LINERESET(t_cfg->rx_linereset));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_BASE_NVAL_07_00, i),
+ RX_BASE_NVAL_L(t_cfg->rx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_BASE_NVAL_15_08, i),
+ RX_BASE_NVAL_H(t_cfg->rx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_GRAN_NVAL_07_00, i),
+ RX_GRAN_NVAL_L(t_cfg->rx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_GRAN_NVAL_10_08, i),
+ RX_GRAN_NVAL_H(t_cfg->rx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_OV_SLEEP_CNT_TIMER, i),
+ RX_OV_SLEEP_CNT(t_cfg->rx_sleep_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_OV_STALL_CNT_TIMER, i),
+ RX_OV_STALL_CNT(t_cfg->rx_stall_cnt));
+ }
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LINERESET_P_VAL, i),
+ TX_LINERESET_P(t_cfg->tx_linereset_p));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_HIGH_Z_CNT_07_00, i),
+ TX_HIGH_Z_CNT_L(t_cfg->tx_high_z_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_HIGH_Z_CNT_11_08, i),
+ TX_HIGH_Z_CNT_H(t_cfg->tx_high_z_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_BASE_NVAL_07_00, i),
+ TX_BASE_NVAL_L(t_cfg->tx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_BASE_NVAL_15_08, i),
+ TX_BASE_NVAL_H(t_cfg->tx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_GRAN_NVAL_07_00, i),
+ TX_GRAN_NVAL_L(t_cfg->tx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_GRAN_NVAL_10_08, i),
+ TX_GRAN_NVAL_H(t_cfg->tx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_OV_SLEEP_CNT_TIMER, i),
+ TX_OV_H8_ENTER_EN |
+ TX_OV_SLEEP_CNT(t_cfg->tx_sleep_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, i),
+ ufs->drv_data->uic_attr->tx_min_activatetime);
+ }
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+static void exynos_ufs_config_phy_cap_attr(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G1_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g1_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G2_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g2_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G3_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g3_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G1_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g1_prep_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G2_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g2_prep_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G3_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g3_prep_sync_len_cap);
+ }
+
+ if (attr->rx_adv_fine_gran_sup_en == 0) {
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_GRANULARITY_CAP, i), 0);
+
+ if (attr->rx_min_actv_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_MIN_ACTIVATETIME_CAP,
+ i), attr->rx_min_actv_time_cap);
+
+ if (attr->rx_hibern8_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HIBERN8TIME_CAP, i),
+ attr->rx_hibern8_time_cap);
+ }
+ } else if (attr->rx_adv_fine_gran_sup_en == 1) {
+ for_each_ufs_rx_lane(ufs, i) {
+ if (attr->rx_adv_fine_gran_step)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_GRANULARITY_CAP,
+ i), RX_ADV_FINE_GRAN_STEP(
+ attr->rx_adv_fine_gran_step));
+
+ if (attr->rx_adv_min_actv_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(
+ RX_ADV_MIN_ACTIVATETIME_CAP, i),
+ attr->rx_adv_min_actv_time_cap);
+
+ if (attr->rx_adv_hibern8_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_HIBERN8TIME_CAP,
+ i),
+ attr->rx_adv_hibern8_time_cap);
+ }
+ }
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+static void exynos_ufs_establish_connt(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ enum {
+ DEV_ID = 0x00,
+ PEER_DEV_ID = 0x01,
+ PEER_CPORT_ID = 0x00,
+ TRAFFIC_CLASS = 0x00,
+ };
+
+ /* allow cport attributes to be set */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), CPORT_IDLE);
+
+ /* local unipro attributes */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID), DEV_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID_VALID), TRUE);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID), PEER_DEV_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID), PEER_CPORT_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS), CPORT_DEF_FLAGS);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS), TRAFFIC_CLASS);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), CPORT_CONNECTED);
+}
+
+static void exynos_ufs_config_smu(struct exynos_ufs *ufs)
+{
+ u32 reg, val;
+
+ exynos_ufs_disable_auto_ctrl_hcc_save(ufs, &val);
+
+ /* make encryption disabled by default */
+ reg = ufsp_readl(ufs, UFSPRSECURITY);
+ ufsp_writel(ufs, reg | NSSMU, UFSPRSECURITY);
+ ufsp_writel(ufs, 0x0, UFSPSBEGIN0);
+ ufsp_writel(ufs, 0xffffffff, UFSPSEND0);
+ ufsp_writel(ufs, 0xff, UFSPSLUN0);
+ ufsp_writel(ufs, 0xf1, UFSPSCTRL0);
+
+ exynos_ufs_auto_ctrl_hcc_restore(ufs, &val);
+}
+
+static void exynos_ufs_config_sync_pattern_mask(struct exynos_ufs *ufs,
+ struct uic_pwr_mode *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+ u8 g = pwr->gear;
+ u32 mask, sync_len;
+ enum {
+ SYNC_LEN_G1 = 80 * 1000, /* 80us */
+ SYNC_LEN_G2 = 40 * 1000, /* 44us */
+ SYNC_LEN_G3 = 20 * 1000, /* 20us */
+ };
+ int i;
+
+ if (g == 1)
+ sync_len = SYNC_LEN_G1;
+ else if (g == 2)
+ sync_len = SYNC_LEN_G2;
+ else if (g == 3)
+ sync_len = SYNC_LEN_G3;
+ else
+ return;
+
+ mask = exynos_ufs_calc_time_cntr(ufs, sync_len);
+ mask = (mask >> 8) & 0xff;
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_SYNC_MASK_LENGTH, i), mask);
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+static int exynos_ufs_pre_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_max,
+ struct ufs_pa_layer_attr *final)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ struct uic_pwr_mode *pwr_req = &ufs->pwr_req;
+ struct uic_pwr_mode *pwr_act = &ufs->pwr_act;
+
+ final->gear_rx
+ = pwr_act->gear = min_t(u32, pwr_max->gear_rx, pwr_req->gear);
+ final->gear_tx
+ = pwr_act->gear = min_t(u32, pwr_max->gear_tx, pwr_req->gear);
+ final->lane_rx
+ = pwr_act->lane = min_t(u32, pwr_max->lane_rx, pwr_req->lane);
+ final->lane_tx
+ = pwr_act->lane = min_t(u32, pwr_max->lane_tx, pwr_req->lane);
+ final->pwr_rx = pwr_act->mode = pwr_req->mode;
+ final->pwr_tx = pwr_act->mode = pwr_req->mode;
+ final->hs_rate = pwr_act->hs_series = pwr_req->hs_series;
+
+ /* save and configure l2 timer */
+ pwr_act->l_l2_timer[0] = pwr_req->l_l2_timer[0];
+ pwr_act->l_l2_timer[1] = pwr_req->l_l2_timer[1];
+ pwr_act->l_l2_timer[2] = pwr_req->l_l2_timer[2];
+ pwr_act->r_l2_timer[0] = pwr_req->r_l2_timer[0];
+ pwr_act->r_l2_timer[1] = pwr_req->r_l2_timer[1];
+ pwr_act->r_l2_timer[2] = pwr_req->r_l2_timer[2];
+
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(DL_FC0PROTTIMEOUTVAL), pwr_act->l_l2_timer[0]);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(DL_TC0REPLAYTIMEOUTVAL), pwr_act->l_l2_timer[1]);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), pwr_act->l_l2_timer[2]);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(PA_PWRMODEUSERDATA0), pwr_act->r_l2_timer[0]);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(PA_PWRMODEUSERDATA1), pwr_act->r_l2_timer[1]);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(PA_PWRMODEUSERDATA2), pwr_act->r_l2_timer[2]);
+
+ unipro_writel(ufs,
+ pwr_act->l_l2_timer[0], UNIPRO_DME_PWR_REQ_LOCALL2TIMER0);
+ unipro_writel(ufs,
+ pwr_act->l_l2_timer[1], UNIPRO_DME_PWR_REQ_LOCALL2TIMER1);
+ unipro_writel(ufs,
+ pwr_act->l_l2_timer[2], UNIPRO_DME_PWR_REQ_LOCALL2TIMER2);
+ unipro_writel(ufs,
+ pwr_act->r_l2_timer[0], UNIPRO_DME_PWR_REQ_REMOTEL2TIMER0);
+ unipro_writel(ufs,
+ pwr_act->r_l2_timer[1], UNIPRO_DME_PWR_REQ_REMOTEL2TIMER1);
+ unipro_writel(ufs,
+ pwr_act->r_l2_timer[2], UNIPRO_DME_PWR_REQ_REMOTEL2TIMER2);
+
+ if (ufs->drv_data->pre_pwr_change)
+ ufs->drv_data->pre_pwr_change(ufs, pwr_act);
+
+ if (IS_UFS_PWR_MODE_HS(pwr_act->mode)) {
+ exynos_ufs_config_sync_pattern_mask(ufs, pwr_act);
+
+ switch (pwr_act->hs_series) {
+ case PA_HS_MODE_A:
+ case PA_HS_MODE_B:
+ exynos_ufs_phy_calibrate(generic_phy, CFG_PRE_PWR_HS,
+ PWR_MODE_HS(pwr_act->gear, pwr_act->hs_series));
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#define PWR_MODE_STR_LEN 64
+static int exynos_ufs_post_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_max,
+ struct ufs_pa_layer_attr *pwr_req)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ struct uic_pwr_mode *pwr = &ufs->pwr_act;
+ char pwr_str[PWR_MODE_STR_LEN] = "";
+ int ret = 0;
+
+ if (ufs->drv_data->post_pwr_change)
+ ufs->drv_data->post_pwr_change(ufs, pwr);
+
+ if (IS_UFS_PWR_MODE_HS(pwr->mode)) {
+ switch (pwr->hs_series) {
+ case PA_HS_MODE_A:
+ case PA_HS_MODE_B:
+ exynos_ufs_phy_calibrate(generic_phy, CFG_POST_PWR_HS,
+ PWR_MODE_HS(pwr->gear, pwr->hs_series));
+ break;
+ }
+
+ ret = exynos_ufs_phy_wait_for_lock_acq(generic_phy);
+ snprintf(pwr_str, sizeof(pwr_str), "Fast%s series_%s G_%d L_%d",
+ pwr->mode == FASTAUTO_MODE ? "_Auto" : "",
+ pwr->hs_series == PA_HS_MODE_A ? "A" : "B",
+ pwr->gear, pwr->lane);
+ } else if (IS_UFS_PWR_MODE_PWM(pwr->mode)) {
+ snprintf(pwr_str, sizeof(pwr_str), "Slow%s G_%d L_%d",
+ pwr->mode == SLOWAUTO_MODE ? "_Auto" : "",
+ pwr->gear, pwr->lane);
+ }
+
+ dev_info(hba->dev, "Power mode change %d : %s\n", ret, pwr_str);
+ return ret;
+}
+
+static void exynos_ufs_specify_nexus_t_xfer_req(struct ufs_hba *hba,
+ int tag, struct scsi_cmnd *cmd)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ u32 type;
+
+ type = hci_readl(ufs, HCI_UTRL_NEXUS_TYPE);
+
+ if (cmd)
+ hci_writel(ufs, type | (1 << tag), HCI_UTRL_NEXUS_TYPE);
+ else
+ hci_writel(ufs, type & ~(1 << tag), HCI_UTRL_NEXUS_TYPE);
+}
+
+static void exynos_ufs_specify_nexus_t_tm_req(struct ufs_hba *hba,
+ int tag, u8 func)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ u32 type;
+
+ type = hci_readl(ufs, HCI_UTMRL_NEXUS_TYPE);
+
+ switch (func) {
+ case UFS_ABORT_TASK:
+ case UFS_QUERY_TASK:
+ hci_writel(ufs, type | (1 << tag), HCI_UTMRL_NEXUS_TYPE);
+ break;
+ case UFS_ABORT_TASK_SET:
+ case UFS_CLEAR_TASK_SET:
+ case UFS_LOGICAL_RESET:
+ case UFS_QUERY_TASK_SET:
+ hci_writel(ufs, type & ~(1 << tag), HCI_UTMRL_NEXUS_TYPE);
+ break;
+ }
+}
+
+static void exynos_ufs_phy_init(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct phy *generic_phy = ufs->phy;
+
+ if (ufs->avail_ln_rx == 0 || ufs->avail_ln_tx == 0) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES),
+ &ufs->avail_ln_rx);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES),
+ &ufs->avail_ln_tx);
+ WARN(ufs->avail_ln_rx != ufs->avail_ln_tx,
+ "available data lane is not equal(rx:%d, tx:%d)\n",
+ ufs->avail_ln_rx, ufs->avail_ln_tx);
+ }
+
+ exynos_ufs_phy_set_lane_cnt(generic_phy, ufs->avail_ln_rx);
+ exynos_ufs_phy_calibrate(generic_phy, CFG_PRE_INIT, PWR_MODE_ANY);
+
+}
+
+static void exynos_ufs_config_unipro(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_CLK_PERIOD),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTRAILINGCLOCKS),
+ ufs->drv_data->uic_attr->tx_trailingclks);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE),
+ ufs->drv_data->uic_attr->pa_dbg_option_suite);
+}
+
+static void exynos_ufs_config_intr(struct exynos_ufs *ufs, u32 errs, u8 index)
+{
+ switch (index) {
+ case UNIPRO_L1_5:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_PA_LAYER);
+ break;
+ case UNIPRO_L2:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_DL_LAYER);
+ break;
+ case UNIPRO_L3:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_N_LAYER);
+ break;
+ case UNIPRO_L4:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_T_LAYER);
+ break;
+ case UNIPRO_DME:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_DME_LAYER);
+ break;
+ }
+}
+
+static int exynos_ufs_pre_link(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ /* hci */
+ exynos_ufs_config_intr(ufs, DFES_DEF_L2_ERRS, UNIPRO_L2);
+ exynos_ufs_config_intr(ufs, DFES_DEF_L3_ERRS, UNIPRO_L3);
+ exynos_ufs_config_intr(ufs, DFES_DEF_L4_ERRS, UNIPRO_L4);
+ exynos_ufs_set_unipro_pclk_div(ufs);
+
+ /* unipro */
+ exynos_ufs_config_unipro(ufs);
+
+ /* m-phy */
+ exynos_ufs_phy_init(ufs);
+ exynos_ufs_config_phy_time_attr(ufs);
+ exynos_ufs_config_phy_cap_attr(ufs);
+
+ if (ufs->drv_data->pre_link)
+ ufs->drv_data->pre_link(ufs);
+
+ return 0;
+}
+
+static void exynos_ufs_fit_aggr_timeout(struct exynos_ufs *ufs)
+{
+ const u8 cntr_div = 40;
+ u32 val;
+
+ val = exynos_ufs_calc_time_cntr(ufs, IATOVAL_NSEC / cntr_div);
+ hci_writel(ufs, val & CNT_VAL_1US_MASK, HCI_1US_TO_CNT_VAL);
+}
+
+static int exynos_ufs_post_link(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+
+ exynos_ufs_establish_connt(ufs);
+ exynos_ufs_fit_aggr_timeout(ufs);
+
+ hci_writel(ufs, 0xa, HCI_DATA_REORDER);
+ hci_writel(ufs, PRDT_PREFECT_EN | PRDT_SET_SIZE(12),
+ HCI_TXPRDT_ENTRY_SIZE);
+ hci_writel(ufs, PRDT_SET_SIZE(12), HCI_RXPRDT_ENTRY_SIZE);
+ hci_writel(ufs, (1 << hba->nutrs) - 1, HCI_UTRL_NEXUS_TYPE);
+ hci_writel(ufs, (1 << hba->nutmrs) - 1, HCI_UTMRL_NEXUS_TYPE);
+ hci_writel(ufs, 0xf, HCI_AXIDMA_RWDATA_BURST_LEN);
+
+ if (ufs->opts & EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(T_DBG_SKIP_INIT_HIBERN8_EXIT), TRUE);
+
+ if (attr->pa_granularity) {
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ attr->pa_granularity);
+ exynos_ufs_disable_dbg_mode(hba);
+
+ if (attr->pa_tactivate)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ attr->pa_tactivate);
+ if (attr->pa_hibern8time &&
+ !(ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER))
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME),
+ attr->pa_hibern8time);
+ }
+
+ if (ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER) {
+ if (!attr->pa_granularity)
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ &attr->pa_granularity);
+ if (!attr->pa_hibern8time)
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_HIBERN8TIME),
+ &attr->pa_hibern8time);
+ /*
+ * not wait for HIBERN8 time to exit hibernation
+ */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), 0);
+
+ if (attr->pa_granularity < 1 || attr->pa_granularity > 6) {
+ /* Valid range for granularity: 1 ~ 6 */
+ dev_warn(hba->dev,
+ "%s: pa_granularty %d is invalid, assuming backwards compatibility\n",
+ __func__,
+ attr->pa_granularity);
+ attr->pa_granularity = 6;
+ }
+ }
+
+ exynos_ufs_phy_calibrate(generic_phy, CFG_POST_INIT, PWR_MODE_ANY);
+
+ if (ufs->drv_data->post_link)
+ ufs->drv_data->post_link(ufs);
+
+ return 0;
+}
+
+static void exynos_ufs_specify_pwr_mode(struct device_node *np,
+ struct exynos_ufs *ufs)
+{
+ struct uic_pwr_mode *pwr = &ufs->pwr_req;
+ const char *str = NULL;
+
+ if (!of_property_read_string(np, "ufs,pwr-attr-mode", &str)) {
+ if (!strncmp(str, "FAST", sizeof("FAST")))
+ pwr->mode = FAST_MODE;
+ else if (!strncmp(str, "SLOW", sizeof("SLOW")))
+ pwr->mode = SLOW_MODE;
+ else if (!strncmp(str, "FAST_auto", sizeof("FAST_auto")))
+ pwr->mode = FASTAUTO_MODE;
+ else if (!strncmp(str, "SLOW_auto", sizeof("SLOW_auto")))
+ pwr->mode = SLOWAUTO_MODE;
+ else
+ pwr->mode = FAST_MODE;
+ } else {
+ pwr->mode = FAST_MODE;
+ }
+
+ if (of_property_read_u32(np, "ufs,pwr-attr-lane", &pwr->lane))
+ pwr->lane = 1;
+
+ if (of_property_read_u32(np, "ufs,pwr-attr-gear", &pwr->gear))
+ pwr->gear = 1;
+
+ if (IS_UFS_PWR_MODE_HS(pwr->mode)) {
+ if (!of_property_read_string(np,
+ "ufs,pwr-attr-hs-series", &str)) {
+ if (!strncmp(str, "HS_rate_b", sizeof("HS_rate_b")))
+ pwr->hs_series = PA_HS_MODE_B;
+ else if (!strncmp(str, "HS_rate_a",
+ sizeof("HS_rate_a")))
+ pwr->hs_series = PA_HS_MODE_A;
+ else
+ pwr->hs_series = PA_HS_MODE_A;
+ } else {
+ pwr->hs_series = PA_HS_MODE_A;
+ }
+ }
+
+ if (of_property_read_u32_array(
+ np, "ufs,pwr-local-l2-timer", pwr->l_l2_timer, 3)) {
+ pwr->l_l2_timer[0] = FC0PROTTIMEOUTVAL;
+ pwr->l_l2_timer[1] = TC0REPLAYTIMEOUTVAL;
+ pwr->l_l2_timer[2] = AFC0REQTIMEOUTVAL;
+ }
+
+ if (of_property_read_u32_array(
+ np, "ufs,pwr-remote-l2-timer", pwr->r_l2_timer, 3)) {
+ pwr->r_l2_timer[0] = FC0PROTTIMEOUTVAL;
+ pwr->r_l2_timer[1] = TC0REPLAYTIMEOUTVAL;
+ pwr->r_l2_timer[2] = AFC0REQTIMEOUTVAL;
+ }
+}
+
+static int exynos_ufs_parse_dt(struct device *dev, struct exynos_ufs *ufs)
+{
+ struct device_node *np = dev->of_node;
+ struct exynos_ufs_drv_data *drv_data = exynos_ufs_drvs;
+ struct exynos_ufs_uic_attr *attr;
+ u32 freq[2];
+ int ret;
+
+ while (drv_data->compatible) {
+ if (of_device_is_compatible(np, drv_data->compatible)) {
+ ufs->drv_data = drv_data;
+ break;
+ }
+ drv_data++;
+ }
+
+ if (ufs->drv_data && ufs->drv_data->uic_attr) {
+ attr = ufs->drv_data->uic_attr;
+ } else {
+ dev_err(dev, "failed to get uic attributes\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = of_property_read_u32_array(np,
+ "pclk-freq-avail-range", freq, ARRAY_SIZE(freq));
+ if (!ret) {
+ ufs->pclk_avail_min = freq[0];
+ ufs->pclk_avail_max = freq[1];
+ } else {
+ dev_err(dev, "failed to get available pclk range\n");
+ goto out;
+ }
+
+ exynos_ufs_specify_pwr_mode(np, ufs);
+
+ if (!of_property_read_u32(np, "ufs-rx-adv-fine-gran-sup_en",
+ &attr->rx_adv_fine_gran_sup_en)) {
+ if (attr->rx_adv_fine_gran_sup_en == 0) {
+ /* 100us step */
+ if (of_property_read_u32(np,
+ "ufs-rx-min-activate-time-cap",
+ &attr->rx_min_actv_time_cap))
+ dev_warn(dev,
+ "ufs-rx-min-activate-time-cap is empty\n");
+
+ if (of_property_read_u32(np,
+ "ufs-rx-hibern8-time-cap",
+ &attr->rx_hibern8_time_cap))
+ dev_warn(dev,
+ "ufs-rx-hibern8-time-cap is empty\n");
+ } else if (attr->rx_adv_fine_gran_sup_en == 1) {
+ /* fine granularity step */
+ if (of_property_read_u32(np,
+ "ufs-rx-adv-fine-gran-step",
+ &attr->rx_adv_fine_gran_step))
+ dev_warn(dev,
+ "ufs-rx-adv-fine-gran-step is empty\n");
+
+ if (of_property_read_u32(np,
+ "ufs-rx-adv-min-activate-time-cap",
+ &attr->rx_adv_min_actv_time_cap))
+ dev_warn(dev,
+ "ufs-rx-adv-min-activate-time-cap is empty\n");
+
+ if (of_property_read_u32(np,
+ "ufs-rx-adv-hibern8-time-cap",
+ &attr->rx_adv_hibern8_time_cap))
+ dev_warn(dev,
+ "ufs-rx-adv-hibern8-time-cap is empty\n");
+ } else {
+ dev_warn(dev,
+ "not supported val for ufs-rx-adv-fine-gran-sup_en %d\n",
+ attr->rx_adv_fine_gran_sup_en);
+ }
+ } else {
+ attr->rx_adv_fine_gran_sup_en = 0xf;
+ }
+
+ if (!of_property_read_u32(np,
+ "ufs-pa-granularity", &attr->pa_granularity)) {
+ if (of_property_read_u32(np,
+ "ufs-pa-tacctivate", &attr->pa_tactivate))
+ dev_warn(dev, "ufs-pa-tacctivate is empty\n");
+
+ if (of_property_read_u32(np,
+ "ufs-pa-hibern8time", &attr->pa_hibern8time))
+ dev_warn(dev, "ufs-pa-hibern8time is empty\n");
+ }
+
+out:
+ return ret;
+}
+
+static int exynos_ufs_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_ufs *ufs;
+ struct resource *res;
+ int ret;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ /* exynos-specific hci */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vs_hci");
+ ufs->reg_hci = devm_ioremap_resource(dev, res);
+ if (!ufs->reg_hci) {
+ dev_err(dev, "cannot ioremap for hci vendor register\n");
+ return -ENOMEM;
+ }
+
+ /* unipro */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "unipro");
+ ufs->reg_unipro = devm_ioremap_resource(dev, res);
+ if (!ufs->reg_unipro) {
+ dev_err(dev, "cannot ioremap for unipro register\n");
+ return -ENOMEM;
+ }
+
+ /* ufs protector */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ufsp");
+ ufs->reg_ufsp = devm_ioremap_resource(dev, res);
+ if (!ufs->reg_ufsp) {
+ dev_err(dev, "cannot ioremap for ufs protector register\n");
+ return -ENOMEM;
+ }
+
+ ret = exynos_ufs_parse_dt(dev, ufs);
+ if (ret) {
+ dev_err(dev, "failed to get dt info.\n");
+ goto out;
+ }
+
+ ufs->phy = devm_phy_get(dev, "ufs-phy");
+ if (IS_ERR(ufs->phy)) {
+ ret = PTR_ERR(ufs->phy);
+ dev_err(dev, "failed to get ufs-phy\n");
+ goto out;
+ }
+
+ phy_init(ufs->phy);
+ ret = phy_power_on(ufs->phy);
+ if (ret)
+ goto phy_exit;
+
+ ufs->hba = hba;
+ ufs->opts = ufs->drv_data->opts |
+ EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB |
+ EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER;
+ ufs->rx_sel_idx = PA_MAXDATALANES;
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX)
+ ufs->rx_sel_idx = 0;
+ hba->priv = (void *)ufs;
+ hba->quirks = ufs->drv_data->quirks;
+ if (ufs->drv_data->drv_init) {
+ ret = ufs->drv_data->drv_init(dev, ufs);
+ if (ret) {
+ dev_err(dev, "failed to init drv-data\n");
+ goto phy_off;
+ }
+ }
+
+ ret = exynos_ufs_get_clk_info(ufs);
+ if (ret)
+ goto phy_off;
+ exynos_ufs_specify_phy_time_attr(ufs);
+ exynos_ufs_config_smu(ufs);
+ return 0;
+
+phy_off:
+ phy_power_off(ufs->phy);
+phy_exit:
+ phy_exit(ufs->phy);
+ hba->priv = NULL;
+out:
+ return ret;
+}
+
+static int exynos_ufs_host_reset(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ unsigned long timeout = jiffies + msecs_to_jiffies(1);
+ u32 val;
+ int ret = 0;
+
+ exynos_ufs_disable_auto_ctrl_hcc_save(ufs, &val);
+
+ hci_writel(ufs, UFS_SW_RST_MASK, HCI_SW_RST);
+
+ do {
+ if (!(hci_readl(ufs, HCI_SW_RST) & UFS_SW_RST_MASK))
+ goto out;
+ } while (time_before(jiffies, timeout));
+
+ dev_err(hba->dev, "timeout host sw-reset\n");
+ ret = -ETIMEDOUT;
+
+out:
+ exynos_ufs_auto_ctrl_hcc_restore(ufs, &val);
+ return ret;
+}
+
+static void exynos_ufs_dev_hw_reset(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ hci_writel(ufs, 0 << 0, HCI_GPIO_OUT);
+ udelay(5);
+ hci_writel(ufs, 1 << 0, HCI_GPIO_OUT);
+}
+
+static void exynos_ufs_pre_hibern8(struct ufs_hba *hba, u8 enter)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+
+ if (!enter) {
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_disable_auto_ctrl_hcc(ufs);
+ exynos_ufs_ungate_clks(ufs);
+
+ if (ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER) {
+ const unsigned int granularity_tbl[] = {
+ 1, 4, 8, 16, 32, 100
+ };
+ int h8_time = attr->pa_hibern8time *
+ granularity_tbl[attr->pa_granularity - 1];
+ unsigned long us;
+ s64 delta;
+
+ do {
+ delta = h8_time - ktime_us_delta(ktime_get(),
+ ufs->entry_hibern8_t);
+ if (delta <= 0)
+ break;
+
+ us = min_t(s64, delta, USEC_PER_MSEC);
+ if (us >= 10)
+ usleep_range(us, us + 10);
+ } while (1);
+ }
+ }
+}
+
+static void exynos_ufs_post_hibern8(struct ufs_hba *hba, u8 enter)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (!enter) {
+ struct uic_pwr_mode *pwr = &ufs->pwr_act;
+ u32 mode = 0;
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PWRMODE), &mode);
+ if (mode != (pwr->mode << 4 | pwr->mode)) {
+ dev_warn(hba->dev, "%s: power mode change\n", __func__);
+ hba->pwr_info.pwr_rx = (mode >> 4) & 0xf;
+ hba->pwr_info.pwr_tx = mode & 0xf;
+ ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info);
+ }
+
+ if (!(ufs->opts & EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB))
+ exynos_ufs_establish_connt(ufs);
+ } else {
+ ufs->entry_hibern8_t = ktime_get();
+ exynos_ufs_gate_clks(ufs);
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_enable_auto_ctrl_hcc(ufs);
+ }
+}
+
+static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ ret = exynos_ufs_host_reset(hba);
+ if (ret)
+ return ret;
+ exynos_ufs_dev_hw_reset(hba);
+ break;
+ case POST_CHANGE:
+ exynos_ufs_calc_pwm_clk_div(ufs);
+ if (!(ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL))
+ exynos_ufs_enable_auto_ctrl_hcc(ufs);
+ break;
+ }
+
+ return ret;
+}
+
+static int exynos_ufs_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ ret = exynos_ufs_pre_link(hba);
+ break;
+ case POST_CHANGE:
+ ret = exynos_ufs_post_link(hba);
+ break;
+ }
+
+ return ret;
+}
+
+static int exynos_ufs_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_max,
+ struct ufs_pa_layer_attr *pwr_req)
+{
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ ret = exynos_ufs_pre_pwr_mode(hba, pwr_max, pwr_req);
+ break;
+ case POST_CHANGE:
+ ret = exynos_ufs_post_pwr_mode(hba, NULL, pwr_req);
+ break;
+ }
+
+ return ret;
+}
+
+static void exynos_ufs_hibern8_notify(struct ufs_hba *hba,
+ bool enter, bool notify)
+{
+ switch ((u8)notify) {
+ case PRE_CHANGE:
+ exynos_ufs_pre_hibern8(hba, enter);
+ break;
+ case POST_CHANGE:
+ exynos_ufs_post_hibern8(hba, enter);
+ break;
+ }
+}
+
+static int exynos_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (!ufshcd_is_link_active(hba))
+ phy_power_off(ufs->phy);
+
+ return 0;
+}
+
+static int exynos_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (!ufshcd_is_link_active(hba))
+ phy_power_on(ufs->phy);
+
+ exynos_ufs_config_smu(ufs);
+
+ return 0;
+}
+
+static struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
+ .name = "exynos_ufs",
+ .init = exynos_ufs_init,
+ .hce_enable_notify = exynos_ufs_hce_enable_notify,
+ .link_startup_notify = exynos_ufs_link_startup_notify,
+ .pwr_change_notify = exynos_ufs_pwr_change_notify,
+ .specify_nexus_t_xfer_req = exynos_ufs_specify_nexus_t_xfer_req,
+ .specify_nexus_t_tm_req = exynos_ufs_specify_nexus_t_tm_req,
+ .hibern8_notify = exynos_ufs_hibern8_notify,
+ .suspend = exynos_ufs_suspend,
+ .resume = exynos_ufs_resume,
+};
+
+static int exynos_ufs_probe(struct platform_device *pdev)
+{
+ int err;
+ struct device *dev = &pdev->dev;
+
+ err = ufshcd_pltfrm_init(pdev, &ufs_hba_exynos_ops);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+static int exynos_ufs_remove(struct platform_device *pdev)
+{
+ struct ufs_hba *hba = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&(pdev)->dev);
+ ufshcd_remove(hba);
+ return 0;
+}
+
+static const struct of_device_id exynos_ufs_of_match[] = {
+ { .compatible = "samsung,exynos7-ufs"},
+ {},
+};
+
+static const struct dev_pm_ops exynos_ufs_pm_ops = {
+ .suspend = ufshcd_pltfrm_suspend,
+ .resume = ufshcd_pltfrm_resume,
+ .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
+ .runtime_resume = ufshcd_pltfrm_runtime_resume,
+ .runtime_idle = ufshcd_pltfrm_runtime_idle,
+};
+
+static struct platform_driver exynos_ufs_pltform = {
+ .probe = exynos_ufs_probe,
+ .remove = exynos_ufs_remove,
+ .shutdown = ufshcd_pltfrm_shutdown,
+ .driver = {
+ .name = "exynos-ufshc",
+ .pm = &exynos_ufs_pm_ops,
+ .of_match_table = of_match_ptr(exynos_ufs_of_match),
+ },
+};
+module_platform_driver(exynos_ufs_pltform);
diff --git a/drivers/scsi/ufs/ufs-exynos.h b/drivers/scsi/ufs/ufs-exynos.h
new file mode 100644
index 000000000000..2b1a8b344cd9
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos.h
@@ -0,0 +1,247 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _UFS_EXYNOS_H_
+#define _UFS_EXYNOS_H_
+
+/*
+ * UNIPRO registers
+ */
+#define UNIPRO_DBG_FORCE_DME_CTRL_STATE 0x150
+
+/*
+ * MIBs for PA debug registers
+ */
+#define PA_DBG_CLK_PERIOD 0x9514
+#define PA_DBG_TXPHY_CFGUPDT 0x9518
+#define PA_DBG_RXPHY_CFGUPDT 0x9519
+#define PA_DBG_MODE 0x9529
+#define PA_DBG_SKIP_RESET_PHY 0x9539
+#define PA_DBG_OV_TM 0x9540
+#define PA_DBG_SKIP_LINE_RESET 0x9541
+#define PA_DBG_LINE_RESET_REQ 0x9543
+#define PA_DBG_OPTION_SUITE 0x9564
+#define PA_DBG_OPTION_SUITE_DYN 0x9565
+
+/*
+ * MIBs for Transport Layer debug registers
+ */
+#define T_DBG_SKIP_INIT_HIBERN8_EXIT 0xc001
+
+/*
+ * Exynos MPHY attributes
+ */
+#define TX_LINERESET_N_VAL 0x0277
+#define TX_LINERESET_N(v) (((v) >> 10) & 0xFF)
+#define TX_LINERESET_P_VAL 0x027D
+#define TX_LINERESET_P(v) (((v) >> 12) & 0xFF)
+#define TX_OV_SLEEP_CNT_TIMER 0x028E
+#define TX_OV_H8_ENTER_EN (1 << 7)
+#define TX_OV_SLEEP_CNT(v) (((v) >> 5) & 0x7F)
+#define TX_HIGH_Z_CNT_11_08 0x028C
+#define TX_HIGH_Z_CNT_H(v) (((v) >> 8) & 0xF)
+#define TX_HIGH_Z_CNT_07_00 0x028D
+#define TX_HIGH_Z_CNT_L(v) ((v) & 0xFF)
+#define TX_BASE_NVAL_07_00 0x0293
+#define TX_BASE_NVAL_L(v) ((v) & 0xFF)
+#define TX_BASE_NVAL_15_08 0x0294
+#define TX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
+#define TX_GRAN_NVAL_07_00 0x0295
+#define TX_GRAN_NVAL_L(v) ((v) & 0xFF)
+#define TX_GRAN_NVAL_10_08 0x0296
+#define TX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
+
+#define RX_FILLER_ENABLE 0x0316
+#define RX_FILLER_EN (1 << 1)
+#define RX_LINERESET_VAL 0x0317
+#define RX_LINERESET(v) (((v) >> 12) & 0xFF)
+#define RX_LCC_IGNORE 0x0318
+#define RX_SYNC_MASK_LENGTH 0x0321
+#define RX_HIBERN8_WAIT_VAL_BIT_20_16 0x0331
+#define RX_HIBERN8_WAIT_VAL_BIT_15_08 0x0332
+#define RX_HIBERN8_WAIT_VAL_BIT_07_00 0x0333
+#define RX_OV_SLEEP_CNT_TIMER 0x0340
+#define RX_OV_SLEEP_CNT(v) (((v) >> 6) & 0x1F)
+#define RX_OV_STALL_CNT_TIMER 0x0341
+#define RX_OV_STALL_CNT(v) (((v) >> 4) & 0xFF)
+#define RX_BASE_NVAL_07_00 0x0355
+#define RX_BASE_NVAL_L(v) ((v) & 0xFF)
+#define RX_BASE_NVAL_15_08 0x0354
+#define RX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
+#define RX_GRAN_NVAL_07_00 0x0353
+#define RX_GRAN_NVAL_L(v) ((v) & 0xFF)
+#define RX_GRAN_NVAL_10_08 0x0352
+#define RX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
+
+#define CMN_PWM_CLK_CTRL 0x0402
+#define PWM_CLK_CTRL_MASK 0x3
+
+#define IATOVAL_NSEC 20000 /* unit: ns */
+#define UNIPRO_PCLK_PERIOD(ufs) (NSEC_PER_SEC / ufs->pclk_rate)
+
+struct exynos_ufs;
+
+struct uic_pwr_mode {
+ u32 lane;
+ u32 gear;
+ u8 mode;
+ u8 hs_series;
+ u32 l_l2_timer[3]; /* local */
+ u32 r_l2_timer[3]; /* remote */
+};
+
+struct exynos_ufs_uic_attr {
+ /* TX Attributes */
+ unsigned int tx_trailingclks;
+ unsigned int tx_dif_p_nsec;
+ unsigned int tx_dif_n_nsec;
+ unsigned int tx_high_z_cnt_nsec;
+ unsigned int tx_base_unit_nsec;
+ unsigned int tx_gran_unit_nsec;
+ unsigned int tx_sleep_cnt;
+ unsigned int tx_min_activatetime;
+ /* RX Attributes */
+ unsigned int rx_filler_enable;
+ unsigned int rx_dif_p_nsec;
+ unsigned int rx_hibern8_wait_nsec;
+ unsigned int rx_base_unit_nsec;
+ unsigned int rx_gran_unit_nsec;
+ unsigned int rx_sleep_cnt;
+ unsigned int rx_stall_cnt;
+ unsigned int rx_hs_g1_sync_len_cap;
+ unsigned int rx_hs_g2_sync_len_cap;
+ unsigned int rx_hs_g3_sync_len_cap;
+ unsigned int rx_hs_g1_prep_sync_len_cap;
+ unsigned int rx_hs_g2_prep_sync_len_cap;
+ unsigned int rx_hs_g3_prep_sync_len_cap;
+ /* Common Attributes */
+ unsigned int cmn_pwm_clk_ctrl;
+ /* Internal Attributes */
+ unsigned int pa_dbg_option_suite;
+ /* Changeable Attributes */
+ unsigned int rx_adv_fine_gran_sup_en;
+ unsigned int rx_adv_fine_gran_step;
+ unsigned int rx_min_actv_time_cap;
+ unsigned int rx_hibern8_time_cap;
+ unsigned int rx_adv_min_actv_time_cap;
+ unsigned int rx_adv_hibern8_time_cap;
+ unsigned int pa_granularity;
+ unsigned int pa_tactivate;
+ unsigned int pa_hibern8time;
+};
+
+struct exynos_ufs_drv_data {
+ char *compatible;
+ struct exynos_ufs_uic_attr *uic_attr;
+ unsigned int quirks;
+ unsigned int opts;
+ /* SoC's specific operations */
+ int (*drv_init)(struct device *dev, struct exynos_ufs *ufs);
+ int (*pre_link)(struct exynos_ufs *ufs);
+ int (*post_link)(struct exynos_ufs *ufs);
+ int (*pre_pwr_change)(struct exynos_ufs *ufs, struct uic_pwr_mode *pwr);
+ int (*post_pwr_change)(struct exynos_ufs *ufs,
+ struct uic_pwr_mode *pwr);
+};
+
+struct ufs_phy_time_cfg {
+ u32 tx_linereset_p;
+ u32 tx_linereset_n;
+ u32 tx_high_z_cnt;
+ u32 tx_base_n_val;
+ u32 tx_gran_n_val;
+ u32 tx_sleep_cnt;
+ u32 rx_linereset;
+ u32 rx_hibern8_wait;
+ u32 rx_base_n_val;
+ u32 rx_gran_n_val;
+ u32 rx_sleep_cnt;
+ u32 rx_stall_cnt;
+};
+
+struct exynos_ufs {
+ struct ufs_hba *hba;
+ struct phy *phy;
+ void __iomem *reg_hci;
+ void __iomem *reg_unipro;
+ void __iomem *reg_ufsp;
+ struct clk *clk_hci_core;
+ struct clk *clk_unipro_main;
+ struct clk *clk_apb;
+ u32 pclk_rate;
+ u32 pclk_div;
+ u32 pclk_avail_min;
+ u32 pclk_avail_max;
+ u32 mclk_rate;
+ int avail_ln_rx;
+ int avail_ln_tx;
+ int rx_sel_idx;
+ struct uic_pwr_mode pwr_req; /* requested power mode */
+ struct uic_pwr_mode pwr_act; /* actual power mode */
+ struct ufs_phy_time_cfg t_cfg;
+ ktime_t entry_hibern8_t;
+ struct exynos_ufs_drv_data *drv_data;
+
+ u32 opts;
+#define EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL BIT(0)
+#define EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB BIT(1)
+#define EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL BIT(2)
+#define EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX BIT(3)
+#define EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER BIT(4)
+};
+
+#define for_each_ufs_rx_lane(ufs, i) \
+ for (i = (ufs)->rx_sel_idx; \
+ i < (ufs)->rx_sel_idx + (ufs)->avail_ln_rx; i++)
+#define for_each_ufs_tx_lane(ufs, i) \
+ for (i = 0; i < (ufs)->avail_ln_tx; i++)
+
+#define EXYNOS_UFS_MMIO_FUNC(name) \
+static inline void name##_writel(struct exynos_ufs *ufs, u32 val, u32 reg)\
+{ \
+ writel(val, ufs->reg_##name + reg); \
+} \
+ \
+static inline u32 name##_readl(struct exynos_ufs *ufs, u32 reg) \
+{ \
+ return readl(ufs->reg_##name + reg); \
+}
+
+EXYNOS_UFS_MMIO_FUNC(hci);
+EXYNOS_UFS_MMIO_FUNC(unipro);
+EXYNOS_UFS_MMIO_FUNC(ufsp);
+#undef EXYNOS_UFS_MMIO_FUNC
+
+extern long exynos_ufs_calc_time_cntr(struct exynos_ufs *, long);
+
+static inline void exynos_ufs_enable_ov_tm(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), TRUE);
+}
+
+static inline void exynos_ufs_disable_ov_tm(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), FALSE);
+}
+
+static inline void exynos_ufs_enable_dbg_mode(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), TRUE);
+}
+
+static inline void exynos_ufs_disable_dbg_mode(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), FALSE);
+}
+
+extern struct exynos_ufs_drv_data exynos_ufs_drvs[];
+
+#endif /* _UFS_EXYNOS_H_ */
diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h
index 0ae0967aaed8..1c734000d395 100644
--- a/drivers/scsi/ufs/ufshci.h
+++ b/drivers/scsi/ufs/ufshci.h
@@ -169,15 +169,39 @@ enum {
/* UECDL - Host UIC Error Code Data Link Layer 3Ch */
#define UIC_DATA_LINK_LAYER_ERROR UFS_BIT(31)
#define UIC_DATA_LINK_LAYER_ERROR_CODE_MASK 0x7FFF
-#define UIC_DATA_LINK_LAYER_ERROR_PA_INIT 0x2000
+#define UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED UFS_BIT(0)
+#define UIC_DATA_LINK_LAYER_ERROR_TCX_REP_TIMER_EXP UFS_BIT(1)
+#define UIC_DATA_LINK_LAYER_ERROR_AFCX_REQ_TIMER_EXP UFS_BIT(2)
+#define UIC_DATA_LINK_LAYER_ERROR_FCX_PRO_TIMER_EXP UFS_BIT(3)
+#define UIC_DATA_LINK_LAYER_ERROR_CRC UFS_BIT(4)
+#define UIC_DATA_LINK_LAYER_ERROR_RX_BUF_OVERFLOW UFS_BIT(5)
+#define UIC_DATA_LINK_LAYER_ERROR_MASK_FRAME_LEN_EXCD UFS_BIT(6)
+#define UIC_DATA_LINK_LAYER_ERROR_WRONG_SEQ_NUM UFS_BIT(7)
+#define UIC_DATA_LINK_LAYER_ERROR_AFC_FRAME_SYNTAX UFS_BIT(8)
+#define UIC_DATA_LINK_LAYER_ERROR_NAC_FRAME_SYNTAX UFS_BIT(9)
+#define UIC_DATA_LINK_LAYER_ERROR_EOF_SYNTAX UFS_BIT(10)
+#define UIC_DATA_LINK_LAYER_ERROR_FRAME_SYNTAX UFS_BIT(11)
+#define UIC_DATA_LINK_LAYER_ERROR_BAD_CTRL_SYMB_TYPE UFS_BIT(12)
+#define UIC_DATA_LINK_LAYER_ERROR_PA_INIT UFS_BIT(13)
+#define UIC_DATA_LINK_LAYER_ERROR_PA_ERROR_IND_RECV UFS_BIT(14)

/* UECN - Host UIC Error Code Network Layer 40h */
#define UIC_NETWORK_LAYER_ERROR UFS_BIT(31)
#define UIC_NETWORK_LAYER_ERROR_CODE_MASK 0x7
+#define UIC_NETWORK_LAYER_ERROR_UNSUPPORTED_HEADER_TYPE UFS_BIT(0)
+#define UIC_NETWORK_LAYER_ERROR_BAD_DEVICEID_ENC UFS_BIT(1)
+#define UIC_NETWORK_LAYER_ERROR_LHDR_TRAP_PACKET_DROPPING UFS_BIT(2)

/* UECT - Host UIC Error Code Transport Layer 44h */
#define UIC_TRANSPORT_LAYER_ERROR UFS_BIT(31)
#define UIC_TRANSPORT_LAYER_ERROR_CODE_MASK 0x7F
+#define UIC_TRANSPORT_LAYER_ERROR_UNSUPPORTED_HEADER_TYPE UFS_BIT(0)
+#define UIC_TRANSPORT_LAYER_ERROR_UNKNOWN_CPORTID UFS_BIT(1)
+#define UIC_TRANSPORT_LAYER_ERROR_NO_CONNECTION_RX UFS_BIT(2)
+#define UIC_TRANSPORT_LAYER_ERROR_CONTROLLED_SEGMENT_DROPPING UFS_BIT(3)
+#define UIC_TRANSPORT_LAYER_ERROR_BAD_TC UFS_BIT(4)
+#define UIC_TRANSPORT_LAYER_ERROR_E2E_CREDIT_OVERFLOW UFS_BIT(5)
+#define UIC_TRANSPORT_LAYER_ERROR_SAFETY_VALUE_DROPPING UFS_BIT(6)

/* UECDME - Host UIC Error Code DME 48h */
#define UIC_DME_ERROR UFS_BIT(31)
diff --git a/drivers/scsi/ufs/unipro.h b/drivers/scsi/ufs/unipro.h
index 816a8a46efb8..68cfece1a67a 100644
--- a/drivers/scsi/ufs/unipro.h
+++ b/drivers/scsi/ufs/unipro.h
@@ -49,7 +49,27 @@
#define RX_BYPASS_8B10B_ENABLE 0x00A8
#define RX_TERMINATION_FORCE_ENABLE 0x0089

+/*
+ * M-RX Capability Attributes
+ */
+#define RX_HS_G1_SYNC_LENGTH_CAP 0x008B
+#define RX_HS_G1_PREP_LENGTH_CAP 0x008C
+#define RX_HS_G2_SYNC_LENGTH_CAP 0x0094
+#define RX_HS_G3_SYNC_LENGTH_CAP 0x0095
+#define RX_HS_G2_PREP_LENGTH_CAP 0x0096
+#define RX_HS_G3_PREP_LENGTH_CAP 0x0097
+#define RX_ADV_GRANULARITY_CAP 0x0098
+#define RX_MIN_ACTIVATETIME_CAP 0x008F
+#define RX_HIBERN8TIME_CAP 0x0092
+#define RX_ADV_HIBERN8TIME_CAP 0x0099
+#define RX_ADV_MIN_ACTIVATETIME_CAP 0x009A
+
#define is_mphy_tx_attr(attr) (attr < RX_MODE)
+#define RX_ADV_FINE_GRAN_STEP(x) ((((x) & 0x3) << 1) | 0x1)
+#define SYNC_LEN_FINE(x) ((x) & 0x3F)
+#define SYNC_LEN_COARSE(x) ((1 << 6) | ((x) & 0x3F))
+#define PREP_LEN(x) ((x) & 0xF)
+
/*
* PHY Adpater attributes
*/
@@ -87,6 +107,7 @@
#define PA_PACPREQEOBTIMEOUT 0x1591
#define PA_HIBERN8TIME 0x15A7
#define PA_LOCALVERINFO 0x15A9
+#define PA_GRANULARITY 0x15AA
#define PA_TACTIVATE 0x15A8
#define PA_PACPFRAMECOUNT 0x15C0
#define PA_PACPERRORCOUNT 0x15C1
@@ -110,6 +131,9 @@
#define PA_STALLNOCONFIGTIME 0x15A3
#define PA_SAVECONFIGTIME 0x15A4

+/* PHY Adapter protocal constants */
+#define PA_MAXDATALANES 4
+
/* PA power modes */
enum {
FAST_MODE = 1,
@@ -119,6 +143,9 @@ enum {
UNCHANGED = 7,
};

+#define IS_UFS_PWR_MODE_HS(m) (((m) == FAST_MODE) || ((m) == FASTAUTO_MODE))
+#define IS_UFS_PWR_MODE_PWM(m) (((m) == SLOW_MODE) || ((m) == SLOWAUTO_MODE))
+
/* PA TX/RX Frequency Series */
enum {
PA_HS_MODE_A = 1,
@@ -170,6 +197,11 @@ enum ufs_hs_gear_tag {
#define DL_PEERTC1PRESENT 0x2066
#define DL_PEERTC1RXINITCREVAL 0x2067

+/* Default value of L2 Timer */
+#define FC0PROTTIMEOUTVAL 8191
+#define TC0REPLAYTIMEOUTVAL 65535
+#define AFC0REQTIMEOUTVAL 32767
+
/*
* Network Layer Attributes
*/
@@ -212,4 +244,19 @@ enum {
TRUE,
};

+/* CPort setting */
+#define E2EFC_ON (1 << 0)
+#define E2EFC_OFF (0 << 0)
+#define CSD_N_ON (0 << 1)
+#define CSD_N_OFF (1 << 1)
+#define CSV_N_ON (0 << 2)
+#define CSV_N_OFF (1 << 2)
+#define CPORT_DEF_FLAGS (CSV_N_OFF | CSD_N_OFF | E2EFC_OFF)
+
+/* CPort connection state */
+enum {
+ CPORT_IDLE = 0,
+ CPORT_CONNECTED,
+};
+
#endif /* _UNIPRO_H_ */
--
1.7.10.4

2015-11-09 16:23:21

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v5 10/11] Documentation: devicetree: ufs: Add DT bindings for exynos UFS host controller

On Mon, Nov 09, 2015 at 10:56:26AM +0530, Alim Akhtar wrote:
> From: Seungwon Jeon <[email protected]>
>
> This adds Exynos Universal Flash Storage (UFS) Host Controller DT bindings.
>
> Signed-off-by: Seungwon Jeon <[email protected]>
> Signed-off-by: Alim Akhtar <[email protected]>
> ---
> .../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++++++++++++++++++++
> 1 file changed, 104 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt
>
> diff --git a/Documentation/devicetree/bindings/ufs/ufs-exynos.txt b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
> new file mode 100644
> index 000000000000..08e2d1497b1b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
> @@ -0,0 +1,104 @@
> +* Exynos Universal Flash Storage (UFS) Host Controller
> +
> +UFSHC nodes are defined to describe on-chip UFS host controllers.
> +Each UFS controller instance should have its own node.
> +
> +Required properties:
> +- compatible : compatible name, contains "samsung,exynos7-ufs"
> +- interrupts : <interrupt mapping for UFS host controller IRQ>
> +- reg : Should contain HCI, vendor specific, UNIPRO and
> + UFS protector address space
> +- reg-names : "hci", "vs_hci", "unipro", "ufsp";

No phy for MPHY?

> +
> +Optional properties:
> +- vdd-hba-supply : phandle to UFS host controller supply regulator node
> +- vcc-supply : phandle to VCC supply regulator node
> +- vccq-supply : phandle to VCCQ supply regulator node
> +- vccq2-supply : phandle to VCCQ2 supply regulator node
> +- vcc-supply-1p8 : For embedded UFS devices, valid VCC range is 1.7-1.95V
> + or 2.7-3.6V. This boolean property when set, specifies
> + to use low voltage range of 1.7-1.95V. Note for external
> + UFS cards this property is invalid and valid VCC range is
> + always 2.7-3.6V.
> +- vcc-max-microamp : specifies max. load that can be drawn from vcc supply
> +- vccq-max-microamp : specifies max. load that can be drawn from vccq supply
> +- vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply

Some of these are supplies to the flash chip, so you should make
these common properties (in a common doc).

> +- <name>-fixed-regulator : boolean property specifying that <name>-supply is a fixed regulator

This should be determined from the regulator.

> +
> +- clocks : List of phandle and clock specifier pairs
> +- clock-names : List of clock input name strings sorted in the same
> + order as the clocks property.
> + "core", "sclk_unipro_main", "ref" and ref_parent
> +
> +- freq-table-hz : Array of <min max> operating frequencies stored in the same
> + order as the clocks property. If this property is not
> + defined or a value in the array is "0" then it is assumed
> + that the frequency is set by the parent clock or a
> + fixed rate clock source.
> +- pclk-freq-avail-range : specifies available frequency range(min/max) for APB clock
> +- ufs,pwr-attr-mode : specifies mode value for power mode change, possible values are
> + "FAST", "SLOW", "FAST_auto" and "SLOW_auto"

ufs is not a vendor. Use a '-' rather than ','.

> +- ufs,pwr-attr-lane : specifies lane count value for power mode change
> + allowed values are 1 or 2
> +- ufs,pwr-attr-gear : specifies gear count value for power mode change
> + allowed values are 1 or 2
> +- ufs,pwr-attr-hs-series : specifies HS rate series for power mode change
> + can be one of "HS_rate_b" or "HS_rate_a"
> +- ufs,pwr-local-l2-timer : specifies array of local UNIPRO L2 timer values
> + 3 timers supported
> + <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
> +- ufs,pwr-remote-l2-timer : specifies array of remote UNIPRO L2 timer values
> + 3 timers supported
> + <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
> +- ufs-rx-adv-fine-gran-sup_en : specifies support of fine granularity of MPHY,
> + this is a boolean property.
> +- ufs-rx-adv-fine-gran-step : specifies granularity steps of MPHY,
> + allowed step size is 0 to 3
> +- ufs-rx-adv-min-activate-time-cap : specifies rx advanced minimum activate time of MPHY
> + range is 1 to 9
> +- ufs-pa-granularity : specifies Granularity for PA_TActivate and PA_Hibern8Time
> +- ufs-pa-tacctivate : specifies time to wake-up remote M-RX
> +- ufs-pa-hibern8time : specifies minimum time to wait in HIBERN8 state

These are all M-PHY properties?

> +
> +Note: If above properties are not defined it can be assumed that the supply
> +regulators or clocks are always on.
> +
> +Example:
> + ufshc@0x15570000 {
> + compatible = "samsung,exynos7-ufs";
> + reg = <0x15570000 0x100>,
> + <0x15570100 0x100>,
> + <0x15571000 0x200>,
> + <0x15572000 0x300>;
> + reg-names = "hci", "vs_hci", "unipro", "ufsp";
> + interrupts = <0 200 0>;
> +
> + vdd-hba-supply = <&xxx_reg0>;
> + vdd-hba-fixed-regulator;
> + vcc-supply = <&xxx_reg1>;
> + vcc-supply-1p8;
> + vccq-supply = <&xxx_reg2>;
> + vccq2-supply = <&xxx_reg3>;
> + vcc-max-microamp = 500000;
> + vccq-max-microamp = 200000;
> + vccq2-max-microamp = 200000;
> +
> + clocks = <&core 0>, <&ref 0>, <&iface 0>;
> + clock-names = "core", "sclk_unipro_main", "ref", "ref_parent";
> + freq-table-hz = <100000000 200000000>, <0 0>, <0 0>, <0 0>;
> +
> + pclk-freq-avail-range = <70000000 133000000>;
> +
> + ufs,pwr-attr-mode = "FAST";
> + ufs,pwr-attr-lane = <2>;
> + ufs,pwr-attr-gear = <2>;
> + ufs,pwr-attr-hs-series = "HS_rate_b";
> + ufs,pwr-local-l2-timer = <8000 28000 20000>;
> + ufs,pwr-remote-l2-timer = <12000 32000 16000>;
> + ufs-rx-adv-fine-gran-sup_en = <1>;
> + ufs-rx-adv-fine-gran-step = <3>;
> + ufs-rx-adv-min-activate-time-cap = <9>;
> + ufs-pa-granularity = <6>;
> + ufs-pa-tacctivate = <6>;
> + ufs-pa-hibern8time = <20>;
> + };
> --
> 1.7.10.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2015-11-10 08:35:01

by Alim Akhtar

[permalink] [raw]
Subject: Re: [PATCH v5 10/11] Documentation: devicetree: ufs: Add DT bindings for exynos UFS host controller

Hi Rob,

On 11/09/2015 09:53 PM, Rob Herring wrote:
> On Mon, Nov 09, 2015 at 10:56:26AM +0530, Alim Akhtar wrote:
>> From: Seungwon Jeon <[email protected]>
>>
>> This adds Exynos Universal Flash Storage (UFS) Host Controller DT bindings.
>>
>> Signed-off-by: Seungwon Jeon <[email protected]>
>> Signed-off-by: Alim Akhtar <[email protected]>
>> ---
>> .../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++++++++++++++++++++
>> 1 file changed, 104 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt
>>
>> diff --git a/Documentation/devicetree/bindings/ufs/ufs-exynos.txt b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
>> new file mode 100644
>> index 000000000000..08e2d1497b1b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/ufs/ufs-exynos.txt
>> @@ -0,0 +1,104 @@
>> +* Exynos Universal Flash Storage (UFS) Host Controller
>> +
>> +UFSHC nodes are defined to describe on-chip UFS host controllers.
>> +Each UFS controller instance should have its own node.
>> +
>> +Required properties:
>> +- compatible : compatible name, contains "samsung,exynos7-ufs"
>> +- interrupts : <interrupt mapping for UFS host controller IRQ>
>> +- reg : Should contain HCI, vendor specific, UNIPRO and
>> + UFS protector address space
>> +- reg-names : "hci", "vs_hci", "unipro", "ufsp";
>
> No phy for MPHY?
>
ufs-phy is documented, see 01/11 of this series.
>> +
>> +Optional properties:
>> +- vdd-hba-supply : phandle to UFS host controller supply regulator node
>> +- vcc-supply : phandle to VCC supply regulator node
>> +- vccq-supply : phandle to VCCQ supply regulator node
>> +- vccq2-supply : phandle to VCCQ2 supply regulator node
>> +- vcc-supply-1p8 : For embedded UFS devices, valid VCC range is 1.7-1.95V
>> + or 2.7-3.6V. This boolean property when set, specifies
>> + to use low voltage range of 1.7-1.95V. Note for external
>> + UFS cards this property is invalid and valid VCC range is
>> + always 2.7-3.6V.
>> +- vcc-max-microamp : specifies max. load that can be drawn from vcc supply
>> +- vccq-max-microamp : specifies max. load that can be drawn from vccq supply
>> +- vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply
>
> Some of these are supplies to the flash chip, so you should make
> these common properties (in a common doc).
>
Ah, yes these are duplicated, already documented in common ufshcd file
Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt
What is the recommended way, do I need to include it here as well? or
just mentioning that "same as documented in ufshcd-pltfrm.txt"?

>> +- <name>-fixed-regulator : boolean property specifying that <name>-supply is a fixed regulator
>
> This should be determined from the regulator.
>
Same as above already documented as part of ufshcd-pltfrm binding.
>> +
>> +- clocks : List of phandle and clock specifier pairs
>> +- clock-names : List of clock input name strings sorted in the same
>> + order as the clocks property.
>> + "core", "sclk_unipro_main", "ref" and ref_parent
>> +
>> +- freq-table-hz : Array of <min max> operating frequencies stored in the same
>> + order as the clocks property. If this property is not
>> + defined or a value in the array is "0" then it is assumed
>> + that the frequency is set by the parent clock or a
>> + fixed rate clock source.
>> +- pclk-freq-avail-range : specifies available frequency range(min/max) for APB clock
>> +- ufs,pwr-attr-mode : specifies mode value for power mode change, possible values are
>> + "FAST", "SLOW", "FAST_auto" and "SLOW_auto"
>
> ufs is not a vendor. Use a '-' rather than ','.
>
Ok will change.
>> +- ufs,pwr-attr-lane : specifies lane count value for power mode change
>> + allowed values are 1 or 2
>> +- ufs,pwr-attr-gear : specifies gear count value for power mode change
>> + allowed values are 1 or 2
>> +- ufs,pwr-attr-hs-series : specifies HS rate series for power mode change
>> + can be one of "HS_rate_b" or "HS_rate_a"
>> +- ufs,pwr-local-l2-timer : specifies array of local UNIPRO L2 timer values
>> + 3 timers supported
>> + <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
>> +- ufs,pwr-remote-l2-timer : specifies array of remote UNIPRO L2 timer values
>> + 3 timers supported
>> + <FC0ProtectionTimeOutVal,TC0ReplayTimeOutVal, AFC0ReqTimeOutVal>
>> +- ufs-rx-adv-fine-gran-sup_en : specifies support of fine granularity of MPHY,
>> + this is a boolean property.
>> +- ufs-rx-adv-fine-gran-step : specifies granularity steps of MPHY,
>> + allowed step size is 0 to 3
>> +- ufs-rx-adv-min-activate-time-cap : specifies rx advanced minimum activate time of MPHY
>> + range is 1 to 9
>> +- ufs-pa-granularity : specifies Granularity for PA_TActivate and PA_Hibern8Time
>> +- ufs-pa-tacctivate : specifies time to wake-up remote M-RX
>> +- ufs-pa-hibern8time : specifies minimum time to wait in HIBERN8 state
>
> These are all M-PHY properties?
>
These are UIC (ufs interconnect) attributes related to M-PHY and Unipro
blocks.
>> +
>> +Note: If above properties are not defined it can be assumed that the supply
>> +regulators or clocks are always on.
>> +
>> +Example:
>> + ufshc@0x15570000 {
>> + compatible = "samsung,exynos7-ufs";
>> + reg = <0x15570000 0x100>,
>> + <0x15570100 0x100>,
>> + <0x15571000 0x200>,
>> + <0x15572000 0x300>;
>> + reg-names = "hci", "vs_hci", "unipro", "ufsp";
>> + interrupts = <0 200 0>;
>> +
>> + vdd-hba-supply = <&xxx_reg0>;
>> + vdd-hba-fixed-regulator;
>> + vcc-supply = <&xxx_reg1>;
>> + vcc-supply-1p8;
>> + vccq-supply = <&xxx_reg2>;
>> + vccq2-supply = <&xxx_reg3>;
>> + vcc-max-microamp = 500000;
>> + vccq-max-microamp = 200000;
>> + vccq2-max-microamp = 200000;
>> +
>> + clocks = <&core 0>, <&ref 0>, <&iface 0>;
>> + clock-names = "core", "sclk_unipro_main", "ref", "ref_parent";
>> + freq-table-hz = <100000000 200000000>, <0 0>, <0 0>, <0 0>;
>> +
>> + pclk-freq-avail-range = <70000000 133000000>;
>> +
>> + ufs,pwr-attr-mode = "FAST";
>> + ufs,pwr-attr-lane = <2>;
>> + ufs,pwr-attr-gear = <2>;
>> + ufs,pwr-attr-hs-series = "HS_rate_b";
>> + ufs,pwr-local-l2-timer = <8000 28000 20000>;
>> + ufs,pwr-remote-l2-timer = <12000 32000 16000>;
>> + ufs-rx-adv-fine-gran-sup_en = <1>;
>> + ufs-rx-adv-fine-gran-step = <3>;
>> + ufs-rx-adv-min-activate-time-cap = <9>;
>> + ufs-pa-granularity = <6>;
>> + ufs-pa-tacctivate = <6>;
>> + ufs-pa-hibern8time = <20>;
>> + };
>> --
>> 1.7.10.4
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe devicetree" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>

2015-11-16 01:01:53

by Alim Akhtar

[permalink] [raw]
Subject: Re: [PATCH v5 00/11] exynos-ufs: add support for Exynos

Hi Kishon,

Any more concern on the PHY part of this series?

Thanks!

On Mon, Nov 9, 2015 at 10:56 AM, Alim Akhtar <[email protected]> wrote:
> This patch-set introduces UFS (Universal Flash Storage) host support
> for Samsung Exynos SoC. Mostly, it consists of UFS PHY and host specific driver.
> And it also contains some quirks handling for Exynos.
>
> NOTE: ** This series has a dependency on [4]. **
>
> -Changes since v4:
> * Removed platform specific PHY ops as suggested by Kishon
> * Rebased on the top of Yaniv Gardi's work [4]
> * make use of newly introduce ufshcd_{get,set}_variant
> * other small changes and improvements.
> * rebased on the top of linux next-20151109
>
> -Changes since v3:
> * Fixed compilation warrings as reported by "Kbuild Test Robot"[5].
> * Restructure the driver to make it as a platform driver, rebased on top of [4].
> * Addressed review comments from Arnd Bergmann[5].
> * Other misc changes and improvements.
>
> -Changes since v2:
> * Addressed review comments from Kishon[1] and Rob Herring [2]
> * Splited ufs dt binding documetation from ufs driver patch
>
> -Changes since v1:
> * Addressed review comments from Alexey[3] and various review comments from Amit.
> * Updated email id of Seungwon as his samsung id is void now.
> * Added ufs platform data
>
> [1]-> https://lkml.org/lkml/2015/9/18/29
> [2]-> https://lkml.org/lkml/2015/9/21/668
> [3]-> https://lkml.org/lkml/2015/8/23/124
> [4]-> https://lkml.org/lkml/2015/10/28/271
> [5]-> https://lkml.org/lkml/2015/10/1/402
>
> This patch set is tested on exynos7-espresso board.
>
>
> Alim Akhtar (1):
> Documentation: samsung-phy: Add dt bindings for UFS
>
> Seungwon Jeon (10):
> phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC
> scsi: ufs: add quirk to contain unconformable utrd field
> scsi: ufs: add quirk to fix mishandling utrlclr/utmrlclr
> scsi: ufs: add quirk not to allow reset of interrupt aggregation
> scsi: ufs: add quirk to enable host controller without hce
> scsi: ufs: add specific callback for nexus type
> scsi: ufs: add add specific callback for hibern8
> scsi: ufs: make ufshcd_config_pwr_mode of non-static func
> Documentation: devicetree: ufs: Add DT bindings for exynos UFS host
> controller
> scsi: ufs-exynos: add UFS host support for Exynos SoCs
>
> .../devicetree/bindings/phy/samsung-phy.txt | 22 +
> .../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++
> drivers/phy/Kconfig | 7 +
> drivers/phy/Makefile | 1 +
> drivers/phy/phy-exynos-ufs.c | 241 ++++
> drivers/phy/phy-exynos-ufs.h | 85 ++
> drivers/phy/phy-exynos7-ufs.h | 89 ++
> drivers/scsi/ufs/Kconfig | 12 +
> drivers/scsi/ufs/Makefile | 1 +
> drivers/scsi/ufs/ufs-exynos-hw.c | 131 ++
> drivers/scsi/ufs/ufs-exynos-hw.h | 43 +
> drivers/scsi/ufs/ufs-exynos.c | 1304 ++++++++++++++++++++
> drivers/scsi/ufs/ufs-exynos.h | 247 ++++
> drivers/scsi/ufs/ufshcd.c | 168 ++-
> drivers/scsi/ufs/ufshcd.h | 54 +
> drivers/scsi/ufs/ufshci.h | 26 +-
> drivers/scsi/ufs/unipro.h | 47 +
> include/linux/phy/phy-exynos-ufs.h | 85 ++
> 18 files changed, 2647 insertions(+), 20 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt
> create mode 100644 drivers/phy/phy-exynos-ufs.c
> create mode 100644 drivers/phy/phy-exynos-ufs.h
> create mode 100644 drivers/phy/phy-exynos7-ufs.h
> create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.c
> create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.h
> create mode 100644 drivers/scsi/ufs/ufs-exynos.c
> create mode 100644 drivers/scsi/ufs/ufs-exynos.h
> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>
> --
> 1.7.10.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html



--
Regards,
Alim

2015-11-17 05:31:57

by Kishon Vijay Abraham I

[permalink] [raw]
Subject: Re: [PATCH v5 00/11] exynos-ufs: add support for Exynos

Hi Alim,

On Monday 16 November 2015 06:31 AM, Alim Akhtar wrote:
> Hi Kishon,
>
> Any more concern on the PHY part of this series?

Sorry for the late reply. Yes, I still have concerns. I'll comment on your patch.

Thanks
Kishon
>
> Thanks!
>
> On Mon, Nov 9, 2015 at 10:56 AM, Alim Akhtar <[email protected]> wrote:
>> This patch-set introduces UFS (Universal Flash Storage) host support
>> for Samsung Exynos SoC. Mostly, it consists of UFS PHY and host specific driver.
>> And it also contains some quirks handling for Exynos.
>>
>> NOTE: ** This series has a dependency on [4]. **
>>
>> -Changes since v4:
>> * Removed platform specific PHY ops as suggested by Kishon
>> * Rebased on the top of Yaniv Gardi's work [4]
>> * make use of newly introduce ufshcd_{get,set}_variant
>> * other small changes and improvements.
>> * rebased on the top of linux next-20151109
>>
>> -Changes since v3:
>> * Fixed compilation warrings as reported by "Kbuild Test Robot"[5].
>> * Restructure the driver to make it as a platform driver, rebased on top of [4].
>> * Addressed review comments from Arnd Bergmann[5].
>> * Other misc changes and improvements.
>>
>> -Changes since v2:
>> * Addressed review comments from Kishon[1] and Rob Herring [2]
>> * Splited ufs dt binding documetation from ufs driver patch
>>
>> -Changes since v1:
>> * Addressed review comments from Alexey[3] and various review comments from Amit.
>> * Updated email id of Seungwon as his samsung id is void now.
>> * Added ufs platform data
>>
>> [1]-> https://lkml.org/lkml/2015/9/18/29
>> [2]-> https://lkml.org/lkml/2015/9/21/668
>> [3]-> https://lkml.org/lkml/2015/8/23/124
>> [4]-> https://lkml.org/lkml/2015/10/28/271
>> [5]-> https://lkml.org/lkml/2015/10/1/402
>>
>> This patch set is tested on exynos7-espresso board.
>>
>>
>> Alim Akhtar (1):
>> Documentation: samsung-phy: Add dt bindings for UFS
>>
>> Seungwon Jeon (10):
>> phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC
>> scsi: ufs: add quirk to contain unconformable utrd field
>> scsi: ufs: add quirk to fix mishandling utrlclr/utmrlclr
>> scsi: ufs: add quirk not to allow reset of interrupt aggregation
>> scsi: ufs: add quirk to enable host controller without hce
>> scsi: ufs: add specific callback for nexus type
>> scsi: ufs: add add specific callback for hibern8
>> scsi: ufs: make ufshcd_config_pwr_mode of non-static func
>> Documentation: devicetree: ufs: Add DT bindings for exynos UFS host
>> controller
>> scsi: ufs-exynos: add UFS host support for Exynos SoCs
>>
>> .../devicetree/bindings/phy/samsung-phy.txt | 22 +
>> .../devicetree/bindings/ufs/ufs-exynos.txt | 104 ++
>> drivers/phy/Kconfig | 7 +
>> drivers/phy/Makefile | 1 +
>> drivers/phy/phy-exynos-ufs.c | 241 ++++
>> drivers/phy/phy-exynos-ufs.h | 85 ++
>> drivers/phy/phy-exynos7-ufs.h | 89 ++
>> drivers/scsi/ufs/Kconfig | 12 +
>> drivers/scsi/ufs/Makefile | 1 +
>> drivers/scsi/ufs/ufs-exynos-hw.c | 131 ++
>> drivers/scsi/ufs/ufs-exynos-hw.h | 43 +
>> drivers/scsi/ufs/ufs-exynos.c | 1304 ++++++++++++++++++++
>> drivers/scsi/ufs/ufs-exynos.h | 247 ++++
>> drivers/scsi/ufs/ufshcd.c | 168 ++-
>> drivers/scsi/ufs/ufshcd.h | 54 +
>> drivers/scsi/ufs/ufshci.h | 26 +-
>> drivers/scsi/ufs/unipro.h | 47 +
>> include/linux/phy/phy-exynos-ufs.h | 85 ++
>> 18 files changed, 2647 insertions(+), 20 deletions(-)
>> create mode 100644 Documentation/devicetree/bindings/ufs/ufs-exynos.txt
>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>> create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.c
>> create mode 100644 drivers/scsi/ufs/ufs-exynos-hw.h
>> create mode 100644 drivers/scsi/ufs/ufs-exynos.c
>> create mode 100644 drivers/scsi/ufs/ufs-exynos.h
>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>
>> --
>> 1.7.10.4
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>

2015-11-17 06:16:47

by Kishon Vijay Abraham I

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi,

On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
> From: Seungwon Jeon <[email protected]>
>
> This patch introduces Exynos 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 <[email protected]>
> Signed-off-by: Alim Akhtar <[email protected]>
> Cc: Kishon Vijay Abraham I <[email protected]>
> ---
> drivers/phy/Kconfig | 7 ++
> drivers/phy/Makefile | 1 +
> drivers/phy/phy-exynos-ufs.c | 241 ++++++++++++++++++++++++++++++++++++
> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
> 6 files changed, 508 insertions(+)
> create mode 100644 drivers/phy/phy-exynos-ufs.c
> create mode 100644 drivers/phy/phy-exynos-ufs.h
> create mode 100644 drivers/phy/phy-exynos7-ufs.h
> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index 7eb5859dd035..7d38a92e0297 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
> Enable this to support the Broadcom Cygnus PCIe PHY.
> If unsure, say N.
>
> +config PHY_EXYNOS_UFS
> + tristate "EXYNOS SoC series UFS PHY driver"
> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
> + select GENERIC_PHY
> + help
> + Support for UFS PHY on Samsung EXYNOS chipsets.
> +
> endmenu
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 075db1a81aa5..9bec4d1a89e1 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o
> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
> 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_UFS) += phy-exynos-ufs.o
> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
> diff --git a/drivers/phy/phy-exynos-ufs.c b/drivers/phy/phy-exynos-ufs.c
> new file mode 100644
> index 000000000000..cb1aeaa3d4eb
> --- /dev/null
> +++ b/drivers/phy/phy-exynos-ufs.c
> @@ -0,0 +1,241 @@
> +/*
> + * UFS PHY driver for Samsung EXYNOS SoC
> + *
> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
> + * Author: Seungwon Jeon <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-exynos-ufs.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
> + const struct exynos_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;
> + }
> +}
> +
> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
> +{
> + if (IS_PWR_MODE_ANY(desc))
> + return true;
> +
> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
> + return true;
> +
> + if (COMP_PWR_MODE(required_pwr, desc))
> + return true;
> +
> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
> + COMP_PWR_MODE_SER(required_pwr, desc))
> + return true;
> +
> + return false;
> +}
> +
> +int exynos_ufs_phy_calibrate(struct phy *phy,
> + enum phy_cfg_tag tag, u8 pwr)

This is similar to the first version of your patch without EXPORT_SYMBOL.

I think you have to create a new generic PHY_OPS for calibrate PHY while making
sure that it is as generic as possible (which means calibrate_phy shouldn't
have tag and pwr arguments or a strong justification as to why those arguments
are required in a generic API).
> +{
> + struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
> + struct exynos_ufs_phy_cfg **cfgs = ufs_phy->cfg;
> + const struct exynos_ufs_phy_cfg *cfg;
> + int i;
> +
> + if (unlikely(tag < CFG_PRE_INIT || tag >= CFG_TAG_MAX)) {
> + dev_err(ufs_phy->dev, "invalid phy config index %d\n", tag);
> + return -EINVAL;
> + }
> +
> + cfg = cfgs[tag];
> + if (!cfg)
> + goto out;
> +
> + for_each_phy_cfg(cfg) {
> + for_each_phy_lane(ufs_phy, i) {
> + if (match_cfg_to_pwr_mode(cfg->desc, pwr))
> + exynos_ufs_phy_config(ufs_phy, cfg, i);
> + }
> + }
> +
> +out:
> + return 0;
> +}
> +
> +void exynos_ufs_phy_set_lane_cnt(struct phy *phy, u8 lane_cnt)

why can't phy_set_bus_width be reused here?
> +{
> + struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
> +
> + ufs_phy->lane_cnt = lane_cnt;
> +}
> +
> +int exynos_ufs_phy_wait_for_lock_acq(struct phy *phy)
> +{

As I mentioned before the only interface to the PHY driver should be using PHY
ops. So ideally the PHY driver should have only static functions.
> + struct exynos_ufs_phy *ufs_phy = get_exynos_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;
> +}
> +
> +static int exynos_ufs_phy_power_on(struct phy *phy)
> +{
> + struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
> +
> + exynos_ufs_phy_ctrl_isol(_phy, false);
> + return 0;
> +}
> +
> +static int exynos_ufs_phy_power_off(struct phy *phy)
> +{
> + struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
> +
> + exynos_ufs_phy_ctrl_isol(_phy, true);
> + return 0;
> +}
> +
> +static struct phy_ops exynos_ufs_phy_ops = {
> + .power_on = exynos_ufs_phy_power_on,
> + .power_off = exynos_ufs_phy_power_off,
.owner is required for phy_ops.
> +}
> +;
> +static const struct of_device_id exynos_ufs_phy_match[];
> +
> +static int exynos_ufs_phy_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct resource *res;
> + const struct of_device_id *match;
> + struct exynos_ufs_phy *phy;
> + struct phy *gen_phy;
> + struct phy_provider *phy_provider;
> + const struct exynos_ufs_phy_drvdata *drvdata;
> + int err = 0;
> +
> + match = of_match_node(exynos_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, &exynos_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 exynos_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 exynos_ufs_phy_match[] = {
> + {
> + .compatible = "samsung,exynos7-ufs-phy",
> + .data = &exynos7_ufs_phy,
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos_ufs_phy_match);
> +
> +static struct platform_driver exynos_ufs_phy_driver = {
> + .probe = exynos_ufs_phy_probe,
> + .driver = {
> + .name = "exynos-ufs-phy",
> + .of_match_table = exynos_ufs_phy_match,
> + },
> +};
> +module_platform_driver(exynos_ufs_phy_driver);
> +MODULE_DESCRIPTION("EXYNOS SoC UFS PHY Driver");
> +MODULE_AUTHOR("Seungwon Jeon <[email protected]>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/phy/phy-exynos-ufs.h b/drivers/phy/phy-exynos-ufs.h
> new file mode 100644
> index 000000000000..820d879f393c
> --- /dev/null
> +++ b/drivers/phy/phy-exynos-ufs.h
> @@ -0,0 +1,85 @@
> +/*
> + * UFS PHY driver for Samsung EXYNOS SoC
> + *
> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
> + * Author: Seungwon Jeon <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +#ifndef _PHY_EXYNOS_UFS_
> +#define _PHY_EXYNOS_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)
> +
> +struct exynos_ufs_phy_cfg {
> + u32 off_0;
> + u32 off_1;
> + u32 val;
> + u8 desc;
> + u8 id;
> +};
> +
> +struct exynos_ufs_phy_drvdata {
> + const struct exynos_ufs_phy_cfg **cfg;
> + struct pmu_isol {
> + u32 offset;
> + u32 mask;
> + u32 en;
> + } isol;
> +};
> +
> +struct exynos_ufs_phy {
> + struct device *dev;
> + void __iomem *reg_pma;
> + struct regmap *reg_pmu;
> + const struct exynos_ufs_phy_drvdata *drvdata;
> + struct exynos_ufs_phy_cfg **cfg;
> + const struct pmu_isol *isol;
> + u8 lane_cnt;
> +};
> +
> +static inline struct exynos_ufs_phy *get_exynos_ufs_phy(struct phy *phy)
> +{
> + return (struct exynos_ufs_phy *)phy_get_drvdata(phy);
> +}

This wrapper function for phy_get_drvdata is unnecessary.
> +
> +static inline void exynos_ufs_phy_ctrl_isol(
> + struct exynos_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_EXYNOS_UFS_ */
> diff --git a/drivers/phy/phy-exynos7-ufs.h b/drivers/phy/phy-exynos7-ufs.h
> new file mode 100644
> index 000000000000..6cd29d7fb200
> --- /dev/null
> +++ b/drivers/phy/phy-exynos7-ufs.h
> @@ -0,0 +1,89 @@
> +/*
> + * UFS PHY driver for Samsung EXYNOS SoC
> + *
> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +#ifndef _PHY_EXYNOS7_UFS_H_
> +#define _PHY_EXYNOS7_UFS_H_
> +
> +#include "phy-exynos-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 exynos_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),

Are these the usual PHY calbiration settings swing, deemphasis, bias etc.. If
so, we should think about adding standard dt properties and adding the values
for these setting in dt.

Thanks
Kishon

2015-11-17 08:11:06

by Alim Akhtar

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi
Thanks again for looking into this.

On 11/17/2015 11:46 AM, Kishon Vijay Abraham I wrote:
> Hi,
>
> On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
>> From: Seungwon Jeon <[email protected]>
>>
>> This patch introduces Exynos 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 <[email protected]>
>> Signed-off-by: Alim Akhtar <[email protected]>
>> Cc: Kishon Vijay Abraham I <[email protected]>
>> ---
>> drivers/phy/Kconfig | 7 ++
>> drivers/phy/Makefile | 1 +
>> drivers/phy/phy-exynos-ufs.c | 241 ++++++++++++++++++++++++++++++++++++
>> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
>> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
>> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
>> 6 files changed, 508 insertions(+)
>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>
>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
>> index 7eb5859dd035..7d38a92e0297 100644
>> --- a/drivers/phy/Kconfig
>> +++ b/drivers/phy/Kconfig
>> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
>> Enable this to support the Broadcom Cygnus PCIe PHY.
>> If unsure, say N.
>>
>> +config PHY_EXYNOS_UFS
>> + tristate "EXYNOS SoC series UFS PHY driver"
>> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
>> + select GENERIC_PHY
>> + help
>> + Support for UFS PHY on Samsung EXYNOS chipsets.
>> +
>> endmenu
>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
>> index 075db1a81aa5..9bec4d1a89e1 100644
>> --- a/drivers/phy/Makefile
>> +++ b/drivers/phy/Makefile
>> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o
>> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
>> 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_UFS) += phy-exynos-ufs.o
>> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
>> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
>> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
>> diff --git a/drivers/phy/phy-exynos-ufs.c b/drivers/phy/phy-exynos-ufs.c
>> new file mode 100644
>> index 000000000000..cb1aeaa3d4eb
>> --- /dev/null
>> +++ b/drivers/phy/phy-exynos-ufs.c
>> @@ -0,0 +1,241 @@
>> +/*
>> + * UFS PHY driver for Samsung EXYNOS SoC
>> + *
>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>> + * Author: Seungwon Jeon <[email protected]>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/io.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/phy/phy-exynos-ufs.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +
>> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
>> + const struct exynos_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;
>> + }
>> +}
>> +
>> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
>> +{
>> + if (IS_PWR_MODE_ANY(desc))
>> + return true;
>> +
>> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
>> + return true;
>> +
>> + if (COMP_PWR_MODE(required_pwr, desc))
>> + return true;
>> +
>> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
>> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
>> + COMP_PWR_MODE_SER(required_pwr, desc))
>> + return true;
>> +
>> + return false;
>> +}
>> +
>> +int exynos_ufs_phy_calibrate(struct phy *phy,
>> + enum phy_cfg_tag tag, u8 pwr)
>
> This is similar to the first version of your patch without EXPORT_SYMBOL.
>
> I think you have to create a new generic PHY_OPS for calibrate PHY while making
> sure that it is as generic as possible (which means calibrate_phy shouldn't
> have tag and pwr arguments or a strong justification as to why those arguments
> are required in a generic API).
I don't see the advantage to making this a generic phy_ops, this is
exynos specific ufs-phy, please have a look at other implementations
drivers/phy/phy-qcom-ufs.c (which I belive mereged recently)
may be other vendors might come with there own implementation of phy.
I am using what is currently provided by the generic phy framework.
>> +{
>> + struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
>> + struct exynos_ufs_phy_cfg **cfgs = ufs_phy->cfg;
>> + const struct exynos_ufs_phy_cfg *cfg;
>> + int i;
>> +
>> + if (unlikely(tag < CFG_PRE_INIT || tag >= CFG_TAG_MAX)) {
>> + dev_err(ufs_phy->dev, "invalid phy config index %d\n", tag);
>> + return -EINVAL;
>> + }
>> +
>> + cfg = cfgs[tag];
>> + if (!cfg)
>> + goto out;
>> +
>> + for_each_phy_cfg(cfg) {
>> + for_each_phy_lane(ufs_phy, i) {
>> + if (match_cfg_to_pwr_mode(cfg->desc, pwr))
>> + exynos_ufs_phy_config(ufs_phy, cfg, i);
>> + }
>> + }
>> +
>> +out:
>> + return 0;
>> +}
>> +
>> +void exynos_ufs_phy_set_lane_cnt(struct phy *phy, u8 lane_cnt)
>
> why can't phy_set_bus_width be reused here?
Ah..I missed that, will take a look.
>> +{
>> + struct exynos_ufs_phy *ufs_phy = get_exynos_ufs_phy(phy);
>> +
>> + ufs_phy->lane_cnt = lane_cnt;
>> +}
>> +
>> +int exynos_ufs_phy_wait_for_lock_acq(struct phy *phy)
>> +{
>
> As I mentioned before the only interface to the PHY driver should be using PHY
> ops. So ideally the PHY driver should have only static functions.
>> + struct exynos_ufs_phy *ufs_phy = get_exynos_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;
>> +}
>> +
>> +static int exynos_ufs_phy_power_on(struct phy *phy)
>> +{
>> + struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
>> +
>> + exynos_ufs_phy_ctrl_isol(_phy, false);
>> + return 0;
>> +}
>> +
>> +static int exynos_ufs_phy_power_off(struct phy *phy)
>> +{
>> + struct exynos_ufs_phy *_phy = get_exynos_ufs_phy(phy);
>> +
>> + exynos_ufs_phy_ctrl_isol(_phy, true);
>> + return 0;
>> +}
>> +
>> +static struct phy_ops exynos_ufs_phy_ops = {
>> + .power_on = exynos_ufs_phy_power_on,
>> + .power_off = exynos_ufs_phy_power_off,
> .owner is required for phy_ops.
ok
>> +}
>> +;
>> +static const struct of_device_id exynos_ufs_phy_match[];
>> +
>> +static int exynos_ufs_phy_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct resource *res;
>> + const struct of_device_id *match;
>> + struct exynos_ufs_phy *phy;
>> + struct phy *gen_phy;
>> + struct phy_provider *phy_provider;
>> + const struct exynos_ufs_phy_drvdata *drvdata;
>> + int err = 0;
>> +
>> + match = of_match_node(exynos_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, &exynos_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 exynos_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 exynos_ufs_phy_match[] = {
>> + {
>> + .compatible = "samsung,exynos7-ufs-phy",
>> + .data = &exynos7_ufs_phy,
>> + },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos_ufs_phy_match);
>> +
>> +static struct platform_driver exynos_ufs_phy_driver = {
>> + .probe = exynos_ufs_phy_probe,
>> + .driver = {
>> + .name = "exynos-ufs-phy",
>> + .of_match_table = exynos_ufs_phy_match,
>> + },
>> +};
>> +module_platform_driver(exynos_ufs_phy_driver);
>> +MODULE_DESCRIPTION("EXYNOS SoC UFS PHY Driver");
>> +MODULE_AUTHOR("Seungwon Jeon <[email protected]>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/phy/phy-exynos-ufs.h b/drivers/phy/phy-exynos-ufs.h
>> new file mode 100644
>> index 000000000000..820d879f393c
>> --- /dev/null
>> +++ b/drivers/phy/phy-exynos-ufs.h
>> @@ -0,0 +1,85 @@
>> +/*
>> + * UFS PHY driver for Samsung EXYNOS SoC
>> + *
>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>> + * Author: Seungwon Jeon <[email protected]>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +#ifndef _PHY_EXYNOS_UFS_
>> +#define _PHY_EXYNOS_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)
>> +
>> +struct exynos_ufs_phy_cfg {
>> + u32 off_0;
>> + u32 off_1;
>> + u32 val;
>> + u8 desc;
>> + u8 id;
>> +};
>> +
>> +struct exynos_ufs_phy_drvdata {
>> + const struct exynos_ufs_phy_cfg **cfg;
>> + struct pmu_isol {
>> + u32 offset;
>> + u32 mask;
>> + u32 en;
>> + } isol;
>> +};
>> +
>> +struct exynos_ufs_phy {
>> + struct device *dev;
>> + void __iomem *reg_pma;
>> + struct regmap *reg_pmu;
>> + const struct exynos_ufs_phy_drvdata *drvdata;
>> + struct exynos_ufs_phy_cfg **cfg;
>> + const struct pmu_isol *isol;
>> + u8 lane_cnt;
>> +};
>> +
>> +static inline struct exynos_ufs_phy *get_exynos_ufs_phy(struct phy *phy)
>> +{
>> + return (struct exynos_ufs_phy *)phy_get_drvdata(phy);
>> +}
>
> This wrapper function for phy_get_drvdata is unnecessary.
>> +
>> +static inline void exynos_ufs_phy_ctrl_isol(
>> + struct exynos_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_EXYNOS_UFS_ */
>> diff --git a/drivers/phy/phy-exynos7-ufs.h b/drivers/phy/phy-exynos7-ufs.h
>> new file mode 100644
>> index 000000000000..6cd29d7fb200
>> --- /dev/null
>> +++ b/drivers/phy/phy-exynos7-ufs.h
>> @@ -0,0 +1,89 @@
>> +/*
>> + * UFS PHY driver for Samsung EXYNOS SoC
>> + *
>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +#ifndef _PHY_EXYNOS7_UFS_H_
>> +#define _PHY_EXYNOS7_UFS_H_
>> +
>> +#include "phy-exynos-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 exynos_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),
>
> Are these the usual PHY calbiration settings swing, deemphasis, bias etc.. If
> so, we should think about adding standard dt properties and adding the values
> for these setting in dt.
>
Well these are the initial values need to programmed at a given
offset/sequence before we do a ufs linkstart up.
well we can explore dt option, but at this point of time this look
simple, I have access to just a single h/w which using this phy, will
try to get access/see how mush other exynos ufs-phy differs (if possible
for me), till then I prefere to have this way.

> Thanks
> Kishon
>

2015-11-17 08:43:45

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v5 06/11] scsi: ufs: add quirk to enable host controller without hce

On Monday 09 November 2015 10:56:22 Alim Akhtar wrote:
> +static int ufshcd_hba_enable(struct ufs_hba *hba)
> +{
> + int ret;
> +
> + if (hba->quirks & UFSHCI_QUIRK_BROKEN_HCE) {
> + ufshcd_set_link_off(hba);
> + ufshcd_vops_hce_enable_notify(hba, PRE_CHANGE);
> +
> + /* enable UIC related interrupts */
> + ufshcd_enable_intr(hba, UFSHCD_UIC_MASK);
> + ret = ufshcd_dme_reset(hba);
> + if (!ret) {
> + ret = ufshcd_dme_enable(hba);
> + if (!ret)
> + ufshcd_vops_hce_enable_notify(hba, POST_CHANGE);
> + if (ret)
> + dev_err(hba->dev,
> + "Host controller enable failed with non-hce\n");
> + }
> + } else {
> + ret = ufshcd_hba_execute_hce(hba);
> + }
> +
> + return ret;

quirks are often not the best way to deal with host controller specific
differences. How about using a function pointer for ufshcd_hba_enable()
that can be one or the oither implementation?

Arnd

2015-11-17 08:50:39

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v5 11/11] scsi: ufs-exynos: add UFS host support for Exynos SoCs

On Monday 09 November 2015 10:56:27 Alim Akhtar wrote:
> From: Seungwon Jeon <[email protected]>
>
> This patch introduces Exynos UFS host controller driver,
> which mainly handles vendor-specific operations including
> link startup, power mode change and hibernation/unhibernation.
>
> Signed-off-by: Seungwon Jeon <[email protected]>
> Signed-off-by: Alim Akhtar <[email protected]>

I had looked at earlier versions of this and the new version looks
much better. Just a few things I noticed:

> drivers/scsi/ufs/Kconfig | 12 +
> drivers/scsi/ufs/Makefile | 1 +
> drivers/scsi/ufs/ufs-exynos-hw.c | 131 ++++
> drivers/scsi/ufs/ufs-exynos-hw.h | 43 ++
> drivers/scsi/ufs/ufs-exynos.c | 1304 ++++++++++++++++++++++++++++++++++++++
> drivers/scsi/ufs/ufs-exynos.h | 247 ++++++++
> drivers/scsi/ufs/ufshci.h | 26 +-
> drivers/scsi/ufs/unipro.h | 47 ++

It looks like an arbitrary split to separate out ufs-exynos-hw.{c,h} from
ufs-exynos.{c,h}, it would become simpler if you just put the four files
into one.

>
> diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
> index 5f4530744e0a..bc602be94458 100644
> --- a/drivers/scsi/ufs/Kconfig
> +++ b/drivers/scsi/ufs/Kconfig
> @@ -83,3 +83,15 @@ config SCSI_UFS_QCOM
>
> Select this if you have UFS controller on QCOM chipset.
> If unsure, say N.
> +
> +config SCSI_UFS_EXYNOS
> + bool "EXYNOS specific hooks to UFS controller platform driver"
> + depends on SCSI_UFSHCD_PLATFORM && ARCH_EXYNOS || COMPILE_TEST

Maybe use

depends on SCSI_UFSHCD_PLATFORM && (ARCH_EXYNOS || COMPILE_TEST)

for clarity? nobody can remember the order in which this gets evaluated. ;-)

> + select PHY_EXYNOS_UFS
> + help
> + This selects the EXYNOS specific additions to UFSHCD platform driver.
> + UFS host on EXYNOS includes HCI and UNIPRO layer, and associates with
> + UFS-PHY driver.

Is it allowed to select PHY_EXYNOS_UFS without having a dependency on other
symbols?

> +static int exynos7_ufs_pre_link(struct exynos_ufs *ufs)
> +{
> + struct ufs_hba *hba = ufs->hba;
> + u32 val = ufs->drv_data->uic_attr->pa_dbg_option_suite;
> + int i;
> +
> + exynos_ufs_enable_ov_tm(hba);
> + for_each_ufs_tx_lane(ufs, i)
> + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x297, i), 0x17);
> + for_each_ufs_rx_lane(ufs, i) {
> + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x362, i), 0xff);
> + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x363, i), 0x00);
> + }
> + exynos_ufs_disable_ov_tm(hba);
> +
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE_DYN), 0xf);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE_DYN), 0xf);
> + for_each_ufs_tx_lane(ufs, i)
> + ufshcd_dme_set(hba,
> + UIC_ARG_MIB_SEL(TX_HIBERN8_CONTROL, i), 0x0);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_TXPHY_CFGUPDT), 0x1);
> + udelay(1);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE), val | (1 << 12));
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_RESET_PHY), 0x1);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_LINE_RESET), 0x1);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_LINE_RESET_REQ), 0x1);
> + udelay(1600);
> + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OPTION_SUITE), val);

1.6 ms is a long time to block the CPU for. Are you allowed to call
msleep() or uleep_range() in this function instead?

> +struct exynos_ufs_drv_data exynos_ufs_drvs[] = {
> +{
> + .compatible = "samsung,exynos7-ufs",
> + .uic_attr = &exynos7_uic_attr,
> + .quirks = UFSHCI_QUIRK_BYTE_ALIGN_UTRD |
> + UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR |
> + UFSHCI_QUIRK_BROKEN_HCE |
> + UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR,
> + .opts = EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL |
> + EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
> + EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX,
> + .drv_init = exynos7_ufs_drv_init,
> + .pre_link = exynos7_ufs_pre_link,
> + .post_link = exynos7_ufs_post_link,
> + .pre_pwr_change = exynos7_ufs_pre_pwr_change,
> + .post_pwr_change = exynos7_ufs_post_pwr_change,
> +}, {
> +}, };

An array like this is probably not the ideal way to express it,
in particular when there is only one entry. More on that below.

> +static int exynos_ufs_parse_dt(struct device *dev, struct exynos_ufs *ufs)
> +{
> + struct device_node *np = dev->of_node;
> + struct exynos_ufs_drv_data *drv_data = exynos_ufs_drvs;
> + struct exynos_ufs_uic_attr *attr;
> + u32 freq[2];
> + int ret;
> +
> + while (drv_data->compatible) {
> + if (of_device_is_compatible(np, drv_data->compatible)) {
> + ufs->drv_data = drv_data;
> + break;
> + }
> + drv_data++;
> + }

Here you loop over the array, but you actually match the compatible list
already in the platform driver subsystem.

> +static const struct of_device_id exynos_ufs_of_match[] = {
> + { .compatible = "samsung,exynos7-ufs"},
> + {},
> +};

So just put a pointer to the right data into the .data member of of_device_id.

Arnd

2015-11-19 13:39:59

by Kishon Vijay Abraham I

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi,

On Tuesday 17 November 2015 01:41 PM, Alim Akhtar wrote:
> Hi
> Thanks again for looking into this.
>
> On 11/17/2015 11:46 AM, Kishon Vijay Abraham I wrote:
>> Hi,
>>
>> On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
>>> From: Seungwon Jeon <[email protected]>
>>>
>>> This patch introduces Exynos 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 <[email protected]>
>>> Signed-off-by: Alim Akhtar <[email protected]>
>>> Cc: Kishon Vijay Abraham I <[email protected]>
>>> ---
>>> drivers/phy/Kconfig | 7 ++
>>> drivers/phy/Makefile | 1 +
>>> drivers/phy/phy-exynos-ufs.c | 241
>>> ++++++++++++++++++++++++++++++++++++
>>> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
>>> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>> 6 files changed, 508 insertions(+)
>>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>>
>>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
>>> index 7eb5859dd035..7d38a92e0297 100644
>>> --- a/drivers/phy/Kconfig
>>> +++ b/drivers/phy/Kconfig
>>> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
>>> Enable this to support the Broadcom Cygnus PCIe PHY.
>>> If unsure, say N.
>>>
>>> +config PHY_EXYNOS_UFS
>>> + tristate "EXYNOS SoC series UFS PHY driver"
>>> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
>>> + select GENERIC_PHY
>>> + help
>>> + Support for UFS PHY on Samsung EXYNOS chipsets.
>>> +
>>> endmenu
>>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
>>> index 075db1a81aa5..9bec4d1a89e1 100644
>>> --- a/drivers/phy/Makefile
>>> +++ b/drivers/phy/Makefile
>>> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) +=
>>> phy-armada375-usb2.o
>>> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
>>> 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_UFS) += phy-exynos-ufs.o
>>> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
>>> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
>>> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
>>> diff --git a/drivers/phy/phy-exynos-ufs.c b/drivers/phy/phy-exynos-ufs.c
>>> new file mode 100644
>>> index 000000000000..cb1aeaa3d4eb
>>> --- /dev/null
>>> +++ b/drivers/phy/phy-exynos-ufs.c
>>> @@ -0,0 +1,241 @@
>>> +/*
>>> + * UFS PHY driver for Samsung EXYNOS SoC
>>> + *
>>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>>> + * Author: Seungwon Jeon <[email protected]>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License as published by
>>> + * the Free Software Foundation; either version 2 of the License, or
>>> + * (at your option) any later version.
>>> + */
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/err.h>
>>> +#include <linux/io.h>
>>> +#include <linux/iopoll.h>
>>> +#include <linux/mfd/syscon.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/phy/phy.h>
>>> +#include <linux/phy/phy-exynos-ufs.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/regmap.h>
>>> +
>>> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
>>> + const struct exynos_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;
>>> + }
>>> +}
>>> +
>>> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
>>> +{
>>> + if (IS_PWR_MODE_ANY(desc))
>>> + return true;
>>> +
>>> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
>>> + return true;
>>> +
>>> + if (COMP_PWR_MODE(required_pwr, desc))
>>> + return true;
>>> +
>>> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
>>> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
>>> + COMP_PWR_MODE_SER(required_pwr, desc))
>>> + return true;
>>> +
>>> + return false;
>>> +}
>>> +
>>> +int exynos_ufs_phy_calibrate(struct phy *phy,
>>> + enum phy_cfg_tag tag, u8 pwr)
>>
>> This is similar to the first version of your patch without EXPORT_SYMBOL.
>>
>> I think you have to create a new generic PHY_OPS for calibrate PHY while making
>> sure that it is as generic as possible (which means calibrate_phy shouldn't
>> have tag and pwr arguments or a strong justification as to why those arguments
>> are required in a generic API).
> I don't see the advantage to making this a generic phy_ops, this is exynos
> specific ufs-phy, please have a look at other implementations

only the implementation is specific to exynos. I've seen lot of other vendors
want to do something like calibrate phy.

So if we add something like (*calibrate)(struct phy *phy), then it can be used
by others as well. Russell King also want to minimize the code to program
calibration settings. So it would be good to come up with a set of standard
bindings like 'phy,tx-swing', 'phy,emphasis', 'phy,amplitude' etc.. to program
these settings.
> drivers/phy/phy-qcom-ufs.c (which I belive mereged recently)

Thats why I hate when someone else merge PHY drivers :-( That driver can as
well be in drivers/misc as it doesn't use PHY framework as it is supposed to be
used. It just exports a dozen of API's to be used by controller drivers. ick..

> may be other vendors might come with there own implementation of phy.

right, it's all about providing the correct callback functions.
> I am using what is currently provided by the generic phy framework.

I think for your use case, what is currently provided in the PHY framework is
not sufficient.

Thanks
Kishon

2017-03-01 14:19:12

by Kishon Vijay Abraham I

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi,

On Tuesday 28 February 2017 01:51 PM, Alim Akhtar wrote:
> Hi Kishon,
>
> On 02/28/2017 09:04 AM, Kishon Vijay Abraham I wrote:
>> Hi,
>>
>> On Monday 27 February 2017 07:40 PM, Alim Akhtar wrote:
>>> Hi Kishon,
>>>
>>> On 02/27/2017 10:56 AM, Kishon Vijay Abraham I wrote:
>>>> Hi,
>>>>
>>>> On Thursday 23 February 2017 12:20 AM, Alim Akhtar wrote:
>>>>> On Fri, Feb 3, 2017 at 2:49 PM, Alim Akhtar <[email protected]> wrote:
>>>>>> Hi Kishon,
>>>>>>
>>>>>>
>>>>>> On 11/19/2015 07:09 PM, Kishon Vijay Abraham I wrote:
>>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> On Tuesday 17 November 2015 01:41 PM, Alim Akhtar wrote:
>>>>>>>>
>>>>>>>> Hi
>>>>>>>> Thanks again for looking into this.
>>>>>>>>
>>>>>>>> On 11/17/2015 11:46 AM, Kishon Vijay Abraham I wrote:
>>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
>>>>>>>>>>
>>>>>>>>>> From: Seungwon Jeon <[email protected]>
>>>>>>>>>>
>>>>>>>>>> This patch introduces Exynos 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 <[email protected]>
>>>>>>>>>> Signed-off-by: Alim Akhtar <[email protected]>
>>>>>>>>>> Cc: Kishon Vijay Abraham I <[email protected]>
>>>>>>>>>> ---
>>>>>>>>>> drivers/phy/Kconfig | 7 ++
>>>>>>>>>> drivers/phy/Makefile | 1 +
>>>>>>>>>> drivers/phy/phy-exynos-ufs.c | 241
>>>>>>>>>> ++++++++++++++++++++++++++++++++++++
>>>>>>>>>> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
>>>>>>>>>> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>> 6 files changed, 508 insertions(+)
>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>>>>>>>>>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>>>>>>>>>
>>>>>>>>>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
>>>>>>>>>> index 7eb5859dd035..7d38a92e0297 100644
>>>>>>>>>> --- a/drivers/phy/Kconfig
>>>>>>>>>> +++ b/drivers/phy/Kconfig
>>>>>>>>>> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
>>>>>>>>>> Enable this to support the Broadcom Cygnus PCIe PHY.
>>>>>>>>>> If unsure, say N.
>>>>>>>>>>
>>>>>>>>>> +config PHY_EXYNOS_UFS
>>>>>>>>>> + tristate "EXYNOS SoC series UFS PHY driver"
>>>>>>>>>> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
>>>>>>>>>> + select GENERIC_PHY
>>>>>>>>>> + help
>>>>>>>>>> + Support for UFS PHY on Samsung EXYNOS chipsets.
>>>>>>>>>> +
>>>>>>>>>> endmenu
>>>>>>>>>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
>>>>>>>>>> index 075db1a81aa5..9bec4d1a89e1 100644
>>>>>>>>>> --- a/drivers/phy/Makefile
>>>>>>>>>> +++ b/drivers/phy/Makefile
>>>>>>>>>> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) +=
>>>>>>>>>> phy-armada375-usb2.o
>>>>>>>>>> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
>>>>>>>>>> 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_UFS) += phy-exynos-ufs.o
>>>>>>>>>> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
>>>>>>>>>> diff --git a/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>> b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000000..cb1aeaa3d4eb
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>> @@ -0,0 +1,241 @@
>>>>>>>>>> +/*
>>>>>>>>>> + * UFS PHY driver for Samsung EXYNOS SoC
>>>>>>>>>> + *
>>>>>>>>>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>>>>>>>>>> + * Author: Seungwon Jeon <[email protected]>
>>>>>>>>>> + *
>>>>>>>>>> + * This program is free software; you can redistribute it and/or
>>>>>>>>>> modify
>>>>>>>>>> + * it under the terms of the GNU General Public License as published
>>>>>>>>>> by
>>>>>>>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>>>>>>>> + * (at your option) any later version.
>>>>>>>>>> + */
>>>>>>>>>> +#include <linux/clk.h>
>>>>>>>>>> +#include <linux/delay.h>
>>>>>>>>>> +#include <linux/err.h>
>>>>>>>>>> +#include <linux/io.h>
>>>>>>>>>> +#include <linux/iopoll.h>
>>>>>>>>>> +#include <linux/mfd/syscon.h>
>>>>>>>>>> +#include <linux/module.h>
>>>>>>>>>> +#include <linux/of.h>
>>>>>>>>>> +#include <linux/phy/phy.h>
>>>>>>>>>> +#include <linux/phy/phy-exynos-ufs.h>
>>>>>>>>>> +#include <linux/platform_device.h>
>>>>>>>>>> +#include <linux/regmap.h>
>>>>>>>>>> +
>>>>>>>>>> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
>>>>>>>>>> + const struct exynos_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;
>>>>>>>>>> + }
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
>>>>>>>>>> +{
>>>>>>>>>> + if (IS_PWR_MODE_ANY(desc))
>>>>>>>>>> + return true;
>>>>>>>>>> +
>>>>>>>>>> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
>>>>>>>>>> + return true;
>>>>>>>>>> +
>>>>>>>>>> + if (COMP_PWR_MODE(required_pwr, desc))
>>>>>>>>>> + return true;
>>>>>>>>>> +
>>>>>>>>>> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
>>>>>>>>>> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
>>>>>>>>>> + COMP_PWR_MODE_SER(required_pwr, desc))
>>>>>>>>>> + return true;
>>>>>>>>>> +
>>>>>>>>>> + return false;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +int exynos_ufs_phy_calibrate(struct phy *phy,
>>>>>>>>>> + enum phy_cfg_tag tag, u8 pwr)
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> This is similar to the first version of your patch without
>>>>>>>>> EXPORT_SYMBOL.
>>>>>>>>>
>>>>>>>>> I think you have to create a new generic PHY_OPS for calibrate PHY while
>>>>>>>>> making
>>>>>>>>> sure that it is as generic as possible (which means calibrate_phy
>>>>>>>>> shouldn't
>>>>>>>>> have tag and pwr arguments or a strong justification as to why those
>>>>>>>>> arguments
>>>>>>>>> are required in a generic API).
>>>>>>>>
>>>>>>>> I don't see the advantage to making this a generic phy_ops, this is
>>>>>>>> exynos
>>>>>>>> specific ufs-phy, please have a look at other implementations
>>>>>>>
>>>>>>>
>>>>>>> only the implementation is specific to exynos. I've seen lot of other
>>>>>>> vendors
>>>>>>> want to do something like calibrate phy.
>>>>>>>
>>>>>>> So if we add something like (*calibrate)(struct phy *phy), then it can be
>>>>>>> used
>>>>>>> by others as well. Russell King also want to minimize the code to program
>>>>>>> calibration settings. So it would be good to come up with a set of
>>>>>>> standard
>>>>>>> bindings like 'phy,tx-swing', 'phy,emphasis', 'phy,amplitude' etc.. to
>>>>>>> program
>>>>>>> these settings.
>>>>>>>>
>>>>>>>> drivers/phy/phy-qcom-ufs.c (which I belive mereged recently)
>>>>>>>
>>>>>>>
>>>>>>> Thats why I hate when someone else merge PHY drivers :-( That driver can
>>>>>>> as
>>>>>>> well be in drivers/misc as it doesn't use PHY framework as it is supposed
>>>>>>> to be
>>>>>>> used. It just exports a dozen of API's to be used by controller drivers.
>>>>>>> ick..
>>>>>>>
>>>>>>>> may be other vendors might come with there own implementation of phy.
>>>>>>>
>>>>>>>
>>>>>>> right, it's all about providing the correct callback functions.
>>>>>>>>
>>>>>>>> I am using what is currently provided by the generic phy framework.
>>>>>>>
>>>>>>>
>>>>>>> I think for your use case, what is currently provided in the PHY framework
>>>>>>> is
>>>>>>> not sufficient.
>>>>>>>
>>>>>> Its little over a year since last time we discuss about adding a generic
>>>>>> calibration API. I can see in the past people tried adding *calibration* API
>>>>>> [1] but not sure why [1] was not landed in mainline.
>>>>>> Anyway now we have many users of phy_calibration API, like UFS, USB and may
>>>>>> be PCIe, there is a real need to add this functionality. So, here is my
>>>>>> approach:
>>>>
>>>> Agree, there are quite a few users that require calibration of phy parameters.
>>>> I think previously it was accommodated in phy_init, hence it was not merged.
>>> Ok, thanks for this information.
>>>
>>>>>> * Along with [1], we can add a void *priv for handling device specific phy
>>>>>> private data, and before calling phy_calibration() from phy consumer,
>>>>>> phy->priv is populated with private data.
>>>>
>>>> Not sure how you plan to use priv here?
>>>>
>>> From ufs driver I am populating PHY _priv_ data and calling phy_calibrate()
>>>
>>> e.g
>>> ----------------------- from ufs-exynos.c
>>> Instead of using below code earlier
>>> - exynos_ufs_phy_calibrate(generic_phy,CFG_PRE_INIT,PWR_MODE_ANY);
>>>
>>> Now I am using below from ufs-exynos driver
>>>
>>> + generic_phy->priv =(void*)CFG_PRE_INIT;
>>> + phy_calibrate(generic_phy);
>>>
>>> and in drivers/phy/phy-exynos-ufs.c
>>> using phy->priv in calibration function.
>>
>> Don't prefer passing of such private pointers between drivers. Why is this needed?
>>
> As already explained before, this is needed to pass the calibration
> point (when you want to do the calibration?, like before init, after
> init or before/after _mode_ change etc).
>
> I Don't think we have much option here, if we want to make
> phy_calibration generic enough.
>
> We have few options:
> 1> One way is to have phy_calibration takes some argument like
> calibration point (which was part of my v3~v5), but looking at the
> current implementation of phy-qcom-ufs.c, this approach might not be
> generic enough.
>
> 2> And using EXPORT_SYMBOL way is not encourage (as in my V1, even
> though others in phy drivers uses it).
>
> 3> the current proposal of using _priv_ data.
>
> Out of the above 3 option, I feel using _priv_ data is more generic way
> (and most of the major sub-system in Linux uses it).
>
> lets see what other people think about __priv__ approach.
>
> Please suggest your prefer way to handle this.

Generally calibration has to be done at a single point. Having to do
calibration in multiple places seems to be specific to Exynos.

Can implementing a small state machine in exynos driver help?

Thanks
Kishon

2017-03-06 11:46:08

by Alim Akhtar

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi Kishon

On 03/01/2017 10:07 AM, Kishon Vijay Abraham I wrote:
> Hi,
>
> On Tuesday 28 February 2017 01:51 PM, Alim Akhtar wrote:
>> Hi Kishon,
>>
>> On 02/28/2017 09:04 AM, Kishon Vijay Abraham I wrote:
>>> Hi,
>>>
>>> On Monday 27 February 2017 07:40 PM, Alim Akhtar wrote:
>>>> Hi Kishon,
>>>>
>>>> On 02/27/2017 10:56 AM, Kishon Vijay Abraham I wrote:
>>>>> Hi,
>>>>>
>>>>> On Thursday 23 February 2017 12:20 AM, Alim Akhtar wrote:
>>>>>> On Fri, Feb 3, 2017 at 2:49 PM, Alim Akhtar <[email protected]> wrote:
>>>>>>> Hi Kishon,
>>>>>>>
>>>>>>>
>>>>>>> On 11/19/2015 07:09 PM, Kishon Vijay Abraham I wrote:
>>>>>>>>
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> On Tuesday 17 November 2015 01:41 PM, Alim Akhtar wrote:
>>>>>>>>>
>>>>>>>>> Hi
>>>>>>>>> Thanks again for looking into this.
>>>>>>>>>
>>>>>>>>> On 11/17/2015 11:46 AM, Kishon Vijay Abraham I wrote:
>>>>>>>>>>
>>>>>>>>>> Hi,
>>>>>>>>>>
>>>>>>>>>> On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
>>>>>>>>>>>
>>>>>>>>>>> From: Seungwon Jeon <[email protected]>
>>>>>>>>>>>
>>>>>>>>>>> This patch introduces Exynos 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 <[email protected]>
>>>>>>>>>>> Signed-off-by: Alim Akhtar <[email protected]>
>>>>>>>>>>> Cc: Kishon Vijay Abraham I <[email protected]>
>>>>>>>>>>> ---
>>>>>>>>>>> drivers/phy/Kconfig | 7 ++
>>>>>>>>>>> drivers/phy/Makefile | 1 +
>>>>>>>>>>> drivers/phy/phy-exynos-ufs.c | 241
>>>>>>>>>>> ++++++++++++++++++++++++++++++++++++
>>>>>>>>>>> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>>> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
>>>>>>>>>>> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>>> 6 files changed, 508 insertions(+)
>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>>>>>>>>>>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>>>>>>>>>>
>>>>>>>>>>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
>>>>>>>>>>> index 7eb5859dd035..7d38a92e0297 100644
>>>>>>>>>>> --- a/drivers/phy/Kconfig
>>>>>>>>>>> +++ b/drivers/phy/Kconfig
>>>>>>>>>>> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
>>>>>>>>>>> Enable this to support the Broadcom Cygnus PCIe PHY.
>>>>>>>>>>> If unsure, say N.
>>>>>>>>>>>
>>>>>>>>>>> +config PHY_EXYNOS_UFS
>>>>>>>>>>> + tristate "EXYNOS SoC series UFS PHY driver"
>>>>>>>>>>> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
>>>>>>>>>>> + select GENERIC_PHY
>>>>>>>>>>> + help
>>>>>>>>>>> + Support for UFS PHY on Samsung EXYNOS chipsets.
>>>>>>>>>>> +
>>>>>>>>>>> endmenu
>>>>>>>>>>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
>>>>>>>>>>> index 075db1a81aa5..9bec4d1a89e1 100644
>>>>>>>>>>> --- a/drivers/phy/Makefile
>>>>>>>>>>> +++ b/drivers/phy/Makefile
>>>>>>>>>>> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) +=
>>>>>>>>>>> phy-armada375-usb2.o
>>>>>>>>>>> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
>>>>>>>>>>> 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_UFS) += phy-exynos-ufs.o
>>>>>>>>>>> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
>>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
>>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
>>>>>>>>>>> diff --git a/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>> b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000000..cb1aeaa3d4eb
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>> @@ -0,0 +1,241 @@
>>>>>>>>>>> +/*
>>>>>>>>>>> + * UFS PHY driver for Samsung EXYNOS SoC
>>>>>>>>>>> + *
>>>>>>>>>>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>>>>>>>>>>> + * Author: Seungwon Jeon <[email protected]>
>>>>>>>>>>> + *
>>>>>>>>>>> + * This program is free software; you can redistribute it and/or
>>>>>>>>>>> modify
>>>>>>>>>>> + * it under the terms of the GNU General Public License as published
>>>>>>>>>>> by
>>>>>>>>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>>>>>>>>> + * (at your option) any later version.
>>>>>>>>>>> + */
>>>>>>>>>>> +#include <linux/clk.h>
>>>>>>>>>>> +#include <linux/delay.h>
>>>>>>>>>>> +#include <linux/err.h>
>>>>>>>>>>> +#include <linux/io.h>
>>>>>>>>>>> +#include <linux/iopoll.h>
>>>>>>>>>>> +#include <linux/mfd/syscon.h>
>>>>>>>>>>> +#include <linux/module.h>
>>>>>>>>>>> +#include <linux/of.h>
>>>>>>>>>>> +#include <linux/phy/phy.h>
>>>>>>>>>>> +#include <linux/phy/phy-exynos-ufs.h>
>>>>>>>>>>> +#include <linux/platform_device.h>
>>>>>>>>>>> +#include <linux/regmap.h>
>>>>>>>>>>> +
>>>>>>>>>>> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
>>>>>>>>>>> + const struct exynos_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;
>>>>>>>>>>> + }
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
>>>>>>>>>>> +{
>>>>>>>>>>> + if (IS_PWR_MODE_ANY(desc))
>>>>>>>>>>> + return true;
>>>>>>>>>>> +
>>>>>>>>>>> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
>>>>>>>>>>> + return true;
>>>>>>>>>>> +
>>>>>>>>>>> + if (COMP_PWR_MODE(required_pwr, desc))
>>>>>>>>>>> + return true;
>>>>>>>>>>> +
>>>>>>>>>>> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
>>>>>>>>>>> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
>>>>>>>>>>> + COMP_PWR_MODE_SER(required_pwr, desc))
>>>>>>>>>>> + return true;
>>>>>>>>>>> +
>>>>>>>>>>> + return false;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +int exynos_ufs_phy_calibrate(struct phy *phy,
>>>>>>>>>>> + enum phy_cfg_tag tag, u8 pwr)
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> This is similar to the first version of your patch without
>>>>>>>>>> EXPORT_SYMBOL.
>>>>>>>>>>
>>>>>>>>>> I think you have to create a new generic PHY_OPS for calibrate PHY while
>>>>>>>>>> making
>>>>>>>>>> sure that it is as generic as possible (which means calibrate_phy
>>>>>>>>>> shouldn't
>>>>>>>>>> have tag and pwr arguments or a strong justification as to why those
>>>>>>>>>> arguments
>>>>>>>>>> are required in a generic API).
>>>>>>>>>
>>>>>>>>> I don't see the advantage to making this a generic phy_ops, this is
>>>>>>>>> exynos
>>>>>>>>> specific ufs-phy, please have a look at other implementations
>>>>>>>>
>>>>>>>>
>>>>>>>> only the implementation is specific to exynos. I've seen lot of other
>>>>>>>> vendors
>>>>>>>> want to do something like calibrate phy.
>>>>>>>>
>>>>>>>> So if we add something like (*calibrate)(struct phy *phy), then it can be
>>>>>>>> used
>>>>>>>> by others as well. Russell King also want to minimize the code to program
>>>>>>>> calibration settings. So it would be good to come up with a set of
>>>>>>>> standard
>>>>>>>> bindings like 'phy,tx-swing', 'phy,emphasis', 'phy,amplitude' etc.. to
>>>>>>>> program
>>>>>>>> these settings.
>>>>>>>>>
>>>>>>>>> drivers/phy/phy-qcom-ufs.c (which I belive mereged recently)
>>>>>>>>
>>>>>>>>
>>>>>>>> Thats why I hate when someone else merge PHY drivers :-( That driver can
>>>>>>>> as
>>>>>>>> well be in drivers/misc as it doesn't use PHY framework as it is supposed
>>>>>>>> to be
>>>>>>>> used. It just exports a dozen of API's to be used by controller drivers.
>>>>>>>> ick..
>>>>>>>>
>>>>>>>>> may be other vendors might come with there own implementation of phy.
>>>>>>>>
>>>>>>>>
>>>>>>>> right, it's all about providing the correct callback functions.
>>>>>>>>>
>>>>>>>>> I am using what is currently provided by the generic phy framework.
>>>>>>>>
>>>>>>>>
>>>>>>>> I think for your use case, what is currently provided in the PHY framework
>>>>>>>> is
>>>>>>>> not sufficient.
>>>>>>>>
>>>>>>> Its little over a year since last time we discuss about adding a generic
>>>>>>> calibration API. I can see in the past people tried adding *calibration* API
>>>>>>> [1] but not sure why [1] was not landed in mainline.
>>>>>>> Anyway now we have many users of phy_calibration API, like UFS, USB and may
>>>>>>> be PCIe, there is a real need to add this functionality. So, here is my
>>>>>>> approach:
>>>>>
>>>>> Agree, there are quite a few users that require calibration of phy parameters.
>>>>> I think previously it was accommodated in phy_init, hence it was not merged.
>>>> Ok, thanks for this information.
>>>>
>>>>>>> * Along with [1], we can add a void *priv for handling device specific phy
>>>>>>> private data, and before calling phy_calibration() from phy consumer,
>>>>>>> phy->priv is populated with private data.
>>>>>
>>>>> Not sure how you plan to use priv here?
>>>>>
>>>> From ufs driver I am populating PHY _priv_ data and calling phy_calibrate()
>>>>
>>>> e.g
>>>> ----------------------- from ufs-exynos.c
>>>> Instead of using below code earlier
>>>> - exynos_ufs_phy_calibrate(generic_phy,CFG_PRE_INIT,PWR_MODE_ANY);
>>>>
>>>> Now I am using below from ufs-exynos driver
>>>>
>>>> + generic_phy->priv =(void*)CFG_PRE_INIT;
>>>> + phy_calibrate(generic_phy);
>>>>
>>>> and in drivers/phy/phy-exynos-ufs.c
>>>> using phy->priv in calibration function.
>>>
>>> Don't prefer passing of such private pointers between drivers. Why is this needed?
>>>
>> As already explained before, this is needed to pass the calibration
>> point (when you want to do the calibration?, like before init, after
>> init or before/after _mode_ change etc).
>>
>> I Don't think we have much option here, if we want to make
>> phy_calibration generic enough.
>>
>> We have few options:
>> 1> One way is to have phy_calibration takes some argument like
>> calibration point (which was part of my v3~v5), but looking at the
>> current implementation of phy-qcom-ufs.c, this approach might not be
>> generic enough.
>>
>> 2> And using EXPORT_SYMBOL way is not encourage (as in my V1, even
>> though others in phy drivers uses it).
>>
>> 3> the current proposal of using _priv_ data.
>>
>> Out of the above 3 option, I feel using _priv_ data is more generic way
>> (and most of the major sub-system in Linux uses it).
>>
>> lets see what other people think about __priv__ approach.
>>
>> Please suggest your prefer way to handle this.
>
> Generally calibration has to be done at a single point. Having to do
> calibration in multiple places seems to be specific to Exynos.
>
Ok, calibration as such in not Exynos specific, calibration point may be.
> Can implementing a small state machine in exynos driver help?
>
Sorry I didn't get you here. What do mean by implement a state machine?
when it can be easy handled with the proposed generic _calibration_
method. Why to complicate the thing?

You said, you "don't prefer passing a _priv_ pointer between driver",
can you please explain why? what is the potential problem do you see
with _priv_ pointer?

Look at the current implementation of
"drivers/phy/phy-qcom-ufs-qmp-14nm.c" they have used "specific ops for
Phy" and that too with the EXPORT_SYMBOL, don't you think this can be
correct if we have a generic phy_calibration call back?

CCed Subhash Jadavani (who is doing most of the Qcom UFS stuffs) to get
his input as well.



> Thanks
> Kishon
>
>
>

2017-03-06 13:44:47

by Kishon Vijay Abraham I

[permalink] [raw]
Subject: Re: [PATCH v5 02/11] phy: exynos-ufs: add UFS PHY driver for EXYNOS SoC

Hi,

On Monday 06 March 2017 05:12 PM, Alim Akhtar wrote:
> Hi Kishon
>
> On 03/01/2017 10:07 AM, Kishon Vijay Abraham I wrote:
>> Hi,
>>
>> On Tuesday 28 February 2017 01:51 PM, Alim Akhtar wrote:
>>> Hi Kishon,
>>>
>>> On 02/28/2017 09:04 AM, Kishon Vijay Abraham I wrote:
>>>> Hi,
>>>>
>>>> On Monday 27 February 2017 07:40 PM, Alim Akhtar wrote:
>>>>> Hi Kishon,
>>>>>
>>>>> On 02/27/2017 10:56 AM, Kishon Vijay Abraham I wrote:
>>>>>> Hi,
>>>>>>
>>>>>> On Thursday 23 February 2017 12:20 AM, Alim Akhtar wrote:
>>>>>>> On Fri, Feb 3, 2017 at 2:49 PM, Alim Akhtar <[email protected]> wrote:
>>>>>>>> Hi Kishon,
>>>>>>>>
>>>>>>>>
>>>>>>>> On 11/19/2015 07:09 PM, Kishon Vijay Abraham I wrote:
>>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> On Tuesday 17 November 2015 01:41 PM, Alim Akhtar wrote:
>>>>>>>>>>
>>>>>>>>>> Hi
>>>>>>>>>> Thanks again for looking into this.
>>>>>>>>>>
>>>>>>>>>> On 11/17/2015 11:46 AM, Kishon Vijay Abraham I wrote:
>>>>>>>>>>>
>>>>>>>>>>> Hi,
>>>>>>>>>>>
>>>>>>>>>>> On Monday 09 November 2015 10:56 AM, Alim Akhtar wrote:
>>>>>>>>>>>>
>>>>>>>>>>>> From: Seungwon Jeon <[email protected]>
>>>>>>>>>>>>
>>>>>>>>>>>> This patch introduces Exynos 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 <[email protected]>
>>>>>>>>>>>> Signed-off-by: Alim Akhtar <[email protected]>
>>>>>>>>>>>> Cc: Kishon Vijay Abraham I <[email protected]>
>>>>>>>>>>>> ---
>>>>>>>>>>>> drivers/phy/Kconfig | 7 ++
>>>>>>>>>>>> drivers/phy/Makefile | 1 +
>>>>>>>>>>>> drivers/phy/phy-exynos-ufs.c | 241
>>>>>>>>>>>> ++++++++++++++++++++++++++++++++++++
>>>>>>>>>>>> drivers/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>>>> drivers/phy/phy-exynos7-ufs.h | 89 +++++++++++++
>>>>>>>>>>>> include/linux/phy/phy-exynos-ufs.h | 85 +++++++++++++
>>>>>>>>>>>> 6 files changed, 508 insertions(+)
>>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos-ufs.h
>>>>>>>>>>>> create mode 100644 drivers/phy/phy-exynos7-ufs.h
>>>>>>>>>>>> create mode 100644 include/linux/phy/phy-exynos-ufs.h
>>>>>>>>>>>>
>>>>>>>>>>>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
>>>>>>>>>>>> index 7eb5859dd035..7d38a92e0297 100644
>>>>>>>>>>>> --- a/drivers/phy/Kconfig
>>>>>>>>>>>> +++ b/drivers/phy/Kconfig
>>>>>>>>>>>> @@ -389,4 +389,11 @@ config PHY_CYGNUS_PCIE
>>>>>>>>>>>> Enable this to support the Broadcom Cygnus PCIe PHY.
>>>>>>>>>>>> If unsure, say N.
>>>>>>>>>>>>
>>>>>>>>>>>> +config PHY_EXYNOS_UFS
>>>>>>>>>>>> + tristate "EXYNOS SoC series UFS PHY driver"
>>>>>>>>>>>> + depends on OF && ARCH_EXYNOS || COMPILE_TEST
>>>>>>>>>>>> + select GENERIC_PHY
>>>>>>>>>>>> + help
>>>>>>>>>>>> + Support for UFS PHY on Samsung EXYNOS chipsets.
>>>>>>>>>>>> +
>>>>>>>>>>>> endmenu
>>>>>>>>>>>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
>>>>>>>>>>>> index 075db1a81aa5..9bec4d1a89e1 100644
>>>>>>>>>>>> --- a/drivers/phy/Makefile
>>>>>>>>>>>> +++ b/drivers/phy/Makefile
>>>>>>>>>>>> @@ -10,6 +10,7 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) +=
>>>>>>>>>>>> phy-armada375-usb2.o
>>>>>>>>>>>> obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
>>>>>>>>>>>> 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_UFS) += phy-exynos-ufs.o
>>>>>>>>>>>> obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
>>>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o
>>>>>>>>>>>> obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o
>>>>>>>>>>>> diff --git a/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>>> b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000000..cb1aeaa3d4eb
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/drivers/phy/phy-exynos-ufs.c
>>>>>>>>>>>> @@ -0,0 +1,241 @@
>>>>>>>>>>>> +/*
>>>>>>>>>>>> + * UFS PHY driver for Samsung EXYNOS SoC
>>>>>>>>>>>> + *
>>>>>>>>>>>> + * Copyright (C) 2015 Samsung Electronics Co., Ltd.
>>>>>>>>>>>> + * Author: Seungwon Jeon <[email protected]>
>>>>>>>>>>>> + *
>>>>>>>>>>>> + * This program is free software; you can redistribute it and/or
>>>>>>>>>>>> modify
>>>>>>>>>>>> + * it under the terms of the GNU General Public License as published
>>>>>>>>>>>> by
>>>>>>>>>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>>>>>>>>>> + * (at your option) any later version.
>>>>>>>>>>>> + */
>>>>>>>>>>>> +#include <linux/clk.h>
>>>>>>>>>>>> +#include <linux/delay.h>
>>>>>>>>>>>> +#include <linux/err.h>
>>>>>>>>>>>> +#include <linux/io.h>
>>>>>>>>>>>> +#include <linux/iopoll.h>
>>>>>>>>>>>> +#include <linux/mfd/syscon.h>
>>>>>>>>>>>> +#include <linux/module.h>
>>>>>>>>>>>> +#include <linux/of.h>
>>>>>>>>>>>> +#include <linux/phy/phy.h>
>>>>>>>>>>>> +#include <linux/phy/phy-exynos-ufs.h>
>>>>>>>>>>>> +#include <linux/platform_device.h>
>>>>>>>>>>>> +#include <linux/regmap.h>
>>>>>>>>>>>> +
>>>>>>>>>>>> +#include "phy-exynos-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 exynos_ufs_phy_config(struct exynos_ufs_phy *phy,
>>>>>>>>>>>> + const struct exynos_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;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +static bool match_cfg_to_pwr_mode(u8 desc, u8 required_pwr)
>>>>>>>>>>>> +{
>>>>>>>>>>>> + if (IS_PWR_MODE_ANY(desc))
>>>>>>>>>>>> + return true;
>>>>>>>>>>>> +
>>>>>>>>>>>> + if (IS_PWR_MODE_HS(required_pwr) && IS_PWR_MODE_HS_ANY(desc))
>>>>>>>>>>>> + return true;
>>>>>>>>>>>> +
>>>>>>>>>>>> + if (COMP_PWR_MODE(required_pwr, desc))
>>>>>>>>>>>> + return true;
>>>>>>>>>>>> +
>>>>>>>>>>>> + if (COMP_PWR_MODE_MD(required_pwr, desc) &&
>>>>>>>>>>>> + COMP_PWR_MODE_GEAR(required_pwr, desc) &&
>>>>>>>>>>>> + COMP_PWR_MODE_SER(required_pwr, desc))
>>>>>>>>>>>> + return true;
>>>>>>>>>>>> +
>>>>>>>>>>>> + return false;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +int exynos_ufs_phy_calibrate(struct phy *phy,
>>>>>>>>>>>> + enum phy_cfg_tag tag, u8 pwr)
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> This is similar to the first version of your patch without
>>>>>>>>>>> EXPORT_SYMBOL.
>>>>>>>>>>>
>>>>>>>>>>> I think you have to create a new generic PHY_OPS for calibrate PHY while
>>>>>>>>>>> making
>>>>>>>>>>> sure that it is as generic as possible (which means calibrate_phy
>>>>>>>>>>> shouldn't
>>>>>>>>>>> have tag and pwr arguments or a strong justification as to why those
>>>>>>>>>>> arguments
>>>>>>>>>>> are required in a generic API).
>>>>>>>>>>
>>>>>>>>>> I don't see the advantage to making this a generic phy_ops, this is
>>>>>>>>>> exynos
>>>>>>>>>> specific ufs-phy, please have a look at other implementations
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> only the implementation is specific to exynos. I've seen lot of other
>>>>>>>>> vendors
>>>>>>>>> want to do something like calibrate phy.
>>>>>>>>>
>>>>>>>>> So if we add something like (*calibrate)(struct phy *phy), then it can be
>>>>>>>>> used
>>>>>>>>> by others as well. Russell King also want to minimize the code to program
>>>>>>>>> calibration settings. So it would be good to come up with a set of
>>>>>>>>> standard
>>>>>>>>> bindings like 'phy,tx-swing', 'phy,emphasis', 'phy,amplitude' etc.. to
>>>>>>>>> program
>>>>>>>>> these settings.
>>>>>>>>>>
>>>>>>>>>> drivers/phy/phy-qcom-ufs.c (which I belive mereged recently)
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Thats why I hate when someone else merge PHY drivers :-( That driver can
>>>>>>>>> as
>>>>>>>>> well be in drivers/misc as it doesn't use PHY framework as it is supposed
>>>>>>>>> to be
>>>>>>>>> used. It just exports a dozen of API's to be used by controller drivers.
>>>>>>>>> ick..
>>>>>>>>>
>>>>>>>>>> may be other vendors might come with there own implementation of phy.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> right, it's all about providing the correct callback functions.
>>>>>>>>>>
>>>>>>>>>> I am using what is currently provided by the generic phy framework.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> I think for your use case, what is currently provided in the PHY framework
>>>>>>>>> is
>>>>>>>>> not sufficient.
>>>>>>>>>
>>>>>>>> Its little over a year since last time we discuss about adding a generic
>>>>>>>> calibration API. I can see in the past people tried adding *calibration* API
>>>>>>>> [1] but not sure why [1] was not landed in mainline.
>>>>>>>> Anyway now we have many users of phy_calibration API, like UFS, USB and may
>>>>>>>> be PCIe, there is a real need to add this functionality. So, here is my
>>>>>>>> approach:
>>>>>>
>>>>>> Agree, there are quite a few users that require calibration of phy parameters.
>>>>>> I think previously it was accommodated in phy_init, hence it was not merged.
>>>>> Ok, thanks for this information.
>>>>>
>>>>>>>> * Along with [1], we can add a void *priv for handling device specific phy
>>>>>>>> private data, and before calling phy_calibration() from phy consumer,
>>>>>>>> phy->priv is populated with private data.
>>>>>>
>>>>>> Not sure how you plan to use priv here?
>>>>>>
>>>>> From ufs driver I am populating PHY _priv_ data and calling phy_calibrate()
>>>>>
>>>>> e.g
>>>>> ----------------------- from ufs-exynos.c
>>>>> Instead of using below code earlier
>>>>> - exynos_ufs_phy_calibrate(generic_phy,CFG_PRE_INIT,PWR_MODE_ANY);
>>>>>
>>>>> Now I am using below from ufs-exynos driver
>>>>>
>>>>> + generic_phy->priv =(void*)CFG_PRE_INIT;
>>>>> + phy_calibrate(generic_phy);
>>>>>
>>>>> and in drivers/phy/phy-exynos-ufs.c
>>>>> using phy->priv in calibration function.
>>>>
>>>> Don't prefer passing of such private pointers between drivers. Why is this needed?
>>>>
>>> As already explained before, this is needed to pass the calibration
>>> point (when you want to do the calibration?, like before init, after
>>> init or before/after _mode_ change etc).
>>>
>>> I Don't think we have much option here, if we want to make
>>> phy_calibration generic enough.
>>>
>>> We have few options:
>>> 1> One way is to have phy_calibration takes some argument like
>>> calibration point (which was part of my v3~v5), but looking at the
>>> current implementation of phy-qcom-ufs.c, this approach might not be
>>> generic enough.
>>>
>>> 2> And using EXPORT_SYMBOL way is not encourage (as in my V1, even
>>> though others in phy drivers uses it).
>>>
>>> 3> the current proposal of using _priv_ data.
>>>
>>> Out of the above 3 option, I feel using _priv_ data is more generic way
>>> (and most of the major sub-system in Linux uses it).
>>>
>>> lets see what other people think about __priv__ approach.
>>>
>>> Please suggest your prefer way to handle this.
>>
>> Generally calibration has to be done at a single point. Having to do
>> calibration in multiple places seems to be specific to Exynos.
>>
> Ok, calibration as such in not Exynos specific, calibration point may be.
>> Can implementing a small state machine in exynos driver help?
>>
> Sorry I didn't get you here. What do mean by implement a state machine?
> when it can be easy handled with the proposed generic _calibration_
> method. Why to complicate the thing?

I meant implementing a state machine instead of passing the _priv_ pointer in
calibrate ops. IIUC the _priv_ pointer is used to perform different settings
based on different calibration points? Instead of _priv_ pointer, maintain a
state machine within the driver and based on the current state, the calibrate
ops should perform different settings,
>
> You said, you "don't prefer passing a _priv_ pointer between driver",
> can you please explain why? what is the potential problem do you see
> with _priv_ pointer?

It's easy to start abusing such API's. One driver can pass an address in it's
address space to be written by another driver. Moreover it's better to have
clearly defined API's and arguments so that it's easier to review/test/debug.

>
> Look at the current implementation of
> "drivers/phy/phy-qcom-ufs-qmp-14nm.c" they have used "specific ops for
> Phy" and that too with the EXPORT_SYMBOL, don't you think this can be
> correct if we have a generic phy_calibration call back?

yes, that has to be cleaned up as well.

Thanks
Kishon