1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
// SPDX-License-Identifier: GPL-2.0
/* Look-up table query interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*/
#include "rtl8365mb_table.h"
#include <linux/regmap.h>
/* Table access control register */
#define RTL8365MB_TABLE_CTRL_REG 0x0500
/* Should be one of rtl8365mb_table enum members */
#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0)
/* Should be one of rtl8365mb_table_op enum members */
#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3)
/* Should be one of rtl8365mb_table_l2_method enum members */
#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
/* Table access address register */
#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
/* Table status register */
#define RTL8365MB_TABLE_STATUS_REG 0x0502
#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0)
/* set for L3, unset for L2 */
#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11)
#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
/* Table read/write registers */
#define RTL8365MB_TABLE_WRITE_BASE 0x0510
#define RTL8365MB_TABLE_WRITE_REG(_x) \
(RTL8365MB_TABLE_WRITE_BASE + (_x))
#define RTL8365MB_TABLE_READ_BASE 0x0520
#define RTL8365MB_TABLE_READ_REG(_x) \
(RTL8365MB_TABLE_READ_BASE + (_x))
#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0)
#define RTL8365MB_TABLE_WRITE_10TH_REG \
RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1)
static int rtl8365mb_table_poll_busy(struct realtek_priv *priv)
{
u32 val;
return regmap_read_poll_timeout(priv->map_nolock,
RTL8365MB_TABLE_STATUS_REG, val,
!FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val),
10, 10000);
}
int rtl8365mb_table_query(struct realtek_priv *priv,
enum rtl8365mb_table table,
enum rtl8365mb_table_op op, u16 *addr,
enum rtl8365mb_table_l2_method method,
u16 port, u16 *data, size_t size)
{
bool addr_as_input = true;
bool write_data = false;
int ret = 0;
u32 cmd;
u32 val;
u32 hit;
/* Prepare target table and operation (read or write) */
cmd = 0;
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table);
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op);
if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) {
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method);
switch (method) {
case RTL8365MB_TABLE_L2_METHOD_MAC:
/*
* Method MAC requires as input the same L2 table format
* you'll get as result. However, it might only use mac
* address and FID/VID fields.
*/
write_data = true;
/* METHOD_MAC does not use addr as input, but may return
* the matched index.
*/
addr_as_input = false;
break;
case RTL8365MB_TABLE_L2_METHOD_ADDR:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
break;
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
break;
default:
return -EINVAL;
}
} else if (op == RTL8365MB_TABLE_OP_WRITE) {
write_data = true;
/* Writing to L2 does not use addr as input, as the table index
* is derived from key fields.
*/
if (table == RTL8365MB_TABLE_L2)
addr_as_input = false;
}
/* To prevent concurrent access to the look-up tables, take the regmap
* lock manually and access via the map_nolock regmap.
*/
mutex_lock(&priv->map_lock);
/* Protect from a busy table access (i.e. previous access timeouts) */
ret = rtl8365mb_table_poll_busy(priv);
if (ret)
goto out;
/* Write entry data if writing to the table (or L2_METHOD_MAC) */
if (write_data) {
/* bulk write data up to 9th word */
ret = regmap_bulk_write(priv->map_nolock,
RTL8365MB_TABLE_WRITE_BASE,
data,
min_t(size_t, size,
RTL8365MB_TABLE_ENTRY_MAX_SIZE -
1));
if (ret)
goto out;
/* 10th register uses only 4 least significant bits */
if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK,
data[size - 1]);
ret = regmap_update_bits(priv->map_nolock,
RTL8365MB_TABLE_WRITE_10TH_REG,
RTL8365MB_TABLE_10TH_DATA_MASK,
val);
}
if (ret)
goto out;
}
/* Write address (if needed) */
if (addr_as_input) {
ret = regmap_write(priv->map_nolock,
RTL8365MB_TABLE_ACCESS_ADDR_REG,
FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
*addr));
if (ret)
goto out;
}
/* Execute */
ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd);
if (ret)
goto out;
/* Poll for completion */
ret = rtl8365mb_table_poll_busy(priv);
if (ret)
goto out;
/* For both reads and writes to the L2 table, check status */
if (table == RTL8365MB_TABLE_L2) {
ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG,
&val);
if (ret)
goto out;
/* Did the query find an entry? */
hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val);
if (!hit) {
ret = -ENOENT;
goto out;
}
/* If so, extract the address */
*addr = 0;
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
<< 11;
/* only set if it is a L3 address */
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
<< 12;
}
/* Finally, get the table entry if we were reading */
if (op == RTL8365MB_TABLE_OP_READ) {
ret = regmap_bulk_read(priv->map_nolock,
RTL8365MB_TABLE_READ_BASE,
data, size);
if (ret)
goto out;
/* For the biggest table entries, the uppermost table
* entry register has space for only one nibble. Mask
* out the remainder bits. Empirically I saw nothing
* wrong with omitting this mask, but it may prevent
* unwanted behaviour. FYI.
*/
if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK,
data[size - 1]);
data[size - 1] = val;
}
}
out:
mutex_unlock(&priv->map_lock);
return ret;
}
|