linux/drivers/phy/phy-sun4i-usb.c
<<
>>
Prefs
   1/*
   2 * Allwinner sun4i USB phy driver
   3 *
   4 * Copyright (C) 2014 Hans de Goede <hdegoede@redhat.com>
   5 *
   6 * Based on code from
   7 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
   8 *
   9 * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver
  10 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
  11 * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
  12 *
  13 * This program is free software; you can redistribute it and/or modify
  14 * it under the terms of the GNU General Public License as published by
  15 * the Free Software Foundation; either version 2 of the License, or
  16 * (at your option) any later version.
  17 *
  18 * This program is distributed in the hope that it will be useful,
  19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21 * GNU General Public License for more details.
  22 */
  23
  24#include <linux/clk.h>
  25#include <linux/io.h>
  26#include <linux/kernel.h>
  27#include <linux/module.h>
  28#include <linux/mutex.h>
  29#include <linux/of.h>
  30#include <linux/of_address.h>
  31#include <linux/phy/phy.h>
  32#include <linux/platform_device.h>
  33#include <linux/regulator/consumer.h>
  34#include <linux/reset.h>
  35
  36#define REG_ISCR                        0x00
  37#define REG_PHYCTL                      0x04
  38#define REG_PHYBIST                     0x08
  39#define REG_PHYTUNE                     0x0c
  40
  41#define PHYCTL_DATA                     BIT(7)
  42
  43#define SUNXI_AHB_ICHR8_EN              BIT(10)
  44#define SUNXI_AHB_INCR4_BURST_EN        BIT(9)
  45#define SUNXI_AHB_INCRX_ALIGN_EN        BIT(8)
  46#define SUNXI_ULPI_BYPASS_EN            BIT(0)
  47
  48/* Common Control Bits for Both PHYs */
  49#define PHY_PLL_BW                      0x03
  50#define PHY_RES45_CAL_EN                0x0c
  51
  52/* Private Control Bits for Each PHY */
  53#define PHY_TX_AMPLITUDE_TUNE           0x20
  54#define PHY_TX_SLEWRATE_TUNE            0x22
  55#define PHY_VBUSVALID_TH_SEL            0x25
  56#define PHY_PULLUP_RES_SEL              0x27
  57#define PHY_OTG_FUNC_EN                 0x28
  58#define PHY_VBUS_DET_EN                 0x29
  59#define PHY_DISCON_TH_SEL               0x2a
  60
  61#define MAX_PHYS                        3
  62
  63struct sun4i_usb_phy_data {
  64        void __iomem *base;
  65        struct mutex mutex;
  66        int num_phys;
  67        u32 disc_thresh;
  68        struct sun4i_usb_phy {
  69                struct phy *phy;
  70                void __iomem *pmu;
  71                struct regulator *vbus;
  72                struct reset_control *reset;
  73                struct clk *clk;
  74                int index;
  75        } phys[MAX_PHYS];
  76};
  77
  78#define to_sun4i_usb_phy_data(phy) \
  79        container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index])
  80
  81static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
  82                                int len)
  83{
  84        struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy);
  85        u32 temp, usbc_bit = BIT(phy->index * 2);
  86        int i;
  87
  88        mutex_lock(&phy_data->mutex);
  89
  90        for (i = 0; i < len; i++) {
  91                temp = readl(phy_data->base + REG_PHYCTL);
  92
  93                /* clear the address portion */
  94                temp &= ~(0xff << 8);
  95
  96                /* set the address */
  97                temp |= ((addr + i) << 8);
  98                writel(temp, phy_data->base + REG_PHYCTL);
  99
 100                /* set the data bit and clear usbc bit*/
 101                temp = readb(phy_data->base + REG_PHYCTL);
 102                if (data & 0x1)
 103                        temp |= PHYCTL_DATA;
 104                else
 105                        temp &= ~PHYCTL_DATA;
 106                temp &= ~usbc_bit;
 107                writeb(temp, phy_data->base + REG_PHYCTL);
 108
 109                /* pulse usbc_bit */
 110                temp = readb(phy_data->base + REG_PHYCTL);
 111                temp |= usbc_bit;
 112                writeb(temp, phy_data->base + REG_PHYCTL);
 113
 114                temp = readb(phy_data->base + REG_PHYCTL);
 115                temp &= ~usbc_bit;
 116                writeb(temp, phy_data->base + REG_PHYCTL);
 117
 118                data >>= 1;
 119        }
 120        mutex_unlock(&phy_data->mutex);
 121}
 122
 123static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable)
 124{
 125        u32 bits, reg_value;
 126
 127        if (!phy->pmu)
 128                return;
 129
 130        bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN |
 131                SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN;
 132
 133        reg_value = readl(phy->pmu);
 134
 135        if (enable)
 136                reg_value |= bits;
 137        else
 138                reg_value &= ~bits;
 139
 140        writel(reg_value, phy->pmu);
 141}
 142
 143static int sun4i_usb_phy_init(struct phy *_phy)
 144{
 145        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
 146        struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy);
 147        int ret;
 148
 149        ret = clk_prepare_enable(phy->clk);
 150        if (ret)
 151                return ret;
 152
 153        ret = reset_control_deassert(phy->reset);
 154        if (ret) {
 155                clk_disable_unprepare(phy->clk);
 156                return ret;
 157        }
 158
 159        /* Adjust PHY's magnitude and rate */
 160        sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
 161
 162        /* Disconnect threshold adjustment */
 163        sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2);
 164
 165        sun4i_usb_phy_passby(phy, 1);
 166
 167        return 0;
 168}
 169
 170static int sun4i_usb_phy_exit(struct phy *_phy)
 171{
 172        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
 173
 174        sun4i_usb_phy_passby(phy, 0);
 175        reset_control_assert(phy->reset);
 176        clk_disable_unprepare(phy->clk);
 177
 178        return 0;
 179}
 180
 181static int sun4i_usb_phy_power_on(struct phy *_phy)
 182{
 183        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
 184        int ret = 0;
 185
 186        if (phy->vbus)
 187                ret = regulator_enable(phy->vbus);
 188
 189        return ret;
 190}
 191
 192static int sun4i_usb_phy_power_off(struct phy *_phy)
 193{
 194        struct sun4i_usb_phy *phy = phy_get_drvdata(_phy);
 195
 196        if (phy->vbus)
 197                regulator_disable(phy->vbus);
 198
 199        return 0;
 200}
 201
 202static struct phy_ops sun4i_usb_phy_ops = {
 203        .init           = sun4i_usb_phy_init,
 204        .exit           = sun4i_usb_phy_exit,
 205        .power_on       = sun4i_usb_phy_power_on,
 206        .power_off      = sun4i_usb_phy_power_off,
 207        .owner          = THIS_MODULE,
 208};
 209
 210static struct phy *sun4i_usb_phy_xlate(struct device *dev,
 211                                        struct of_phandle_args *args)
 212{
 213        struct sun4i_usb_phy_data *data = dev_get_drvdata(dev);
 214
 215        if (WARN_ON(args->args[0] == 0 || args->args[0] >= data->num_phys))
 216                return ERR_PTR(-ENODEV);
 217
 218        return data->phys[args->args[0]].phy;
 219}
 220
 221static int sun4i_usb_phy_probe(struct platform_device *pdev)
 222{
 223        struct sun4i_usb_phy_data *data;
 224        struct device *dev = &pdev->dev;
 225        struct device_node *np = dev->of_node;
 226        struct phy_provider *phy_provider;
 227        bool dedicated_clocks;
 228        struct resource *res;
 229        int i;
 230
 231        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 232        if (!data)
 233                return -ENOMEM;
 234
 235        mutex_init(&data->mutex);
 236
 237        if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy"))
 238                data->num_phys = 2;
 239        else
 240                data->num_phys = 3;
 241
 242        if (of_device_is_compatible(np, "allwinner,sun4i-a10-usb-phy"))
 243                data->disc_thresh = 3;
 244        else
 245                data->disc_thresh = 2;
 246
 247        if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy"))
 248                dedicated_clocks = true;
 249        else
 250                dedicated_clocks = false;
 251
 252        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
 253        data->base = devm_ioremap_resource(dev, res);
 254        if (IS_ERR(data->base))
 255                return PTR_ERR(data->base);
 256
 257        /* Skip 0, 0 is the phy for otg which is not yet supported. */
 258        for (i = 1; i < data->num_phys; i++) {
 259                struct sun4i_usb_phy *phy = data->phys + i;
 260                char name[16];
 261
 262                snprintf(name, sizeof(name), "usb%d_vbus", i);
 263                phy->vbus = devm_regulator_get_optional(dev, name);
 264                if (IS_ERR(phy->vbus)) {
 265                        if (PTR_ERR(phy->vbus) == -EPROBE_DEFER)
 266                                return -EPROBE_DEFER;
 267                        phy->vbus = NULL;
 268                }
 269
 270                if (dedicated_clocks)
 271                        snprintf(name, sizeof(name), "usb%d_phy", i);
 272                else
 273                        strlcpy(name, "usb_phy", sizeof(name));
 274
 275                phy->clk = devm_clk_get(dev, name);
 276                if (IS_ERR(phy->clk)) {
 277                        dev_err(dev, "failed to get clock %s\n", name);
 278                        return PTR_ERR(phy->clk);
 279                }
 280
 281                snprintf(name, sizeof(name), "usb%d_reset", i);
 282                phy->reset = devm_reset_control_get(dev, name);
 283                if (IS_ERR(phy->reset)) {
 284                        dev_err(dev, "failed to get reset %s\n", name);
 285                        return PTR_ERR(phy->reset);
 286                }
 287
 288                if (i) { /* No pmu for usbc0 */
 289                        snprintf(name, sizeof(name), "pmu%d", i);
 290                        res = platform_get_resource_byname(pdev,
 291                                                        IORESOURCE_MEM, name);
 292                        phy->pmu = devm_ioremap_resource(dev, res);
 293                        if (IS_ERR(phy->pmu))
 294                                return PTR_ERR(phy->pmu);
 295                }
 296
 297                phy->phy = devm_phy_create(dev, &sun4i_usb_phy_ops, NULL);
 298                if (IS_ERR(phy->phy)) {
 299                        dev_err(dev, "failed to create PHY %d\n", i);
 300                        return PTR_ERR(phy->phy);
 301                }
 302
 303                phy->index = i;
 304                phy_set_drvdata(phy->phy, &data->phys[i]);
 305        }
 306
 307        dev_set_drvdata(dev, data);
 308        phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate);
 309        if (IS_ERR(phy_provider))
 310                return PTR_ERR(phy_provider);
 311
 312        return 0;
 313}
 314
 315static const struct of_device_id sun4i_usb_phy_of_match[] = {
 316        { .compatible = "allwinner,sun4i-a10-usb-phy" },
 317        { .compatible = "allwinner,sun5i-a13-usb-phy" },
 318        { .compatible = "allwinner,sun6i-a31-usb-phy" },
 319        { .compatible = "allwinner,sun7i-a20-usb-phy" },
 320        { },
 321};
 322MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);
 323
 324static struct platform_driver sun4i_usb_phy_driver = {
 325        .probe  = sun4i_usb_phy_probe,
 326        .driver = {
 327                .of_match_table = sun4i_usb_phy_of_match,
 328                .name  = "sun4i-usb-phy",
 329                .owner = THIS_MODULE,
 330        }
 331};
 332module_platform_driver(sun4i_usb_phy_driver);
 333
 334MODULE_DESCRIPTION("Allwinner sun4i USB phy driver");
 335MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 336MODULE_LICENSE("GPL v2");
 337
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.