diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-07-23 09:52:10 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-07-23 09:52:10 +0200 |
commit | 1859a772e2744da8d4ddb987e010541e312adf69 (patch) | |
tree | 33b27932e8a754221e69477c5ed1cfc47eb1baee /drivers/phy/samsung | |
parent | 4e74eeb27e2873e923fee883df64341dae9ecdbb (diff) | |
parent | 3d7b0ca5300bd01b176f2b4c10e173db802560d8 (diff) | |
download | lwn-1859a772e2744da8d4ddb987e010541e312adf69.tar.gz lwn-1859a772e2744da8d4ddb987e010541e312adf69.zip |
Merge tag 'phy-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy into char-misc-next
Vinod writes:
phy for 5.9
- New PHY Drivers:
- Samsung UFS
- Qcom USB DWC for ipq806x
- Xilinx ZynqMP Gigabit Transceiver
- Qcom USB QMP for IPQ8074
- BCM63xx USBH
- Removed:
- Qcom ufs qmp phy driver
- Updates:
- Support for Qcom SM8250 QMP V4 USB3 UNIPHY
- qcom-snps runtime pm support
- Cleanup of W=1 warns in the subsystem
* tag 'phy-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy: (46 commits)
phy: qualcomm: fix setting of tx_deamp_3_5db when device property read fails
phy: bcm63xx-usbh: Add BCM63xx USBH driver
dt-bindings: phy: add bcm63xx-usbh bindings
phy: armada-38x: fix NETA lockup when repeatedly switching speeds
dt: update Marvell Armada 38x COMPHY binding
phy: samsung-ufs: Fix IS_ERR argument
dt-bindings: phy: renesas,usb3-phy: Add r8a774e1 support
dt-bindings: phy: renesas,usb2-phy: Add r8a774e1 support
phy: renesas: rcar-gen3-usb2: exit if request_irq() failed
phy: renesas: rcar-gen3-usb2: move irq registration to init
devicetree: bindings: phy: Document ipq806x dwc3 qcom phy
phy: qualcomm: add qcom ipq806x dwc usb phy driver
phy: samsung-ufs: add UFS PHY driver for samsung SoC
dt-bindings: phy: Document Samsung UFS PHY bindings
phy: sun4i-usb: explicitly include gpio/consumer.h
phy: stm32: use NULL instead of zero
phy: exynos5-usbdrd: use correct format for structure description
phy: rockchip-typec: use correct format for structure description
phy: xgene: remove unsigned integer comparison with less than zero
phy: mapphone-mdm6600: Add missing description for some structure fields
...
Diffstat (limited to 'drivers/phy/samsung')
-rw-r--r-- | drivers/phy/samsung/Kconfig | 17 | ||||
-rw-r--r-- | drivers/phy/samsung/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-dp-video.c | 4 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-mipi-video.c | 4 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-pcie.c | 2 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos5-usbdrd.c | 16 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos7-ufs.h | 81 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-samsung-ufs.c | 366 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-samsung-ufs.h | 139 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-samsung-usb2.c | 2 |
10 files changed, 615 insertions, 17 deletions
diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig index 9e483d1fdaf2..e20d2fcc9fe7 100644 --- a/drivers/phy/samsung/Kconfig +++ b/drivers/phy/samsung/Kconfig @@ -3,23 +3,23 @@ # Phy drivers for Samsung platforms # config PHY_EXYNOS_DP_VIDEO - tristate "EXYNOS SoC series Display Port PHY driver" + tristate "Exynos SoC series Display Port PHY driver" depends on OF depends on ARCH_EXYNOS || COMPILE_TEST default ARCH_EXYNOS select GENERIC_PHY help - Support for Display Port PHY found on Samsung EXYNOS SoCs. + Support for Display Port PHY found on Samsung Exynos SoCs. config PHY_EXYNOS_MIPI_VIDEO - tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" + tristate "S5P/Exynos SoC series MIPI CSI-2/DSI PHY driver" depends on HAS_IOMEM depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST select GENERIC_PHY default y if ARCH_S5PV210 || ARCH_EXYNOS help Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P - and EXYNOS SoCs. + and Exynos SoCs. config PHY_EXYNOS_PCIE bool "Exynos PCIe PHY driver" @@ -29,6 +29,15 @@ config PHY_EXYNOS_PCIE Enable PCIe PHY support for Exynos SoC series. This driver provides PHY interface for Exynos PCIe controller. +config PHY_SAMSUNG_UFS + tristate "SAMSUNG SoC series UFS PHY driver" + depends on OF && (ARCH_EXYNOS || COMPILE_TEST) + select GENERIC_PHY + help + Enable this to support the Samsung UFS PHY driver for + Samsung SoCs. This driver provides the interface for UFS + host controller to do PHY related programming. + config PHY_SAMSUNG_USB2 tristate "Samsung USB 2.0 PHY driver" depends on HAS_IOMEM diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile index db9b1aa0de6e..3959100fe8a2 100644 --- a/drivers/phy/samsung/Makefile +++ b/drivers/phy/samsung/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o +obj-$(CONFIG_PHY_SAMSUNG_UFS) += phy-samsung-ufs.o obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o phy-exynos-usb2-y += phy-samsung-usb2.o phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o diff --git a/drivers/phy/samsung/phy-exynos-dp-video.c b/drivers/phy/samsung/phy-exynos-dp-video.c index 6c607df1dc9a..2b670ef91deb 100644 --- a/drivers/phy/samsung/phy-exynos-dp-video.c +++ b/drivers/phy/samsung/phy-exynos-dp-video.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Samsung EXYNOS SoC series Display Port PHY driver + * Samsung Exynos SoC series Display Port PHY driver * * Copyright (C) 2013 Samsung Electronics Co., Ltd. * Author: Jingoo Han <jg1.han@samsung.com> @@ -115,5 +115,5 @@ static struct platform_driver exynos_dp_video_phy_driver = { module_platform_driver(exynos_dp_video_phy_driver); MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); -MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver"); +MODULE_DESCRIPTION("Samsung Exynos SoC DP PHY driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c index bb51195f189f..c1df1ef3ee3c 100644 --- a/drivers/phy/samsung/phy-exynos-mipi-video.c +++ b/drivers/phy/samsung/phy-exynos-mipi-video.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * Samsung S5P/Exynos SoC series MIPI CSIS/DSIM DPHY driver * * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd. * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> @@ -364,6 +364,6 @@ static struct platform_driver exynos_mipi_video_phy_driver = { }; module_platform_driver(exynos_mipi_video_phy_driver); -MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver"); +MODULE_DESCRIPTION("Samsung S5P/Exynos SoC MIPI CSI-2/DSI PHY driver"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-exynos-pcie.c b/drivers/phy/samsung/phy-exynos-pcie.c index 659e7ae0a6cf..7e28b1aea0d1 100644 --- a/drivers/phy/samsung/phy-exynos-pcie.c +++ b/drivers/phy/samsung/phy-exynos-pcie.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Samsung EXYNOS SoC series PCIe PHY driver + * Samsung Exynos SoC series PCIe PHY driver * * Phy provider for PCIe controller on Exynos SoC series * diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index e510732afb8b..0d818b77a0d8 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Samsung EXYNOS5 SoC series USB DRD PHY driver + * Samsung Exynos5 SoC series USB DRD PHY driver * * Phy provider for USB 3.0 DRD controller on Exynos5 SoC series * @@ -33,7 +33,7 @@ #define EXYNOS5_FSEL_24MHZ 0x5 #define EXYNOS5_FSEL_50MHZ 0x7 -/* EXYNOS5: USB 3.0 DRD PHY registers */ +/* Exynos5: USB 3.0 DRD PHY registers */ #define EXYNOS5_DRD_LINKSYSTEM 0x04 #define LINKSYSTEM_FLADJ_MASK (0x3f << 1) @@ -180,14 +180,14 @@ struct exynos5_usbdrd_phy_drvdata { * @utmiclk: clock for utmi+ phy * @itpclk: clock for ITP generation * @drv_data: pointer to SoC level driver data structure - * @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY + * @phys: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY * instances each with its 'phy' and 'phy_cfg'. * @extrefclk: frequency select settings when using 'separate * reference clocks' for SS and HS operations * @ref_clk: reference clock to PHY block from which PHY's * operational clocks are derived - * vbus: VBUS regulator for phy - * vbus_boost: Boost regulator for VBUS present on few Exynos boards + * @vbus: VBUS regulator for phy + * @vbus_boost: Boost regulator for VBUS present on few Exynos boards */ struct exynos5_usbdrd_phy { struct device *dev; @@ -714,7 +714,9 @@ static int exynos5_usbdrd_phy_calibrate(struct phy *phy) struct phy_usb_instance *inst = phy_get_drvdata(phy); struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - return exynos5420_usbdrd_phy_calibrate(phy_drd); + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) + return exynos5420_usbdrd_phy_calibrate(phy_drd); + return 0; } static const struct phy_ops exynos5_usbdrd_phy_ops = { @@ -958,7 +960,7 @@ static struct platform_driver exynos5_usb3drd_phy = { }; module_platform_driver(exynos5_usb3drd_phy); -MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver"); +MODULE_DESCRIPTION("Samsung Exynos5 SoCs USB 3.0 DRD controller PHY driver"); MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:exynos5_usb3drd_phy"); diff --git a/drivers/phy/samsung/phy-exynos7-ufs.h b/drivers/phy/samsung/phy-exynos7-ufs.h new file mode 100644 index 000000000000..518923141958 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos7-ufs.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * UFS PHY driver data for Samsung EXYNOS7 SoC + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + */ +#ifndef _PHY_EXYNOS7_UFS_H_ +#define _PHY_EXYNOS7_UFS_H_ + +#include "phy-samsung-ufs.h" + +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL 0x720 +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK 0x1 +#define EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN BIT(0) + +/* Calibration for phy initialization */ +static const struct samsung_ufs_phy_cfg exynos7_pre_init_cfg[] = { + PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x017, 0x84, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x035, 0x58, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x037, 0x40, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x03b, 0x83, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x04c, 0x5b, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_ANY), + PHY_TRSV_REG_CFG(0x05c, 0x14, PWR_MODE_ANY), + END_UFS_PHY_CFG +}; + +/* Calibration for HS mode series A/B */ +static const struct samsung_ufs_phy_cfg exynos7_pre_pwr_hs_cfg[] = { + PHY_COMN_REG_CFG(0x00f, 0xfa, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x010, 0x82, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x011, 0x1e, PWR_MODE_HS_ANY), + /* Setting order: 1st(0x16, 2nd(0x15) */ + PHY_COMN_REG_CFG(0x016, 0xff, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x015, 0x80, PWR_MODE_HS_ANY), + PHY_COMN_REG_CFG(0x017, 0x94, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x036, 0x32, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x037, 0x43, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x038, 0x3f, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x042, 0x88, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x042, 0xbb, PWR_MODE_HS_G2_SER_B), + PHY_TRSV_REG_CFG(0x043, 0xa6, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x048, 0x74, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x034, 0x35, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x034, 0x36, PWR_MODE_HS_G2_SER_B), + PHY_TRSV_REG_CFG(0x035, 0x5b, PWR_MODE_HS_G2_SER_A), + PHY_TRSV_REG_CFG(0x035, 0x5c, PWR_MODE_HS_G2_SER_B), + END_UFS_PHY_CFG +}; + +/* Calibration for HS mode series A/B atfer PMC */ +static const struct samsung_ufs_phy_cfg exynos7_post_pwr_hs_cfg[] = { + PHY_COMN_REG_CFG(0x015, 0x00, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG(0x04d, 0x83, PWR_MODE_HS_ANY), + END_UFS_PHY_CFG +}; + +static const struct samsung_ufs_phy_cfg *exynos7_ufs_phy_cfgs[CFG_TAG_MAX] = { + [CFG_PRE_INIT] = exynos7_pre_init_cfg, + [CFG_PRE_PWR_HS] = exynos7_pre_pwr_hs_cfg, + [CFG_POST_PWR_HS] = exynos7_post_pwr_hs_cfg, +}; + +static struct samsung_ufs_phy_drvdata exynos7_ufs_phy = { + .cfg = exynos7_ufs_phy_cfgs, + .isol = { + .offset = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL, + .mask = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_MASK, + .en = EXYNOS7_EMBEDDED_COMBO_PHY_CTRL_EN, + }, + .has_symbol_clk = 1, +}; + +#endif /* _PHY_EXYNOS7_UFS_H_ */ diff --git a/drivers/phy/samsung/phy-samsung-ufs.c b/drivers/phy/samsung/phy-samsung-ufs.c new file mode 100644 index 000000000000..9832599a0283 --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-ufs.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UFS PHY driver for Samsung SoC + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * Author: Seungwon Jeon <essuuj@gmail.com> + * Author: Alim Akhtar <alim.akhtar@samsung.com> + * + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "phy-samsung-ufs.h" + +#define for_each_phy_lane(phy, i) \ + for (i = 0; i < (phy)->lane_cnt; i++) +#define for_each_phy_cfg(cfg) \ + for (; (cfg)->id; (cfg)++) + +#define PHY_DEF_LANE_CNT 1 + +static void samsung_ufs_phy_config(struct samsung_ufs_phy *phy, + const struct samsung_ufs_phy_cfg *cfg, + u8 lane) +{ + enum {LANE_0, LANE_1}; /* lane index */ + + switch (lane) { + case LANE_0: + writel(cfg->val, (phy)->reg_pma + cfg->off_0); + break; + case LANE_1: + if (cfg->id == PHY_TRSV_BLK) + writel(cfg->val, (phy)->reg_pma + cfg->off_1); + break; + } +} + +static int samsung_ufs_phy_wait_for_lock_acq(struct phy *phy) +{ + struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); + const unsigned int timeout_us = 100000; + const unsigned int sleep_us = 10; + u32 val; + int err; + + err = readl_poll_timeout( + ufs_phy->reg_pma + PHY_APB_ADDR(PHY_PLL_LOCK_STATUS), + val, (val & PHY_PLL_LOCK_BIT), sleep_us, timeout_us); + if (err) { + dev_err(ufs_phy->dev, + "failed to get phy pll lock acquisition %d\n", err); + goto out; + } + + err = readl_poll_timeout( + ufs_phy->reg_pma + PHY_APB_ADDR(PHY_CDR_LOCK_STATUS), + val, (val & PHY_CDR_LOCK_BIT), sleep_us, timeout_us); + if (err) + dev_err(ufs_phy->dev, + "failed to get phy cdr lock acquisition %d\n", err); +out: + return err; +} + +static int samsung_ufs_phy_calibrate(struct phy *phy) +{ + struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); + struct samsung_ufs_phy_cfg **cfgs = ufs_phy->cfg; + const struct samsung_ufs_phy_cfg *cfg; + int err = 0; + int i; + + if (unlikely(ufs_phy->ufs_phy_state < CFG_PRE_INIT || + ufs_phy->ufs_phy_state >= CFG_TAG_MAX)) { + dev_err(ufs_phy->dev, "invalid phy config index %d\n", ufs_phy->ufs_phy_state); + return -EINVAL; + } + + cfg = cfgs[ufs_phy->ufs_phy_state]; + if (!cfg) + goto out; + + for_each_phy_cfg(cfg) { + for_each_phy_lane(ufs_phy, i) { + samsung_ufs_phy_config(ufs_phy, cfg, i); + } + } + + if (ufs_phy->ufs_phy_state == CFG_POST_PWR_HS) + err = samsung_ufs_phy_wait_for_lock_acq(phy); + + /** + * In Samsung ufshci, PHY need to be calibrated at different + * stages / state mainly before Linkstartup, after Linkstartup, + * before power mode change and after power mode change. + * Below state machine to make sure to calibrate PHY in each + * state. Here after configuring PHY in a given state, will + * change the state to next state so that next state phy + * calibration value can be programed + */ +out: + switch (ufs_phy->ufs_phy_state) { + case CFG_PRE_INIT: + ufs_phy->ufs_phy_state = CFG_POST_INIT; + break; + case CFG_POST_INIT: + ufs_phy->ufs_phy_state = CFG_PRE_PWR_HS; + break; + case CFG_PRE_PWR_HS: + ufs_phy->ufs_phy_state = CFG_POST_PWR_HS; + break; + case CFG_POST_PWR_HS: + /* Change back to INIT state */ + ufs_phy->ufs_phy_state = CFG_PRE_INIT; + break; + default: + dev_err(ufs_phy->dev, "wrong state for phy calibration\n"); + } + + return err; +} + +static int samsung_ufs_phy_symbol_clk_init(struct samsung_ufs_phy *phy) +{ + int ret; + + phy->tx0_symbol_clk = devm_clk_get(phy->dev, "tx0_symbol_clk"); + if (IS_ERR(phy->tx0_symbol_clk)) { + dev_err(phy->dev, "failed to get tx0_symbol_clk clock\n"); + return PTR_ERR(phy->tx0_symbol_clk); + } + + phy->rx0_symbol_clk = devm_clk_get(phy->dev, "rx0_symbol_clk"); + if (IS_ERR(phy->rx0_symbol_clk)) { + dev_err(phy->dev, "failed to get rx0_symbol_clk clock\n"); + return PTR_ERR(phy->rx0_symbol_clk); + } + + phy->rx1_symbol_clk = devm_clk_get(phy->dev, "rx1_symbol_clk"); + if (IS_ERR(phy->rx1_symbol_clk)) { + dev_err(phy->dev, "failed to get rx1_symbol_clk clock\n"); + return PTR_ERR(phy->rx1_symbol_clk); + } + + ret = clk_prepare_enable(phy->tx0_symbol_clk); + if (ret) { + dev_err(phy->dev, "%s: tx0_symbol_clk enable failed %d\n", __func__, ret); + goto out; + } + + ret = clk_prepare_enable(phy->rx0_symbol_clk); + if (ret) { + dev_err(phy->dev, "%s: rx0_symbol_clk enable failed %d\n", __func__, ret); + goto out_disable_tx0_clk; + } + + ret = clk_prepare_enable(phy->rx1_symbol_clk); + if (ret) { + dev_err(phy->dev, "%s: rx1_symbol_clk enable failed %d\n", __func__, ret); + goto out_disable_rx0_clk; + } + + return 0; + +out_disable_rx0_clk: + clk_disable_unprepare(phy->rx0_symbol_clk); +out_disable_tx0_clk: + clk_disable_unprepare(phy->tx0_symbol_clk); +out: + return ret; +} + +static int samsung_ufs_phy_clks_init(struct samsung_ufs_phy *phy) +{ + int ret; + + phy->ref_clk = devm_clk_get(phy->dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) + dev_err(phy->dev, "failed to get ref_clk clock\n"); + + ret = clk_prepare_enable(phy->ref_clk); + if (ret) { + dev_err(phy->dev, "%s: ref_clk enable failed %d\n", __func__, ret); + return ret; + } + + dev_dbg(phy->dev, "UFS MPHY ref_clk_rate = %ld\n", clk_get_rate(phy->ref_clk)); + + return 0; +} + +static int samsung_ufs_phy_init(struct phy *phy) +{ + struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); + int ret; + + ss_phy->lane_cnt = phy->attrs.bus_width; + ss_phy->ufs_phy_state = CFG_PRE_INIT; + + if (ss_phy->drvdata->has_symbol_clk) { + ret = samsung_ufs_phy_symbol_clk_init(ss_phy); + if (ret) + dev_err(ss_phy->dev, "failed to set ufs phy symbol clocks\n"); + } + + ret = samsung_ufs_phy_clks_init(ss_phy); + if (ret) + dev_err(ss_phy->dev, "failed to set ufs phy clocks\n"); + + ret = samsung_ufs_phy_calibrate(phy); + if (ret) + dev_err(ss_phy->dev, "ufs phy calibration failed\n"); + + return ret; +} + +static int samsung_ufs_phy_power_on(struct phy *phy) +{ + struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); + + samsung_ufs_phy_ctrl_isol(ss_phy, false); + return 0; +} + +static int samsung_ufs_phy_power_off(struct phy *phy) +{ + struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); + + samsung_ufs_phy_ctrl_isol(ss_phy, true); + return 0; +} + +static int samsung_ufs_phy_set_mode(struct phy *generic_phy, + enum phy_mode mode, int submode) +{ + struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(generic_phy); + + ss_phy->mode = PHY_MODE_INVALID; + + if (mode > 0) + ss_phy->mode = mode; + + return 0; +} + +static int samsung_ufs_phy_exit(struct phy *phy) +{ + struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); + + clk_disable_unprepare(ss_phy->ref_clk); + + if (ss_phy->drvdata->has_symbol_clk) { + clk_disable_unprepare(ss_phy->tx0_symbol_clk); + clk_disable_unprepare(ss_phy->rx0_symbol_clk); + clk_disable_unprepare(ss_phy->rx1_symbol_clk); + } + + return 0; +} + +static struct phy_ops samsung_ufs_phy_ops = { + .init = samsung_ufs_phy_init, + .exit = samsung_ufs_phy_exit, + .power_on = samsung_ufs_phy_power_on, + .power_off = samsung_ufs_phy_power_off, + .calibrate = samsung_ufs_phy_calibrate, + .set_mode = samsung_ufs_phy_set_mode, + .owner = THIS_MODULE, +}; + +static const struct of_device_id samsung_ufs_phy_match[]; + +static int samsung_ufs_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct samsung_ufs_phy *phy; + struct phy *gen_phy; + struct phy_provider *phy_provider; + const struct samsung_ufs_phy_drvdata *drvdata; + int err = 0; + + match = of_match_node(samsung_ufs_phy_match, dev->of_node); + if (!match) { + err = -EINVAL; + dev_err(dev, "failed to get match_node\n"); + goto out; + } + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + err = -ENOMEM; + goto out; + } + + phy->reg_pma = devm_platform_ioremap_resource_byname(pdev, "phy-pma"); + if (IS_ERR(phy->reg_pma)) { + err = PTR_ERR(phy->reg_pma); + goto out; + } + + phy->reg_pmu = syscon_regmap_lookup_by_phandle( + dev->of_node, "samsung,pmu-syscon"); + if (IS_ERR(phy->reg_pmu)) { + err = PTR_ERR(phy->reg_pmu); + dev_err(dev, "failed syscon remap for pmu\n"); + goto out; + } + + gen_phy = devm_phy_create(dev, NULL, &samsung_ufs_phy_ops); + if (IS_ERR(gen_phy)) { + err = PTR_ERR(gen_phy); + dev_err(dev, "failed to create PHY for ufs-phy\n"); + goto out; + } + + drvdata = match->data; + phy->dev = dev; + phy->drvdata = drvdata; + phy->cfg = (struct samsung_ufs_phy_cfg **)drvdata->cfg; + phy->isol = &drvdata->isol; + phy->lane_cnt = PHY_DEF_LANE_CNT; + + phy_set_drvdata(gen_phy, phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + err = PTR_ERR(phy_provider); + dev_err(dev, "failed to register phy-provider\n"); + goto out; + } +out: + return err; +} + +static const struct of_device_id samsung_ufs_phy_match[] = { + { + .compatible = "samsung,exynos7-ufs-phy", + .data = &exynos7_ufs_phy, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_ufs_phy_match); + +static struct platform_driver samsung_ufs_phy_driver = { + .probe = samsung_ufs_phy_probe, + .driver = { + .name = "samsung-ufs-phy", + .of_match_table = samsung_ufs_phy_match, + }, +}; +module_platform_driver(samsung_ufs_phy_driver); +MODULE_DESCRIPTION("Samsung SoC UFS PHY Driver"); +MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>"); +MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-samsung-ufs.h b/drivers/phy/samsung/phy-samsung-ufs.h new file mode 100644 index 000000000000..5de78710524c --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-ufs.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * UFS PHY driver for Samsung EXYNOS SoC + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * Author: Seungwon Jeon <essuuj@gmail.com> + * Author: Alim Akhtar <alim.akhtar@samsung.com> + * + */ +#ifndef _PHY_SAMSUNG_UFS_ +#define _PHY_SAMSUNG_UFS_ + +#define PHY_COMN_BLK 1 +#define PHY_TRSV_BLK 2 +#define END_UFS_PHY_CFG { 0 } +#define PHY_TRSV_CH_OFFSET 0x30 +#define PHY_APB_ADDR(off) ((off) << 2) + +#define PHY_COMN_REG_CFG(o, v, d) { \ + .off_0 = PHY_APB_ADDR((o)), \ + .off_1 = 0, \ + .val = (v), \ + .desc = (d), \ + .id = PHY_COMN_BLK, \ +} + +#define PHY_TRSV_REG_CFG(o, v, d) { \ + .off_0 = PHY_APB_ADDR((o)), \ + .off_1 = PHY_APB_ADDR((o) + PHY_TRSV_CH_OFFSET), \ + .val = (v), \ + .desc = (d), \ + .id = PHY_TRSV_BLK, \ +} + +/* UFS PHY registers */ +#define PHY_PLL_LOCK_STATUS 0x1e +#define PHY_CDR_LOCK_STATUS 0x5e + +#define PHY_PLL_LOCK_BIT BIT(5) +#define PHY_CDR_LOCK_BIT BIT(4) + +/* 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, + /* field mask */ + MD_MASK = 0x3, + SR_MASK = 0x3, + GR_MASK = 0x7, +}; + +#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(g, s, m) ((((g) & GR_MASK) << 4) |\ + (((s) & SR_MASK) << 2) | ((m) & MD_MASK)) +#define PWR_MODE_PWM_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_PWM) +#define PWR_MODE_HS(g, s) ((((g) & GR_MASK) << 4) |\ + (((s) & SR_MASK) << 2) | PWR_DESC_HS) +#define PWR_MODE_HS_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_HS) +#define PWR_MODE_ANY PWR_MODE(PWR_DESC_ANY,\ + PWR_DESC_ANY, PWR_DESC_ANY) +/* PHY calibration point/state */ +enum { + CFG_PRE_INIT, + CFG_POST_INIT, + CFG_PRE_PWR_HS, + CFG_POST_PWR_HS, + CFG_TAG_MAX, +}; + +struct samsung_ufs_phy_cfg { + u32 off_0; + u32 off_1; + u32 val; + u8 desc; + u8 id; +}; + +struct samsung_ufs_phy_drvdata { + const struct samsung_ufs_phy_cfg **cfg; + struct pmu_isol { + u32 offset; + u32 mask; + u32 en; + } isol; + bool has_symbol_clk; +}; + +struct samsung_ufs_phy { + struct device *dev; + void __iomem *reg_pma; + struct regmap *reg_pmu; + struct clk *ref_clk; + struct clk *ref_clk_parent; + struct clk *tx0_symbol_clk; + struct clk *rx0_symbol_clk; + struct clk *rx1_symbol_clk; + const struct samsung_ufs_phy_drvdata *drvdata; + struct samsung_ufs_phy_cfg **cfg; + const struct pmu_isol *isol; + u8 lane_cnt; + int ufs_phy_state; + enum phy_mode mode; +}; + +static inline struct samsung_ufs_phy *get_samsung_ufs_phy(struct phy *phy) +{ + return (struct samsung_ufs_phy *)phy_get_drvdata(phy); +} + +static inline void samsung_ufs_phy_ctrl_isol( + struct samsung_ufs_phy *phy, u32 isol) +{ + regmap_update_bits(phy->reg_pmu, phy->isol->offset, + phy->isol->mask, isol ? 0 : phy->isol->en); +} + +#include "phy-exynos7-ufs.h" + +#endif /* _PHY_SAMSUNG_UFS_ */ diff --git a/drivers/phy/samsung/phy-samsung-usb2.c b/drivers/phy/samsung/phy-samsung-usb2.c index 090aa02e02de..a3ed3ff04690 100644 --- a/drivers/phy/samsung/phy-samsung-usb2.c +++ b/drivers/phy/samsung/phy-samsung-usb2.c @@ -255,7 +255,7 @@ static struct platform_driver samsung_usb2_phy_driver = { }; module_platform_driver(samsung_usb2_phy_driver); -MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); +MODULE_DESCRIPTION("Samsung S5P/Exynos SoC USB PHY driver"); MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:samsung-usb2-phy"); |