| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (c) 2021 Nuvoton Technology Corp. | |
| */ | |
| #include <dm.h> | |
| #include <generic-phy.h> | |
| #include <regmap.h> | |
| #include <reset.h> | |
| #include <syscon.h> | |
| #include <dm/device_compat.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/delay.h> | |
| #include <dt-bindings/phy/nuvoton,npcm-usbphy.h> | |
| /* GCR Register Offsets */ | |
| #define GCR_INTCR3 0x9C | |
| #define GCR_USB1PHYCTL 0x140 | |
| #define GCR_USB2PHYCTL 0x144 | |
| #define GCR_USB3PHYCTL 0x148 | |
| /* USBnPHYCTL bit fields */ | |
| #define PHYCTL_RS BIT(28) | |
| #define USBPHY2SW GENMASK(13, 12) | |
| #define USBPHY3SW GENMASK(15, 14) | |
| #define USBPHY2SW_DEV9_PHY1 FIELD_PREP(USBPHY2SW, 0) | |
| #define USBPHY2SW_HOST1 FIELD_PREP(USBPHY2SW, 1) | |
| #define USBPHY2SW_DEV9_PHY2 FIELD_PREP(USBPHY2SW, 3) | |
| #define USBPHY3SW_DEV8_PHY1 FIELD_PREP(USBPHY3SW, 0) | |
| #define USBPHY3SW_HOST2 FIELD_PREP(USBPHY3SW, 1) | |
| #define USBPHY3SW_DEV8_PHY3 FIELD_PREP(USBPHY3SW, 3) | |
| enum phy_id { | |
| PHY1 = 1, | |
| PHY2, | |
| PHY3, | |
| }; | |
| /* Phy Switch Settings */ | |
| #define USBDPHY1 ((PHY1 << 8) | NPCM_UDC0_7) /* Connect UDC0~7 to PHY1 */ | |
| #define USBD8PHY1 ((PHY1 << 8) | NPCM_UDC8) /* Connect UDC8 to PHY1 */ | |
| #define USBD9PHY1 ((PHY1 << 8) | NPCM_UDC9) /* Connect UDC9 to PHY1 */ | |
| #define USBD9PHY2 ((PHY2 << 8) | NPCM_UDC9) /* Connect UDC9 to PHY2 */ | |
| #define USBH1PHY2 ((PHY2 << 8) | NPCM_USBH1) /* Connect USBH1 to PHY2 */ | |
| #define USBD8PHY3 ((PHY3 << 8) | NPCM_UDC8) /* Connect UDC8 to PHY3 */ | |
| #define USBH2PHY3 ((PHY3 << 8) | NPCM_USBH2) /* Connect USBH2 to PHY3 */ | |
| struct npcm_usbphy { | |
| struct regmap *syscon; | |
| u8 id; | |
| u16 phy_switch; /* (phy_id << 8) | controller_id */ | |
| }; | |
| static int npcm_usb_phy_init(struct phy *phy) | |
| { | |
| struct npcm_usbphy *priv = dev_get_priv(phy->dev); | |
| struct reset_ctl reset; | |
| int ret; | |
| ret = reset_get_by_index(phy->dev, 0, &reset); | |
| if (ret && ret != -ENOENT && ret != -ENOTSUPP) { | |
| dev_err(phy->dev, "can't get phy reset ctrl (err %d)", ret); | |
| return ret; | |
| } | |
| /* setup PHY switch */ | |
| switch (priv->phy_switch) { | |
| case USBD8PHY1: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY3SW, | |
| USBPHY3SW_DEV8_PHY1); | |
| break; | |
| case USBD8PHY3: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY3SW, | |
| USBPHY3SW_DEV8_PHY3); | |
| break; | |
| case USBD9PHY1: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY2SW, | |
| USBPHY2SW_DEV9_PHY1); | |
| break; | |
| case USBD9PHY2: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY2SW, | |
| USBPHY2SW_DEV9_PHY2); | |
| break; | |
| case USBH1PHY2: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY2SW, | |
| USBPHY2SW_HOST1); | |
| break; | |
| case USBH2PHY3: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY3SW, | |
| USBPHY3SW_HOST2); | |
| break; | |
| default: | |
| break; | |
| } | |
| /* reset phy */ | |
| if (reset_valid(&reset)) | |
| reset_assert(&reset); | |
| /* Wait for PHY clocks to stablize for 50us or more */ | |
| udelay(100); | |
| /* release phy from reset */ | |
| if (reset_valid(&reset)) | |
| reset_deassert(&reset); | |
| /* PHY RS bit should be set after reset */ | |
| switch (priv->id) { | |
| case PHY1: | |
| regmap_update_bits(priv->syscon, GCR_USB1PHYCTL, PHYCTL_RS, PHYCTL_RS); | |
| break; | |
| case PHY2: | |
| regmap_update_bits(priv->syscon, GCR_USB2PHYCTL, PHYCTL_RS, PHYCTL_RS); | |
| break; | |
| case PHY3: | |
| regmap_update_bits(priv->syscon, GCR_USB3PHYCTL, PHYCTL_RS, PHYCTL_RS); | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static int npcm_usb_phy_exit(struct phy *phy) | |
| { | |
| struct npcm_usbphy *priv = dev_get_priv(phy->dev); | |
| /* set PHY switch to default state */ | |
| switch (priv->phy_switch) { | |
| case USBD8PHY1: | |
| case USBD8PHY3: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY3SW, | |
| USBPHY3SW_HOST2); | |
| break; | |
| case USBD9PHY1: | |
| case USBD9PHY2: | |
| regmap_update_bits(priv->syscon, GCR_INTCR3, USBPHY2SW, | |
| USBPHY2SW_HOST1); | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static int npcm_usb_phy_xlate(struct phy *phy, struct ofnode_phandle_args *args) | |
| { | |
| struct npcm_usbphy *priv = dev_get_priv(phy->dev); | |
| u16 phy_switch; | |
| if (args->args_count < 1 || args->args[0] > NPCM_MAX_USB_CTRL_ID) | |
| return -EINVAL; | |
| phy_switch = (priv->id << 8) | args->args[0]; | |
| switch (phy_switch) { | |
| case USBD9PHY1: | |
| case USBH2PHY3: | |
| case USBD8PHY3: | |
| if (!IS_ENABLED(CONFIG_ARCH_NPCM8XX)) | |
| return -EINVAL; | |
| case USBDPHY1: | |
| case USBD8PHY1: | |
| case USBD9PHY2: | |
| case USBH1PHY2: | |
| priv->phy_switch = phy_switch; | |
| return 0; | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static int npcm_usb_phy_probe(struct udevice *dev) | |
| { | |
| struct npcm_usbphy *priv = dev_get_priv(dev); | |
| priv->syscon = syscon_regmap_lookup_by_phandle(dev->parent, "syscon"); | |
| if (IS_ERR(priv->syscon)) { | |
| dev_err(dev, "%s: unable to get syscon\n", __func__); | |
| return PTR_ERR(priv->syscon); | |
| } | |
| priv->id = dev_read_u32_default(dev, "reg", -1); | |
| return 0; | |
| } | |
| static const struct udevice_id npcm_phy_ids[] = { | |
| { .compatible = "nuvoton,npcm845-usb-phy",}, | |
| { .compatible = "nuvoton,npcm750-usb-phy",}, | |
| { } | |
| }; | |
| static struct phy_ops npcm_phy_ops = { | |
| .init = npcm_usb_phy_init, | |
| .exit = npcm_usb_phy_exit, | |
| .of_xlate = npcm_usb_phy_xlate, | |
| }; | |
| U_BOOT_DRIVER(npcm_phy) = { | |
| .name = "npcm-usb-phy", | |
| .id = UCLASS_PHY, | |
| .of_match = npcm_phy_ids, | |
| .ops = &npcm_phy_ops, | |
| .probe = npcm_usb_phy_probe, | |
| .priv_auto = sizeof(struct npcm_usbphy), | |
| }; |