Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / net / phy / mdio-mscc-miim.c
1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
2 /*
3  * Driver for the MDIO interface of Microsemi network switches.
4  *
5  * Author: Alexandre Belloni <alexandre.belloni@bootlin.com>
6  * Copyright (c) 2017 Microsemi Corporation
7  */
8
9 #include <linux/kernel.h>
10 #include <linux/module.h>
11 #include <linux/phy.h>
12 #include <linux/platform_device.h>
13 #include <linux/bitops.h>
14 #include <linux/io.h>
15 #include <linux/iopoll.h>
16 #include <linux/of_mdio.h>
17
18 #define MSCC_MIIM_REG_STATUS            0x0
19 #define         MSCC_MIIM_STATUS_STAT_BUSY      BIT(3)
20 #define MSCC_MIIM_REG_CMD               0x8
21 #define         MSCC_MIIM_CMD_OPR_WRITE         BIT(1)
22 #define         MSCC_MIIM_CMD_OPR_READ          BIT(2)
23 #define         MSCC_MIIM_CMD_WRDATA_SHIFT      4
24 #define         MSCC_MIIM_CMD_REGAD_SHIFT       20
25 #define         MSCC_MIIM_CMD_PHYAD_SHIFT       25
26 #define         MSCC_MIIM_CMD_VLD               BIT(31)
27 #define MSCC_MIIM_REG_DATA              0xC
28 #define         MSCC_MIIM_DATA_ERROR            (BIT(16) | BIT(17))
29
30 #define MSCC_PHY_REG_PHY_CFG    0x0
31 #define         PHY_CFG_PHY_ENA         (BIT(0) | BIT(1) | BIT(2) | BIT(3))
32 #define         PHY_CFG_PHY_COMMON_RESET BIT(4)
33 #define         PHY_CFG_PHY_RESET       (BIT(5) | BIT(6) | BIT(7) | BIT(8))
34 #define MSCC_PHY_REG_PHY_STATUS 0x4
35
36 struct mscc_miim_dev {
37         void __iomem *regs;
38         void __iomem *phy_regs;
39 };
40
41 static int mscc_miim_wait_ready(struct mii_bus *bus)
42 {
43         struct mscc_miim_dev *miim = bus->priv;
44         u32 val;
45
46         readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val,
47                            !(val & MSCC_MIIM_STATUS_STAT_BUSY), 100, 250000);
48         if (val & MSCC_MIIM_STATUS_STAT_BUSY)
49                 return -ETIMEDOUT;
50
51         return 0;
52 }
53
54 static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum)
55 {
56         struct mscc_miim_dev *miim = bus->priv;
57         u32 val;
58         int ret;
59
60         ret = mscc_miim_wait_ready(bus);
61         if (ret)
62                 goto out;
63
64         writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
65                (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ,
66                miim->regs + MSCC_MIIM_REG_CMD);
67
68         ret = mscc_miim_wait_ready(bus);
69         if (ret)
70                 goto out;
71
72         val = readl(miim->regs + MSCC_MIIM_REG_DATA);
73         if (val & MSCC_MIIM_DATA_ERROR) {
74                 ret = -EIO;
75                 goto out;
76         }
77
78         ret = val & 0xFFFF;
79 out:
80         return ret;
81 }
82
83 static int mscc_miim_write(struct mii_bus *bus, int mii_id,
84                            int regnum, u16 value)
85 {
86         struct mscc_miim_dev *miim = bus->priv;
87         int ret;
88
89         ret = mscc_miim_wait_ready(bus);
90         if (ret < 0)
91                 goto out;
92
93         writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
94                (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) |
95                (value << MSCC_MIIM_CMD_WRDATA_SHIFT) |
96                MSCC_MIIM_CMD_OPR_WRITE,
97                miim->regs + MSCC_MIIM_REG_CMD);
98
99 out:
100         return ret;
101 }
102
103 static int mscc_miim_reset(struct mii_bus *bus)
104 {
105         struct mscc_miim_dev *miim = bus->priv;
106
107         if (miim->phy_regs) {
108                 writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
109                 writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
110                 mdelay(500);
111         }
112
113         return 0;
114 }
115
116 static int mscc_miim_probe(struct platform_device *pdev)
117 {
118         struct resource *res;
119         struct mii_bus *bus;
120         struct mscc_miim_dev *dev;
121         int ret;
122
123         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
124         if (!res)
125                 return -ENODEV;
126
127         bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev));
128         if (!bus)
129                 return -ENOMEM;
130
131         bus->name = "mscc_miim";
132         bus->read = mscc_miim_read;
133         bus->write = mscc_miim_write;
134         bus->reset = mscc_miim_reset;
135         snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
136         bus->parent = &pdev->dev;
137
138         dev = bus->priv;
139         dev->regs = devm_ioremap_resource(&pdev->dev, res);
140         if (IS_ERR(dev->regs)) {
141                 dev_err(&pdev->dev, "Unable to map MIIM registers\n");
142                 return PTR_ERR(dev->regs);
143         }
144
145         res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
146         if (res) {
147                 dev->phy_regs = devm_ioremap_resource(&pdev->dev, res);
148                 if (IS_ERR(dev->phy_regs)) {
149                         dev_err(&pdev->dev, "Unable to map internal phy registers\n");
150                         return PTR_ERR(dev->phy_regs);
151                 }
152         }
153
154         ret = of_mdiobus_register(bus, pdev->dev.of_node);
155         if (ret < 0) {
156                 dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
157                 return ret;
158         }
159
160         platform_set_drvdata(pdev, bus);
161
162         return 0;
163 }
164
165 static int mscc_miim_remove(struct platform_device *pdev)
166 {
167         struct mii_bus *bus = platform_get_drvdata(pdev);
168
169         mdiobus_unregister(bus);
170
171         return 0;
172 }
173
174 static const struct of_device_id mscc_miim_match[] = {
175         { .compatible = "mscc,ocelot-miim" },
176         { }
177 };
178 MODULE_DEVICE_TABLE(of, mscc_miim_match);
179
180 static struct platform_driver mscc_miim_driver = {
181         .probe = mscc_miim_probe,
182         .remove = mscc_miim_remove,
183         .driver = {
184                 .name = "mscc-miim",
185                 .of_match_table = mscc_miim_match,
186         },
187 };
188
189 module_platform_driver(mscc_miim_driver);
190
191 MODULE_DESCRIPTION("Microsemi MIIM driver");
192 MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
193 MODULE_LICENSE("Dual MIT/GPL");