Commit aba43bdf authored by David S. Miller's avatar David S. Miller

Merge branch 'pcs-xpcs-mmap' into main

Serge Semin <fancer says:

====================
net: pcs: xpcs: Add memory-mapped device support

The main goal of this series is to extend the DW XPCS device support in
the kernel. Particularly the patchset adds a support of the DW XPCS
device with the MCI/APB3 IO interface registered as a platform device. In
order to have them utilized by the DW XPCS core the fwnode-based DW XPCS
descriptor creation procedure has been introduced. Finally the STMMAC
driver has been altered to support the DW XPCS passed via the 'pcs-handle'
property.

Note the series has been significantly re-developed since v1. So I even
had to change the subject. Anyway I've done my best to take all the noted
into account.

The series traditionally starts with a set of the preparation patches.
First one just moves the generic DW XPCS IDs macros from the internal
header file to the external one where some other IDs also reside. Second
patch splits up the xpcs_create() method to a set of the coherent
sub-functions for the sake of the further easier updates and to have it
looking less complicated. The goal of the next three patches is to extend
the DW XPCS ID management code by defining a new dw_xpcs_info structure
with both PCS and PMA IDs.

The next two patches provide the DW XPCS device DT-bindings and the
respective platform-device driver for the memory-mapped DW XPCS devices.
Besides the later patch makes use of the introduced dw_xpcs_info structure
to pre-define the DW XPCS IDs based on the platform-device compatible
string. Thus if there is no way to auto-identify the XPCS device
capabilities it can be done based on the custom device IDs passed via the
MDIO-device platform data.

Final DW XPCS driver change is about adding a new method of the DW XPCS
descriptor creation. The xpcs_create_fwnode() function has been introduced
with the same semantics as a similar method recently added to the Lynx PCS
driver. It's supposed to be called with the fwnode pointing to the DW XPCS
device node, for which the XPCS descriptor will be created.

The series is terminated with two STMMAC driver patches. The former one
simplifies the DW XPCS descriptor registration procedure by dropping the
MDIO-bus scanning and creating the descriptor for the particular device
address. The later patch alters the STMMAC PCS setup method so one would
support the DW XPCS specified via the "pcs-handle" property.

That's it for now. Thanks for review in advance. Any tests are very
welcome. After this series is merged in, I'll submit another one which
adds the generic 10GBase-R and 10GBase-X interfaces support to the STMMAC
and DW XPCS driver with the proper CSRs re-initialization, PMA
initialization and reference clock selection as it's described in the
Synopsys DW XPCS HW manual.

Link: https://lore.kernel.org/netdev/20231205103559.9605-1-fancer.lancer@gmail.com
Changelog v2:
- Drop the patches:
  [PATCH net-next 01/16] net: pcs: xpcs: Drop sentinel entry from 2500basex ifaces list
  [PATCH net-next 02/16] net: pcs: xpcs: Drop redundant workqueue.h include directive
  [PATCH net-next 03/16] net: pcs: xpcs: Return EINVAL in the internal methods
  [PATCH net-next 04/16] net: pcs: xpcs: Explicitly return error on caps validation
  as ones have already been merged into the kernel repo:
Link: https://lore.kernel.org/netdev/20240222175843.26919-1-fancer.lancer@gmail.com/
- Drop the patches:
  [PATCH net-next 14/16] net: stmmac: Pass netdev to XPCS setup function
  [PATCH net-next 15/16] net: stmmac: Add dedicated XPCS cleanup method
  as ones have already been merged into the kernel repo:
Link: https://lore.kernel.org/netdev/20240513-rzn1-gmac1-v7-0-6acf58b5440d@bootlin.com/
- Drop the patch:
  [PATCH net-next 06/16] net: pcs: xpcs: Avoid creating dummy XPCS MDIO device
  [PATCH net-next 09/16] net: mdio: Add Synopsys DW XPCS management interface support
  [PATCH net-next 11/16] net: pcs: xpcs: Change xpcs_create_mdiodev() suffix to "byaddr"
  [PATCH net-next 13/16] net: stmmac: intel: Register generic MDIO device
  as no longer relevant.
- Add new patches:
  [PATCH net-next v2 03/10] net: pcs: xpcs: Convert xpcs_id to dw_xpcs_desc
  [PATCH net-next v2 04/10] net: pcs: xpcs: Convert xpcs_compat to dw_xpcs_compat
  [PATCH net-next v2 05/10] net: pcs: xpcs: Introduce DW XPCS info structure
  [PATCH net-next v2 09/10] net: stmmac: Create DW XPCS device with particular address
- Use the xpcs_create_fwnode() function name and semantics similar to the
  Lynx PCS driver.
- Add kdoc describing the DW XPCS registration functions.
- Convert the memory-mapped DW XPCS device driver to being the
  platform-device driver.
- Convert the DW XPCS DT-bindings to defining both memory-mapped and MDIO
  devices.
- Drop inline'es from the methods statically defined in *.c. (@Maxime)
- Preserve the strict refcount-ing pattern. (@Russell)

Link: https://lore.kernel.org/netdev/20240602143636.5839-1-fancer.lancer@gmail.com/
Changelov v3:
- Implement the ordered clocks constraint. (@Rob)
- Convert xpcs_plat_pm_ops to being defined as static. (@Simon)
- Add the "@interface" argument kdoc to the xpcs_create_mdiodev()
  function. (@Simon)
- Fix the "@fwnode" argument name in the xpcs_create_fwnode() method kdoc.
  (@Simon)
- Move the return value descriptions to the "Return:" section of the
  xpcs_create_mdiodev() and xpcs_create_fwnode() kdoc. (@Simon)
- Drop stmmac_mdio_bus_data::has_xpcs flag and define the PCS-address
  mask with particular XPCS address instead.

Link: https://lore.kernel.org/netdev/20240627004142.8106-1-fancer.lancer@gmail.com/
Changelog v4:
- Make sure the series is applicable to the net-next tree. (@Vladimir)
- Rename entry to desc in the xpcs_init_id() method. (@Andrew)
- Add a comment to the clock-names property constraint about the
  oneOf-subschemas applicability. (@Conor)
- Convert "pclk" clock name to "csr" to match the DW XPCS IP-core
  input signal name. (@Rob)
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 390b14b5 357768c7
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/pcs/snps,dw-xpcs.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Synopsys DesignWare Ethernet PCS
maintainers:
- Serge Semin <fancer.lancer@gmail.com>
description:
Synopsys DesignWare Ethernet Physical Coding Sublayer provides an interface
between Media Access Control and Physical Medium Attachment Sublayer through
the Media Independent Interface (XGMII, USXGMII, XLGMII, GMII, etc)
controlled by means of the IEEE std. Clause 45 registers set. The PCS can be
optionally synthesized with a vendor-specific interface connected to
Synopsys PMA (also called DesignWare Consumer/Enterprise PHY) although in
general it can be used to communicate with any compatible PHY.
The PCS CSRs can be accessible either over the Ethernet MDIO bus or directly
by means of the APB3/MCI interfaces. In the later case the XPCS can be mapped
right to the system IO memory space.
properties:
compatible:
oneOf:
- description: Synopsys DesignWare XPCS with none or unknown PMA
const: snps,dw-xpcs
- description: Synopsys DesignWare XPCS with Consumer Gen1 3G PMA
const: snps,dw-xpcs-gen1-3g
- description: Synopsys DesignWare XPCS with Consumer Gen2 3G PMA
const: snps,dw-xpcs-gen2-3g
- description: Synopsys DesignWare XPCS with Consumer Gen2 6G PMA
const: snps,dw-xpcs-gen2-6g
- description: Synopsys DesignWare XPCS with Consumer Gen4 3G PMA
const: snps,dw-xpcs-gen4-3g
- description: Synopsys DesignWare XPCS with Consumer Gen4 6G PMA
const: snps,dw-xpcs-gen4-6g
- description: Synopsys DesignWare XPCS with Consumer Gen5 10G PMA
const: snps,dw-xpcs-gen5-10g
- description: Synopsys DesignWare XPCS with Consumer Gen5 12G PMA
const: snps,dw-xpcs-gen5-12g
reg:
items:
- description:
In case of the MDIO management interface this just a 5-bits ID
of the MDIO bus device. If DW XPCS CSRs space is accessed over the
MCI or APB3 management interfaces, then the space mapping can be
either 'direct' or 'indirect'. In the former case all Clause 45
registers are contiguously mapped within the address space
MMD '[20:16]', Reg '[15:0]'. In the later case the space is divided
to the multiple 256 register sets. There is a special viewport CSR
which is responsible for the set selection. The upper part of
the CSR address MMD+REG[20:8] is supposed to be written in there
so the corresponding subset would be mapped to the lowest 255 CSRs.
reg-names:
items:
- enum: [ direct, indirect ]
reg-io-width:
description:
The way the CSRs are mapped to the memory is platform depended. Since
each Clause 45 CSR is of 16-bits wide the access instructions must be
two bytes aligned at least.
default: 2
enum: [ 2, 4 ]
interrupts:
description:
System interface interrupt output (sbd_intr_o) indicating Clause 73/37
auto-negotiation events':' Page received, AN is completed or incompatible
link partner.
maxItems: 1
clocks:
description:
The MCI and APB3 interfaces are supposed to be equipped with a clock
source connected to the clk_csr_i line.
PCS/PMA layer can be clocked by an internal reference clock source
(phyN_core_refclk) or by an externally connected (phyN_pad_refclk) clock
generator. Both clocks can be supplied at a time.
minItems: 1
maxItems: 3
clock-names:
oneOf:
- minItems: 1
items: # MDIO
- enum: [core, pad]
- const: pad
- minItems: 1
items: # MCI or APB
- const: csr
- enum: [core, pad]
- const: pad
required:
- compatible
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
ethernet-pcs@1f05d000 {
compatible = "snps,dw-xpcs";
reg = <0x1f05d000 0x1000>;
reg-names = "indirect";
reg-io-width = <4>;
interrupts = <79 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu_pclk>, <&ccu_core>, <&ccu_pad>;
clock-names = "csr", "core", "pad";
};
- |
mdio-bus {
#address-cells = <1>;
#size-cells = <0>;
ethernet-pcs@0 {
compatible = "snps,dw-xpcs";
reg = <0>;
clocks = <&ccu_core>, <&ccu_pad>;
clock-names = "core", "pad";
};
};
...
......@@ -595,7 +595,7 @@ static int intel_mgbe_common_data(struct pci_dev *pdev,
/* Intel mgbe SGMII interface uses pcs-xcps */
if (plat->phy_interface == PHY_INTERFACE_MODE_SGMII ||
plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) {
plat->mdio_bus_data->has_xpcs = true;
plat->mdio_bus_data->pcs_mask = BIT(INTEL_MGBE_XPCS_ADDR);
plat->mdio_bus_data->default_an_inband = true;
plat->select_pcs = intel_mgbe_select_pcs;
}
......
......@@ -497,35 +497,33 @@ int stmmac_mdio_reset(struct mii_bus *bus)
int stmmac_pcs_setup(struct net_device *ndev)
{
struct fwnode_handle *devnode, *pcsnode;
struct dw_xpcs *xpcs = NULL;
struct stmmac_priv *priv;
int ret = -ENODEV;
int mode, addr;
int addr, mode, ret;
priv = netdev_priv(ndev);
mode = priv->plat->phy_interface;
devnode = priv->plat->port_node;
if (priv->plat->pcs_init) {
ret = priv->plat->pcs_init(priv);
} else if (fwnode_property_present(devnode, "pcs-handle")) {
pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
xpcs = xpcs_create_fwnode(pcsnode, mode);
fwnode_handle_put(pcsnode);
ret = PTR_ERR_OR_ZERO(xpcs);
} else if (priv->plat->mdio_bus_data &&
priv->plat->mdio_bus_data->has_xpcs) {
/* Try to probe the XPCS by scanning all addresses */
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
xpcs = xpcs_create_mdiodev(priv->mii, addr, mode);
if (IS_ERR(xpcs))
continue;
ret = 0;
break;
}
priv->plat->mdio_bus_data->pcs_mask) {
addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
xpcs = xpcs_create_mdiodev(priv->mii, addr, mode);
ret = PTR_ERR_OR_ZERO(xpcs);
} else {
return 0;
}
if (ret) {
dev_warn(priv->device, "No xPCS found\n");
return ret;
}
if (ret)
return dev_err_probe(priv->device, ret, "No xPCS found\n");
priv->hw->xpcs = xpcs;
......@@ -610,7 +608,7 @@ int stmmac_mdio_register(struct net_device *ndev)
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x",
new_bus->name, priv->plat->bus_id);
new_bus->priv = ndev;
new_bus->phy_mask = mdio_bus_data->phy_mask;
new_bus->phy_mask = mdio_bus_data->phy_mask | mdio_bus_data->pcs_mask;
new_bus->parent = priv->device;
err = of_mdiobus_register(new_bus, mdio_node);
......
......@@ -6,11 +6,11 @@
menu "PCS device drivers"
config PCS_XPCS
tristate
tristate "Synopsys DesignWare Ethernet XPCS"
select PHYLINK
help
This module provides helper functions for Synopsys DesignWare XPCS
controllers.
This module provides a driver and helper functions for Synopsys
DesignWare XPCS controllers.
config PCS_LYNX
tristate
......
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-nxp.o pcs-xpcs-wx.o
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* Synopsys DesignWare XPCS platform device driver
*
* Copyright (C) 2024 Serge Semin
*/
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/pcs/pcs-xpcs.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/sizes.h>
#include "pcs-xpcs.h"
/* Page select register for the indirect MMIO CSRs access */
#define DW_VR_CSR_VIEWPORT 0xff
struct dw_xpcs_plat {
struct platform_device *pdev;
struct mii_bus *bus;
bool reg_indir;
int reg_width;
void __iomem *reg_base;
struct clk *cclk;
};
static ptrdiff_t xpcs_mmio_addr_format(int dev, int reg)
{
return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg);
}
static u16 xpcs_mmio_addr_page(ptrdiff_t csr)
{
return FIELD_GET(0x1fff00, csr);
}
static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr)
{
return FIELD_GET(0xff, csr);
}
static int xpcs_mmio_read_reg_indirect(struct dw_xpcs_plat *pxpcs,
int dev, int reg)
{
ptrdiff_t csr, ofs;
u16 page;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
page = xpcs_mmio_addr_page(csr);
ofs = xpcs_mmio_addr_offset(csr);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2));
ret = readl(pxpcs->reg_base + (ofs << 2));
break;
default:
writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1));
ret = readw(pxpcs->reg_base + (ofs << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return ret;
}
static int xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat *pxpcs,
int dev, int reg, u16 val)
{
ptrdiff_t csr, ofs;
u16 page;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
page = xpcs_mmio_addr_page(csr);
ofs = xpcs_mmio_addr_offset(csr);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2));
writel(val, pxpcs->reg_base + (ofs << 2));
break;
default:
writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1));
writew(val, pxpcs->reg_base + (ofs << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return 0;
}
static int xpcs_mmio_read_reg_direct(struct dw_xpcs_plat *pxpcs,
int dev, int reg)
{
ptrdiff_t csr;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
ret = readl(pxpcs->reg_base + (csr << 2));
break;
default:
ret = readw(pxpcs->reg_base + (csr << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return ret;
}
static int xpcs_mmio_write_reg_direct(struct dw_xpcs_plat *pxpcs,
int dev, int reg, u16 val)
{
ptrdiff_t csr;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(val, pxpcs->reg_base + (csr << 2));
break;
default:
writew(val, pxpcs->reg_base + (csr << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return 0;
}
static int xpcs_mmio_read_c22(struct mii_bus *bus, int addr, int reg)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_read_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg);
else
return xpcs_mmio_read_reg_direct(pxpcs, MDIO_MMD_VEND2, reg);
}
static int xpcs_mmio_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_write_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg, val);
else
return xpcs_mmio_write_reg_direct(pxpcs, MDIO_MMD_VEND2, reg, val);
}
static int xpcs_mmio_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_read_reg_indirect(pxpcs, dev, reg);
else
return xpcs_mmio_read_reg_direct(pxpcs, dev, reg);
}
static int xpcs_mmio_write_c45(struct mii_bus *bus, int addr, int dev,
int reg, u16 val)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_write_reg_indirect(pxpcs, dev, reg, val);
else
return xpcs_mmio_write_reg_direct(pxpcs, dev, reg, val);
}
static struct dw_xpcs_plat *xpcs_plat_create_data(struct platform_device *pdev)
{
struct dw_xpcs_plat *pxpcs;
pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL);
if (!pxpcs)
return ERR_PTR(-ENOMEM);
pxpcs->pdev = pdev;
dev_set_drvdata(&pdev->dev, pxpcs);
return pxpcs;
}
static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs)
{
struct platform_device *pdev = pxpcs->pdev;
struct device *dev = &pdev->dev;
resource_size_t spc_size;
struct resource *res;
if (!device_property_read_u32(dev, "reg-io-width", &pxpcs->reg_width)) {
if (pxpcs->reg_width != 2 && pxpcs->reg_width != 4) {
dev_err(dev, "Invalid reg-space data width\n");
return -EINVAL;
}
} else {
pxpcs->reg_width = 2;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "direct") ?:
platform_get_resource_byname(pdev, IORESOURCE_MEM, "indirect");
if (!res) {
dev_err(dev, "No reg-space found\n");
return -EINVAL;
}
if (!strcmp(res->name, "indirect"))
pxpcs->reg_indir = true;
if (pxpcs->reg_indir)
spc_size = pxpcs->reg_width * SZ_256;
else
spc_size = pxpcs->reg_width * SZ_2M;
if (resource_size(res) < spc_size) {
dev_err(dev, "Invalid reg-space size\n");
return -EINVAL;
}
pxpcs->reg_base = devm_ioremap_resource(dev, res);
if (IS_ERR(pxpcs->reg_base)) {
dev_err(dev, "Failed to map reg-space\n");
return PTR_ERR(pxpcs->reg_base);
}
return 0;
}
static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
int ret;
pxpcs->cclk = devm_clk_get(dev, "csr");
if (IS_ERR(pxpcs->cclk))
return dev_err_probe(dev, PTR_ERR(pxpcs->cclk),
"Failed to get CSR clock\n");
pm_runtime_set_active(dev);
ret = devm_pm_runtime_enable(dev);
if (ret) {
dev_err(dev, "Failed to enable runtime-PM\n");
return ret;
}
return 0;
}
static int xpcs_plat_init_bus(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
static atomic_t id = ATOMIC_INIT(-1);
int ret;
pxpcs->bus = devm_mdiobus_alloc_size(dev, 0);
if (!pxpcs->bus)
return -ENOMEM;
pxpcs->bus->name = "DW XPCS MCI/APB3";
pxpcs->bus->read = xpcs_mmio_read_c22;
pxpcs->bus->write = xpcs_mmio_write_c22;
pxpcs->bus->read_c45 = xpcs_mmio_read_c45;
pxpcs->bus->write_c45 = xpcs_mmio_write_c45;
pxpcs->bus->phy_mask = ~0;
pxpcs->bus->parent = dev;
pxpcs->bus->priv = pxpcs;
snprintf(pxpcs->bus->id, MII_BUS_ID_SIZE,
"dwxpcs-%x", atomic_inc_return(&id));
/* MDIO-bus here serves as just a back-end engine abstracting out
* the MDIO and MCI/APB3 IO interfaces utilized for the DW XPCS CSRs
* access.
*/
ret = devm_mdiobus_register(dev, pxpcs->bus);
if (ret) {
dev_err(dev, "Failed to create MDIO bus\n");
return ret;
}
return 0;
}
/* Note there is no need in the next function antagonist because the MDIO-bus
* de-registration will effectively remove and destroy all the MDIO-devices
* registered on the bus.
*/
static int xpcs_plat_init_dev(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
struct mdio_device *mdiodev;
int ret;
/* There is a single memory-mapped DW XPCS device */
mdiodev = mdio_device_create(pxpcs->bus, 0);
if (IS_ERR(mdiodev))
return PTR_ERR(mdiodev);
/* Associate the FW-node with the device structure so it can be looked
* up later. Make sure DD-core is aware of the OF-node being re-used.
*/
device_set_node(&mdiodev->dev, fwnode_handle_get(dev_fwnode(dev)));
mdiodev->dev.of_node_reused = true;
/* Pass the data further so the DW XPCS driver core could use it */
mdiodev->dev.platform_data = (void *)device_get_match_data(dev);
ret = mdio_device_register(mdiodev);
if (ret) {
dev_err(dev, "Failed to register MDIO device\n");
goto err_clean_data;
}
return 0;
err_clean_data:
mdiodev->dev.platform_data = NULL;
fwnode_handle_put(dev_fwnode(&mdiodev->dev));
device_set_node(&mdiodev->dev, NULL);
mdio_device_free(mdiodev);
return ret;
}
static int xpcs_plat_probe(struct platform_device *pdev)
{
struct dw_xpcs_plat *pxpcs;
int ret;
pxpcs = xpcs_plat_create_data(pdev);
if (IS_ERR(pxpcs))
return PTR_ERR(pxpcs);
ret = xpcs_plat_init_res(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_clk(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_bus(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_dev(pxpcs);
if (ret)
return ret;
return 0;
}
static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev)
{
struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
clk_disable_unprepare(pxpcs->cclk);
return 0;
}
static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev)
{
struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
return clk_prepare_enable(pxpcs->cclk);
}
static const struct dev_pm_ops xpcs_plat_pm_ops = {
SET_RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend,
xpcs_plat_pm_runtime_resume,
NULL)
};
DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID);
static const struct of_device_id xpcs_of_ids[] = {
{ .compatible = "snps,dw-xpcs", .data = &xpcs_generic },
{ .compatible = "snps,dw-xpcs-gen1-3g", .data = &xpcs_pma_gen1_3g },
{ .compatible = "snps,dw-xpcs-gen2-3g", .data = &xpcs_pma_gen2_3g },
{ .compatible = "snps,dw-xpcs-gen2-6g", .data = &xpcs_pma_gen2_6g },
{ .compatible = "snps,dw-xpcs-gen4-3g", .data = &xpcs_pma_gen4_3g },
{ .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g },
{ .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g },
{ .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, xpcs_of_ids);
static struct platform_driver xpcs_plat_driver = {
.probe = xpcs_plat_probe,
.driver = {
.name = "dwxpcs",
.pm = &xpcs_plat_pm_ops,
.of_match_table = xpcs_of_ids,
},
};
module_platform_driver(xpcs_plat_driver);
MODULE_DESCRIPTION("Synopsys DesignWare XPCS platform device driver");
MODULE_AUTHOR("Signed-off-by: Serge Semin <fancer.lancer@gmail.com>");
MODULE_LICENSE("GPL");
......@@ -6,10 +6,13 @@
* Author: Jose Abreu <Jose.Abreu@synopsys.com>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/pcs/pcs-xpcs.h>
#include <linux/mdio.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <linux/property.h>
#include "pcs-xpcs.h"
......@@ -143,7 +146,7 @@ enum {
DW_XPCS_INTERFACE_MAX,
};
struct xpcs_compat {
struct dw_xpcs_compat {
const int *supported;
const phy_interface_t *interface;
int num_interfaces;
......@@ -151,19 +154,19 @@ struct xpcs_compat {
int (*pma_config)(struct dw_xpcs *xpcs);
};
struct xpcs_id {
struct dw_xpcs_desc {
u32 id;
u32 mask;
const struct xpcs_compat *compat;
const struct dw_xpcs_compat *compat;
};
static const struct xpcs_compat *xpcs_find_compat(const struct xpcs_id *id,
phy_interface_t interface)
static const struct dw_xpcs_compat *
xpcs_find_compat(const struct dw_xpcs_desc *desc, phy_interface_t interface)
{
int i, j;
for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) {
const struct xpcs_compat *compat = &id->compat[i];
const struct dw_xpcs_compat *compat = &desc->compat[i];
for (j = 0; j < compat->num_interfaces; j++)
if (compat->interface[j] == interface)
......@@ -175,9 +178,9 @@ static const struct xpcs_compat *xpcs_find_compat(const struct xpcs_id *id,
int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
{
const struct xpcs_compat *compat;
const struct dw_xpcs_compat *compat;
compat = xpcs_find_compat(xpcs->id, interface);
compat = xpcs_find_compat(xpcs->desc, interface);
if (!compat)
return -ENODEV;
......@@ -185,7 +188,7 @@ int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
}
EXPORT_SYMBOL_GPL(xpcs_get_an_mode);
static bool __xpcs_linkmode_supported(const struct xpcs_compat *compat,
static bool __xpcs_linkmode_supported(const struct dw_xpcs_compat *compat,
enum ethtool_link_mode_bit_indices linkmode)
{
int i;
......@@ -237,29 +240,6 @@ int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val)
return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val);
}
static int xpcs_dev_flag(struct dw_xpcs *xpcs)
{
int ret, oui;
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID1);
if (ret < 0)
return ret;
oui = ret;
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID2);
if (ret < 0)
return ret;
ret = (ret >> 10) & 0x3F;
oui |= ret << 16;
if (oui == DW_OUI_WX)
xpcs->dev_flag = DW_DEV_TXGBE;
return 0;
}
static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev)
{
/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
......@@ -277,7 +257,7 @@ static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev)
}
static int xpcs_soft_reset(struct dw_xpcs *xpcs,
const struct xpcs_compat *compat)
const struct dw_xpcs_compat *compat)
{
int ret, dev;
......@@ -418,7 +398,7 @@ static void xpcs_config_usxgmii(struct dw_xpcs *xpcs, int speed)
}
static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
const struct xpcs_compat *compat)
const struct dw_xpcs_compat *compat)
{
int ret, adv;
......@@ -463,7 +443,7 @@ static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
}
static int xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
const struct xpcs_compat *compat)
const struct dw_xpcs_compat *compat)
{
int ret;
......@@ -482,7 +462,7 @@ static int xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
static int xpcs_aneg_done_c73(struct dw_xpcs *xpcs,
struct phylink_link_state *state,
const struct xpcs_compat *compat, u16 an_stat1)
const struct dw_xpcs_compat *compat, u16 an_stat1)
{
int ret;
......@@ -607,12 +587,12 @@ static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(xpcs_supported) = { 0, };
const struct xpcs_compat *compat;
const struct dw_xpcs_compat *compat;
struct dw_xpcs *xpcs;
int i;
xpcs = phylink_pcs_to_xpcs(pcs);
compat = xpcs_find_compat(xpcs->id, state->interface);
compat = xpcs_find_compat(xpcs->desc, state->interface);
if (!compat)
return -EINVAL;
......@@ -633,7 +613,7 @@ void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
int i, j;
for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) {
const struct xpcs_compat *compat = &xpcs->id->compat[i];
const struct dw_xpcs_compat *compat = &xpcs->desc->compat[i];
for (j = 0; j < compat->num_interfaces; j++)
__set_bit(compat->interface[j], interfaces);
......@@ -684,7 +664,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
{
int ret, mdio_ctrl, tx_conf;
if (xpcs->dev_flag == DW_DEV_TXGBE)
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
/* For AN for C37 SGMII mode, the settings are :-
......@@ -722,7 +702,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
ret |= (DW_VR_MII_PCS_MODE_C37_SGMII <<
DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT &
DW_VR_MII_PCS_MODE_MASK);
if (xpcs->dev_flag == DW_DEV_TXGBE) {
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
ret |= DW_VR_MII_AN_CTRL_8BIT;
/* Hardware requires it to be PHY side SGMII */
tx_conf = DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII;
......@@ -744,7 +724,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
else
ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
if (xpcs->dev_flag == DW_DEV_TXGBE)
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
ret |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret);
......@@ -766,7 +746,7 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
int ret, mdio_ctrl, adv;
bool changed = 0;
if (xpcs->dev_flag == DW_DEV_TXGBE)
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
/* According to Chap 7.12, to set 1000BASE-X C37 AN, AN must
......@@ -850,14 +830,14 @@ static int xpcs_config_2500basex(struct dw_xpcs *xpcs)
int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
const unsigned long *advertising, unsigned int neg_mode)
{
const struct xpcs_compat *compat;
const struct dw_xpcs_compat *compat;
int ret;
compat = xpcs_find_compat(xpcs->id, interface);
compat = xpcs_find_compat(xpcs->desc, interface);
if (!compat)
return -ENODEV;
if (xpcs->dev_flag == DW_DEV_TXGBE) {
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
ret = txgbe_xpcs_switch_mode(xpcs, interface);
if (ret)
return ret;
......@@ -915,7 +895,7 @@ static int xpcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
static int xpcs_get_state_c73(struct dw_xpcs *xpcs,
struct phylink_link_state *state,
const struct xpcs_compat *compat)
const struct dw_xpcs_compat *compat)
{
bool an_enabled;
int pcs_stat1;
......@@ -1115,10 +1095,10 @@ static void xpcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
const struct xpcs_compat *compat;
const struct dw_xpcs_compat *compat;
int ret;
compat = xpcs_find_compat(xpcs->id, state->interface);
compat = xpcs_find_compat(xpcs->desc, state->interface);
if (!compat)
return;
......@@ -1229,47 +1209,73 @@ static void xpcs_an_restart(struct phylink_pcs *pcs)
}
}
static u32 xpcs_get_id(struct dw_xpcs *xpcs)
static int xpcs_get_id(struct dw_xpcs *xpcs)
{
int ret;
u32 id;
/* First, search C73 PCS using PCS MMD */
/* First, search C73 PCS using PCS MMD 3. Return ENODEV if communication
* failed indicating that device couldn't be reached.
*/
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID1);
if (ret < 0)
return 0xffffffff;
return -ENODEV;
id = ret << 16;
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID2);
if (ret < 0)
return 0xffffffff;
return ret;
id |= ret;
/* If Device IDs are not all zeros or all ones,
* we found C73 AN-type device
/* If Device IDs are not all zeros or ones, then 10GBase-X/R or C73
* KR/KX4 PCS found. Otherwise fallback to detecting 1000Base-X or C37
* PCS in MII MMD 31.
*/
if ((id | ret) && (id | ret) != 0xffffffff)
return id | ret;
if (!id || id == 0xffffffff) {
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID1);
if (ret < 0)
return ret;
id = ret << 16;
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID2);
if (ret < 0)
return ret;
/* Next, search C37 PCS using Vendor-Specific MII MMD */
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID1);
id |= ret;
}
/* Set the PCS ID if it hasn't been pre-initialized */
if (xpcs->info.pcs == DW_XPCS_ID_NATIVE)
xpcs->info.pcs = id;
/* Find out PMA/PMD ID from MMD 1 device ID registers */
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID1);
if (ret < 0)
return 0xffffffff;
return ret;
id = ret << 16;
id = ret;
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID2);
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID2);
if (ret < 0)
return 0xffffffff;
return ret;
/* Note the inverted dword order and masked out Model/Revision numbers
* with respect to what is done with the PCS ID...
*/
ret = (ret >> 10) & 0x3F;
id |= ret << 16;
/* If Device IDs are not all zeros, we found C37 AN-type device */
if (id | ret)
return id | ret;
/* Set the PMA ID if it hasn't been pre-initialized */
if (xpcs->info.pma == DW_XPCS_PMA_ID_NATIVE)
xpcs->info.pma = id;
return 0xffffffff;
return 0;
}
static const struct xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
static const struct dw_xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
[DW_XPCS_USXGMII] = {
.supported = xpcs_usxgmii_features,
.interface = xpcs_usxgmii_interfaces,
......@@ -1314,7 +1320,7 @@ static const struct xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
},
};
static const struct xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
[DW_XPCS_SGMII] = {
.supported = xpcs_sgmii_features,
.interface = xpcs_sgmii_interfaces,
......@@ -1324,7 +1330,7 @@ static const struct xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] =
},
};
static const struct xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
[DW_XPCS_SGMII] = {
.supported = xpcs_sgmii_features,
.interface = xpcs_sgmii_interfaces,
......@@ -1341,18 +1347,18 @@ static const struct xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] =
},
};
static const struct xpcs_id xpcs_id_list[] = {
static const struct dw_xpcs_desc xpcs_desc_list[] = {
{
.id = SYNOPSYS_XPCS_ID,
.mask = SYNOPSYS_XPCS_MASK,
.id = DW_XPCS_ID,
.mask = DW_XPCS_ID_MASK,
.compat = synopsys_xpcs_compat,
}, {
.id = NXP_SJA1105_XPCS_ID,
.mask = SYNOPSYS_XPCS_MASK,
.mask = DW_XPCS_ID_MASK,
.compat = nxp_sja1105_xpcs_compat,
}, {
.id = NXP_SJA1110_XPCS_ID,
.mask = SYNOPSYS_XPCS_MASK,
.mask = DW_XPCS_ID_MASK,
.compat = nxp_sja1110_xpcs_compat,
},
};
......@@ -1365,12 +1371,9 @@ static const struct phylink_pcs_ops xpcs_phylink_ops = {
.pcs_link_up = xpcs_link_up,
};
static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
phy_interface_t interface)
static struct dw_xpcs *xpcs_create_data(struct mdio_device *mdiodev)
{
struct dw_xpcs *xpcs;
u32 xpcs_id;
int i, ret;
xpcs = kzalloc(sizeof(*xpcs), GFP_KERNEL);
if (!xpcs)
......@@ -1378,59 +1381,142 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
mdio_device_get(mdiodev);
xpcs->mdiodev = mdiodev;
xpcs->pcs.ops = &xpcs_phylink_ops;
xpcs->pcs.neg_mode = true;
xpcs->pcs.poll = true;
return xpcs;
}
xpcs_id = xpcs_get_id(xpcs);
static void xpcs_free_data(struct dw_xpcs *xpcs)
{
mdio_device_put(xpcs->mdiodev);
kfree(xpcs);
}
for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) {
const struct xpcs_id *entry = &xpcs_id_list[i];
const struct xpcs_compat *compat;
static int xpcs_init_clks(struct dw_xpcs *xpcs)
{
static const char *ids[DW_XPCS_NUM_CLKS] = {
[DW_XPCS_CORE_CLK] = "core",
[DW_XPCS_PAD_CLK] = "pad",
};
struct device *dev = &xpcs->mdiodev->dev;
int ret, i;
if ((xpcs_id & entry->mask) != entry->id)
continue;
for (i = 0; i < DW_XPCS_NUM_CLKS; ++i)
xpcs->clks[i].id = ids[i];
xpcs->id = entry;
ret = clk_bulk_get_optional(dev, DW_XPCS_NUM_CLKS, xpcs->clks);
if (ret)
return dev_err_probe(dev, ret, "Failed to get clocks\n");
compat = xpcs_find_compat(entry, interface);
if (!compat) {
ret = -ENODEV;
goto out;
}
ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable clocks\n");
ret = xpcs_dev_flag(xpcs);
if (ret)
goto out;
return 0;
}
xpcs->pcs.ops = &xpcs_phylink_ops;
xpcs->pcs.neg_mode = true;
static void xpcs_clear_clks(struct dw_xpcs *xpcs)
{
clk_bulk_disable_unprepare(DW_XPCS_NUM_CLKS, xpcs->clks);
if (xpcs->dev_flag != DW_DEV_TXGBE) {
xpcs->pcs.poll = true;
clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks);
}
ret = xpcs_soft_reset(xpcs, compat);
if (ret)
goto out;
}
static int xpcs_init_id(struct dw_xpcs *xpcs)
{
const struct dw_xpcs_info *info;
int i, ret;
return xpcs;
info = dev_get_platdata(&xpcs->mdiodev->dev);
if (!info) {
xpcs->info.pcs = DW_XPCS_ID_NATIVE;
xpcs->info.pma = DW_XPCS_PMA_ID_NATIVE;
} else {
xpcs->info = *info;
}
ret = -ENODEV;
ret = xpcs_get_id(xpcs);
if (ret < 0)
return ret;
out:
mdio_device_put(mdiodev);
kfree(xpcs);
for (i = 0; i < ARRAY_SIZE(xpcs_desc_list); i++) {
const struct dw_xpcs_desc *desc = &xpcs_desc_list[i];
return ERR_PTR(ret);
if ((xpcs->info.pcs & desc->mask) != desc->id)
continue;
xpcs->desc = desc;
break;
}
if (!xpcs->desc)
return -ENODEV;
return 0;
}
void xpcs_destroy(struct dw_xpcs *xpcs)
static int xpcs_init_iface(struct dw_xpcs *xpcs, phy_interface_t interface)
{
if (xpcs)
mdio_device_put(xpcs->mdiodev);
kfree(xpcs);
const struct dw_xpcs_compat *compat;
compat = xpcs_find_compat(xpcs->desc, interface);
if (!compat)
return -EINVAL;
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
xpcs->pcs.poll = false;
return 0;
}
return xpcs_soft_reset(xpcs, compat);
}
EXPORT_SYMBOL_GPL(xpcs_destroy);
static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
phy_interface_t interface)
{
struct dw_xpcs *xpcs;
int ret;
xpcs = xpcs_create_data(mdiodev);
if (IS_ERR(xpcs))
return xpcs;
ret = xpcs_init_clks(xpcs);
if (ret)
goto out_free_data;
ret = xpcs_init_id(xpcs);
if (ret)
goto out_clear_clks;
ret = xpcs_init_iface(xpcs, interface);
if (ret)
goto out_clear_clks;
return xpcs;
out_clear_clks:
xpcs_clear_clks(xpcs);
out_free_data:
xpcs_free_data(xpcs);
return ERR_PTR(ret);
}
/**
* xpcs_create_mdiodev() - create a DW xPCS instance with the MDIO @addr
* @bus: pointer to the MDIO-bus descriptor for the device to be looked at
* @addr: device MDIO-bus ID
* @interface: requested PHY interface
*
* Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
* the PCS device couldn't be found on the bus and other negative errno related
* to the data allocation and MDIO-bus communications.
*/
struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
phy_interface_t interface)
{
......@@ -1455,5 +1541,54 @@ struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
}
EXPORT_SYMBOL_GPL(xpcs_create_mdiodev);
/**
* xpcs_create_fwnode() - Create a DW xPCS instance from @fwnode
* @fwnode: fwnode handle poining to the DW XPCS device
* @interface: requested PHY interface
*
* Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
* the fwnode device is unavailable or the PCS device couldn't be found on the
* bus, -EPROBE_DEFER if the respective MDIO-device instance couldn't be found,
* other negative errno related to the data allocations and MDIO-bus
* communications.
*/
struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
phy_interface_t interface)
{
struct mdio_device *mdiodev;
struct dw_xpcs *xpcs;
if (!fwnode_device_is_available(fwnode))
return ERR_PTR(-ENODEV);
mdiodev = fwnode_mdio_find_device(fwnode);
if (!mdiodev)
return ERR_PTR(-EPROBE_DEFER);
xpcs = xpcs_create(mdiodev, interface);
/* xpcs_create() has taken a refcount on the mdiodev if it was
* successful. If xpcs_create() fails, this will free the mdio
* device here. In any case, we don't need to hold our reference
* anymore, and putting it here will allow mdio_device_put() in
* xpcs_destroy() to automatically free the mdio device.
*/
mdio_device_put(mdiodev);
return xpcs;
}
EXPORT_SYMBOL_GPL(xpcs_create_fwnode);
void xpcs_destroy(struct dw_xpcs *xpcs)
{
if (!xpcs)
return;
xpcs_clear_clks(xpcs);
xpcs_free_data(xpcs);
}
EXPORT_SYMBOL_GPL(xpcs_destroy);
MODULE_DESCRIPTION("Synopsys DesignWare XPCS library");
MODULE_LICENSE("GPL v2");
......@@ -6,8 +6,8 @@
* Author: Jose Abreu <Jose.Abreu@synopsys.com>
*/
#define SYNOPSYS_XPCS_ID 0x7996ced0
#define SYNOPSYS_XPCS_MASK 0xffffffff
#include <linux/bits.h>
#include <linux/pcs/pcs-xpcs.h>
/* Vendor regs access */
#define DW_VENDOR BIT(15)
......@@ -120,6 +120,9 @@
/* VR MII EEE Control 1 defines */
#define DW_VR_MII_EEE_TRN_LPI BIT(0) /* Transparent Mode Enable */
#define DW_XPCS_INFO_DECLARE(_name, _pcs, _pma) \
static const struct dw_xpcs_info _name = { .pcs = _pcs, .pma = _pma }
int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
......
......@@ -7,11 +7,12 @@
#ifndef __LINUX_PCS_XPCS_H
#define __LINUX_PCS_XPCS_H
#include <linux/clk.h>
#include <linux/fwnode.h>
#include <linux/mdio.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#define NXP_SJA1105_XPCS_ID 0x00000010
#define NXP_SJA1110_XPCS_ID 0x00000020
#include <linux/types.h>
/* AN mode */
#define DW_AN_C73 1
......@@ -20,20 +21,46 @@
#define DW_AN_C37_1000BASEX 4
#define DW_10GBASER 5
/* device vendor OUI */
#define DW_OUI_WX 0x0018fc80
struct dw_xpcs_desc;
enum dw_xpcs_pcs_id {
DW_XPCS_ID_NATIVE = 0,
NXP_SJA1105_XPCS_ID = 0x00000010,
NXP_SJA1110_XPCS_ID = 0x00000020,
DW_XPCS_ID = 0x7996ced0,
DW_XPCS_ID_MASK = 0xffffffff,
};
/* dev_flag */
#define DW_DEV_TXGBE BIT(0)
enum dw_xpcs_pma_id {
DW_XPCS_PMA_ID_NATIVE = 0,
DW_XPCS_PMA_GEN1_3G_ID,
DW_XPCS_PMA_GEN2_3G_ID,
DW_XPCS_PMA_GEN2_6G_ID,
DW_XPCS_PMA_GEN4_3G_ID,
DW_XPCS_PMA_GEN4_6G_ID,
DW_XPCS_PMA_GEN5_10G_ID,
DW_XPCS_PMA_GEN5_12G_ID,
WX_TXGBE_XPCS_PMA_10G_ID = 0x0018fc80,
};
struct xpcs_id;
struct dw_xpcs_info {
u32 pcs;
u32 pma;
};
enum dw_xpcs_clock {
DW_XPCS_CORE_CLK,
DW_XPCS_PAD_CLK,
DW_XPCS_NUM_CLKS,
};
struct dw_xpcs {
struct dw_xpcs_info info;
const struct dw_xpcs_desc *desc;
struct mdio_device *mdiodev;
const struct xpcs_id *id;
struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
struct phylink_pcs pcs;
phy_interface_t interface;
int dev_flag;
};
int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface);
......@@ -46,6 +73,8 @@ int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns,
int enable);
struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
phy_interface_t interface);
struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
phy_interface_t interface);
void xpcs_destroy(struct dw_xpcs *xpcs);
#endif /* __LINUX_PCS_XPCS_H */
......@@ -82,7 +82,7 @@ struct stmmac_priv;
struct stmmac_mdio_bus_data {
unsigned int phy_mask;
unsigned int has_xpcs;
unsigned int pcs_mask;
unsigned int default_an_inband;
int *irqs;
int probed_phy_irq;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment