1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) Tehuti Networks Ltd. */
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/phylink.h>
#include "tn40.h"
#define TN40_MDIO_DEVAD_MASK GENMASK(4, 0)
#define TN40_MDIO_PRTAD_MASK GENMASK(9, 5)
#define TN40_MDIO_CMD_VAL(device, port) \
(FIELD_PREP(TN40_MDIO_DEVAD_MASK, (device)) | \
(FIELD_PREP(TN40_MDIO_PRTAD_MASK, (port))))
#define TN40_MDIO_CMD_READ BIT(15)
#define AQR105_FIRMWARE "tehuti/aqr105-tn40xx.cld"
static void tn40_mdio_set_speed(struct tn40_priv *priv, u32 speed)
{
void __iomem *regs = priv->regs;
int mdio_cfg;
if (speed == TN40_MDIO_SPEED_1MHZ)
mdio_cfg = (0x7d << 7) | 0x08; /* 1MHz */
else
mdio_cfg = 0xA08; /* 6MHz */
mdio_cfg |= (1 << 6);
writel(mdio_cfg, regs + TN40_REG_MDIO_CMD_STAT);
msleep(100);
}
static u32 tn40_mdio_stat(struct tn40_priv *priv)
{
void __iomem *regs = priv->regs;
return readl(regs + TN40_REG_MDIO_CMD_STAT);
}
static int tn40_mdio_wait_nobusy(struct tn40_priv *priv, u32 *val)
{
u32 stat;
int ret;
ret = readx_poll_timeout_atomic(tn40_mdio_stat, priv, stat,
TN40_GET_MDIO_BUSY(stat) == 0, 10,
10000);
if (val)
*val = stat;
return ret;
}
static int tn40_mdio_read(struct tn40_priv *priv, int port, int device,
u16 regnum)
{
void __iomem *regs = priv->regs;
u32 i;
/* wait until MDIO is not busy */
if (tn40_mdio_wait_nobusy(priv, NULL))
return -EIO;
i = TN40_MDIO_CMD_VAL(device, port);
writel(i, regs + TN40_REG_MDIO_CMD);
writel((u32)regnum, regs + TN40_REG_MDIO_ADDR);
if (tn40_mdio_wait_nobusy(priv, NULL))
return -EIO;
writel(TN40_MDIO_CMD_READ | i, regs + TN40_REG_MDIO_CMD);
/* read CMD_STAT until not busy */
if (tn40_mdio_wait_nobusy(priv, NULL))
return -EIO;
return lower_16_bits(readl(regs + TN40_REG_MDIO_DATA));
}
static int tn40_mdio_write(struct tn40_priv *priv, int port, int device,
u16 regnum, u16 data)
{
void __iomem *regs = priv->regs;
u32 tmp_reg = 0;
int ret;
/* wait until MDIO is not busy */
if (tn40_mdio_wait_nobusy(priv, NULL))
return -EIO;
writel(TN40_MDIO_CMD_VAL(device, port), regs + TN40_REG_MDIO_CMD);
writel((u32)regnum, regs + TN40_REG_MDIO_ADDR);
if (tn40_mdio_wait_nobusy(priv, NULL))
return -EIO;
writel((u32)data, regs + TN40_REG_MDIO_DATA);
/* read CMD_STAT until not busy */
ret = tn40_mdio_wait_nobusy(priv, &tmp_reg);
if (ret)
return -EIO;
if (TN40_GET_MDIO_RD_ERR(tmp_reg)) {
dev_err(&priv->pdev->dev, "MDIO error after write command\n");
return -EIO;
}
return 0;
}
static int tn40_mdio_read_c45(struct mii_bus *mii_bus, int addr, int devnum,
int regnum)
{
return tn40_mdio_read(mii_bus->priv, addr, devnum, regnum);
}
static int tn40_mdio_write_c45(struct mii_bus *mii_bus, int addr, int devnum,
int regnum, u16 val)
{
return tn40_mdio_write(mii_bus->priv, addr, devnum, regnum, val);
}
/* registers an mdio node and an aqr105 PHY at address 1
* tn40_mdio-%id {
* ethernet-phy@1 {
* compatible = "ethernet-phy-id03a1.b4a3";
* reg = <1>;
* firmware-name = AQR105_FIRMWARE;
* };
* };
*/
static int tn40_swnodes_register(struct tn40_priv *priv)
{
struct tn40_nodes *nodes = &priv->nodes;
struct pci_dev *pdev = priv->pdev;
struct software_node *swnodes;
u32 id;
id = pci_dev_id(pdev);
snprintf(nodes->phy_name, sizeof(nodes->phy_name), "ethernet-phy@1");
snprintf(nodes->mdio_name, sizeof(nodes->mdio_name), "tn40_mdio-%x",
id);
swnodes = nodes->swnodes;
swnodes[SWNODE_MDIO] = NODE_PROP(nodes->mdio_name, NULL);
nodes->phy_props[0] = PROPERTY_ENTRY_STRING("compatible",
"ethernet-phy-id03a1.b4a3");
nodes->phy_props[1] = PROPERTY_ENTRY_U32("reg", 1);
nodes->phy_props[2] = PROPERTY_ENTRY_STRING("firmware-name",
AQR105_FIRMWARE);
swnodes[SWNODE_PHY] = NODE_PAR_PROP(nodes->phy_name,
&swnodes[SWNODE_MDIO],
nodes->phy_props);
nodes->group[SWNODE_PHY] = &swnodes[SWNODE_PHY];
nodes->group[SWNODE_MDIO] = &swnodes[SWNODE_MDIO];
return software_node_register_node_group(nodes->group);
}
void tn40_swnodes_cleanup(struct tn40_priv *priv)
{
/* cleanup of swnodes is only needed for AQR105-based cards */
if (priv->pdev->device == PCI_DEVICE_ID_TEHUTI_TN9510) {
fwnode_handle_put(dev_fwnode(&priv->mdio->dev));
device_remove_software_node(&priv->mdio->dev);
software_node_unregister_node_group(priv->nodes.group);
}
}
int tn40_mdiobus_init(struct tn40_priv *priv)
{
struct pci_dev *pdev = priv->pdev;
struct mii_bus *bus;
int ret;
bus = devm_mdiobus_alloc(&pdev->dev);
if (!bus)
return -ENOMEM;
bus->name = TN40_DRV_NAME;
bus->parent = &pdev->dev;
snprintf(bus->id, MII_BUS_ID_SIZE, "tn40xx-%x-%x",
pci_domain_nr(pdev->bus), pci_dev_id(pdev));
bus->priv = priv;
bus->read_c45 = tn40_mdio_read_c45;
bus->write_c45 = tn40_mdio_write_c45;
priv->mdio = bus;
/* provide swnodes for AQR105-based cards only */
if (pdev->device == PCI_DEVICE_ID_TEHUTI_TN9510) {
ret = tn40_swnodes_register(priv);
if (ret) {
pr_err("swnodes failed\n");
return ret;
}
ret = device_add_software_node(&bus->dev,
priv->nodes.group[SWNODE_MDIO]);
if (ret) {
dev_err(&pdev->dev,
"device_add_software_node failed: %d\n", ret);
goto err_swnodes_unregister;
}
}
tn40_mdio_set_speed(priv, TN40_MDIO_SPEED_6MHZ);
ret = devm_mdiobus_register(&pdev->dev, bus);
if (ret) {
dev_err(&pdev->dev, "failed to register mdiobus %d %u %u\n",
ret, bus->state, MDIOBUS_UNREGISTERED);
goto err_swnodes_cleanup;
}
return 0;
err_swnodes_unregister:
software_node_unregister_node_group(priv->nodes.group);
return ret;
err_swnodes_cleanup:
tn40_swnodes_cleanup(priv);
return ret;
}
MODULE_FIRMWARE(AQR105_FIRMWARE);
|