1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Copyright (C) 2019 IBM Corp. */ |
3 | |
4 | #include <linux/bitfield.h> |
5 | #include <linux/delay.h> |
6 | #include <linux/reset.h> |
7 | #include <linux/iopoll.h> |
8 | #include <linux/mdio.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_mdio.h> |
12 | #include <linux/phy.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | #define DRV_NAME "mdio-aspeed" |
16 | |
17 | #define ASPEED_MDIO_CTRL 0x0 |
18 | #define ASPEED_MDIO_CTRL_FIRE BIT(31) |
19 | #define ASPEED_MDIO_CTRL_ST BIT(28) |
20 | #define ASPEED_MDIO_CTRL_ST_C45 0 |
21 | #define ASPEED_MDIO_CTRL_ST_C22 1 |
22 | #define ASPEED_MDIO_CTRL_OP GENMASK(27, 26) |
23 | #define MDIO_C22_OP_WRITE 0b01 |
24 | #define MDIO_C22_OP_READ 0b10 |
25 | #define MDIO_C45_OP_ADDR 0b00 |
26 | #define MDIO_C45_OP_WRITE 0b01 |
27 | #define MDIO_C45_OP_PREAD 0b10 |
28 | #define MDIO_C45_OP_READ 0b11 |
29 | #define ASPEED_MDIO_CTRL_PHYAD GENMASK(25, 21) |
30 | #define ASPEED_MDIO_CTRL_REGAD GENMASK(20, 16) |
31 | #define ASPEED_MDIO_CTRL_MIIWDATA GENMASK(15, 0) |
32 | |
33 | #define ASPEED_MDIO_DATA 0x4 |
34 | #define ASPEED_MDIO_DATA_MDC_THRES GENMASK(31, 24) |
35 | #define ASPEED_MDIO_DATA_MDIO_EDGE BIT(23) |
36 | #define ASPEED_MDIO_DATA_MDIO_LATCH GENMASK(22, 20) |
37 | #define ASPEED_MDIO_DATA_IDLE BIT(16) |
38 | #define ASPEED_MDIO_DATA_MIIRDATA GENMASK(15, 0) |
39 | |
40 | #define ASPEED_MDIO_INTERVAL_US 100 |
41 | #define ASPEED_MDIO_TIMEOUT_US (ASPEED_MDIO_INTERVAL_US * 10) |
42 | |
43 | struct aspeed_mdio { |
44 | void __iomem *base; |
45 | struct reset_control *reset; |
46 | }; |
47 | |
48 | static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad, |
49 | u16 data) |
50 | { |
51 | struct aspeed_mdio *ctx = bus->priv; |
52 | u32 ctrl; |
53 | |
54 | dev_dbg(&bus->dev, "%s: st: %u op: %u, phyad: %u, regad: %u, data: %u\n" , |
55 | __func__, st, op, phyad, regad, data); |
56 | |
57 | ctrl = ASPEED_MDIO_CTRL_FIRE |
58 | | FIELD_PREP(ASPEED_MDIO_CTRL_ST, st) |
59 | | FIELD_PREP(ASPEED_MDIO_CTRL_OP, op) |
60 | | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, phyad) |
61 | | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regad) |
62 | | FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data); |
63 | |
64 | iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); |
65 | |
66 | return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, |
67 | !(ctrl & ASPEED_MDIO_CTRL_FIRE), |
68 | ASPEED_MDIO_INTERVAL_US, |
69 | ASPEED_MDIO_TIMEOUT_US); |
70 | } |
71 | |
72 | static int aspeed_mdio_get_data(struct mii_bus *bus) |
73 | { |
74 | struct aspeed_mdio *ctx = bus->priv; |
75 | u32 data; |
76 | int rc; |
77 | |
78 | rc = readl_poll_timeout(ctx->base + ASPEED_MDIO_DATA, data, |
79 | data & ASPEED_MDIO_DATA_IDLE, |
80 | ASPEED_MDIO_INTERVAL_US, |
81 | ASPEED_MDIO_TIMEOUT_US); |
82 | if (rc < 0) |
83 | return rc; |
84 | |
85 | return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data); |
86 | } |
87 | |
88 | static int aspeed_mdio_read_c22(struct mii_bus *bus, int addr, int regnum) |
89 | { |
90 | int rc; |
91 | |
92 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_READ, |
93 | phyad: addr, regad: regnum, data: 0); |
94 | if (rc < 0) |
95 | return rc; |
96 | |
97 | return aspeed_mdio_get_data(bus); |
98 | } |
99 | |
100 | static int aspeed_mdio_write_c22(struct mii_bus *bus, int addr, int regnum, |
101 | u16 val) |
102 | { |
103 | return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_WRITE, |
104 | phyad: addr, regad: regnum, data: val); |
105 | } |
106 | |
107 | static int aspeed_mdio_read_c45(struct mii_bus *bus, int addr, int devad, |
108 | int regnum) |
109 | { |
110 | int rc; |
111 | |
112 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, |
113 | phyad: addr, regad: devad, data: regnum); |
114 | if (rc < 0) |
115 | return rc; |
116 | |
117 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_READ, |
118 | phyad: addr, regad: devad, data: 0); |
119 | if (rc < 0) |
120 | return rc; |
121 | |
122 | return aspeed_mdio_get_data(bus); |
123 | } |
124 | |
125 | static int aspeed_mdio_write_c45(struct mii_bus *bus, int addr, int devad, |
126 | int regnum, u16 val) |
127 | { |
128 | int rc; |
129 | |
130 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, |
131 | phyad: addr, regad: devad, data: regnum); |
132 | if (rc < 0) |
133 | return rc; |
134 | |
135 | return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_WRITE, |
136 | phyad: addr, regad: devad, data: val); |
137 | } |
138 | |
139 | static int aspeed_mdio_probe(struct platform_device *pdev) |
140 | { |
141 | struct aspeed_mdio *ctx; |
142 | struct mii_bus *bus; |
143 | int rc; |
144 | |
145 | bus = devm_mdiobus_alloc_size(dev: &pdev->dev, sizeof_priv: sizeof(*ctx)); |
146 | if (!bus) |
147 | return -ENOMEM; |
148 | |
149 | ctx = bus->priv; |
150 | ctx->base = devm_platform_ioremap_resource(pdev, index: 0); |
151 | if (IS_ERR(ptr: ctx->base)) |
152 | return PTR_ERR(ptr: ctx->base); |
153 | |
154 | ctx->reset = devm_reset_control_get_optional_shared(dev: &pdev->dev, NULL); |
155 | if (IS_ERR(ptr: ctx->reset)) |
156 | return PTR_ERR(ptr: ctx->reset); |
157 | |
158 | reset_control_deassert(rstc: ctx->reset); |
159 | |
160 | bus->name = DRV_NAME; |
161 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s%d" , pdev->name, pdev->id); |
162 | bus->parent = &pdev->dev; |
163 | bus->read = aspeed_mdio_read_c22; |
164 | bus->write = aspeed_mdio_write_c22; |
165 | bus->read_c45 = aspeed_mdio_read_c45; |
166 | bus->write_c45 = aspeed_mdio_write_c45; |
167 | |
168 | rc = of_mdiobus_register(mdio: bus, np: pdev->dev.of_node); |
169 | if (rc) { |
170 | dev_err(&pdev->dev, "Cannot register MDIO bus!\n" ); |
171 | reset_control_assert(rstc: ctx->reset); |
172 | return rc; |
173 | } |
174 | |
175 | platform_set_drvdata(pdev, data: bus); |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | static void aspeed_mdio_remove(struct platform_device *pdev) |
181 | { |
182 | struct mii_bus *bus = (struct mii_bus *)platform_get_drvdata(pdev); |
183 | struct aspeed_mdio *ctx = bus->priv; |
184 | |
185 | reset_control_assert(rstc: ctx->reset); |
186 | mdiobus_unregister(bus); |
187 | } |
188 | |
189 | static const struct of_device_id aspeed_mdio_of_match[] = { |
190 | { .compatible = "aspeed,ast2600-mdio" , }, |
191 | { }, |
192 | }; |
193 | MODULE_DEVICE_TABLE(of, aspeed_mdio_of_match); |
194 | |
195 | static struct platform_driver aspeed_mdio_driver = { |
196 | .driver = { |
197 | .name = DRV_NAME, |
198 | .of_match_table = aspeed_mdio_of_match, |
199 | }, |
200 | .probe = aspeed_mdio_probe, |
201 | .remove_new = aspeed_mdio_remove, |
202 | }; |
203 | |
204 | module_platform_driver(aspeed_mdio_driver); |
205 | |
206 | MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>" ); |
207 | MODULE_LICENSE("GPL" ); |
208 | MODULE_DESCRIPTION("ASPEED MDIO bus controller" ); |
209 | |