mirror of
https://github.com/torvalds/linux.git
synced 2026-10-17 14:30:03 +02:00
Add a generic table lookup interface to centralize access to the RTL8365MB internal tables. This interface abstracts the low-level table access logic and will be used by subsequent commits to implement FDB and VLAN operations. Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk> Reviewed-by: Linus Walleij <linusw@kernel.org> Reviewed-by: Mieczyslaw Nalewaj <namiltd@yahoo.com> Co-developed-by: Luiz Angelo Daros de Luca <luizluca@gmail.com> Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com> Link: https://patch.msgid.link/20260606-realtek_forward-v13-5-b9e409687cbe@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
215 lines
6.1 KiB
C
215 lines
6.1 KiB
C
// 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;
|
|
}
|