1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright Sunplus Technology Co., Ltd. |
3 | * All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/platform_device.h> |
7 | #include <linux/netdevice.h> |
8 | #include <linux/bitfield.h> |
9 | #include <linux/of_mdio.h> |
10 | |
11 | #include "spl2sw_register.h" |
12 | #include "spl2sw_define.h" |
13 | #include "spl2sw_mdio.h" |
14 | |
15 | #define SPL2SW_MDIO_READ_CMD 0x02 |
16 | #define SPL2SW_MDIO_WRITE_CMD 0x01 |
17 | |
18 | static int spl2sw_mdio_access(struct spl2sw_common *comm, u8 cmd, u8 addr, u8 regnum, u16 wdata) |
19 | { |
20 | u32 reg, reg2; |
21 | u32 val; |
22 | int ret; |
23 | |
24 | /* Note that addr (of phy) should match either ext_phy0_addr |
25 | * or ext_phy1_addr, or mdio commands won't be sent out. |
26 | */ |
27 | reg = readl(addr: comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); |
28 | reg &= ~MAC_EXT_PHY0_ADDR; |
29 | reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, addr); |
30 | |
31 | reg2 = FIELD_PREP(MAC_CPU_PHY_WT_DATA, wdata) | FIELD_PREP(MAC_CPU_PHY_CMD, cmd) | |
32 | FIELD_PREP(MAC_CPU_PHY_REG_ADDR, regnum) | FIELD_PREP(MAC_CPU_PHY_ADDR, addr); |
33 | |
34 | /* Set ext_phy0_addr and then issue mdio command. |
35 | * No interrupt is allowed in between. |
36 | */ |
37 | spin_lock_irq(lock: &comm->mdio_lock); |
38 | writel(val: reg, addr: comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); |
39 | writel(val: reg2, addr: comm->l2sw_reg_base + L2SW_PHY_CNTL_REG0); |
40 | spin_unlock_irq(lock: &comm->mdio_lock); |
41 | |
42 | ret = read_poll_timeout(readl, val, val & cmd, 1, 1000, true, |
43 | comm->l2sw_reg_base + L2SW_PHY_CNTL_REG1); |
44 | |
45 | /* Set ext_phy0_addr back to 31 to prevent |
46 | * from sending mdio command to phy by |
47 | * hardware auto-mdio function. |
48 | */ |
49 | reg = readl(addr: comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); |
50 | reg &= ~MAC_EXT_PHY0_ADDR; |
51 | reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, 31); |
52 | writel(val: reg, addr: comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); |
53 | |
54 | if (ret == 0) |
55 | return val >> 16; |
56 | else |
57 | return ret; |
58 | } |
59 | |
60 | static int spl2sw_mii_read(struct mii_bus *bus, int addr, int regnum) |
61 | { |
62 | struct spl2sw_common *comm = bus->priv; |
63 | |
64 | return spl2sw_mdio_access(comm, SPL2SW_MDIO_READ_CMD, addr, regnum, wdata: 0); |
65 | } |
66 | |
67 | static int spl2sw_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val) |
68 | { |
69 | struct spl2sw_common *comm = bus->priv; |
70 | int ret; |
71 | |
72 | ret = spl2sw_mdio_access(comm, SPL2SW_MDIO_WRITE_CMD, addr, regnum, wdata: val); |
73 | if (ret < 0) |
74 | return ret; |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | u32 spl2sw_mdio_init(struct spl2sw_common *comm) |
80 | { |
81 | struct device_node *mdio_np; |
82 | struct mii_bus *mii_bus; |
83 | int ret; |
84 | |
85 | /* Get mdio child node. */ |
86 | mdio_np = of_get_child_by_name(node: comm->pdev->dev.of_node, name: "mdio" ); |
87 | if (!mdio_np) { |
88 | dev_err(&comm->pdev->dev, "No mdio child node found!\n" ); |
89 | return -ENODEV; |
90 | } |
91 | |
92 | /* Allocate and register mdio bus. */ |
93 | mii_bus = devm_mdiobus_alloc(dev: &comm->pdev->dev); |
94 | if (!mii_bus) { |
95 | ret = -ENOMEM; |
96 | goto out; |
97 | } |
98 | |
99 | mii_bus->name = "sunplus_mii_bus" ; |
100 | mii_bus->parent = &comm->pdev->dev; |
101 | mii_bus->priv = comm; |
102 | mii_bus->read = spl2sw_mii_read; |
103 | mii_bus->write = spl2sw_mii_write; |
104 | snprintf(buf: mii_bus->id, MII_BUS_ID_SIZE, fmt: "%s-mii" , dev_name(dev: &comm->pdev->dev)); |
105 | |
106 | ret = of_mdiobus_register(mdio: mii_bus, np: mdio_np); |
107 | if (ret) { |
108 | dev_err(&comm->pdev->dev, "Failed to register mdiobus!\n" ); |
109 | goto out; |
110 | } |
111 | |
112 | comm->mii_bus = mii_bus; |
113 | |
114 | out: |
115 | of_node_put(node: mdio_np); |
116 | return ret; |
117 | } |
118 | |
119 | void spl2sw_mdio_remove(struct spl2sw_common *comm) |
120 | { |
121 | if (comm->mii_bus) { |
122 | mdiobus_unregister(bus: comm->mii_bus); |
123 | comm->mii_bus = NULL; |
124 | } |
125 | } |
126 | |