linux/drivers/phy/broadcom/phy-bcm63xx-usbh.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * BCM6328 USBH PHY Controller Driver
   4 *
   5 * Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
   6 * Copyright (C) 2015 Simon Arlott
   7 *
   8 * Derived from bcm963xx_4.12L.06B_consumer/kernel/linux/arch/mips/bcm963xx/setup.c:
   9 * Copyright (C) 2002 Broadcom Corporation
  10 *
  11 * Derived from OpenWrt patches:
  12 * Copyright (C) 2013 Jonas Gorski <jonas.gorski@gmail.com>
  13 * Copyright (C) 2013 Florian Fainelli <f.fainelli@gmail.com>
  14 * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
  15 */
  16
  17#include <linux/clk.h>
  18#include <linux/io.h>
  19#include <linux/module.h>
  20#include <linux/phy/phy.h>
  21#include <linux/platform_device.h>
  22#include <linux/reset.h>
  23
  24/* USBH control register offsets */
  25enum usbh_regs {
  26        USBH_BRT_CONTROL1 = 0,
  27        USBH_BRT_CONTROL2,
  28        USBH_BRT_STATUS1,
  29        USBH_BRT_STATUS2,
  30        USBH_UTMI_CONTROL1,
  31#define   USBH_UC1_DEV_MODE_SEL         BIT(0)
  32        USBH_TEST_PORT_CONTROL,
  33        USBH_PLL_CONTROL1,
  34#define   USBH_PLLC_REFCLKSEL_SHIFT     0
  35#define   USBH_PLLC_REFCLKSEL_MASK      (0x3 << USBH_PLLC_REFCLKSEL_SHIFT)
  36#define   USBH_PLLC_CLKSEL_SHIFT        2
  37#define   USBH_PLLC_CLKSEL_MASK         (0x3 << USBH_PLLC_CLKSEL_MASK)
  38#define   USBH_PLLC_XTAL_PWRDWNB        BIT(4)
  39#define   USBH_PLLC_PLL_PWRDWNB         BIT(5)
  40#define   USBH_PLLC_PLL_CALEN           BIT(6)
  41#define   USBH_PLLC_PHYPLL_BYP          BIT(7)
  42#define   USBH_PLLC_PLL_RESET           BIT(8)
  43#define   USBH_PLLC_PLL_IDDQ_PWRDN      BIT(9)
  44#define   USBH_PLLC_PLL_PWRDN_DELAY     BIT(10)
  45#define   USBH_6318_PLLC_PLL_SUSPEND_EN BIT(27)
  46#define   USBH_6318_PLLC_PHYPLL_BYP     BIT(29)
  47#define   USBH_6318_PLLC_PLL_RESET      BIT(30)
  48#define   USBH_6318_PLLC_PLL_IDDQ_PWRDN BIT(31)
  49        USBH_SWAP_CONTROL,
  50#define   USBH_SC_OHCI_DATA_SWAP        BIT(0)
  51#define   USBH_SC_OHCI_ENDIAN_SWAP      BIT(1)
  52#define   USBH_SC_OHCI_LOGICAL_ADDR_EN  BIT(2)
  53#define   USBH_SC_EHCI_DATA_SWAP        BIT(3)
  54#define   USBH_SC_EHCI_ENDIAN_SWAP      BIT(4)
  55#define   USBH_SC_EHCI_LOGICAL_ADDR_EN  BIT(5)
  56#define   USBH_SC_USB_DEVICE_SEL        BIT(6)
  57        USBH_GENERIC_CONTROL,
  58#define   USBH_GC_PLL_SUSPEND_EN        BIT(1)
  59        USBH_FRAME_ADJUST_VALUE,
  60        USBH_SETUP,
  61#define   USBH_S_IOC                    BIT(4)
  62#define   USBH_S_IPP                    BIT(5)
  63        USBH_MDIO,
  64        USBH_MDIO32,
  65        USBH_USB_SIM_CONTROL,
  66#define   USBH_USC_LADDR_SEL            BIT(5)
  67
  68        __USBH_ENUM_SIZE
  69};
  70
  71struct bcm63xx_usbh_phy_variant {
  72        /* Registers */
  73        long regs[__USBH_ENUM_SIZE];
  74
  75        /* PLLC bits to set/clear for power on */
  76        u32 power_pllc_clr;
  77        u32 power_pllc_set;
  78
  79        /* Setup bits to set/clear for power on */
  80        u32 setup_clr;
  81        u32 setup_set;
  82
  83        /* Swap Control bits to set */
  84        u32 swapctl_dev_set;
  85
  86        /* Test Port Control value to set if non-zero */
  87        u32 tpc_val;
  88
  89        /* USB Sim Control bits to set */
  90        u32 usc_set;
  91
  92        /* UTMI Control 1 bits to set */
  93        u32 utmictl1_dev_set;
  94};
  95
  96struct bcm63xx_usbh_phy {
  97        void __iomem *base;
  98        struct clk *usbh_clk;
  99        struct clk *usb_ref_clk;
 100        struct reset_control *reset;
 101        const struct bcm63xx_usbh_phy_variant *variant;
 102        bool device_mode;
 103};
 104
 105static const struct bcm63xx_usbh_phy_variant usbh_bcm6318 = {
 106        .regs = {
 107                [USBH_BRT_CONTROL1] = -1,
 108                [USBH_BRT_CONTROL2] = -1,
 109                [USBH_BRT_STATUS1] = -1,
 110                [USBH_BRT_STATUS2] = -1,
 111                [USBH_UTMI_CONTROL1] = 0x2c,
 112                [USBH_TEST_PORT_CONTROL] = 0x1c,
 113                [USBH_PLL_CONTROL1] = 0x04,
 114                [USBH_SWAP_CONTROL] = 0x0c,
 115                [USBH_GENERIC_CONTROL] = -1,
 116                [USBH_FRAME_ADJUST_VALUE] = 0x08,
 117                [USBH_SETUP] = 0x00,
 118                [USBH_MDIO] = 0x14,
 119                [USBH_MDIO32] = 0x18,
 120                [USBH_USB_SIM_CONTROL] = 0x20,
 121        },
 122        .power_pllc_clr = USBH_6318_PLLC_PLL_IDDQ_PWRDN,
 123        .power_pllc_set = USBH_6318_PLLC_PLL_SUSPEND_EN,
 124        .setup_set = USBH_S_IOC,
 125        .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
 126        .usc_set = USBH_USC_LADDR_SEL,
 127        .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
 128};
 129
 130static const struct bcm63xx_usbh_phy_variant usbh_bcm6328 = {
 131        .regs = {
 132                [USBH_BRT_CONTROL1] = 0x00,
 133                [USBH_BRT_CONTROL2] = 0x04,
 134                [USBH_BRT_STATUS1] = 0x08,
 135                [USBH_BRT_STATUS2] = 0x0c,
 136                [USBH_UTMI_CONTROL1] = 0x10,
 137                [USBH_TEST_PORT_CONTROL] = 0x14,
 138                [USBH_PLL_CONTROL1] = 0x18,
 139                [USBH_SWAP_CONTROL] = 0x1c,
 140                [USBH_GENERIC_CONTROL] = 0x20,
 141                [USBH_FRAME_ADJUST_VALUE] = 0x24,
 142                [USBH_SETUP] = 0x28,
 143                [USBH_MDIO] = 0x2c,
 144                [USBH_MDIO32] = 0x30,
 145                [USBH_USB_SIM_CONTROL] = 0x34,
 146        },
 147        .setup_set = USBH_S_IOC,
 148        .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
 149        .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
 150};
 151
 152static const struct bcm63xx_usbh_phy_variant usbh_bcm6358 = {
 153        .regs = {
 154                [USBH_BRT_CONTROL1] = -1,
 155                [USBH_BRT_CONTROL2] = -1,
 156                [USBH_BRT_STATUS1] = -1,
 157                [USBH_BRT_STATUS2] = -1,
 158                [USBH_UTMI_CONTROL1] = -1,
 159                [USBH_TEST_PORT_CONTROL] = 0x24,
 160                [USBH_PLL_CONTROL1] = -1,
 161                [USBH_SWAP_CONTROL] = 0x00,
 162                [USBH_GENERIC_CONTROL] = -1,
 163                [USBH_FRAME_ADJUST_VALUE] = -1,
 164                [USBH_SETUP] = -1,
 165                [USBH_MDIO] = -1,
 166                [USBH_MDIO32] = -1,
 167                [USBH_USB_SIM_CONTROL] = -1,
 168        },
 169        /*
 170         * The magic value comes for the original vendor BSP
 171         * and is needed for USB to work. Datasheet does not
 172         * help, so the magic value is used as-is.
 173         */
 174        .tpc_val = 0x1c0020,
 175};
 176
 177static const struct bcm63xx_usbh_phy_variant usbh_bcm6368 = {
 178        .regs = {
 179                [USBH_BRT_CONTROL1] = 0x00,
 180                [USBH_BRT_CONTROL2] = 0x04,
 181                [USBH_BRT_STATUS1] = 0x08,
 182                [USBH_BRT_STATUS2] = 0x0c,
 183                [USBH_UTMI_CONTROL1] = 0x10,
 184                [USBH_TEST_PORT_CONTROL] = 0x14,
 185                [USBH_PLL_CONTROL1] = 0x18,
 186                [USBH_SWAP_CONTROL] = 0x1c,
 187                [USBH_GENERIC_CONTROL] = -1,
 188                [USBH_FRAME_ADJUST_VALUE] = 0x24,
 189                [USBH_SETUP] = 0x28,
 190                [USBH_MDIO] = 0x2c,
 191                [USBH_MDIO32] = 0x30,
 192                [USBH_USB_SIM_CONTROL] = 0x34,
 193        },
 194        .power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
 195        .setup_set = USBH_S_IOC,
 196        .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
 197        .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
 198};
 199
 200static const struct bcm63xx_usbh_phy_variant usbh_bcm63268 = {
 201        .regs = {
 202                [USBH_BRT_CONTROL1] = 0x00,
 203                [USBH_BRT_CONTROL2] = 0x04,
 204                [USBH_BRT_STATUS1] = 0x08,
 205                [USBH_BRT_STATUS2] = 0x0c,
 206                [USBH_UTMI_CONTROL1] = 0x10,
 207                [USBH_TEST_PORT_CONTROL] = 0x14,
 208                [USBH_PLL_CONTROL1] = 0x18,
 209                [USBH_SWAP_CONTROL] = 0x1c,
 210                [USBH_GENERIC_CONTROL] = 0x20,
 211                [USBH_FRAME_ADJUST_VALUE] = 0x24,
 212                [USBH_SETUP] = 0x28,
 213                [USBH_MDIO] = 0x2c,
 214                [USBH_MDIO32] = 0x30,
 215                [USBH_USB_SIM_CONTROL] = 0x34,
 216        },
 217        .power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
 218        .setup_clr = USBH_S_IPP,
 219        .setup_set = USBH_S_IOC,
 220        .swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
 221        .utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
 222};
 223
 224static inline bool usbh_has_reg(struct bcm63xx_usbh_phy *usbh, int reg)
 225{
 226        return (usbh->variant->regs[reg] >= 0);
 227}
 228
 229static inline u32 usbh_readl(struct bcm63xx_usbh_phy *usbh, int reg)
 230{
 231        return __raw_readl(usbh->base + usbh->variant->regs[reg]);
 232}
 233
 234static inline void usbh_writel(struct bcm63xx_usbh_phy *usbh, int reg,
 235                               u32 value)
 236{
 237        __raw_writel(value, usbh->base + usbh->variant->regs[reg]);
 238}
 239
 240static int bcm63xx_usbh_phy_init(struct phy *phy)
 241{
 242        struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
 243        int ret;
 244
 245        ret = clk_prepare_enable(usbh->usbh_clk);
 246        if (ret) {
 247                dev_err(&phy->dev, "unable to enable usbh clock: %d\n", ret);
 248                return ret;
 249        }
 250
 251        ret = clk_prepare_enable(usbh->usb_ref_clk);
 252        if (ret) {
 253                dev_err(&phy->dev, "unable to enable usb_ref clock: %d\n", ret);
 254                clk_disable_unprepare(usbh->usbh_clk);
 255                return ret;
 256        }
 257
 258        ret = reset_control_reset(usbh->reset);
 259        if (ret) {
 260                dev_err(&phy->dev, "unable to reset device: %d\n", ret);
 261                clk_disable_unprepare(usbh->usb_ref_clk);
 262                clk_disable_unprepare(usbh->usbh_clk);
 263                return ret;
 264        }
 265
 266        /* Configure to work in native CPU endian */
 267        if (usbh_has_reg(usbh, USBH_SWAP_CONTROL)) {
 268                u32 val = usbh_readl(usbh, USBH_SWAP_CONTROL);
 269
 270                val |= USBH_SC_EHCI_DATA_SWAP;
 271                val &= ~USBH_SC_EHCI_ENDIAN_SWAP;
 272
 273                val |= USBH_SC_OHCI_DATA_SWAP;
 274                val &= ~USBH_SC_OHCI_ENDIAN_SWAP;
 275
 276                if (usbh->device_mode && usbh->variant->swapctl_dev_set)
 277                        val |= usbh->variant->swapctl_dev_set;
 278
 279                usbh_writel(usbh, USBH_SWAP_CONTROL, val);
 280        }
 281
 282        if (usbh_has_reg(usbh, USBH_SETUP)) {
 283                u32 val = usbh_readl(usbh, USBH_SETUP);
 284
 285                val |= usbh->variant->setup_set;
 286                val &= ~usbh->variant->setup_clr;
 287
 288                usbh_writel(usbh, USBH_SETUP, val);
 289        }
 290
 291        if (usbh_has_reg(usbh, USBH_USB_SIM_CONTROL)) {
 292                u32 val = usbh_readl(usbh, USBH_USB_SIM_CONTROL);
 293
 294                val |= usbh->variant->usc_set;
 295
 296                usbh_writel(usbh, USBH_USB_SIM_CONTROL, val);
 297        }
 298
 299        if (usbh->variant->tpc_val &&
 300            usbh_has_reg(usbh, USBH_TEST_PORT_CONTROL))
 301                usbh_writel(usbh, USBH_TEST_PORT_CONTROL,
 302                            usbh->variant->tpc_val);
 303
 304        if (usbh->device_mode &&
 305            usbh_has_reg(usbh, USBH_UTMI_CONTROL1) &&
 306            usbh->variant->utmictl1_dev_set) {
 307                u32 val = usbh_readl(usbh, USBH_UTMI_CONTROL1);
 308
 309                val |= usbh->variant->utmictl1_dev_set;
 310
 311                usbh_writel(usbh, USBH_UTMI_CONTROL1, val);
 312        }
 313
 314        return 0;
 315}
 316
 317static int bcm63xx_usbh_phy_power_on(struct phy *phy)
 318{
 319        struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
 320
 321        if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
 322                u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
 323
 324                val |= usbh->variant->power_pllc_set;
 325                val &= ~usbh->variant->power_pllc_clr;
 326
 327                usbh_writel(usbh, USBH_PLL_CONTROL1, val);
 328        }
 329
 330        return 0;
 331}
 332
 333static int bcm63xx_usbh_phy_power_off(struct phy *phy)
 334{
 335        struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
 336
 337        if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
 338                u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
 339
 340                val &= ~usbh->variant->power_pllc_set;
 341                val |= usbh->variant->power_pllc_clr;
 342
 343                usbh_writel(usbh, USBH_PLL_CONTROL1, val);
 344        }
 345
 346        return 0;
 347}
 348
 349static int bcm63xx_usbh_phy_exit(struct phy *phy)
 350{
 351        struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
 352
 353        clk_disable_unprepare(usbh->usbh_clk);
 354        clk_disable_unprepare(usbh->usb_ref_clk);
 355
 356        return 0;
 357}
 358
 359static const struct phy_ops bcm63xx_usbh_phy_ops = {
 360        .exit = bcm63xx_usbh_phy_exit,
 361        .init = bcm63xx_usbh_phy_init,
 362        .power_off = bcm63xx_usbh_phy_power_off,
 363        .power_on = bcm63xx_usbh_phy_power_on,
 364        .owner = THIS_MODULE,
 365};
 366
 367static struct phy *bcm63xx_usbh_phy_xlate(struct device *dev,
 368                                          struct of_phandle_args *args)
 369{
 370        struct bcm63xx_usbh_phy *usbh = dev_get_drvdata(dev);
 371
 372        usbh->device_mode = !!args->args[0];
 373
 374        return of_phy_simple_xlate(dev, args);
 375}
 376
 377static int __init bcm63xx_usbh_phy_probe(struct platform_device *pdev)
 378{
 379        struct device *dev = &pdev->dev;
 380        struct bcm63xx_usbh_phy *usbh;
 381        const struct bcm63xx_usbh_phy_variant *variant;
 382        struct phy *phy;
 383        struct phy_provider *phy_provider;
 384
 385        usbh = devm_kzalloc(dev, sizeof(*usbh), GFP_KERNEL);
 386        if (!usbh)
 387                return -ENOMEM;
 388
 389        variant = device_get_match_data(dev);
 390        if (!variant)
 391                return -EINVAL;
 392        usbh->variant = variant;
 393
 394        usbh->base = devm_platform_ioremap_resource(pdev, 0);
 395        if (IS_ERR(usbh->base))
 396                return PTR_ERR(usbh->base);
 397
 398        usbh->reset = devm_reset_control_get_exclusive(dev, NULL);
 399        if (IS_ERR(usbh->reset)) {
 400                if (PTR_ERR(usbh->reset) != -EPROBE_DEFER)
 401                        dev_err(dev, "failed to get reset\n");
 402                return PTR_ERR(usbh->reset);
 403        }
 404
 405        usbh->usbh_clk = devm_clk_get_optional(dev, "usbh");
 406        if (IS_ERR(usbh->usbh_clk))
 407                return PTR_ERR(usbh->usbh_clk);
 408
 409        usbh->usb_ref_clk = devm_clk_get_optional(dev, "usb_ref");
 410        if (IS_ERR(usbh->usb_ref_clk))
 411                return PTR_ERR(usbh->usb_ref_clk);
 412
 413        phy = devm_phy_create(dev, NULL, &bcm63xx_usbh_phy_ops);
 414        if (IS_ERR(phy)) {
 415                dev_err(dev, "failed to create PHY\n");
 416                return PTR_ERR(phy);
 417        }
 418
 419        platform_set_drvdata(pdev, usbh);
 420        phy_set_drvdata(phy, usbh);
 421
 422        phy_provider = devm_of_phy_provider_register(dev,
 423                                                     bcm63xx_usbh_phy_xlate);
 424        if (IS_ERR(phy_provider)) {
 425                dev_err(dev, "failed to register PHY provider\n");
 426                return PTR_ERR(phy_provider);
 427        }
 428
 429        dev_dbg(dev, "Registered BCM63xx USB PHY driver\n");
 430
 431        return 0;
 432}
 433
 434static const struct of_device_id bcm63xx_usbh_phy_ids[] __initconst = {
 435        { .compatible = "brcm,bcm6318-usbh-phy", .data = &usbh_bcm6318 },
 436        { .compatible = "brcm,bcm6328-usbh-phy", .data = &usbh_bcm6328 },
 437        { .compatible = "brcm,bcm6358-usbh-phy", .data = &usbh_bcm6358 },
 438        { .compatible = "brcm,bcm6362-usbh-phy", .data = &usbh_bcm6368 },
 439        { .compatible = "brcm,bcm6368-usbh-phy", .data = &usbh_bcm6368 },
 440        { .compatible = "brcm,bcm63268-usbh-phy", .data = &usbh_bcm63268 },
 441        { /* sentinel */ }
 442};
 443MODULE_DEVICE_TABLE(of, bcm63xx_usbh_phy_ids);
 444
 445static struct platform_driver bcm63xx_usbh_phy_driver __refdata = {
 446        .driver = {
 447                .name = "bcm63xx-usbh-phy",
 448                .of_match_table = bcm63xx_usbh_phy_ids,
 449        },
 450        .probe  = bcm63xx_usbh_phy_probe,
 451};
 452module_platform_driver(bcm63xx_usbh_phy_driver);
 453
 454MODULE_DESCRIPTION("BCM63xx USBH PHY driver");
 455MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
 456MODULE_AUTHOR("Simon Arlott");
 457MODULE_LICENSE("GPL");
 458