Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
e7ba0fad VD |
2 | /* |
3 | * Marvell 88E6xxx System Management Interface (SMI) support | |
4 | * | |
5 | * Copyright (c) 2008 Marvell Semiconductor | |
6 | * | |
7 | * Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com> | |
e7ba0fad VD |
8 | */ |
9 | ||
10 | #include "chip.h" | |
11 | #include "smi.h" | |
12 | ||
13 | /* The switch ADDR[4:1] configuration pins define the chip SMI device address | |
14 | * (ADDR[0] is always zero, thus only even SMI addresses can be strapped). | |
15 | * | |
16 | * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it | |
17 | * is the only device connected to the SMI master. In this mode it responds to | |
18 | * all 32 possible SMI addresses, and thus maps directly the internal devices. | |
19 | * | |
20 | * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing | |
21 | * multiple devices to share the SMI interface. In this mode it responds to only | |
22 | * 2 registers, used to indirectly access the internal SMI devices. | |
f30a19b8 RV |
23 | * |
24 | * Some chips use a different scheme: Only the ADDR4 pin is used for | |
25 | * configuration, and the device responds to 16 of the 32 SMI | |
26 | * addresses, allowing two to coexist on the same SMI interface. | |
e7ba0fad VD |
27 | */ |
28 | ||
29 | static int mv88e6xxx_smi_direct_read(struct mv88e6xxx_chip *chip, | |
30 | int dev, int reg, u16 *data) | |
31 | { | |
32 | int ret; | |
33 | ||
34 | ret = mdiobus_read_nested(chip->bus, dev, reg); | |
35 | if (ret < 0) | |
36 | return ret; | |
37 | ||
38 | *data = ret & 0xffff; | |
39 | ||
40 | return 0; | |
41 | } | |
42 | ||
43 | static int mv88e6xxx_smi_direct_write(struct mv88e6xxx_chip *chip, | |
44 | int dev, int reg, u16 data) | |
45 | { | |
46 | int ret; | |
47 | ||
48 | ret = mdiobus_write_nested(chip->bus, dev, reg, data); | |
49 | if (ret < 0) | |
50 | return ret; | |
51 | ||
52 | return 0; | |
53 | } | |
54 | ||
55 | static int mv88e6xxx_smi_direct_wait(struct mv88e6xxx_chip *chip, | |
56 | int dev, int reg, int bit, int val) | |
57 | { | |
35da1dfd | 58 | const unsigned long timeout = jiffies + msecs_to_jiffies(50); |
e7ba0fad VD |
59 | u16 data; |
60 | int err; | |
61 | int i; | |
62 | ||
35da1dfd TW |
63 | /* Even if the initial poll takes longer than 50ms, always do |
64 | * at least one more attempt. | |
65 | */ | |
66 | for (i = 0; time_before(jiffies, timeout) || (i < 2); i++) { | |
e7ba0fad VD |
67 | err = mv88e6xxx_smi_direct_read(chip, dev, reg, &data); |
68 | if (err) | |
69 | return err; | |
70 | ||
1c6463b6 | 71 | if (!!(data & BIT(bit)) == !!val) |
e7ba0fad | 72 | return 0; |
eede2361 | 73 | |
35da1dfd TW |
74 | if (i < 2) |
75 | cpu_relax(); | |
76 | else | |
77 | usleep_range(1000, 2000); | |
e7ba0fad VD |
78 | } |
79 | ||
80 | return -ETIMEDOUT; | |
81 | } | |
82 | ||
83 | static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_direct_ops = { | |
84 | .read = mv88e6xxx_smi_direct_read, | |
85 | .write = mv88e6xxx_smi_direct_write, | |
86 | }; | |
87 | ||
f30a19b8 RV |
88 | static int mv88e6xxx_smi_dual_direct_read(struct mv88e6xxx_chip *chip, |
89 | int dev, int reg, u16 *data) | |
90 | { | |
91 | return mv88e6xxx_smi_direct_read(chip, chip->sw_addr + dev, reg, data); | |
92 | } | |
93 | ||
94 | static int mv88e6xxx_smi_dual_direct_write(struct mv88e6xxx_chip *chip, | |
95 | int dev, int reg, u16 data) | |
96 | { | |
97 | return mv88e6xxx_smi_direct_write(chip, chip->sw_addr + dev, reg, data); | |
98 | } | |
99 | ||
100 | static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_dual_direct_ops = { | |
101 | .read = mv88e6xxx_smi_dual_direct_read, | |
102 | .write = mv88e6xxx_smi_dual_direct_write, | |
103 | }; | |
104 | ||
e7ba0fad VD |
105 | /* Offset 0x00: SMI Command Register |
106 | * Offset 0x01: SMI Data Register | |
107 | */ | |
108 | ||
109 | static int mv88e6xxx_smi_indirect_read(struct mv88e6xxx_chip *chip, | |
110 | int dev, int reg, u16 *data) | |
111 | { | |
112 | int err; | |
113 | ||
e7ba0fad VD |
114 | err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, |
115 | MV88E6XXX_SMI_CMD, | |
116 | MV88E6XXX_SMI_CMD_BUSY | | |
117 | MV88E6XXX_SMI_CMD_MODE_22 | | |
118 | MV88E6XXX_SMI_CMD_OP_22_READ | | |
119 | (dev << 5) | reg); | |
120 | if (err) | |
121 | return err; | |
122 | ||
123 | err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, | |
124 | MV88E6XXX_SMI_CMD, 15, 0); | |
125 | if (err) | |
126 | return err; | |
127 | ||
128 | return mv88e6xxx_smi_direct_read(chip, chip->sw_addr, | |
129 | MV88E6XXX_SMI_DATA, data); | |
130 | } | |
131 | ||
132 | static int mv88e6xxx_smi_indirect_write(struct mv88e6xxx_chip *chip, | |
133 | int dev, int reg, u16 data) | |
134 | { | |
135 | int err; | |
136 | ||
e7ba0fad VD |
137 | err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, |
138 | MV88E6XXX_SMI_DATA, data); | |
139 | if (err) | |
140 | return err; | |
141 | ||
142 | err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, | |
143 | MV88E6XXX_SMI_CMD, | |
144 | MV88E6XXX_SMI_CMD_BUSY | | |
145 | MV88E6XXX_SMI_CMD_MODE_22 | | |
146 | MV88E6XXX_SMI_CMD_OP_22_WRITE | | |
147 | (dev << 5) | reg); | |
148 | if (err) | |
149 | return err; | |
150 | ||
151 | return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, | |
152 | MV88E6XXX_SMI_CMD, 15, 0); | |
153 | } | |
154 | ||
7bca16b2 TW |
155 | static int mv88e6xxx_smi_indirect_init(struct mv88e6xxx_chip *chip) |
156 | { | |
157 | /* Ensure that the chip starts out in the ready state. As both | |
158 | * reads and writes always ensure this on return, they can | |
159 | * safely depend on the chip not being busy on entry. | |
160 | */ | |
161 | return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, | |
162 | MV88E6XXX_SMI_CMD, 15, 0); | |
163 | } | |
164 | ||
e7ba0fad VD |
165 | static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_indirect_ops = { |
166 | .read = mv88e6xxx_smi_indirect_read, | |
167 | .write = mv88e6xxx_smi_indirect_write, | |
7bca16b2 | 168 | .init = mv88e6xxx_smi_indirect_init, |
e7ba0fad VD |
169 | }; |
170 | ||
171 | int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip, | |
172 | struct mii_bus *bus, int sw_addr) | |
173 | { | |
f30a19b8 RV |
174 | if (chip->info->dual_chip) |
175 | chip->smi_ops = &mv88e6xxx_smi_dual_direct_ops; | |
176 | else if (sw_addr == 0) | |
e7ba0fad VD |
177 | chip->smi_ops = &mv88e6xxx_smi_direct_ops; |
178 | else if (chip->info->multi_chip) | |
179 | chip->smi_ops = &mv88e6xxx_smi_indirect_ops; | |
180 | else | |
181 | return -EINVAL; | |
182 | ||
183 | chip->bus = bus; | |
184 | chip->sw_addr = sw_addr; | |
185 | ||
7bca16b2 TW |
186 | if (chip->smi_ops->init) |
187 | return chip->smi_ops->init(chip); | |
188 | ||
e7ba0fad VD |
189 | return 0; |
190 | } |