1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* MOXA ART Ethernet (RTL8201CP) MDIO interface driver |
3 | * |
4 | * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com> |
5 | */ |
6 | |
7 | #include <linux/delay.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/mutex.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/of_mdio.h> |
13 | #include <linux/phy.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #define REG_PHY_CTRL 0 |
17 | #define REG_PHY_WRITE_DATA 4 |
18 | |
19 | /* REG_PHY_CTRL */ |
20 | #define MIIWR BIT(27) /* init write sequence (auto cleared)*/ |
21 | #define MIIRD BIT(26) |
22 | #define REGAD_MASK 0x3e00000 |
23 | #define PHYAD_MASK 0x1f0000 |
24 | #define MIIRDATA_MASK 0xffff |
25 | |
26 | /* REG_PHY_WRITE_DATA */ |
27 | #define MIIWDATA_MASK 0xffff |
28 | |
29 | struct moxart_mdio_data { |
30 | void __iomem *base; |
31 | }; |
32 | |
33 | static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum) |
34 | { |
35 | struct moxart_mdio_data *data = bus->priv; |
36 | u32 ctrl = 0; |
37 | unsigned int count = 5; |
38 | |
39 | dev_dbg(&bus->dev, "%s\n" , __func__); |
40 | |
41 | ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) | |
42 | ((regnum << 21) & REGAD_MASK); |
43 | |
44 | writel(val: ctrl, addr: data->base + REG_PHY_CTRL); |
45 | |
46 | do { |
47 | ctrl = readl(addr: data->base + REG_PHY_CTRL); |
48 | |
49 | if (!(ctrl & MIIRD)) |
50 | return ctrl & MIIRDATA_MASK; |
51 | |
52 | mdelay(10); |
53 | count--; |
54 | } while (count > 0); |
55 | |
56 | dev_dbg(&bus->dev, "%s timed out\n" , __func__); |
57 | |
58 | return -ETIMEDOUT; |
59 | } |
60 | |
61 | static int moxart_mdio_write(struct mii_bus *bus, int mii_id, |
62 | int regnum, u16 value) |
63 | { |
64 | struct moxart_mdio_data *data = bus->priv; |
65 | u32 ctrl = 0; |
66 | unsigned int count = 5; |
67 | |
68 | dev_dbg(&bus->dev, "%s\n" , __func__); |
69 | |
70 | ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) | |
71 | ((regnum << 21) & REGAD_MASK); |
72 | |
73 | value &= MIIWDATA_MASK; |
74 | |
75 | writel(val: value, addr: data->base + REG_PHY_WRITE_DATA); |
76 | writel(val: ctrl, addr: data->base + REG_PHY_CTRL); |
77 | |
78 | do { |
79 | ctrl = readl(addr: data->base + REG_PHY_CTRL); |
80 | |
81 | if (!(ctrl & MIIWR)) |
82 | return 0; |
83 | |
84 | mdelay(10); |
85 | count--; |
86 | } while (count > 0); |
87 | |
88 | dev_dbg(&bus->dev, "%s timed out\n" , __func__); |
89 | |
90 | return -ETIMEDOUT; |
91 | } |
92 | |
93 | static int moxart_mdio_reset(struct mii_bus *bus) |
94 | { |
95 | int data, i; |
96 | |
97 | for (i = 0; i < PHY_MAX_ADDR; i++) { |
98 | data = moxart_mdio_read(bus, mii_id: i, MII_BMCR); |
99 | if (data < 0) |
100 | continue; |
101 | |
102 | data |= BMCR_RESET; |
103 | if (moxart_mdio_write(bus, mii_id: i, MII_BMCR, value: data) < 0) |
104 | continue; |
105 | } |
106 | |
107 | return 0; |
108 | } |
109 | |
110 | static int moxart_mdio_probe(struct platform_device *pdev) |
111 | { |
112 | struct device_node *np = pdev->dev.of_node; |
113 | struct mii_bus *bus; |
114 | struct moxart_mdio_data *data; |
115 | int ret, i; |
116 | |
117 | bus = mdiobus_alloc_size(size: sizeof(*data)); |
118 | if (!bus) |
119 | return -ENOMEM; |
120 | |
121 | bus->name = "MOXA ART Ethernet MII" ; |
122 | bus->read = &moxart_mdio_read; |
123 | bus->write = &moxart_mdio_write; |
124 | bus->reset = &moxart_mdio_reset; |
125 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s-%d-mii" , pdev->name, pdev->id); |
126 | bus->parent = &pdev->dev; |
127 | |
128 | /* Setting PHY_MAC_INTERRUPT here even if it has no effect, |
129 | * of_mdiobus_register() sets these PHY_POLL. |
130 | * Ideally, the interrupt from MAC controller could be used to |
131 | * detect link state changes, not polling, i.e. if there was |
132 | * a way phy_driver could set PHY_HAS_INTERRUPT but have that |
133 | * interrupt handled in ethernet drivercode. |
134 | */ |
135 | for (i = 0; i < PHY_MAX_ADDR; i++) |
136 | bus->irq[i] = PHY_MAC_INTERRUPT; |
137 | |
138 | data = bus->priv; |
139 | data->base = devm_platform_ioremap_resource(pdev, index: 0); |
140 | if (IS_ERR(ptr: data->base)) { |
141 | ret = PTR_ERR(ptr: data->base); |
142 | goto err_out_free_mdiobus; |
143 | } |
144 | |
145 | ret = of_mdiobus_register(mdio: bus, np); |
146 | if (ret < 0) |
147 | goto err_out_free_mdiobus; |
148 | |
149 | platform_set_drvdata(pdev, data: bus); |
150 | |
151 | return 0; |
152 | |
153 | err_out_free_mdiobus: |
154 | mdiobus_free(bus); |
155 | return ret; |
156 | } |
157 | |
158 | static void moxart_mdio_remove(struct platform_device *pdev) |
159 | { |
160 | struct mii_bus *bus = platform_get_drvdata(pdev); |
161 | |
162 | mdiobus_unregister(bus); |
163 | mdiobus_free(bus); |
164 | } |
165 | |
166 | static const struct of_device_id moxart_mdio_dt_ids[] = { |
167 | { .compatible = "moxa,moxart-mdio" }, |
168 | { } |
169 | }; |
170 | MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids); |
171 | |
172 | static struct platform_driver moxart_mdio_driver = { |
173 | .probe = moxart_mdio_probe, |
174 | .remove_new = moxart_mdio_remove, |
175 | .driver = { |
176 | .name = "moxart-mdio" , |
177 | .of_match_table = moxart_mdio_dt_ids, |
178 | }, |
179 | }; |
180 | |
181 | module_platform_driver(moxart_mdio_driver); |
182 | |
183 | MODULE_DESCRIPTION("MOXA ART MDIO interface driver" ); |
184 | MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>" ); |
185 | MODULE_LICENSE("GPL v2" ); |
186 | |