Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Sergei Shtylyov | 1461 | 98.58% | 1 | 14.29% |
Yoshihiro Shimoda | 10 | 0.67% | 2 | 28.57% |
Simon Horman | 7 | 0.47% | 1 | 14.29% |
Wolfram Sang | 2 | 0.13% | 1 | 14.29% |
Axel Lin | 1 | 0.07% | 1 | 14.29% |
Boqun Feng | 1 | 0.07% | 1 | 14.29% |
Total | 1482 | 7 |
// SPDX-License-Identifier: GPL-2.0 /* * Renesas R-Car Gen2 PHY driver * * Copyright (C) 2014 Renesas Solutions Corp. * Copyright (C) 2014 Cogent Embedded, Inc. */ #include <linux/clk.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/spinlock.h> #include <linux/atomic.h> #define USBHS_LPSTS 0x02 #define USBHS_UGCTRL 0x80 #define USBHS_UGCTRL2 0x84 #define USBHS_UGSTS 0x88 /* From technical update */ /* Low Power Status register (LPSTS) */ #define USBHS_LPSTS_SUSPM 0x4000 /* USB General control register (UGCTRL) */ #define USBHS_UGCTRL_CONNECT 0x00000004 #define USBHS_UGCTRL_PLLRESET 0x00000001 /* USB General control register 2 (UGCTRL2) */ #define USBHS_UGCTRL2_USB2SEL 0x80000000 #define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000 #define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000 #define USBHS_UGCTRL2_USB0SEL 0x00000030 #define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010 #define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030 /* USB General status register (UGSTS) */ #define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */ #define PHYS_PER_CHANNEL 2 struct rcar_gen2_phy { struct phy *phy; struct rcar_gen2_channel *channel; int number; u32 select_value; }; struct rcar_gen2_channel { struct device_node *of_node; struct rcar_gen2_phy_driver *drv; struct rcar_gen2_phy phys[PHYS_PER_CHANNEL]; int selected_phy; u32 select_mask; }; struct rcar_gen2_phy_driver { void __iomem *base; struct clk *clk; spinlock_t lock; int num_channels; struct rcar_gen2_channel *channels; }; static int rcar_gen2_phy_init(struct phy *p) { struct rcar_gen2_phy *phy = phy_get_drvdata(p); struct rcar_gen2_channel *channel = phy->channel; struct rcar_gen2_phy_driver *drv = channel->drv; unsigned long flags; u32 ugctrl2; /* * Try to acquire exclusive access to PHY. The first driver calling * phy_init() on a given channel wins, and all attempts to use another * PHY on this channel will fail until phy_exit() is called by the first * driver. Achieving this with cmpxcgh() should be SMP-safe. */ if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1) return -EBUSY; clk_prepare_enable(drv->clk); spin_lock_irqsave(&drv->lock, flags); ugctrl2 = readl(drv->base + USBHS_UGCTRL2); ugctrl2 &= ~channel->select_mask; ugctrl2 |= phy->select_value; writel(ugctrl2, drv->base + USBHS_UGCTRL2); spin_unlock_irqrestore(&drv->lock, flags); return 0; } static int rcar_gen2_phy_exit(struct phy *p) { struct rcar_gen2_phy *phy = phy_get_drvdata(p); struct rcar_gen2_channel *channel = phy->channel; clk_disable_unprepare(channel->drv->clk); channel->selected_phy = -1; return 0; } static int rcar_gen2_phy_power_on(struct phy *p) { struct rcar_gen2_phy *phy = phy_get_drvdata(p); struct rcar_gen2_phy_driver *drv = phy->channel->drv; void __iomem *base = drv->base; unsigned long flags; u32 value; int err = 0, i; /* Skip if it's not USBHS */ if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) return 0; spin_lock_irqsave(&drv->lock, flags); /* Power on USBHS PHY */ value = readl(base + USBHS_UGCTRL); value &= ~USBHS_UGCTRL_PLLRESET; writel(value, base + USBHS_UGCTRL); value = readw(base + USBHS_LPSTS); value |= USBHS_LPSTS_SUSPM; writew(value, base + USBHS_LPSTS); for (i = 0; i < 20; i++) { value = readl(base + USBHS_UGSTS); if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) { value = readl(base + USBHS_UGCTRL); value |= USBHS_UGCTRL_CONNECT; writel(value, base + USBHS_UGCTRL); goto out; } udelay(1); } /* Timed out waiting for the PLL lock */ err = -ETIMEDOUT; out: spin_unlock_irqrestore(&drv->lock, flags); return err; } static int rcar_gen2_phy_power_off(struct phy *p) { struct rcar_gen2_phy *phy = phy_get_drvdata(p); struct rcar_gen2_phy_driver *drv = phy->channel->drv; void __iomem *base = drv->base; unsigned long flags; u32 value; /* Skip if it's not USBHS */ if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) return 0; spin_lock_irqsave(&drv->lock, flags); /* Power off USBHS PHY */ value = readl(base + USBHS_UGCTRL); value &= ~USBHS_UGCTRL_CONNECT; writel(value, base + USBHS_UGCTRL); value = readw(base + USBHS_LPSTS); value &= ~USBHS_LPSTS_SUSPM; writew(value, base + USBHS_LPSTS); value = readl(base + USBHS_UGCTRL); value |= USBHS_UGCTRL_PLLRESET; writel(value, base + USBHS_UGCTRL); spin_unlock_irqrestore(&drv->lock, flags); return 0; } static const struct phy_ops rcar_gen2_phy_ops = { .init = rcar_gen2_phy_init, .exit = rcar_gen2_phy_exit, .power_on = rcar_gen2_phy_power_on, .power_off = rcar_gen2_phy_power_off, .owner = THIS_MODULE, }; static const struct of_device_id rcar_gen2_phy_match_table[] = { { .compatible = "renesas,usb-phy-r8a7790" }, { .compatible = "renesas,usb-phy-r8a7791" }, { .compatible = "renesas,usb-phy-r8a7794" }, { .compatible = "renesas,rcar-gen2-usb-phy" }, { } }; MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); static struct phy *rcar_gen2_phy_xlate(struct device *dev, struct of_phandle_args *args) { struct rcar_gen2_phy_driver *drv; struct device_node *np = args->np; int i; drv = dev_get_drvdata(dev); if (!drv) return ERR_PTR(-EINVAL); for (i = 0; i < drv->num_channels; i++) { if (np == drv->channels[i].of_node) break; } if (i >= drv->num_channels || args->args[0] >= 2) return ERR_PTR(-ENODEV); return drv->channels[i].phys[args->args[0]].phy; } static const u32 select_mask[] = { [0] = USBHS_UGCTRL2_USB0SEL, [2] = USBHS_UGCTRL2_USB2SEL, }; static const u32 select_value[][PHYS_PER_CHANNEL] = { [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB }, [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 }, }; static int rcar_gen2_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rcar_gen2_phy_driver *drv; struct phy_provider *provider; struct device_node *np; struct resource *res; void __iomem *base; struct clk *clk; int i = 0; if (!dev->of_node) { dev_err(dev, "This driver is required to be instantiated from device tree\n"); return -EINVAL; } clk = devm_clk_get(dev, "usbhs"); if (IS_ERR(clk)) { dev_err(dev, "Can't get USBHS clock\n"); return PTR_ERR(clk); } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) return -ENOMEM; spin_lock_init(&drv->lock); drv->clk = clk; drv->base = base; drv->num_channels = of_get_child_count(dev->of_node); drv->channels = devm_kcalloc(dev, drv->num_channels, sizeof(struct rcar_gen2_channel), GFP_KERNEL); if (!drv->channels) return -ENOMEM; for_each_child_of_node(dev->of_node, np) { struct rcar_gen2_channel *channel = drv->channels + i; u32 channel_num; int error, n; channel->of_node = np; channel->drv = drv; channel->selected_phy = -1; error = of_property_read_u32(np, "reg", &channel_num); if (error || channel_num > 2) { dev_err(dev, "Invalid \"reg\" property\n"); return error; } channel->select_mask = select_mask[channel_num]; for (n = 0; n < PHYS_PER_CHANNEL; n++) { struct rcar_gen2_phy *phy = &channel->phys[n]; phy->channel = channel; phy->number = n; phy->select_value = select_value[channel_num][n]; phy->phy = devm_phy_create(dev, NULL, &rcar_gen2_phy_ops); if (IS_ERR(phy->phy)) { dev_err(dev, "Failed to create PHY\n"); return PTR_ERR(phy->phy); } phy_set_drvdata(phy->phy, phy); } i++; } provider = devm_of_phy_provider_register(dev, rcar_gen2_phy_xlate); if (IS_ERR(provider)) { dev_err(dev, "Failed to register PHY provider\n"); return PTR_ERR(provider); } dev_set_drvdata(dev, drv); return 0; } static struct platform_driver rcar_gen2_phy_driver = { .driver = { .name = "phy_rcar_gen2", .of_match_table = rcar_gen2_phy_match_table, }, .probe = rcar_gen2_phy_probe, }; module_platform_driver(rcar_gen2_phy_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Renesas R-Car Gen2 PHY"); MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1