diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/phy/renesas | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/phy/renesas')
| -rw-r--r-- | drivers/phy/renesas/Kconfig | 51 | ||||
| -rw-r--r-- | drivers/phy/renesas/Makefile | 7 | ||||
| -rw-r--r-- | drivers/phy/renesas/phy-rcar-gen2.c | 439 | ||||
| -rw-r--r-- | drivers/phy/renesas/phy-rcar-gen3-pcie.c | 146 | ||||
| -rw-r--r-- | drivers/phy/renesas/phy-rcar-gen3-usb2.c | 1129 | ||||
| -rw-r--r-- | drivers/phy/renesas/phy-rcar-gen3-usb3.c | 219 | ||||
| -rw-r--r-- | drivers/phy/renesas/phy-rzg3e-usb3.c | 257 | ||||
| -rw-r--r-- | drivers/phy/renesas/r8a779f0-ether-serdes.c | 489 |
8 files changed, 2737 insertions, 0 deletions
diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig new file mode 100644 index 000000000..90a9ca2db --- /dev/null +++ b/drivers/phy/renesas/Kconfig @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Phy drivers for Renesas platforms +# +# NOTE: Please sorted config names alphabetically. +config PHY_R8A779F0_ETHERNET_SERDES + tristate "Renesas R-Car S4-8 Ethernet SERDES driver" + depends on ARCH_RENESAS || COMPILE_TEST + select GENERIC_PHY + help + Support for Ethernet SERDES found on Renesas R-Car S4-8 SoCs. + +config PHY_RCAR_GEN2 + tristate "Renesas R-Car generation 2 USB PHY driver" + depends on ARCH_RENESAS + select GENERIC_PHY + help + Support for USB PHY found on Renesas R-Car generation 2 SoCs. + +config PHY_RCAR_GEN3_PCIE + tristate "Renesas R-Car generation 3 PCIe PHY driver" + depends on ARCH_RENESAS + select GENERIC_PHY + help + Support for the PCIe PHY found on Renesas R-Car generation 3 SoCs. + +config PHY_RCAR_GEN3_USB2 + tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" + depends on ARCH_RENESAS + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in + depends on USB_SUPPORT + depends on REGULATOR + select GENERIC_PHY + select MULTIPLEXER + select USB_COMMON + help + Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. + +config PHY_RCAR_GEN3_USB3 + tristate "Renesas R-Car generation 3 USB 3.0 PHY driver" + depends on ARCH_RENESAS || COMPILE_TEST + select GENERIC_PHY + help + Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs. + +config PHY_RZ_G3E_USB3 + tristate "Renesas RZ/G3E USB 3.0 PHY driver" + depends on ARCH_RENESAS || COMPILE_TEST + select GENERIC_PHY + help + Support for USB 3.0 PHY found on Renesas RZ/G3E SoCs. diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile new file mode 100644 index 000000000..0e98083f2 --- /dev/null +++ b/drivers/phy/renesas/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PHY_R8A779F0_ETHERNET_SERDES) += r8a779f0-ether-serdes.o +obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_PCIE) += phy-rcar-gen3-pcie.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB3) += phy-rcar-gen3-usb3.o +obj-$(CONFIG_PHY_RZ_G3E_USB3) += phy-rzg3e-usb3.o diff --git a/drivers/phy/renesas/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c new file mode 100644 index 000000000..5a272e25e --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen2.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen2 PHY driver + * + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + * Copyright (C) 2019 Renesas Electronics Corp. + */ + +#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 +#define USBHS_UGCTRL2_USB0SEL_USB20 0x00000010 +#define USBHS_UGCTRL2_USB0SEL_HS_USB20 0x00000020 + +/* 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; +}; + +struct rcar_gen2_phy_data { + const struct phy_ops *gen2_phy_ops; + const u32 (*select_value)[PHYS_PER_CHANNEL]; + const u32 num_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 cmpxchg() 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 int rz_g1c_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; + + spin_lock_irqsave(&drv->lock, flags); + + /* Power on USBHS PHY */ + value = readl(base + USBHS_UGCTRL); + value &= ~USBHS_UGCTRL_PLLRESET; + writel(value, base + USBHS_UGCTRL); + + /* As per the data sheet wait 340 micro sec for power stable */ + udelay(340); + + if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) { + value = readw(base + USBHS_LPSTS); + value |= USBHS_LPSTS_SUSPM; + writew(value, base + USBHS_LPSTS); + } + + spin_unlock_irqrestore(&drv->lock, flags); + + return 0; +} + +static int rz_g1c_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; + + spin_lock_irqsave(&drv->lock, flags); + /* Power off USBHS PHY */ + if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) { + 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 phy_ops rz_g1c_phy_ops = { + .init = rcar_gen2_phy_init, + .exit = rcar_gen2_phy_exit, + .power_on = rz_g1c_phy_power_on, + .power_off = rz_g1c_phy_power_off, + .owner = THIS_MODULE, +}; + +static const u32 pci_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 const u32 usb20_select_value[][PHYS_PER_CHANNEL] = { + { USBHS_UGCTRL2_USB0SEL_USB20, USBHS_UGCTRL2_USB0SEL_HS_USB20 }, +}; + +static const struct rcar_gen2_phy_data rcar_gen2_usb_phy_data = { + .gen2_phy_ops = &rcar_gen2_phy_ops, + .select_value = pci_select_value, + .num_channels = ARRAY_SIZE(pci_select_value), +}; + +static const struct rcar_gen2_phy_data rz_g1c_usb_phy_data = { + .gen2_phy_ops = &rz_g1c_phy_ops, + .select_value = usb20_select_value, + .num_channels = ARRAY_SIZE(usb20_select_value), +}; + +static const struct of_device_id rcar_gen2_phy_match_table[] = { + { + .compatible = "renesas,usb-phy-r8a77470", + .data = &rz_g1c_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7790", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7791", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,usb-phy-r8a7794", + .data = &rcar_gen2_usb_phy_data, + }, + { + .compatible = "renesas,rcar-gen2-usb-phy", + .data = &rcar_gen2_usb_phy_data, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); + +static struct phy *rcar_gen2_phy_xlate(struct device *dev, + const 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 int rcar_gen2_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen2_phy_driver *drv; + struct phy_provider *provider; + void __iomem *base; + struct clk *clk; + const struct rcar_gen2_phy_data *data; + 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); + } + + base = devm_platform_ioremap_resource(pdev, 0); + 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; + + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + + 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_scoped(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 >= data->num_channels) { + 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 = data->select_value[channel_num][n]; + + phy->phy = devm_phy_create(dev, NULL, + data->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>"); diff --git a/drivers/phy/renesas/phy-rcar-gen3-pcie.c b/drivers/phy/renesas/phy-rcar-gen3-pcie.c new file mode 100644 index 000000000..c0e5a4ac8 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-pcie.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen3 PCIe PHY driver + * + * Copyright (C) 2018 Cogent Embedded, Inc. + */ + +#include <linux/clk.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> + +#define PHY_CTRL 0x4000 /* R8A77980 only */ + +/* PHY control register (PHY_CTRL) */ +#define PHY_CTRL_PHY_PWDN BIT(2) + +struct rcar_gen3_phy { + struct phy *phy; + spinlock_t lock; + void __iomem *base; +}; + +static void rcar_gen3_phy_pcie_modify_reg(struct phy *p, unsigned int reg, + u32 clear, u32 set) +{ + struct rcar_gen3_phy *phy = phy_get_drvdata(p); + void __iomem *base = phy->base; + unsigned long flags; + u32 value; + + spin_lock_irqsave(&phy->lock, flags); + + value = readl(base + reg); + value &= ~clear; + value |= set; + writel(value, base + reg); + + spin_unlock_irqrestore(&phy->lock, flags); +} + +static int r8a77980_phy_pcie_power_on(struct phy *p) +{ + /* Power on the PCIe PHY */ + rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, PHY_CTRL_PHY_PWDN, 0); + + return 0; +} + +static int r8a77980_phy_pcie_power_off(struct phy *p) +{ + /* Power off the PCIe PHY */ + rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, 0, PHY_CTRL_PHY_PWDN); + + return 0; +} + +static const struct phy_ops r8a77980_phy_pcie_ops = { + .power_on = r8a77980_phy_pcie_power_on, + .power_off = r8a77980_phy_pcie_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_pcie_match_table[] = { + { .compatible = "renesas,r8a77980-pcie-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_pcie_match_table); + +static int rcar_gen3_phy_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct rcar_gen3_phy *phy; + void __iomem *base; + int error; + + if (!dev->of_node) { + dev_err(dev, + "This driver must only be instantiated from the device tree\n"); + return -EINVAL; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + spin_lock_init(&phy->lock); + + phy->base = base; + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime PM for this device. + */ + pm_runtime_enable(dev); + + phy->phy = devm_phy_create(dev, NULL, &r8a77980_phy_pcie_ops); + if (IS_ERR(phy->phy)) { + dev_err(dev, "Failed to create PCIe PHY\n"); + error = PTR_ERR(phy->phy); + goto error; + } + phy_set_drvdata(phy->phy, phy); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + error = PTR_ERR(provider); + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return error; +} + +static void rcar_gen3_phy_pcie_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); +} + +static struct platform_driver rcar_gen3_phy_driver = { + .driver = { + .name = "phy_rcar_gen3_pcie", + .of_match_table = rcar_gen3_phy_pcie_match_table, + }, + .probe = rcar_gen3_phy_pcie_probe, + .remove = rcar_gen3_phy_pcie_remove, +}; + +module_platform_driver(rcar_gen3_phy_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 PCIe PHY"); +MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>"); diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c new file mode 100644 index 000000000..9ae9975d3 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -0,0 +1,1129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen3 for USB2.0 PHY driver + * + * Copyright (C) 2015-2017 Renesas Electronics Corporation + * + * This is based on the phy-rcar-gen2 driver: + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/extcon-provider.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mux/consumer.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/reset.h> +#include <linux/string.h> +#include <linux/usb/of.h> +#include <linux/workqueue.h> + +/******* USB2.0 Host registers (original offset is +0x200) *******/ +#define USB2_INT_ENABLE 0x000 +#define USB2_AHB_BUS_CTR 0x008 +#define USB2_USBCTR 0x00c +#define USB2_REGEN_CG_CTRL 0x104 /* RZ/V2H(P) only */ +#define USB2_SPD_RSM_TIMSET 0x10c +#define USB2_OC_TIMSET 0x110 +#define USB2_UTMI_CTRL 0x118 /* RZ/V2H(P) only */ +#define USB2_COMMCTRL 0x600 +#define USB2_OBINTSTA 0x604 +#define USB2_OBINTEN 0x608 +#define USB2_VBCTRL 0x60c +#define USB2_LINECTRL1 0x610 +#define USB2_ADPCTRL 0x630 + +/* INT_ENABLE */ +#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */ +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */ + +/* AHB_BUS_CTR */ +#define USB2_AHB_BUS_CTR_MBL_MASK GENMASK(1, 0) +#define USB2_AHB_BUS_CTR_MBL_INCR4 2 + +/* USBCTR */ +#define USB2_USBCTR_DIRPD BIT(2) +#define USB2_USBCTR_PLL_RST BIT(1) + +/* REGEN_CG_CTRL*/ +#define USB2_REGEN_CG_CTRL_UPHY_WEN BIT(0) + +/* SPD_RSM_TIMSET */ +#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b + +/* OC_TIMSET */ +#define USB2_OC_TIMSET_INIT 0x000209ab + +/* UTMI_CTRL */ +#define USB2_UTMI_CTRL_INIT 0x8000018f + +/* COMMCTRL */ +#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ + +/* OBINTSTA and OBINTEN */ +#define USB2_OBINTSTA_CLEAR GENMASK(31, 0) +#define USB2_OBINT_SESSVLDCHG BIT(12) +#define USB2_OBINT_IDDIGCHG BIT(11) +#define USB2_OBINT_VBSTAINT BIT(3) +#define USB2_OBINT_IDCHG_EN BIT(0) /* RZ/G2L specific */ + +/* VBCTRL */ +#define USB2_VBCTRL_VBSTA_MASK GENMASK(31, 28) +#define USB2_VBCTRL_VBSTA_DEFAULT 2 +#define USB2_VBCTRL_VBLVL_MASK GENMASK(23, 20) +#define USB2_VBCTRL_VBLVL(m) FIELD_PREP_CONST(USB2_VBCTRL_VBLVL_MASK, (m)) +#define USB2_VBCTRL_OCCLREN BIT(16) +#define USB2_VBCTRL_DRVVBUSSEL BIT(8) +#define USB2_VBCTRL_SIDDQREL BIT(2) +#define USB2_VBCTRL_VBOUT BIT(0) + +/* LINECTRL1 */ +#define USB2_LINECTRL1_DPRPD_EN BIT(19) +#define USB2_LINECTRL1_DP_RPD BIT(18) +#define USB2_LINECTRL1_DMRPD_EN BIT(17) +#define USB2_LINECTRL1_DM_RPD BIT(16) +#define USB2_LINECTRL1_OPMODE_NODRV BIT(6) + +/* ADPCTRL */ +#define USB2_ADPCTRL_OTGSESSVLD BIT(20) +#define USB2_ADPCTRL_IDDIG BIT(19) +#define USB2_ADPCTRL_VBUSVALID BIT(18) +#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ +#define USB2_ADPCTRL_DRVVBUS BIT(4) + +/* RZ/G2L specific */ +#define USB2_LINECTRL1_USB2_IDMON BIT(0) + +#define NUM_OF_PHYS 4 +enum rcar_gen3_phy_index { + PHY_INDEX_BOTH_HC, + PHY_INDEX_OHCI, + PHY_INDEX_EHCI, + PHY_INDEX_HSUSB +}; + +static const u32 rcar_gen3_int_enable[NUM_OF_PHYS] = { + USB2_INT_ENABLE_USBH_INTB_EN | USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTA_EN, + USB2_INT_ENABLE_USBH_INTB_EN, + 0 +}; + +struct rcar_gen3_phy { + struct phy *phy; + struct rcar_gen3_chan *ch; + u32 int_enable_bits; + bool initialized; + bool powered; +}; + +struct rcar_gen3_chan { + void __iomem *base; + struct device *dev; /* platform_device's device */ + const struct rcar_gen3_phy_drv_data *phy_data; + struct extcon_dev *extcon; + struct reset_control *rstc; + struct rcar_gen3_phy rphys[NUM_OF_PHYS]; + struct regulator *vbus; + struct work_struct work; + spinlock_t lock; /* protects access to hardware and driver data structure. */ + enum usb_dr_mode dr_mode; + bool extcon_host; + bool is_otg_channel; + bool uses_otg_pins; + bool otg_internal_reg; +}; + +struct rcar_gen3_phy_drv_data { + const struct phy_ops *phy_usb2_ops; + bool no_adp_ctrl; + bool init_bus; + bool utmi_ctrl; + bool vblvl_ctrl; + u32 obint_enable_bits; +}; + +/* + * Combination about is_otg_channel and uses_otg_pins: + * + * Parameters || Behaviors + * is_otg_channel | uses_otg_pins || irqs | role sysfs + * ---------------------+---------------++--------------+------------ + * true | true || enabled | enabled + * true | false || disabled | enabled + * false | any || disabled | disabled + */ + +static void rcar_gen3_phy_usb2_work(struct work_struct *work) +{ + struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan, + work); + + if (ch->extcon_host) { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, true); + extcon_set_state_sync(ch->extcon, EXTCON_USB, false); + } else { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, false); + extcon_set_state_sync(ch->extcon, EXTCON_USB, true); + } +} + +static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_COMMCTRL); + + dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, host); + if (host) + val &= ~USB2_COMMCTRL_OTG_PERI; + else + val |= USB2_COMMCTRL_OTG_PERI; + writel(val, usb2_base + USB2_COMMCTRL); +} + +static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_LINECTRL1); + + dev_vdbg(ch->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); + if (dp) + val |= USB2_LINECTRL1_DP_RPD; + if (dm) + val |= USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_phy_usb2_set_vbus(struct rcar_gen3_chan *ch, + u32 vbus_ctrl_reg, + u32 vbus_ctrl_val, + bool enable) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + vbus_ctrl_reg); + if (enable) + val |= vbus_ctrl_val; + else + val &= ~vbus_ctrl_val; + writel(val, usb2_base + vbus_ctrl_reg); + + dev_vdbg(ch->dev, "%s: reg=0x%08x, val=%08x, enable=%d\n", + __func__, vbus_ctrl_reg, val, enable); +} + +static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) +{ + if (ch->otg_internal_reg) { + regulator_hardware_enable(ch->vbus, vbus); + return; + } + + if (ch->phy_data->no_adp_ctrl || ch->phy_data->vblvl_ctrl) { + if (ch->vbus) + regulator_hardware_enable(ch->vbus, vbus); + + rcar_gen3_phy_usb2_set_vbus(ch, USB2_VBCTRL, + USB2_VBCTRL_VBOUT, vbus); + return; + } + + rcar_gen3_phy_usb2_set_vbus(ch, USB2_ADPCTRL, + USB2_ADPCTRL_DRVVBUS, vbus); +} + +static void rcar_gen3_control_otg_irq(struct rcar_gen3_chan *ch, int enable) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_OBINTEN); + + if (ch->uses_otg_pins && enable) + val |= ch->phy_data->obint_enable_bits; + else + val &= ~ch->phy_data->obint_enable_bits; + writel(val, usb2_base + USB2_OBINTEN); +} + +static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 1); + + ch->extcon_host = true; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + ch->extcon_host = false; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_b_host(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val | USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); + + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val & ~USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 1); +} + +static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_control_otg_irq(ch, 0); + + rcar_gen3_enable_vbus_ctrl(ch, 1); + rcar_gen3_init_for_host(ch); + + rcar_gen3_control_otg_irq(ch, 1); +} + +static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) +{ + if (ch->phy_data->vblvl_ctrl) { + u32 val = readl(ch->base + USB2_ADPCTRL); + bool vbus_valid = val & USB2_ADPCTRL_VBUSVALID; + bool device = val & USB2_ADPCTRL_IDDIG; + + return device == vbus_valid; + } + + if (!ch->uses_otg_pins) + return ch->dr_mode != USB_DR_MODE_HOST; + + if (ch->phy_data->no_adp_ctrl) + return !!(readl(ch->base + USB2_LINECTRL1) & USB2_LINECTRL1_USB2_IDMON); + + return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); +} + +static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) +{ + if (!rcar_gen3_check_id(ch)) + rcar_gen3_init_for_host(ch); + else + rcar_gen3_init_for_peri(ch); +} + +static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch) +{ + return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI); +} + +static enum phy_mode rcar_gen3_get_phy_mode(struct rcar_gen3_chan *ch) +{ + if (rcar_gen3_is_host(ch)) + return PHY_MODE_USB_HOST; + + return PHY_MODE_USB_DEVICE; +} + +static bool rcar_gen3_is_any_rphy_initialized(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].initialized) + return true; + } + + return false; +} + +static bool rcar_gen3_is_any_otg_rphy_initialized(struct rcar_gen3_chan *ch) +{ + for (enum rcar_gen3_phy_index i = PHY_INDEX_BOTH_HC; i <= PHY_INDEX_EHCI; + i++) { + if (ch->rphys[i].initialized) + return true; + } + + return false; +} + +static bool rcar_gen3_are_all_rphys_power_off(struct rcar_gen3_chan *ch) +{ + int i; + + for (i = 0; i < NUM_OF_PHYS; i++) { + if (ch->rphys[i].powered) + return false; + } + + return true; +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + bool is_b_device; + enum phy_mode cur_mode, new_mode; + + guard(spinlock_irqsave)(&ch->lock); + + if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) + return -EIO; + + if (sysfs_streq(buf, "host")) + new_mode = PHY_MODE_USB_HOST; + else if (sysfs_streq(buf, "peripheral")) + new_mode = PHY_MODE_USB_DEVICE; + else + return -EINVAL; + + /* is_b_device: true is B-Device. false is A-Device. */ + is_b_device = rcar_gen3_check_id(ch); + cur_mode = rcar_gen3_get_phy_mode(ch); + + /* If current and new mode is the same, this returns the error */ + if (cur_mode == new_mode) + return -EINVAL; + + if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ + if (!is_b_device) /* A-Peripheral */ + rcar_gen3_init_from_a_peri_to_a_host(ch); + else /* B-Peripheral */ + rcar_gen3_init_for_b_host(ch); + } else { /* And is_host must be true */ + if (!is_b_device) /* A-Host */ + rcar_gen3_init_for_a_peri(ch); + else /* B-Host */ + rcar_gen3_init_for_peri(ch); + } + + return count; +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) + return -EIO; + + return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : + "peripheral"); +} +static DEVICE_ATTR_RW(role); + +static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + if (!ch->is_otg_channel || rcar_gen3_is_any_otg_rphy_initialized(ch)) + return; + + /* Should not use functions of read-modify-write a register */ + val = readl(usb2_base + USB2_LINECTRL1); + val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | + USB2_LINECTRL1_DMRPD_EN | USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); + + if (!ch->phy_data->no_adp_ctrl) { + if (ch->phy_data->vblvl_ctrl) { + val = readl(usb2_base + USB2_VBCTRL); + val = (val & ~USB2_VBCTRL_VBLVL_MASK) | USB2_VBCTRL_VBLVL(2); + writel(val, usb2_base + USB2_VBCTRL); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP | USB2_ADPCTRL_DRVVBUS, + usb2_base + USB2_ADPCTRL); + } else { + val = readl(usb2_base + USB2_VBCTRL); + val &= ~USB2_VBCTRL_OCCLREN; + writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); + } + } + mdelay(20); + + writel(0xffffffff, usb2_base + USB2_OBINTSTA); + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTEN); + + rcar_gen3_device_recognition(ch); +} + +static void rcar_gen3_configure_vblvl_ctrl(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + if (!ch->phy_data->vblvl_ctrl) + return; + + val = readl(usb2_base + USB2_VBCTRL); + if ((val & USB2_VBCTRL_VBSTA_MASK) == + FIELD_PREP_CONST(USB2_VBCTRL_VBSTA_MASK, USB2_VBCTRL_VBSTA_DEFAULT)) + val &= ~USB2_VBCTRL_VBLVL_MASK; + else + val |= USB2_VBCTRL_VBLVL(USB2_VBCTRL_VBSTA_DEFAULT); + writel(val, usb2_base + USB2_VBCTRL); +} + +static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) +{ + struct rcar_gen3_chan *ch = _ch; + void __iomem *usb2_base = ch->base; + struct device *dev = ch->dev; + irqreturn_t ret = IRQ_NONE; + u32 status; + + pm_runtime_get_noresume(dev); + + if (pm_runtime_suspended(dev)) + goto rpm_put; + + scoped_guard(spinlock, &ch->lock) { + status = readl(usb2_base + USB2_OBINTSTA); + if (status & ch->phy_data->obint_enable_bits) { + dev_vdbg(dev, "%s: %08x\n", __func__, status); + if (ch->phy_data->vblvl_ctrl) + writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); + else + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); + rcar_gen3_device_recognition(ch); + rcar_gen3_configure_vblvl_ctrl(ch); + ret = IRQ_HANDLED; + } + } + +rpm_put: + pm_runtime_put_noidle(dev); + return ret; +} + +static int rcar_gen3_phy_usb2_init(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + + guard(spinlock_irqsave)(&channel->lock); + + /* Initialize USB2 part */ + val = readl(usb2_base + USB2_INT_ENABLE); + val |= USB2_INT_ENABLE_UCOM_INTEN | rphy->int_enable_bits; + writel(val, usb2_base + USB2_INT_ENABLE); + + if (!rcar_gen3_is_any_rphy_initialized(channel)) { + writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); + writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); + } + + /* Initialize otg part (only if we initialize a PHY with IRQs). */ + if (rphy->int_enable_bits) + rcar_gen3_init_otg(channel); + + if (channel->phy_data->vblvl_ctrl) { + /* SIDDQ mode release */ + writel(readl(usb2_base + USB2_VBCTRL) | USB2_VBCTRL_SIDDQREL, + usb2_base + USB2_VBCTRL); + udelay(250); + } + + if (channel->phy_data->utmi_ctrl) { + val = readl(usb2_base + USB2_REGEN_CG_CTRL) | USB2_REGEN_CG_CTRL_UPHY_WEN; + writel(val, usb2_base + USB2_REGEN_CG_CTRL); + + writel(USB2_UTMI_CTRL_INIT, usb2_base + USB2_UTMI_CTRL); + writel(val & ~USB2_REGEN_CG_CTRL_UPHY_WEN, usb2_base + USB2_REGEN_CG_CTRL); + } + + rphy->initialized = true; + + return 0; +} + +static int rcar_gen3_phy_usb2_exit(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + + guard(spinlock_irqsave)(&channel->lock); + + rphy->initialized = false; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~rphy->int_enable_bits; + if (!rcar_gen3_is_any_rphy_initialized(channel)) + val &= ~USB2_INT_ENABLE_UCOM_INTEN; + writel(val, usb2_base + USB2_INT_ENABLE); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_on(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + void __iomem *usb2_base = channel->base; + u32 val; + int ret = 0; + + if (channel->vbus && !channel->otg_internal_reg) { + ret = regulator_enable(channel->vbus); + if (ret) + return ret; + } + + guard(spinlock_irqsave)(&channel->lock); + + if (!rcar_gen3_are_all_rphys_power_off(channel)) + goto out; + + val = readl(usb2_base + USB2_USBCTR); + val |= USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + val &= ~USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + +out: + /* The powered flag should be set for any other phys anyway */ + rphy->powered = true; + + return 0; +} + +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_phy *rphy = phy_get_drvdata(p); + struct rcar_gen3_chan *channel = rphy->ch; + int ret = 0; + + scoped_guard(spinlock_irqsave, &channel->lock) { + rphy->powered = false; + + if (rcar_gen3_are_all_rphys_power_off(channel)) { + u32 val = readl(channel->base + USB2_USBCTR); + + val |= USB2_USBCTR_PLL_RST; + writel(val, channel->base + USB2_USBCTR); + } + } + + if (channel->vbus && !channel->otg_internal_reg) + ret = regulator_disable(channel->vbus); + + return ret; +} + +static const struct phy_ops rcar_gen3_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, + .owner = THIS_MODULE, +}; + +static const struct phy_ops rz_g1c_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .owner = THIS_MODULE, +}; + +static const struct rcar_gen3_phy_drv_data rcar_gen3_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = false, + .obint_enable_bits = USB2_OBINT_SESSVLDCHG | + USB2_OBINT_IDDIGCHG, +}; + +static const struct rcar_gen3_phy_drv_data rz_g1c_phy_usb2_data = { + .phy_usb2_ops = &rz_g1c_phy_usb2_ops, + .no_adp_ctrl = false, + .obint_enable_bits = USB2_OBINT_SESSVLDCHG | + USB2_OBINT_IDDIGCHG, +}; + +static const struct rcar_gen3_phy_drv_data rz_g2l_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = true, + .obint_enable_bits = USB2_OBINT_IDCHG_EN, +}; + +static const struct rcar_gen3_phy_drv_data rz_g3s_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = true, + .init_bus = true, + .obint_enable_bits = USB2_OBINT_IDCHG_EN, +}; + +static const struct rcar_gen3_phy_drv_data rz_t2h_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .vblvl_ctrl = true, + .obint_enable_bits = USB2_OBINT_IDCHG_EN | USB2_OBINT_VBSTAINT, +}; + +static const struct rcar_gen3_phy_drv_data rz_v2h_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = true, + .utmi_ctrl = true, + .obint_enable_bits = USB2_OBINT_IDCHG_EN, +}; + +static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { + { + .compatible = "renesas,usb2-phy-r8a77470", + .data = &rz_g1c_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a7795", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a7796", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r8a77965", + .data = &rcar_gen3_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r9a08g045", + .data = &rz_g3s_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r9a08g046", + .data = &rz_g3s_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r9a09g057", + .data = &rz_v2h_phy_usb2_data, + }, + { + .compatible = "renesas,usb2-phy-r9a09g077", + .data = &rz_t2h_phy_usb2_data, + }, + { + .compatible = "renesas,rzg2l-usb2-phy", + .data = &rz_g2l_phy_usb2_data, + }, + { + .compatible = "renesas,rcar-gen3-usb2-phy", + .data = &rcar_gen3_phy_usb2_data, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); + +static const unsigned int rcar_gen3_phy_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static struct phy *rcar_gen3_phy_usb2_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (args->args_count == 0) /* For old version dts */ + return ch->rphys[PHY_INDEX_BOTH_HC].phy; + else if (args->args_count > 1) /* Prevent invalid args count */ + return ERR_PTR(-ENODEV); + + if (args->args[0] >= NUM_OF_PHYS) + return ERR_PTR(-ENODEV); + + return ch->rphys[args->args[0]].phy; +} + +static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np) +{ + enum usb_dr_mode candidate = USB_DR_MODE_UNKNOWN; + int i; + + /* + * If one of device nodes has other dr_mode except UNKNOWN, + * this function returns UNKNOWN. To achieve backward compatibility, + * this loop starts the index as 0. + */ + for (i = 0; i < NUM_OF_PHYS; i++) { + enum usb_dr_mode mode = of_usb_get_dr_mode_by_phy(np, i); + + if (mode != USB_DR_MODE_UNKNOWN) { + if (candidate == USB_DR_MODE_UNKNOWN) + candidate = mode; + else if (candidate != mode) + return USB_DR_MODE_UNKNOWN; + } + } + + return candidate; +} + +static void rcar_gen3_reset_assert(void *data) +{ + reset_control_assert(data); +} + +static int rcar_gen3_phy_usb2_init_bus(struct rcar_gen3_chan *channel) +{ + struct device *dev = channel->dev; + int ret; + u32 val; + + if (!channel->phy_data->init_bus) + return 0; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + val = readl(channel->base + USB2_AHB_BUS_CTR); + val &= ~USB2_AHB_BUS_CTR_MBL_MASK; + val |= USB2_AHB_BUS_CTR_MBL_INCR4; + writel(val, channel->base + USB2_AHB_BUS_CTR); + + pm_runtime_put(dev); + + return 0; +} + +static int rcar_gen3_phy_usb2_regulator_endisable(struct regulator_dev *rdev, + bool enable) +{ + struct rcar_gen3_chan *channel = rdev_get_drvdata(rdev); + struct device *dev = channel->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_warn(dev, "pm_runtime_get failed: %i\n", ret); + return ret; + } + + rcar_gen3_phy_usb2_set_vbus(channel, USB2_VBCTRL, + USB2_VBCTRL_VBOUT, enable); + pm_runtime_put_noidle(dev); + + return ret; +} + +static int rcar_gen3_phy_usb2_regulator_enable(struct regulator_dev *rdev) +{ + return rcar_gen3_phy_usb2_regulator_endisable(rdev, true); +} + +static int rcar_gen3_phy_usb2_regulator_disable(struct regulator_dev *rdev) +{ + return rcar_gen3_phy_usb2_regulator_endisable(rdev, false); +} + +static int rcar_gen3_phy_usb2_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct rcar_gen3_chan *channel = rdev_get_drvdata(rdev); + void __iomem *usb2_base = channel->base; + struct device *dev = channel->dev; + u32 vbus_ctrl_reg = USB2_VBCTRL; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_warn(dev, "pm_runtime_get failed: %i\n", ret); + return ret; + } + + val = readl(usb2_base + vbus_ctrl_reg); + + pm_runtime_put_noidle(dev); + dev_dbg(channel->dev, "%s: %08x\n", __func__, val); + + return (val & USB2_VBCTRL_VBOUT) ? 1 : 0; +} + +static const struct regulator_ops rcar_gen3_phy_usb2_regulator_ops = { + .enable = rcar_gen3_phy_usb2_regulator_enable, + .disable = rcar_gen3_phy_usb2_regulator_disable, + .is_enabled = rcar_gen3_phy_usb2_regulator_is_enabled, +}; + +static const struct regulator_desc rcar_gen3_phy_usb2_regulator = { + .name = "otg-vbus-regulator", + .of_match = of_match_ptr("vbus-regulator"), + .ops = &rcar_gen3_phy_usb2_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + +static void rcar_gen3_phy_usb2_vbus_disable_action(void *data) +{ + struct regulator *vbus = data; + + regulator_disable(vbus); +} + +static int rcar_gen3_phy_usb2_vbus_regulator_get_exclusive_enable(struct rcar_gen3_chan *channel, + bool enable) +{ + struct device *dev = channel->dev; + int ret; + + channel->vbus = devm_regulator_get_exclusive(dev, "vbus"); + if (IS_ERR(channel->vbus)) { + ret = PTR_ERR(channel->vbus); + /* If vbus-regulator node was present vbus regulator should be available */ + if (channel->otg_internal_reg) + return ret; + + if (ret == -EPROBE_DEFER) + return ret; + + return 0; + } + + if (enable) { + ret = regulator_enable(channel->vbus); + if (ret) + return ret; + } + + if (regulator_is_enabled(channel->vbus)) + return devm_add_action_or_reset(dev, rcar_gen3_phy_usb2_vbus_disable_action, + channel->vbus); + + return 0; +} + +static int rcar_gen3_phy_usb2_vbus_regulator_register(struct rcar_gen3_chan *channel) +{ + struct device *dev = channel->dev; + struct regulator_config rcfg = { .dev = dev, }; + struct regulator_dev *rdev; + bool enable = false; + + rcfg.of_node = of_get_available_child_by_name(dev->of_node, + "vbus-regulator"); + if (rcfg.of_node) { + rcfg.driver_data = channel; + rdev = devm_regulator_register(dev, &rcar_gen3_phy_usb2_regulator, + &rcfg); + of_node_put(rcfg.of_node); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), + "Failed to create vbus-regulator\n"); + + channel->otg_internal_reg = true; + enable = true; + } + + return rcar_gen3_phy_usb2_vbus_regulator_get_exclusive_enable(channel, enable); +} + +static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_chan *channel; + struct phy_provider *provider; + struct mux_state *mux_state; + int ret = 0, i, irq; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + channel->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(channel->base)) + return PTR_ERR(channel->base); + + channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node); + if (channel->dr_mode != USB_DR_MODE_UNKNOWN) { + channel->is_otg_channel = true; + channel->uses_otg_pins = !of_property_read_bool(dev->of_node, + "renesas,no-otg-pins"); + channel->extcon = devm_extcon_dev_allocate(dev, + rcar_gen3_phy_cable); + if (IS_ERR(channel->extcon)) + return PTR_ERR(channel->extcon); + + ret = devm_extcon_dev_register(dev, channel->extcon); + if (ret < 0) { + dev_err(dev, "Failed to register extcon\n"); + return ret; + } + } + + channel->rstc = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(channel->rstc)) + return PTR_ERR(channel->rstc); + + ret = reset_control_deassert(channel->rstc); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, rcar_gen3_reset_assert, channel->rstc); + if (ret) + return ret; + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + channel->phy_data = of_device_get_match_data(dev); + if (!channel->phy_data) + return -EINVAL; + + platform_set_drvdata(pdev, channel); + channel->dev = dev; + + ret = rcar_gen3_phy_usb2_init_bus(channel); + if (ret) + return ret; + + spin_lock_init(&channel->lock); + for (i = 0; i < NUM_OF_PHYS; i++) { + channel->rphys[i].phy = devm_phy_create(dev, NULL, + channel->phy_data->phy_usb2_ops); + if (IS_ERR(channel->rphys[i].phy)) + return dev_err_probe(dev, PTR_ERR(channel->rphys[i].phy), + "Failed to create USB2 PHY\n"); + + channel->rphys[i].ch = channel; + channel->rphys[i].int_enable_bits = rcar_gen3_int_enable[i]; + phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]); + } + + mux_state = devm_mux_state_get_optional_selected(dev, NULL); + if (IS_ERR(mux_state)) + return dev_err_probe(dev, PTR_ERR(mux_state), "Failed to get USB mux\n"); + + if (channel->phy_data->no_adp_ctrl && channel->is_otg_channel) { + ret = rcar_gen3_phy_usb2_vbus_regulator_register(channel); + if (ret) + return ret; + } else { + channel->vbus = devm_regulator_get_optional(dev, "vbus"); + } + if (IS_ERR(channel->vbus)) { + if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) + return PTR_ERR(channel->vbus); + + channel->vbus = NULL; + } + + irq = platform_get_irq_optional(pdev, 0); + if (irq < 0 && irq != -ENXIO) { + return irq; + } else if (irq > 0) { + INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work); + ret = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, + IRQF_SHARED, dev_name(dev), channel); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to request irq (%d)\n", + irq); + } + + provider = devm_of_phy_provider_register(dev, rcar_gen3_phy_usb2_xlate); + if (IS_ERR(provider)) { + return dev_err_probe(dev, PTR_ERR(provider), + "Failed to register PHY provider\n"); + } else if (channel->is_otg_channel) { + ret = device_create_file(dev, &dev_attr_role); + if (ret < 0) + return ret; + } + + return 0; +} + +static void rcar_gen3_phy_usb2_remove(struct platform_device *pdev) +{ + struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); + + if (channel->is_otg_channel) + device_remove_file(&pdev->dev, &dev_attr_role); +} + +static int rcar_gen3_phy_usb2_suspend(struct device *dev) +{ + struct rcar_gen3_chan *channel = dev_get_drvdata(dev); + + return reset_control_assert(channel->rstc); +} + +static int rcar_gen3_phy_usb2_resume(struct device *dev) +{ + struct rcar_gen3_chan *channel = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(channel->rstc); + if (ret) + return ret; + + ret = rcar_gen3_phy_usb2_init_bus(channel); + if (ret) + reset_control_assert(channel->rstc); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rcar_gen3_phy_usb2_pm_ops, + rcar_gen3_phy_usb2_suspend, + rcar_gen3_phy_usb2_resume); + +static struct platform_driver rcar_gen3_phy_usb2_driver = { + .driver = { + .name = "phy_rcar_gen3_usb2", + .of_match_table = rcar_gen3_phy_usb2_match_table, + .pm = pm_ptr(&rcar_gen3_phy_usb2_pm_ops), + }, + .probe = rcar_gen3_phy_usb2_probe, + .remove = rcar_gen3_phy_usb2_remove, +}; +module_platform_driver(rcar_gen3_phy_usb2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb3.c b/drivers/phy/renesas/phy-rcar-gen3-usb3.c new file mode 100644 index 000000000..0420f5b28 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb3.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen3 for USB3.0 PHY driver + * + * Copyright (C) 2017 Renesas Electronics Corporation + */ + +#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/pm_runtime.h> + +#define USB30_CLKSET0 0x034 +#define USB30_CLKSET1 0x036 +#define USB30_SSC_SET 0x038 +#define USB30_PHY_ENABLE 0x060 +#define USB30_VBUS_EN 0x064 + +/* USB30_CLKSET0 */ +#define CLKSET0_PRIVATE 0x05c0 +#define CLKSET0_USB30_FSEL_USB_EXTAL 0x0002 + +/* USB30_CLKSET1 */ +#define CLKSET1_USB30_PLL_MULTI_SHIFT 6 +#define CLKSET1_USB30_PLL_MULTI_USB_EXTAL (0x64 << \ + CLKSET1_USB30_PLL_MULTI_SHIFT) +#define CLKSET1_PHYRESET BIT(4) /* 1: reset */ +#define CLKSET1_REF_CLKDIV BIT(3) /* 1: USB_EXTAL */ +#define CLKSET1_PRIVATE_2_1 BIT(1) /* Write B'01 */ +#define CLKSET1_REF_CLK_SEL BIT(0) /* 1: USB3S0_CLK_P */ + +/* USB30_SSC_SET */ +#define SSC_SET_SSC_EN BIT(12) +#define SSC_SET_RANGE_SHIFT 9 +#define SSC_SET_RANGE_4980 (0x0 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4492 (0x1 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4003 (0x2 << SSC_SET_RANGE_SHIFT) + +/* USB30_PHY_ENABLE */ +#define PHY_ENABLE_RESET_EN BIT(4) + +/* USB30_VBUS_EN */ +#define VBUS_EN_VBUS_EN BIT(1) + +struct rcar_gen3_usb3 { + void __iomem *base; + struct phy *phy; + u32 ssc_range; + bool usb3s_clk; + bool usb_extal; +}; + +static void write_clkset1_for_usb_extal(struct rcar_gen3_usb3 *r, bool reset) +{ + u16 val = CLKSET1_USB30_PLL_MULTI_USB_EXTAL | + CLKSET1_REF_CLKDIV | CLKSET1_PRIVATE_2_1; + + if (reset) + val |= CLKSET1_PHYRESET; + + writew(val, r->base + USB30_CLKSET1); +} + +static void rcar_gen3_phy_usb3_enable_ssc(struct rcar_gen3_usb3 *r) +{ + u16 val = SSC_SET_SSC_EN; + + switch (r->ssc_range) { + case 4980: + val |= SSC_SET_RANGE_4980; + break; + case 4492: + val |= SSC_SET_RANGE_4492; + break; + case 4003: + val |= SSC_SET_RANGE_4003; + break; + default: + dev_err(&r->phy->dev, "%s: unsupported range (%x)\n", __func__, + r->ssc_range); + return; + } + + writew(val, r->base + USB30_SSC_SET); +} + +static void rcar_gen3_phy_usb3_select_usb_extal(struct rcar_gen3_usb3 *r) +{ + write_clkset1_for_usb_extal(r, false); + if (r->ssc_range) + rcar_gen3_phy_usb3_enable_ssc(r); + writew(CLKSET0_PRIVATE | CLKSET0_USB30_FSEL_USB_EXTAL, + r->base + USB30_CLKSET0); + writew(PHY_ENABLE_RESET_EN, r->base + USB30_PHY_ENABLE); + write_clkset1_for_usb_extal(r, true); + usleep_range(10, 20); + write_clkset1_for_usb_extal(r, false); +} + +static int rcar_gen3_phy_usb3_init(struct phy *p) +{ + struct rcar_gen3_usb3 *r = phy_get_drvdata(p); + + dev_vdbg(&r->phy->dev, "%s: enter (%d, %d, %d)\n", __func__, + r->usb3s_clk, r->usb_extal, r->ssc_range); + + if (!r->usb3s_clk && r->usb_extal) + rcar_gen3_phy_usb3_select_usb_extal(r); + + /* Enables VBUS detection anyway */ + writew(VBUS_EN_VBUS_EN, r->base + USB30_VBUS_EN); + + return 0; +} + +static const struct phy_ops rcar_gen3_phy_usb3_ops = { + .init = rcar_gen3_phy_usb3_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_usb3_match_table[] = { + { .compatible = "renesas,rcar-gen3-usb3-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb3_match_table); + +static int rcar_gen3_phy_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_usb3 *r; + struct phy_provider *provider; + int ret = 0; + struct clk *clk; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + + r->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(r->base)) + return PTR_ERR(r->base); + + clk = devm_clk_get(dev, "usb3s_clk"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb3s_clk = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + clk = devm_clk_get(dev, "usb_extal"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb_extal = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + + if (!r->usb3s_clk && !r->usb_extal) { + dev_err(dev, "This driver needs usb3s_clk and/or usb_extal\n"); + ret = -EINVAL; + goto error; + } + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); + + r->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb3_ops); + if (IS_ERR(r->phy)) { + dev_err(dev, "Failed to create USB3 PHY\n"); + ret = PTR_ERR(r->phy); + goto error; + } + + of_property_read_u32(dev->of_node, "renesas,ssc-range", &r->ssc_range); + + platform_set_drvdata(pdev, r); + phy_set_drvdata(r->phy, r); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return ret; +} + +static void rcar_gen3_phy_usb3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); +} + +static struct platform_driver rcar_gen3_phy_usb3_driver = { + .driver = { + .name = "phy_rcar_gen3_usb3", + .of_match_table = rcar_gen3_phy_usb3_match_table, + }, + .probe = rcar_gen3_phy_usb3_probe, + .remove = rcar_gen3_phy_usb3_remove, +}; +module_platform_driver(rcar_gen3_phy_usb3_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 3.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); diff --git a/drivers/phy/renesas/phy-rzg3e-usb3.c b/drivers/phy/renesas/phy-rzg3e-usb3.c new file mode 100644 index 000000000..060309547 --- /dev/null +++ b/drivers/phy/renesas/phy-rzg3e-usb3.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G3E USB3.0 PHY driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#define USB3_TEST_RESET 0x0000 +#define USB3_TEST_UTMICTRL2 0x0b04 +#define USB3_TEST_PRMCTRL5_R 0x0c10 +#define USB3_TEST_PRMCTRL6_R 0x0c14 + +#define USB3_TEST_RSTCTRL 0x1000 +#define USB3_TEST_CLKCTRL 0x1004 +#define USB3_TEST_RAMCTRL 0x100c +#define USB3_TEST_CREGCTRL 0x1010 +#define USB3_TEST_LANECONFIG0 0x1030 + +#define USB3_TEST_RESET_PORTRESET0_CTRL BIT(9) +#define USB3_TEST_RESET_SIDDQ BIT(3) +#define USB3_TEST_RESET_PHY_RESET BIT(2) +#define USB3_TEST_RESET_PORTRESET0 BIT(1) +#define USB3_TEST_RESET_RELEASE_OVERRIDE (0) + +#define USB3_TEST_UTMICTRL2_CTRL_MASK GENMASK(9, 8) +#define USB3_TEST_UTMICTRL2_MODE_MASK GENMASK(1, 0) + +#define USB3_TEST_PRMCTRL5_R_TXPREEMPAMPTUNE0_MASK GENMASK(2, 1) + +#define USB3_TEST_PRMCTRL6_R_OTGTUNE0_MASK GENMASK(2, 0) + +#define USB3_TEST_RSTCTRL_HARDRESET_ODEN BIT(9) +#define USB3_TEST_RSTCTRL_PIPERESET_ODEN BIT(8) +#define USB3_TEST_RSTCTRL_HARDRESET BIT(1) +#define USB3_TEST_RSTCTRL_PIPERESET BIT(0) +#define USB3_TEST_RSTCTRL_ASSERT \ + (USB3_TEST_RSTCTRL_HARDRESET_ODEN | USB3_TEST_RSTCTRL_PIPERESET_ODEN | \ + USB3_TEST_RSTCTRL_HARDRESET | USB3_TEST_RSTCTRL_PIPERESET) +#define USB3_TEST_RSTCTRL_RELEASE_HARDRESET \ + (USB3_TEST_RSTCTRL_HARDRESET_ODEN | USB3_TEST_RSTCTRL_PIPERESET_ODEN | \ + USB3_TEST_RSTCTRL_PIPERESET) +#define USB3_TEST_RSTCTRL_DEASSERT \ + (USB3_TEST_RSTCTRL_HARDRESET_ODEN | USB3_TEST_RSTCTRL_PIPERESET_ODEN) +#define USB3_TEST_RSTCTRL_RELEASE_OVERRIDE (0) + +#define USB3_TEST_CLKCTRL_MPLLA_SSC_EN BIT(2) + +#define USB3_TEST_RAMCTRL_SRAM_INIT_DONE BIT(2) +#define USB3_TEST_RAMCTRL_SRAM_EXT_LD_DONE BIT(0) + +#define USB3_TEST_CREGCTRL_PARA_SEL BIT(8) + +#define USB3_TEST_LANECONFIG0_DEFAULT (0xd) + +struct rz_usb3 { + void __iomem *base; + struct reset_control *rstc; + bool skip_reinit; +}; + +static void rzg3e_phy_usb2test_phy_init(void __iomem *base) +{ + u32 val; + + val = readl(base + USB3_TEST_UTMICTRL2); + val |= USB3_TEST_UTMICTRL2_CTRL_MASK | USB3_TEST_UTMICTRL2_MODE_MASK; + writel(val, base + USB3_TEST_UTMICTRL2); + + val = readl(base + USB3_TEST_PRMCTRL5_R); + FIELD_MODIFY(USB3_TEST_PRMCTRL5_R_TXPREEMPAMPTUNE0_MASK, &val, 2); + writel(val, base + USB3_TEST_PRMCTRL5_R); + + val = readl(base + USB3_TEST_PRMCTRL6_R); + FIELD_MODIFY(USB3_TEST_PRMCTRL6_R_OTGTUNE0_MASK, &val, 7); + writel(val, base + USB3_TEST_PRMCTRL6_R); + + val = readl(base + USB3_TEST_RESET); + val &= ~USB3_TEST_RESET_SIDDQ; + val |= USB3_TEST_RESET_PORTRESET0_CTRL | USB3_TEST_RESET_PHY_RESET | + USB3_TEST_RESET_PORTRESET0; + writel(val, base + USB3_TEST_RESET); + fsleep(10); + + val &= ~(USB3_TEST_RESET_PHY_RESET | USB3_TEST_RESET_PORTRESET0); + writel(val, base + USB3_TEST_RESET); + fsleep(10); + + val = readl(base + USB3_TEST_UTMICTRL2); + val &= ~USB3_TEST_UTMICTRL2_CTRL_MASK; + writel(val, base + USB3_TEST_UTMICTRL2); + + writel(USB3_TEST_RESET_RELEASE_OVERRIDE, base + USB3_TEST_RESET); +} + +static int rzg3e_phy_usb3test_phy_init(void __iomem *base) +{ + int ret; + u32 val; + + writel(USB3_TEST_CREGCTRL_PARA_SEL, base + USB3_TEST_CREGCTRL); + writel(USB3_TEST_RSTCTRL_ASSERT, base + USB3_TEST_RSTCTRL); + fsleep(20); + + writel(USB3_TEST_CLKCTRL_MPLLA_SSC_EN, base + USB3_TEST_CLKCTRL); + writel(USB3_TEST_LANECONFIG0_DEFAULT, base + USB3_TEST_LANECONFIG0); + writel(USB3_TEST_RSTCTRL_RELEASE_HARDRESET, base + USB3_TEST_RSTCTRL); + + ret = readl_poll_timeout_atomic(base + USB3_TEST_RAMCTRL, val, + val & USB3_TEST_RAMCTRL_SRAM_INIT_DONE, 1, 10000); + if (ret) + return ret; + + writel(USB3_TEST_RSTCTRL_DEASSERT, base + USB3_TEST_RSTCTRL); + writel(USB3_TEST_RAMCTRL_SRAM_EXT_LD_DONE, base + USB3_TEST_RAMCTRL); + writel(USB3_TEST_RSTCTRL_RELEASE_OVERRIDE, base + USB3_TEST_RSTCTRL); + + return 0; +} + +static int rzg3e_phy_usb3_init_helper(void __iomem *base) +{ + rzg3e_phy_usb2test_phy_init(base); + + return rzg3e_phy_usb3test_phy_init(base); +} + +static int rzg3e_phy_usb3_init(struct phy *p) +{ + struct rz_usb3 *r = phy_get_drvdata(p); + int ret = 0; + + if (!r->skip_reinit) + ret = rzg3e_phy_usb3_init_helper(r->base); + + return ret; +} + +static const struct phy_ops rzg3e_phy_usb3_ops = { + .init = rzg3e_phy_usb3_init, + .owner = THIS_MODULE, +}; + +static int rzg3e_phy_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct rz_usb3 *r; + struct phy *phy; + int ret; + + r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + + r->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(r->base)) + return PTR_ERR(r->base); + + r->rstc = devm_reset_control_get_shared_deasserted(dev, NULL); + if (IS_ERR(r->rstc)) + return dev_err_probe(dev, PTR_ERR(r->rstc), "failed to get deasserted reset\n"); + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return ret; + + phy = devm_phy_create(dev, NULL, &rzg3e_phy_usb3_ops); + if (IS_ERR(phy)) + return dev_err_probe(dev, PTR_ERR(phy), "failed to create USB3 PHY\n"); + + platform_set_drvdata(pdev, r); + phy_set_drvdata(phy, r); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), "failed to register PHY provider\n"); + + return 0; +} + +static int rzg3e_phy_usb3_suspend(struct device *dev) +{ + struct rz_usb3 *r = dev_get_drvdata(dev); + + pm_runtime_put(dev); + reset_control_assert(r->rstc); + r->skip_reinit = false; + + return 0; +} + +static int rzg3e_phy_usb3_resume(struct device *dev) +{ + struct rz_usb3 *r = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(r->rstc); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto reset_assert; + + ret = rzg3e_phy_usb3_init_helper(r->base); + if (ret) + goto pm_put; + + r->skip_reinit = true; + + return 0; + +pm_put: + pm_runtime_put(dev); +reset_assert: + reset_control_assert(r->rstc); + return ret; +} + +static const struct dev_pm_ops rzg3e_phy_usb3_pm = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(rzg3e_phy_usb3_suspend, rzg3e_phy_usb3_resume) +}; + +static const struct of_device_id rzg3e_phy_usb3_match_table[] = { + { .compatible = "renesas,r9a09g047-usb3-phy" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rzg3e_phy_usb3_match_table); +static struct platform_driver rzg3e_phy_usb3_driver = { + .driver = { + .name = "phy_rzg3e_usb3", + .of_match_table = rzg3e_phy_usb3_match_table, + .pm = pm_sleep_ptr(&rzg3e_phy_usb3_pm), + }, + .probe = rzg3e_phy_usb3_probe, +}; +module_platform_driver(rzg3e_phy_usb3_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas RZ/G3E USB3.0 PHY Driver"); +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>"); diff --git a/drivers/phy/renesas/r8a779f0-ether-serdes.c b/drivers/phy/renesas/r8a779f0-ether-serdes.c new file mode 100644 index 000000000..8a6b6f366 --- /dev/null +++ b/drivers/phy/renesas/r8a779f0-ether-serdes.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Renesas Ethernet SERDES device driver + * + * Copyright (C) 2022-2025 Renesas Electronics Corporation + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#define R8A779F0_ETH_SERDES_NUM 3 +#define R8A779F0_ETH_SERDES_OFFSET 0x0400 +#define R8A779F0_ETH_SERDES_BANK_SELECT 0x03fc +#define R8A779F0_ETH_SERDES_TIMEOUT_US 100000 +#define R8A779F0_ETH_SERDES_NUM_RETRY_LINKUP 3 + +struct r8a779f0_eth_serdes_drv_data; +struct r8a779f0_eth_serdes_channel { + struct r8a779f0_eth_serdes_drv_data *dd; + struct phy *phy; + void __iomem *addr; + phy_interface_t phy_interface; + int speed; + int index; +}; + +struct r8a779f0_eth_serdes_drv_data { + void __iomem *addr; + struct platform_device *pdev; + struct reset_control *reset; + struct r8a779f0_eth_serdes_channel channel[R8A779F0_ETH_SERDES_NUM]; + bool initialized; +}; + +/* + * The datasheet describes initialization procedure without any information + * about registers' name/bits. So, this is all black magic to initialize + * the hardware. + */ +static void r8a779f0_eth_serdes_write32(void __iomem *addr, u32 offs, u32 bank, u32 data) +{ + iowrite32(bank, addr + R8A779F0_ETH_SERDES_BANK_SELECT); + iowrite32(data, addr + offs); +} + +static u32 r8a779f0_eth_serdes_read32(void __iomem *addr, u32 offs, u32 bank) +{ + iowrite32(bank, addr + R8A779F0_ETH_SERDES_BANK_SELECT); + + return ioread32(addr + offs); +} + +static int +r8a779f0_eth_serdes_reg_wait(struct r8a779f0_eth_serdes_channel *channel, + u32 offs, u32 bank, u32 mask, u32 expected) +{ + int ret; + u32 val; + + iowrite32(bank, channel->addr + R8A779F0_ETH_SERDES_BANK_SELECT); + + ret = readl_poll_timeout_atomic(channel->addr + offs, val, + (val & mask) == expected, + 1, R8A779F0_ETH_SERDES_TIMEOUT_US); + if (ret) + dev_dbg(&channel->phy->dev, + "%s: index %d, offs %x, bank %x, mask %x, expected %x\n", + __func__, channel->index, offs, bank, mask, expected); + + return ret; +} + +static int +r8a779f0_eth_serdes_common_init_ram(struct r8a779f0_eth_serdes_drv_data *dd) +{ + struct r8a779f0_eth_serdes_channel *channel; + int i, ret; + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM; i++) { + channel = &dd->channel[i]; + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x026c, 0x180, BIT(0), 0x01); + if (ret) + return ret; + } + + r8a779f0_eth_serdes_write32(dd->addr, 0x026c, 0x180, 0x03); + + return ret; +} + +static int +r8a779f0_eth_serdes_common_setting(struct r8a779f0_eth_serdes_channel *channel) +{ + struct r8a779f0_eth_serdes_drv_data *dd = channel->dd; + + /* Set combination mode */ + r8a779f0_eth_serdes_write32(dd->addr, 0x0244, 0x180, 0x00d7); + r8a779f0_eth_serdes_write32(dd->addr, 0x01cc, 0x180, 0xc200); + r8a779f0_eth_serdes_write32(dd->addr, 0x01c4, 0x180, 0x0042); + r8a779f0_eth_serdes_write32(dd->addr, 0x01c8, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(dd->addr, 0x01dc, 0x180, 0x002f); + r8a779f0_eth_serdes_write32(dd->addr, 0x01d0, 0x180, 0x0060); + r8a779f0_eth_serdes_write32(dd->addr, 0x01d8, 0x180, 0x2200); + r8a779f0_eth_serdes_write32(dd->addr, 0x01d4, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(dd->addr, 0x01e0, 0x180, 0x003d); + + return 0; +} + +static int +r8a779f0_eth_serdes_chan_setting(struct r8a779f0_eth_serdes_channel *channel) +{ + int ret; + + switch (channel->phy_interface) { + case PHY_INTERFACE_MODE_SGMII: + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x380, 0x2000); + r8a779f0_eth_serdes_write32(channel->addr, 0x01c0, 0x180, 0x0011); + r8a779f0_eth_serdes_write32(channel->addr, 0x0248, 0x180, 0x0540); + r8a779f0_eth_serdes_write32(channel->addr, 0x0258, 0x180, 0x0015); + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, 0x0100); + r8a779f0_eth_serdes_write32(channel->addr, 0x01a0, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x00d0, 0x180, 0x0002); + r8a779f0_eth_serdes_write32(channel->addr, 0x0150, 0x180, 0x0003); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c8, 0x180, 0x0100); + r8a779f0_eth_serdes_write32(channel->addr, 0x0148, 0x180, 0x0100); + r8a779f0_eth_serdes_write32(channel->addr, 0x0174, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x0160, 0x180, 0x0007); + r8a779f0_eth_serdes_write32(channel->addr, 0x01ac, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c4, 0x180, 0x0310); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c8, 0x180, 0x0101); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x00c8, 0x0180, BIT(0), 0); + if (ret) + return ret; + + r8a779f0_eth_serdes_write32(channel->addr, 0x0148, 0x180, 0x0101); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0148, 0x0180, BIT(0), 0); + if (ret) + return ret; + + r8a779f0_eth_serdes_write32(channel->addr, 0x00c4, 0x180, 0x1310); + r8a779f0_eth_serdes_write32(channel->addr, 0x00d8, 0x180, 0x1800); + r8a779f0_eth_serdes_write32(channel->addr, 0x00dc, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x001c, 0x300, 0x0001); + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x380, 0x2100); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0000, 0x0380, BIT(8), 0); + if (ret) + return ret; + + if (channel->speed == 1000) + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f00, 0x0140); + else if (channel->speed == 100) + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f00, 0x2100); + + /* For AN_ON */ + r8a779f0_eth_serdes_write32(channel->addr, 0x0004, 0x1f80, 0x0005); + r8a779f0_eth_serdes_write32(channel->addr, 0x0028, 0x1f80, 0x07a1); + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f80, 0x0208); + break; + + case PHY_INTERFACE_MODE_USXGMII: + r8a779f0_eth_serdes_write32(channel->addr, 0x001c, 0x300, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x0014, 0x380, 0x0050); + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x380, 0x2200); + r8a779f0_eth_serdes_write32(channel->addr, 0x001c, 0x380, 0x0400); + r8a779f0_eth_serdes_write32(channel->addr, 0x01c0, 0x180, 0x0001); + r8a779f0_eth_serdes_write32(channel->addr, 0x0248, 0x180, 0x056a); + r8a779f0_eth_serdes_write32(channel->addr, 0x0258, 0x180, 0x0015); + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, 0x1100); + r8a779f0_eth_serdes_write32(channel->addr, 0x01a0, 0x180, 0x0001); + r8a779f0_eth_serdes_write32(channel->addr, 0x00d0, 0x180, 0x0001); + r8a779f0_eth_serdes_write32(channel->addr, 0x0150, 0x180, 0x0001); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c8, 0x180, 0x0300); + r8a779f0_eth_serdes_write32(channel->addr, 0x0148, 0x180, 0x0300); + r8a779f0_eth_serdes_write32(channel->addr, 0x0174, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x0160, 0x180, 0x0004); + r8a779f0_eth_serdes_write32(channel->addr, 0x01ac, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c4, 0x180, 0x0310); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c8, 0x180, 0x0301); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x00c8, 0x180, BIT(0), 0); + if (ret) + return ret; + r8a779f0_eth_serdes_write32(channel->addr, 0x0148, 0x180, 0x0301); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0148, 0x180, BIT(0), 0); + if (ret) + return ret; + r8a779f0_eth_serdes_write32(channel->addr, 0x00c4, 0x180, 0x1310); + r8a779f0_eth_serdes_write32(channel->addr, 0x00d8, 0x180, 0x1800); + r8a779f0_eth_serdes_write32(channel->addr, 0x00dc, 0x180, 0x0000); + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x380, 0x2300); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0000, 0x380, BIT(8), 0); + if (ret) + return ret; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int +r8a779f0_eth_serdes_chan_speed(struct r8a779f0_eth_serdes_channel *channel) +{ + int ret; + + switch (channel->phy_interface) { + case PHY_INTERFACE_MODE_SGMII: + /* For AN_ON */ + if (channel->speed == 1000) + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f00, 0x1140); + else if (channel->speed == 100) + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f00, 0x3100); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0008, 0x1f80, BIT(0), 1); + if (ret) + return ret; + r8a779f0_eth_serdes_write32(channel->addr, 0x0008, 0x1f80, 0x0000); + break; + case PHY_INTERFACE_MODE_USXGMII: + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x1f00, 0x0120); + usleep_range(10, 20); + r8a779f0_eth_serdes_write32(channel->addr, 0x0000, 0x380, 0x2600); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0000, 0x380, BIT(10), 0); + if (ret) + return ret; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + + +static int r8a779f0_eth_serdes_monitor_linkup(struct r8a779f0_eth_serdes_channel *channel) +{ + int i, ret; + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM_RETRY_LINKUP; i++) { + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0004, 0x300, + BIT(2), BIT(2)); + if (!ret) + break; + + /* restart */ + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, 0x0100); + udelay(1); + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, 0x0000); + } + + return ret; +} + +static int r8a779f0_eth_serdes_hw_init(struct r8a779f0_eth_serdes_channel *channel) +{ + struct r8a779f0_eth_serdes_drv_data *dd = channel->dd; + int i, ret; + + if (dd->initialized) + return 0; + + reset_control_reset(dd->reset); + + usleep_range(1000, 2000); + + ret = r8a779f0_eth_serdes_common_init_ram(dd); + if (ret) + return ret; + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM; i++) { + ret = r8a779f0_eth_serdes_reg_wait(&dd->channel[i], 0x0000, + 0x300, BIT(15), 0); + if (ret) + return ret; + } + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM; i++) + r8a779f0_eth_serdes_write32(dd->channel[i].addr, 0x03d4, 0x380, 0x0443); + + ret = r8a779f0_eth_serdes_common_setting(channel); + if (ret) + return ret; + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM; i++) + r8a779f0_eth_serdes_write32(dd->channel[i].addr, 0x03d0, 0x380, 0x0001); + + + r8a779f0_eth_serdes_write32(dd->addr, 0x0000, 0x380, 0x8000); + + ret = r8a779f0_eth_serdes_common_init_ram(dd); + if (ret) + return ret; + + return r8a779f0_eth_serdes_reg_wait(&dd->channel[0], 0x0000, 0x380, BIT(15), 0); +} + +static int r8a779f0_eth_serdes_init(struct phy *p) +{ + struct r8a779f0_eth_serdes_channel *channel = phy_get_drvdata(p); + int ret; + + ret = r8a779f0_eth_serdes_hw_init(channel); + if (!ret) + channel->dd->initialized = true; + + return ret; +} + +static int r8a779f0_eth_serdes_exit(struct phy *p) +{ + struct r8a779f0_eth_serdes_channel *channel = phy_get_drvdata(p); + + channel->dd->initialized = false; + + return 0; +} + +static int r8a779f0_eth_serdes_hw_init_late(struct r8a779f0_eth_serdes_channel +*channel) +{ + int ret; + u32 val; + + ret = r8a779f0_eth_serdes_chan_setting(channel); + if (ret) + return ret; + + ret = r8a779f0_eth_serdes_chan_speed(channel); + if (ret) + return ret; + + r8a779f0_eth_serdes_write32(channel->addr, 0x03c0, 0x380, 0x0000); + + r8a779f0_eth_serdes_write32(channel->addr, 0x03d0, 0x380, 0x0000); + + val = r8a779f0_eth_serdes_read32(channel->addr, 0x00c0, 0x180); + r8a779f0_eth_serdes_write32(channel->addr, 0x00c0, 0x180, val | BIT(8)); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0100, 0x180, BIT(0), 1); + if (ret) + return ret; + r8a779f0_eth_serdes_write32(channel->addr, 0x00c0, 0x180, val & ~BIT(8)); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0100, 0x180, BIT(0), 0); + if (ret) + return ret; + + val = r8a779f0_eth_serdes_read32(channel->addr, 0x0144, 0x180); + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, val | BIT(4)); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0180, 0x180, BIT(0), 1); + if (ret) + return ret; + r8a779f0_eth_serdes_write32(channel->addr, 0x0144, 0x180, val & ~BIT(4)); + ret = r8a779f0_eth_serdes_reg_wait(channel, 0x0180, 0x180, BIT(0), 0); + if (ret) + return ret; + + return r8a779f0_eth_serdes_monitor_linkup(channel); +} + +static int r8a779f0_eth_serdes_power_on(struct phy *p) +{ + struct r8a779f0_eth_serdes_channel *channel = phy_get_drvdata(p); + + return r8a779f0_eth_serdes_hw_init_late(channel); +} + +static int r8a779f0_eth_serdes_set_mode(struct phy *p, enum phy_mode mode, + int submode) +{ + struct r8a779f0_eth_serdes_channel *channel = phy_get_drvdata(p); + + if (mode != PHY_MODE_ETHERNET) + return -EOPNOTSUPP; + + switch (submode) { + case PHY_INTERFACE_MODE_GMII: + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_USXGMII: + channel->phy_interface = submode; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int r8a779f0_eth_serdes_set_speed(struct phy *p, int speed) +{ + struct r8a779f0_eth_serdes_channel *channel = phy_get_drvdata(p); + + channel->speed = speed; + + return 0; +} + +static const struct phy_ops r8a779f0_eth_serdes_ops = { + .init = r8a779f0_eth_serdes_init, + .exit = r8a779f0_eth_serdes_exit, + .power_on = r8a779f0_eth_serdes_power_on, + .set_mode = r8a779f0_eth_serdes_set_mode, + .set_speed = r8a779f0_eth_serdes_set_speed, +}; + +static struct phy *r8a779f0_eth_serdes_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct r8a779f0_eth_serdes_drv_data *dd = dev_get_drvdata(dev); + + if (args->args[0] >= R8A779F0_ETH_SERDES_NUM) + return ERR_PTR(-ENODEV); + + return dd->channel[args->args[0]].phy; +} + +static const struct of_device_id r8a779f0_eth_serdes_of_table[] = { + { .compatible = "renesas,r8a779f0-ether-serdes", }, + { } +}; +MODULE_DEVICE_TABLE(of, r8a779f0_eth_serdes_of_table); + +static int r8a779f0_eth_serdes_probe(struct platform_device *pdev) +{ + struct r8a779f0_eth_serdes_drv_data *dd; + struct phy_provider *provider; + int i; + + dd = devm_kzalloc(&pdev->dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + platform_set_drvdata(pdev, dd); + dd->pdev = pdev; + dd->addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dd->addr)) + return PTR_ERR(dd->addr); + + dd->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(dd->reset)) + return PTR_ERR(dd->reset); + + for (i = 0; i < R8A779F0_ETH_SERDES_NUM; i++) { + struct r8a779f0_eth_serdes_channel *channel = &dd->channel[i]; + + channel->phy = devm_phy_create(&pdev->dev, NULL, + &r8a779f0_eth_serdes_ops); + if (IS_ERR(channel->phy)) + return PTR_ERR(channel->phy); + channel->addr = dd->addr + R8A779F0_ETH_SERDES_OFFSET * i; + channel->dd = dd; + channel->index = i; + phy_set_drvdata(channel->phy, channel); + } + + provider = devm_of_phy_provider_register(&pdev->dev, + r8a779f0_eth_serdes_xlate); + if (IS_ERR(provider)) + return PTR_ERR(provider); + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + return 0; +} + +static void r8a779f0_eth_serdes_remove(struct platform_device *pdev) +{ + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + platform_set_drvdata(pdev, NULL); +} + +static struct platform_driver r8a779f0_eth_serdes_driver_platform = { + .probe = r8a779f0_eth_serdes_probe, + .remove = r8a779f0_eth_serdes_remove, + .driver = { + .name = "r8a779f0_eth_serdes", + .of_match_table = r8a779f0_eth_serdes_of_table, + } +}; +module_platform_driver(r8a779f0_eth_serdes_driver_platform); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_DESCRIPTION("Renesas Ethernet SERDES device driver"); +MODULE_LICENSE("GPL"); |
