]>
Commit | Line | Data |
---|---|---|
6c4bbccc GS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * CPSW MDIO generic driver for TI AMxx/K2x/EMAC devices. | |
4 | * | |
5 | * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ | |
6 | */ | |
7 | ||
8 | #include <common.h> | |
f7ae49fc | 9 | #include <log.h> |
336d4615 | 10 | #include <malloc.h> |
6c4bbccc GS |
11 | #include <asm/io.h> |
12 | #include <miiphy.h> | |
13 | #include <wait_bit.h> | |
14 | ||
15 | struct cpsw_mdio_regs { | |
16 | u32 version; | |
17 | u32 control; | |
18 | #define CONTROL_IDLE BIT(31) | |
19 | #define CONTROL_ENABLE BIT(30) | |
20 | #define CONTROL_FAULT BIT(19) | |
21 | #define CONTROL_FAULT_ENABLE BIT(18) | |
22 | #define CONTROL_DIV_MASK GENMASK(15, 0) | |
23 | ||
24 | u32 alive; | |
25 | u32 link; | |
26 | u32 linkintraw; | |
27 | u32 linkintmasked; | |
28 | u32 __reserved_0[2]; | |
29 | u32 userintraw; | |
30 | u32 userintmasked; | |
31 | u32 userintmaskset; | |
32 | u32 userintmaskclr; | |
33 | u32 __reserved_1[20]; | |
34 | ||
35 | struct { | |
36 | u32 access; | |
37 | u32 physel; | |
38 | #define USERACCESS_GO BIT(31) | |
39 | #define USERACCESS_WRITE BIT(30) | |
40 | #define USERACCESS_ACK BIT(29) | |
41 | #define USERACCESS_READ (0) | |
42 | #define USERACCESS_PHY_REG_SHIFT (21) | |
43 | #define USERACCESS_PHY_ADDR_SHIFT (16) | |
44 | #define USERACCESS_DATA GENMASK(15, 0) | |
45 | } user[0]; | |
46 | }; | |
47 | ||
48 | #define CPSW_MDIO_DIV_DEF 0xff | |
49 | #define PHY_REG_MASK 0x1f | |
50 | #define PHY_ID_MASK 0x1f | |
51 | ||
52 | /* | |
53 | * This timeout definition is a worst-case ultra defensive measure against | |
54 | * unexpected controller lock ups. Ideally, we should never ever hit this | |
55 | * scenario in practice. | |
56 | */ | |
57 | #define CPSW_MDIO_TIMEOUT 100 /* msecs */ | |
58 | ||
59 | struct cpsw_mdio { | |
60 | struct cpsw_mdio_regs *regs; | |
61 | struct mii_dev *bus; | |
62 | int div; | |
63 | }; | |
64 | ||
65 | /* wait until hardware is ready for another user access */ | |
66 | static int cpsw_mdio_wait_for_user_access(struct cpsw_mdio *mdio) | |
67 | { | |
68 | return wait_for_bit_le32(&mdio->regs->user[0].access, | |
69 | USERACCESS_GO, false, | |
70 | CPSW_MDIO_TIMEOUT, false); | |
71 | } | |
72 | ||
73 | static int cpsw_mdio_read(struct mii_dev *bus, int phy_id, | |
74 | int dev_addr, int phy_reg) | |
75 | { | |
76 | struct cpsw_mdio *mdio = bus->priv; | |
77 | int data, ret; | |
78 | u32 reg; | |
79 | ||
80 | if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK) | |
81 | return -EINVAL; | |
82 | ||
83 | ret = cpsw_mdio_wait_for_user_access(mdio); | |
84 | if (ret) | |
85 | return ret; | |
86 | reg = (USERACCESS_GO | USERACCESS_READ | | |
87 | (phy_reg << USERACCESS_PHY_REG_SHIFT) | | |
88 | (phy_id << USERACCESS_PHY_ADDR_SHIFT)); | |
89 | writel(reg, &mdio->regs->user[0].access); | |
90 | ret = cpsw_mdio_wait_for_user_access(mdio); | |
91 | if (ret) | |
92 | return ret; | |
93 | ||
94 | reg = readl(&mdio->regs->user[0].access); | |
95 | data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1; | |
96 | return data; | |
97 | } | |
98 | ||
99 | static int cpsw_mdio_write(struct mii_dev *bus, int phy_id, int dev_addr, | |
100 | int phy_reg, u16 data) | |
101 | { | |
102 | struct cpsw_mdio *mdio = bus->priv; | |
103 | u32 reg; | |
104 | int ret; | |
105 | ||
106 | if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK) | |
107 | return -EINVAL; | |
108 | ||
109 | ret = cpsw_mdio_wait_for_user_access(mdio); | |
110 | if (ret) | |
111 | return ret; | |
112 | reg = (USERACCESS_GO | USERACCESS_WRITE | | |
113 | (phy_reg << USERACCESS_PHY_REG_SHIFT) | | |
114 | (phy_id << USERACCESS_PHY_ADDR_SHIFT) | | |
115 | (data & USERACCESS_DATA)); | |
116 | writel(reg, &mdio->regs->user[0].access); | |
117 | ||
118 | return cpsw_mdio_wait_for_user_access(mdio); | |
119 | } | |
120 | ||
121 | u32 cpsw_mdio_get_alive(struct mii_dev *bus) | |
122 | { | |
123 | struct cpsw_mdio *mdio = bus->priv; | |
124 | u32 val; | |
125 | ||
126 | val = readl(&mdio->regs->control); | |
127 | return val & GENMASK(15, 0); | |
128 | } | |
129 | ||
45e8c055 | 130 | struct mii_dev *cpsw_mdio_init(const char *name, phys_addr_t mdio_base, |
6c4bbccc GS |
131 | u32 bus_freq, int fck_freq) |
132 | { | |
133 | struct cpsw_mdio *cpsw_mdio; | |
134 | int ret; | |
135 | ||
136 | cpsw_mdio = calloc(1, sizeof(*cpsw_mdio)); | |
137 | if (!cpsw_mdio) { | |
138 | debug("failed to alloc cpsw_mdio\n"); | |
139 | return NULL; | |
140 | } | |
141 | ||
142 | cpsw_mdio->bus = mdio_alloc(); | |
143 | if (!cpsw_mdio->bus) { | |
144 | debug("failed to alloc mii bus\n"); | |
145 | free(cpsw_mdio); | |
146 | return NULL; | |
147 | } | |
148 | ||
45e8c055 | 149 | cpsw_mdio->regs = (struct cpsw_mdio_regs *)(uintptr_t)mdio_base; |
6c4bbccc GS |
150 | |
151 | if (!bus_freq || !fck_freq) | |
152 | cpsw_mdio->div = CPSW_MDIO_DIV_DEF; | |
153 | else | |
154 | cpsw_mdio->div = (fck_freq / bus_freq) - 1; | |
155 | cpsw_mdio->div &= CONTROL_DIV_MASK; | |
156 | ||
157 | /* set enable and clock divider */ | |
158 | writel(cpsw_mdio->div | CONTROL_ENABLE | CONTROL_FAULT | | |
159 | CONTROL_FAULT_ENABLE, &cpsw_mdio->regs->control); | |
160 | wait_for_bit_le32(&cpsw_mdio->regs->control, | |
161 | CONTROL_IDLE, false, CPSW_MDIO_TIMEOUT, true); | |
162 | ||
163 | /* | |
164 | * wait for scan logic to settle: | |
165 | * the scan time consists of (a) a large fixed component, and (b) a | |
166 | * small component that varies with the mii bus frequency. These | |
167 | * were estimated using measurements at 1.1 and 2.2 MHz on tnetv107x | |
168 | * silicon. Since the effect of (b) was found to be largely | |
169 | * negligible, we keep things simple here. | |
170 | */ | |
171 | mdelay(1); | |
172 | ||
173 | cpsw_mdio->bus->read = cpsw_mdio_read; | |
174 | cpsw_mdio->bus->write = cpsw_mdio_write; | |
175 | cpsw_mdio->bus->priv = cpsw_mdio; | |
176 | snprintf(cpsw_mdio->bus->name, sizeof(cpsw_mdio->bus->name), name); | |
177 | ||
178 | ret = mdio_register(cpsw_mdio->bus); | |
179 | if (ret < 0) { | |
180 | debug("failed to register mii bus\n"); | |
181 | goto free_bus; | |
182 | } | |
183 | ||
184 | return cpsw_mdio->bus; | |
185 | ||
186 | free_bus: | |
187 | mdio_free(cpsw_mdio->bus); | |
188 | free(cpsw_mdio); | |
189 | return NULL; | |
190 | } | |
191 | ||
192 | void cpsw_mdio_free(struct mii_dev *bus) | |
193 | { | |
194 | struct cpsw_mdio *mdio = bus->priv; | |
195 | u32 reg; | |
196 | ||
197 | /* disable mdio */ | |
198 | reg = readl(&mdio->regs->control); | |
199 | reg &= ~CONTROL_ENABLE; | |
200 | writel(reg, &mdio->regs->control); | |
201 | ||
202 | mdio_unregister(bus); | |
203 | mdio_free(bus); | |
204 | free(mdio); | |
205 | } |