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