-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
phy: usb: Add USB2.0 phy driver for Sunplus SP7021
Add USB2.0 phy driver for Sunplus SP7021 Signed-off-by: Vincent Shih <vincent.sunplus@gmail.com> Link: https://lore.kernel.org/r/1658717052-26142-2-git-send-email-vincent.sunplus@gmail.com [vkoul: remove trailing line in driver file] Signed-off-by: Vinod Koul <vkoul@kernel.org>
- Loading branch information
Vincent Shih
authored and
Vinod Koul
committed
Sep 2, 2022
1 parent
c8c5d5e
commit 99d9ccd
Showing
6 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ obj-y += allwinner/ \ | |
samsung/ \ | ||
socionext/ \ | ||
st/ \ | ||
sunplus/ \ | ||
tegra/ \ | ||
ti/ \ | ||
xilinx/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# SPDX-License-Identifier: GPL-2.0-only | ||
|
||
config PHY_SUNPLUS_USB | ||
tristate "Sunplus SP7021 USB 2.0 PHY driver" | ||
depends on OF && (SOC_SP7021 || COMPILE_TEST) | ||
select GENERIC_PHY | ||
help | ||
Enable this to support the USB 2.0 PHY on Sunplus SP7021 | ||
SoC. The USB 2.0 PHY controller supports battery charger | ||
and synchronous signals, various power down modes including | ||
operating, partial and suspend modes, and high-speed, | ||
full-speed and low-speed data transfer. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# SPDX-License-Identifier: GPL-2.0-only | ||
obj-$(CONFIG_PHY_SUNPLUS_USB) += phy-sunplus-usb2.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
/* | ||
* Sunplus SP7021 USB 2.0 phy driver | ||
* | ||
* Copyright (C) 2022 Sunplus Technology Inc., All rights reserved. | ||
* | ||
* Note 1 : non-posted write command for the registers accesses of | ||
* Sunplus SP7021. | ||
* | ||
*/ | ||
|
||
#include <linux/bitfield.h> | ||
#include <linux/clk.h> | ||
#include <linux/delay.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/nvmem-consumer.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/phy/phy.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/reset.h> | ||
|
||
#define HIGH_MASK_BITS GENMASK(31, 16) | ||
#define LOW_MASK_BITS GENMASK(15, 0) | ||
#define OTP_DISC_LEVEL_DEFAULT 0xd | ||
|
||
/* GROUP UPHY */ | ||
#define CONFIG1 0x4 | ||
#define J_HS_TX_PWRSAV BIT(5) | ||
#define CONFIG3 0xc | ||
#define J_FORCE_DISC_ON BIT(5) | ||
#define J_DEBUG_CTRL_ADDR_MACRO BIT(0) | ||
#define CONFIG7 0x1c | ||
#define J_DISC 0X1f | ||
#define CONFIG9 0x24 | ||
#define J_ECO_PATH BIT(6) | ||
#define CONFIG16 0x40 | ||
#define J_TBCWAIT_MASK GENMASK(6, 5) | ||
#define J_TBCWAIT_1P1_MS FIELD_PREP(J_TBCWAIT_MASK, 0) | ||
#define J_TVDM_SRC_DIS_MASK GENMASK(4, 3) | ||
#define J_TVDM_SRC_DIS_8P2_MS FIELD_PREP(J_TVDM_SRC_DIS_MASK, 3) | ||
#define J_TVDM_SRC_EN_MASK GENMASK(2, 1) | ||
#define J_TVDM_SRC_EN_1P6_MS FIELD_PREP(J_TVDM_SRC_EN_MASK, 0) | ||
#define J_BC_EN BIT(0) | ||
#define CONFIG17 0x44 | ||
#define IBG_TRIM0_MASK GENMASK(7, 5) | ||
#define IBG_TRIM0_SSLVHT FIELD_PREP(IBG_TRIM0_MASK, 4) | ||
#define J_VDATREE_TRIM_MASK GENMASK(4, 1) | ||
#define J_VDATREE_TRIM_DEFAULT FIELD_PREP(J_VDATREE_TRIM_MASK, 9) | ||
#define CONFIG23 0x5c | ||
#define PROB_MASK GENMASK(5, 3) | ||
#define PROB FIELD_PREP(PROB_MASK, 7) | ||
|
||
/* GROUP MOON4 */ | ||
#define UPHY_CONTROL0 0x0 | ||
#define UPHY_CONTROL1 0x4 | ||
#define UPHY_CONTROL2 0x8 | ||
#define MO1_UPHY_RX_CLK_SEL BIT(6) | ||
#define MASK_MO1_UPHY_RX_CLK_SEL BIT(6 + 16) | ||
#define UPHY_CONTROL3 0xc | ||
#define MO1_UPHY_PLL_POWER_OFF_SEL BIT(7) | ||
#define MASK_MO1_UPHY_PLL_POWER_OFF_SEL BIT(7 + 16) | ||
#define MO1_UPHY_PLL_POWER_OFF BIT(3) | ||
#define MASK_UPHY_PLL_POWER_OFF BIT(3 + 16) | ||
|
||
struct sp_usbphy { | ||
struct device *dev; | ||
struct resource *phy_res_mem; | ||
struct resource *moon4_res_mem; | ||
struct reset_control *rstc; | ||
struct clk *phy_clk; | ||
void __iomem *phy_regs; | ||
void __iomem *moon4_regs; | ||
u32 disc_vol_addr_off; | ||
}; | ||
|
||
static int update_disc_vol(struct sp_usbphy *usbphy) | ||
{ | ||
struct nvmem_cell *cell; | ||
char *disc_name = "disc_vol"; | ||
ssize_t otp_l = 0; | ||
char *otp_v; | ||
u32 val, set; | ||
|
||
cell = nvmem_cell_get(usbphy->dev, disc_name); | ||
if (IS_ERR_OR_NULL(cell)) { | ||
if (PTR_ERR(cell) == -EPROBE_DEFER) | ||
return -EPROBE_DEFER; | ||
} | ||
|
||
otp_v = nvmem_cell_read(cell, &otp_l); | ||
nvmem_cell_put(cell); | ||
|
||
if (otp_v) { | ||
set = *(otp_v + 1); | ||
set = (set << (sizeof(char) * 8)) | *otp_v; | ||
set = (set >> usbphy->disc_vol_addr_off) & J_DISC; | ||
} | ||
|
||
if (!otp_v || set == 0) | ||
set = OTP_DISC_LEVEL_DEFAULT; | ||
|
||
val = readl(usbphy->phy_regs + CONFIG7); | ||
val = (val & ~J_DISC) | set; | ||
writel(val, usbphy->phy_regs + CONFIG7); | ||
|
||
return 0; | ||
} | ||
|
||
static int sp_uphy_init(struct phy *phy) | ||
{ | ||
struct sp_usbphy *usbphy = phy_get_drvdata(phy); | ||
u32 val; | ||
int ret; | ||
|
||
ret = clk_prepare_enable(usbphy->phy_clk); | ||
if (ret) | ||
goto err_clk; | ||
|
||
ret = reset_control_deassert(usbphy->rstc); | ||
if (ret) | ||
goto err_reset; | ||
|
||
/* Default value modification */ | ||
writel(HIGH_MASK_BITS | 0x4002, usbphy->moon4_regs + UPHY_CONTROL0); | ||
writel(HIGH_MASK_BITS | 0x8747, usbphy->moon4_regs + UPHY_CONTROL1); | ||
|
||
/* disconnect voltage */ | ||
ret = update_disc_vol(usbphy); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* board uphy 0 internal register modification for tid certification */ | ||
val = readl(usbphy->phy_regs + CONFIG9); | ||
val &= ~(J_ECO_PATH); | ||
writel(val, usbphy->phy_regs + CONFIG9); | ||
|
||
val = readl(usbphy->phy_regs + CONFIG1); | ||
val &= ~(J_HS_TX_PWRSAV); | ||
writel(val, usbphy->phy_regs + CONFIG1); | ||
|
||
val = readl(usbphy->phy_regs + CONFIG23); | ||
val = (val & ~PROB) | PROB; | ||
writel(val, usbphy->phy_regs + CONFIG23); | ||
|
||
/* port 0 uphy clk fix */ | ||
writel(MASK_MO1_UPHY_RX_CLK_SEL | MO1_UPHY_RX_CLK_SEL, | ||
usbphy->moon4_regs + UPHY_CONTROL2); | ||
|
||
/* battery charger */ | ||
writel(J_TBCWAIT_1P1_MS | J_TVDM_SRC_DIS_8P2_MS | J_TVDM_SRC_EN_1P6_MS | J_BC_EN, | ||
usbphy->phy_regs + CONFIG16); | ||
writel(IBG_TRIM0_SSLVHT | J_VDATREE_TRIM_DEFAULT, usbphy->phy_regs + CONFIG17); | ||
|
||
/* chirp mode */ | ||
writel(J_FORCE_DISC_ON | J_DEBUG_CTRL_ADDR_MACRO, usbphy->phy_regs + CONFIG3); | ||
|
||
return 0; | ||
|
||
err_reset: | ||
reset_control_assert(usbphy->rstc); | ||
err_clk: | ||
clk_disable_unprepare(usbphy->phy_clk); | ||
|
||
return ret; | ||
} | ||
|
||
static int sp_uphy_power_on(struct phy *phy) | ||
{ | ||
struct sp_usbphy *usbphy = phy_get_drvdata(phy); | ||
u32 pll_pwr_on, pll_pwr_off; | ||
|
||
/* PLL power off/on twice */ | ||
pll_pwr_off = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) | ||
| MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF; | ||
pll_pwr_on = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) | ||
| MO1_UPHY_PLL_POWER_OFF_SEL; | ||
|
||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
mdelay(1); | ||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
mdelay(1); | ||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
mdelay(1); | ||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
mdelay(1); | ||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
|
||
return 0; | ||
} | ||
|
||
static int sp_uphy_power_off(struct phy *phy) | ||
{ | ||
struct sp_usbphy *usbphy = phy_get_drvdata(phy); | ||
u32 pll_pwr_off; | ||
|
||
pll_pwr_off = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS) | ||
| MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF; | ||
|
||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
mdelay(1); | ||
writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0, | ||
usbphy->moon4_regs + UPHY_CONTROL3); | ||
|
||
return 0; | ||
} | ||
|
||
static int sp_uphy_exit(struct phy *phy) | ||
{ | ||
struct sp_usbphy *usbphy = phy_get_drvdata(phy); | ||
|
||
reset_control_assert(usbphy->rstc); | ||
clk_disable_unprepare(usbphy->phy_clk); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct phy_ops sp_uphy_ops = { | ||
.init = sp_uphy_init, | ||
.power_on = sp_uphy_power_on, | ||
.power_off = sp_uphy_power_off, | ||
.exit = sp_uphy_exit, | ||
}; | ||
|
||
static const struct of_device_id sp_uphy_dt_ids[] = { | ||
{.compatible = "sunplus,sp7021-usb2-phy", }, | ||
{ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, sp_uphy_dt_ids); | ||
|
||
static int sp_usb_phy_probe(struct platform_device *pdev) | ||
{ | ||
struct sp_usbphy *usbphy; | ||
struct phy_provider *phy_provider; | ||
struct phy *phy; | ||
int ret; | ||
|
||
usbphy = devm_kzalloc(&pdev->dev, sizeof(*usbphy), GFP_KERNEL); | ||
if (!usbphy) | ||
return -ENOMEM; | ||
|
||
usbphy->dev = &pdev->dev; | ||
|
||
usbphy->phy_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); | ||
usbphy->phy_regs = devm_ioremap_resource(&pdev->dev, usbphy->phy_res_mem); | ||
if (IS_ERR(usbphy->phy_regs)) | ||
return PTR_ERR(usbphy->phy_regs); | ||
|
||
usbphy->moon4_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon4"); | ||
usbphy->moon4_regs = devm_ioremap(&pdev->dev, usbphy->moon4_res_mem->start, | ||
resource_size(usbphy->moon4_res_mem)); | ||
if (IS_ERR(usbphy->moon4_regs)) | ||
return PTR_ERR(usbphy->moon4_regs); | ||
|
||
usbphy->phy_clk = devm_clk_get(&pdev->dev, NULL); | ||
if (IS_ERR(usbphy->phy_clk)) | ||
return PTR_ERR(usbphy->phy_clk); | ||
|
||
usbphy->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); | ||
if (IS_ERR(usbphy->rstc)) | ||
return PTR_ERR(usbphy->rstc); | ||
|
||
of_property_read_u32(pdev->dev.of_node, "sunplus,disc-vol-addr-off", | ||
&usbphy->disc_vol_addr_off); | ||
|
||
phy = devm_phy_create(&pdev->dev, NULL, &sp_uphy_ops); | ||
if (IS_ERR(phy)) { | ||
ret = -PTR_ERR(phy); | ||
return ret; | ||
} | ||
|
||
phy_set_drvdata(phy, usbphy); | ||
phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); | ||
|
||
return PTR_ERR_OR_ZERO(phy_provider); | ||
} | ||
|
||
static struct platform_driver sunplus_usb_phy_driver = { | ||
.probe = sp_usb_phy_probe, | ||
.driver = { | ||
.name = "sunplus-usb2-phy", | ||
.of_match_table = sp_uphy_dt_ids, | ||
}, | ||
}; | ||
module_platform_driver(sunplus_usb_phy_driver); | ||
|
||
MODULE_AUTHOR("Vincent Shih <vincent.shih@sunplus.com>"); | ||
MODULE_DESCRIPTION("Sunplus USB 2.0 phy driver"); | ||
MODULE_LICENSE("GPL"); |