2018-07-14 11:45:55 +02:00
// SPDX-License-Identifier: GPL-2.0
/* Realtek SMI library helpers for the RTL8366x variants
* RTL8366RB and RTL8366S
*
* Copyright ( C ) 2017 Linus Walleij < linus . walleij @ linaro . org >
* Copyright ( C ) 2009 - 2010 Gabor Juhos < juhosg @ openwrt . org >
* Copyright ( C ) 2010 Antti Seppälä < a . seppala @ gmail . com >
* Copyright ( C ) 2010 Roman Yeryomin < roman @ advem . lv >
* Copyright ( C ) 2011 Colin Leitner < colin . leitner @ googlemail . com >
*/
# include <linux/if_bridge.h>
# include <net/dsa.h>
2019-06-13 13:35:03 +02:00
# include "realtek-smi-core.h"
2018-07-14 11:45:55 +02:00
int rtl8366_mc_is_used ( struct realtek_smi * smi , int mc_index , int * used )
{
int ret ;
int i ;
* used = 0 ;
for ( i = 0 ; i < smi - > num_ports ; i + + ) {
int index = 0 ;
ret = smi - > ops - > get_mc_index ( smi , i , & index ) ;
if ( ret )
return ret ;
if ( mc_index = = index ) {
* used = 1 ;
break ;
}
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( rtl8366_mc_is_used ) ;
2020-09-02 00:09:35 +02:00
/**
* rtl8366_obtain_mc ( ) - retrieve or allocate a VLAN member configuration
* @ smi : the Realtek SMI device instance
* @ vid : the VLAN ID to look up or allocate
* @ vlanmc : the pointer will be assigned to a pointer to a valid member config
* if successful
* @ return : index of a new member config or negative error number
*/
static int rtl8366_obtain_mc ( struct realtek_smi * smi , int vid ,
struct rtl8366_vlan_mc * vlanmc )
{
struct rtl8366_vlan_4k vlan4k ;
int ret ;
int i ;
/* Try to find an existing member config entry for this VID */
for ( i = 0 ; i < smi - > num_vlan_mc ; i + + ) {
ret = smi - > ops - > get_vlan_mc ( smi , i , vlanmc ) ;
if ( ret ) {
dev_err ( smi - > dev , " error searching for VLAN MC %d for VID %d \n " ,
i , vid ) ;
return ret ;
}
if ( vid = = vlanmc - > vid )
return i ;
}
/* We have no MC entry for this VID, try to find an empty one */
for ( i = 0 ; i < smi - > num_vlan_mc ; i + + ) {
ret = smi - > ops - > get_vlan_mc ( smi , i , vlanmc ) ;
if ( ret ) {
dev_err ( smi - > dev , " error searching for VLAN MC %d for VID %d \n " ,
i , vid ) ;
return ret ;
}
if ( vlanmc - > vid = = 0 & & vlanmc - > member = = 0 ) {
/* Update the entry from the 4K table */
ret = smi - > ops - > get_vlan_4k ( smi , vid , & vlan4k ) ;
if ( ret ) {
dev_err ( smi - > dev , " error looking for 4K VLAN MC %d for VID %d \n " ,
i , vid ) ;
return ret ;
}
vlanmc - > vid = vid ;
vlanmc - > member = vlan4k . member ;
vlanmc - > untag = vlan4k . untag ;
vlanmc - > fid = vlan4k . fid ;
ret = smi - > ops - > set_vlan_mc ( smi , i , vlanmc ) ;
if ( ret ) {
dev_err ( smi - > dev , " unable to set/update VLAN MC %d for VID %d \n " ,
i , vid ) ;
return ret ;
}
dev_dbg ( smi - > dev , " created new MC at index %d for VID %d \n " ,
i , vid ) ;
return i ;
}
}
/* MC table is full, try to find an unused entry and replace it */
for ( i = 0 ; i < smi - > num_vlan_mc ; i + + ) {
int used ;
ret = rtl8366_mc_is_used ( smi , i , & used ) ;
if ( ret )
return ret ;
if ( ! used ) {
/* Update the entry from the 4K table */
ret = smi - > ops - > get_vlan_4k ( smi , vid , & vlan4k ) ;
if ( ret )
return ret ;
vlanmc - > vid = vid ;
vlanmc - > member = vlan4k . member ;
vlanmc - > untag = vlan4k . untag ;
vlanmc - > fid = vlan4k . fid ;
ret = smi - > ops - > set_vlan_mc ( smi , i , vlanmc ) ;
if ( ret ) {
dev_err ( smi - > dev , " unable to set/update VLAN MC %d for VID %d \n " ,
i , vid ) ;
return ret ;
}
dev_dbg ( smi - > dev , " recycled MC at index %i for VID %d \n " ,
i , vid ) ;
return i ;
}
}
dev_err ( smi - > dev , " all VLAN member configurations are in use \n " ) ;
return - ENOSPC ;
}
2018-07-14 11:45:55 +02:00
int rtl8366_set_vlan ( struct realtek_smi * smi , int vid , u32 member ,
u32 untag , u32 fid )
{
2020-09-02 00:09:35 +02:00
struct rtl8366_vlan_mc vlanmc ;
2018-07-14 11:45:55 +02:00
struct rtl8366_vlan_4k vlan4k ;
2020-09-02 00:09:35 +02:00
int mc ;
2018-07-14 11:45:55 +02:00
int ret ;
2020-09-02 00:09:34 +02:00
if ( ! smi - > ops - > is_vlan_valid ( smi , vid ) )
return - EINVAL ;
2020-07-27 01:34:39 +02:00
dev_dbg ( smi - > dev ,
" setting VLAN%d 4k members: 0x%02x, untagged: 0x%02x \n " ,
vid , member , untag ) ;
2018-07-14 11:45:55 +02:00
/* Update the 4K table */
ret = smi - > ops - > get_vlan_4k ( smi , vid , & vlan4k ) ;
if ( ret )
return ret ;
2020-07-27 01:34:39 +02:00
vlan4k . member | = member ;
vlan4k . untag | = untag ;
2018-07-14 11:45:55 +02:00
vlan4k . fid = fid ;
ret = smi - > ops - > set_vlan_4k ( smi , & vlan4k ) ;
if ( ret )
return ret ;
2020-07-27 01:34:39 +02:00
dev_dbg ( smi - > dev ,
" resulting VLAN%d 4k members: 0x%02x, untagged: 0x%02x \n " ,
vid , vlan4k . member , vlan4k . untag ) ;
2020-09-02 00:09:35 +02:00
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc ( smi , vid , & vlanmc ) ;
if ( ret < 0 )
return ret ;
mc = ret ;
2020-07-27 01:34:39 +02:00
2020-09-02 00:09:35 +02:00
/* Update the MC entry */
vlanmc . member | = member ;
vlanmc . untag | = untag ;
vlanmc . fid = fid ;
2020-07-27 01:34:39 +02:00
2020-09-02 00:09:35 +02:00
/* Commit updates to the MC entry */
ret = smi - > ops - > set_vlan_mc ( smi , mc , & vlanmc ) ;
if ( ret )
dev_err ( smi - > dev , " failed to commit changes to VLAN MC index %d for VID %d \n " ,
mc , vid ) ;
else
dev_dbg ( smi - > dev ,
" resulting VLAN%d MC members: 0x%02x, untagged: 0x%02x \n " ,
vid , vlanmc . member , vlanmc . untag ) ;
2018-07-14 11:45:55 +02:00
return ret ;
}
EXPORT_SYMBOL_GPL ( rtl8366_set_vlan ) ;
int rtl8366_set_pvid ( struct realtek_smi * smi , unsigned int port ,
unsigned int vid )
{
struct rtl8366_vlan_mc vlanmc ;
2020-09-02 00:09:35 +02:00
int mc ;
2018-07-14 11:45:55 +02:00
int ret ;
2020-09-02 00:09:34 +02:00
if ( ! smi - > ops - > is_vlan_valid ( smi , vid ) )
return - EINVAL ;
2020-09-02 00:09:35 +02:00
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc ( smi , vid , & vlanmc ) ;
if ( ret < 0 )
return ret ;
mc = ret ;
2018-07-14 11:45:55 +02:00
2020-09-02 00:09:35 +02:00
ret = smi - > ops - > set_mc_index ( smi , port , mc ) ;
if ( ret ) {
dev_err ( smi - > dev , " set PVID: failed to set MC index %d for port %d \n " ,
mc , port ) ;
return ret ;
2018-07-14 11:45:55 +02:00
}
2020-09-02 00:09:35 +02:00
dev_dbg ( smi - > dev , " set PVID: the PVID for port %d set to %d using existing MC index %d \n " ,
port , vid , mc ) ;
2018-07-14 11:45:55 +02:00
2020-09-02 00:09:35 +02:00
return 0 ;
2018-07-14 11:45:55 +02:00
}
EXPORT_SYMBOL_GPL ( rtl8366_set_pvid ) ;
int rtl8366_enable_vlan4k ( struct realtek_smi * smi , bool enable )
{
int ret ;
/* To enable 4k VLAN, ordinary VLAN must be enabled first,
* but if we disable 4 k VLAN it is fine to leave ordinary
* VLAN enabled .
*/
if ( enable ) {
/* Make sure VLAN is ON */
ret = smi - > ops - > enable_vlan ( smi , true ) ;
if ( ret )
return ret ;
smi - > vlan_enabled = true ;
}
ret = smi - > ops - > enable_vlan4k ( smi , enable ) ;
if ( ret )
return ret ;
smi - > vlan4k_enabled = enable ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( rtl8366_enable_vlan4k ) ;
int rtl8366_enable_vlan ( struct realtek_smi * smi , bool enable )
{
int ret ;
ret = smi - > ops - > enable_vlan ( smi , enable ) ;
if ( ret )
return ret ;
smi - > vlan_enabled = enable ;
/* If we turn VLAN off, make sure that we turn off
* 4 k VLAN as well , if that happened to be on .
*/
if ( ! enable ) {
smi - > vlan4k_enabled = false ;
ret = smi - > ops - > enable_vlan4k ( smi , false ) ;
}
return ret ;
}
EXPORT_SYMBOL_GPL ( rtl8366_enable_vlan ) ;
int rtl8366_reset_vlan ( struct realtek_smi * smi )
{
struct rtl8366_vlan_mc vlanmc ;
int ret ;
int i ;
rtl8366_enable_vlan ( smi , false ) ;
rtl8366_enable_vlan4k ( smi , false ) ;
/* Clear the 16 VLAN member configurations */
vlanmc . vid = 0 ;
vlanmc . priority = 0 ;
vlanmc . member = 0 ;
vlanmc . untag = 0 ;
vlanmc . fid = 0 ;
for ( i = 0 ; i < smi - > num_vlan_mc ; i + + ) {
ret = smi - > ops - > set_vlan_mc ( smi , i , & vlanmc ) ;
if ( ret )
return ret ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( rtl8366_reset_vlan ) ;
int rtl8366_init_vlan ( struct realtek_smi * smi )
{
int port ;
int ret ;
ret = rtl8366_reset_vlan ( smi ) ;
if ( ret )
return ret ;
/* Loop over the available ports, for each port, associate
* it with the VLAN ( port + 1 )
*/
for ( port = 0 ; port < smi - > num_ports ; port + + ) {
u32 mask ;
if ( port = = smi - > cpu_port )
/* For the CPU port, make all ports members of this
* VLAN .
*/
2020-07-05 22:42:27 +02:00
mask = GENMASK ( ( int ) smi - > num_ports - 1 , 0 ) ;
2018-07-14 11:45:55 +02:00
else
/* For all other ports, enable itself plus the
* CPU port .
*/
mask = BIT ( port ) | BIT ( smi - > cpu_port ) ;
/* For each port, set the port as member of VLAN (port+1)
* and untagged , except for the CPU port : the CPU port ( 5 ) is
* member of VLAN 6 and so are ALL the other ports as well .
* Use filter 0 ( no filter ) .
*/
dev_info ( smi - > dev , " VLAN%d port mask for port %d, %08x \n " ,
( port + 1 ) , port , mask ) ;
ret = rtl8366_set_vlan ( smi , ( port + 1 ) , mask , mask , 0 ) ;
if ( ret )
return ret ;
dev_info ( smi - > dev , " VLAN%d port %d, PVID set to %d \n " ,
( port + 1 ) , port , ( port + 1 ) ) ;
ret = rtl8366_set_pvid ( smi , port , ( port + 1 ) ) ;
if ( ret )
return ret ;
}
return rtl8366_enable_vlan ( smi , true ) ;
}
EXPORT_SYMBOL_GPL ( rtl8366_init_vlan ) ;
2020-10-03 01:06:46 +03:00
int rtl8366_vlan_filtering ( struct dsa_switch * ds , int port , bool vlan_filtering ,
struct switchdev_trans * trans )
2018-07-14 11:45:55 +02:00
{
struct realtek_smi * smi = ds - > priv ;
struct rtl8366_vlan_4k vlan4k ;
int ret ;
2019-06-14 00:25:20 +02:00
/* Use VLAN nr port + 1 since VLAN0 is not valid */
2020-10-03 01:06:46 +03:00
if ( switchdev_trans_ph_prepare ( trans ) ) {
if ( ! smi - > ops - > is_vlan_valid ( smi , port + 1 ) )
return - EINVAL ;
return 0 ;
}
2018-07-14 11:45:55 +02:00
dev_info ( smi - > dev , " %s filtering on port %d \n " ,
vlan_filtering ? " enable " : " disable " ,
port ) ;
/* TODO:
* The hardware support filter ID ( FID ) 0. .7 , I have no clue how to
* support this in the driver when the callback only says on / off .
*/
2019-06-14 00:25:20 +02:00
ret = smi - > ops - > get_vlan_4k ( smi , port + 1 , & vlan4k ) ;
2018-07-14 11:45:55 +02:00
if ( ret )
return ret ;
/* Just set the filter to FID 1 for now then */
2019-06-14 00:25:20 +02:00
ret = rtl8366_set_vlan ( smi , port + 1 ,
2018-07-14 11:45:55 +02:00
vlan4k . member ,
vlan4k . untag ,
1 ) ;
if ( ret )
return ret ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( rtl8366_vlan_filtering ) ;
int rtl8366_vlan_prepare ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
struct realtek_smi * smi = ds - > priv ;
2019-10-01 16:28:43 +02:00
u16 vid ;
2018-07-14 11:45:55 +02:00
2019-10-01 16:28:43 +02:00
for ( vid = vlan - > vid_begin ; vid < vlan - > vid_end ; vid + + )
if ( ! smi - > ops - > is_vlan_valid ( smi , vid ) )
return - EINVAL ;
2018-07-14 11:45:55 +02:00
dev_info ( smi - > dev , " prepare VLANs %04x..%04x \n " ,
vlan - > vid_begin , vlan - > vid_end ) ;
/* Enable VLAN in the hardware
* FIXME : what ' s with this 4 k business ?
* Just rtl8366_enable_vlan ( ) seems inconclusive .
*/
2020-12-09 17:26:21 +08:00
return rtl8366_enable_vlan4k ( smi , true ) ;
2018-07-14 11:45:55 +02:00
}
EXPORT_SYMBOL_GPL ( rtl8366_vlan_prepare ) ;
void rtl8366_vlan_add ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
bool untagged = ! ! ( vlan - > flags & BRIDGE_VLAN_INFO_UNTAGGED ) ;
bool pvid = ! ! ( vlan - > flags & BRIDGE_VLAN_INFO_PVID ) ;
struct realtek_smi * smi = ds - > priv ;
u32 member = 0 ;
u32 untag = 0 ;
u16 vid ;
int ret ;
2019-10-01 16:28:43 +02:00
for ( vid = vlan - > vid_begin ; vid < vlan - > vid_end ; vid + + )
if ( ! smi - > ops - > is_vlan_valid ( smi , vid ) )
return ;
2018-07-14 11:45:55 +02:00
2020-09-02 00:09:35 +02:00
dev_info ( smi - > dev , " add VLAN %d on port %d, %s, %s \n " ,
vlan - > vid_begin ,
2018-07-14 11:45:55 +02:00
port ,
untagged ? " untagged " : " tagged " ,
pvid ? " PVID " : " no PVID " ) ;
if ( dsa_is_dsa_port ( ds , port ) | | dsa_is_cpu_port ( ds , port ) )
dev_err ( smi - > dev , " port is DSA or CPU port \n " ) ;
2020-07-27 01:34:40 +02:00
for ( vid = vlan - > vid_begin ; vid < = vlan - > vid_end ; vid + + ) {
2018-07-14 11:45:55 +02:00
member | = BIT ( port ) ;
if ( untagged )
untag | = BIT ( port ) ;
2020-07-27 01:34:40 +02:00
ret = rtl8366_set_vlan ( smi , vid , member , untag , 0 ) ;
if ( ret )
dev_err ( smi - > dev ,
" failed to set up VLAN %04x " ,
vid ) ;
2020-09-02 00:09:35 +02:00
2020-09-19 00:29:54 +02:00
if ( ! pvid )
continue ;
2020-09-02 00:09:35 +02:00
ret = rtl8366_set_pvid ( smi , port , vid ) ;
if ( ret )
dev_err ( smi - > dev ,
" failed to set PVID on port %d to VLAN %04x " ,
port , vid ) ;
if ( ! ret )
dev_dbg ( smi - > dev , " VLAN add: added VLAN %d with PVID on port %d \n " ,
vid , port ) ;
2020-07-27 01:34:40 +02:00
}
2018-07-14 11:45:55 +02:00
}
EXPORT_SYMBOL_GPL ( rtl8366_vlan_add ) ;
int rtl8366_vlan_del ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
struct realtek_smi * smi = ds - > priv ;
u16 vid ;
int ret ;
dev_info ( smi - > dev , " del VLAN on port %d \n " , port ) ;
for ( vid = vlan - > vid_begin ; vid < = vlan - > vid_end ; + + vid ) {
int i ;
dev_info ( smi - > dev , " del VLAN %04x \n " , vid ) ;
for ( i = 0 ; i < smi - > num_vlan_mc ; i + + ) {
struct rtl8366_vlan_mc vlanmc ;
ret = smi - > ops - > get_vlan_mc ( smi , i , & vlanmc ) ;
if ( ret )
return ret ;
if ( vid = = vlanmc . vid ) {
2020-09-05 12:32:33 +02:00
/* Remove this port from the VLAN */
vlanmc . member & = ~ BIT ( port ) ;
vlanmc . untag & = ~ BIT ( port ) ;
/*
* If no ports are members of this VLAN
* anymore then clear the whole member
* config so it can be reused .
*/
if ( ! vlanmc . member & & vlanmc . untag ) {
vlanmc . vid = 0 ;
vlanmc . priority = 0 ;
vlanmc . fid = 0 ;
}
2018-07-14 11:45:55 +02:00
ret = smi - > ops - > set_vlan_mc ( smi , i , & vlanmc ) ;
if ( ret ) {
dev_err ( smi - > dev ,
" failed to remove VLAN %04x \n " ,
vid ) ;
return ret ;
}
break ;
}
}
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( rtl8366_vlan_del ) ;
void rtl8366_get_strings ( struct dsa_switch * ds , int port , u32 stringset ,
uint8_t * data )
{
struct realtek_smi * smi = ds - > priv ;
struct rtl8366_mib_counter * mib ;
int i ;
if ( port > = smi - > num_ports )
return ;
for ( i = 0 ; i < smi - > num_mib_counters ; i + + ) {
mib = & smi - > mib_counters [ i ] ;
strncpy ( data + i * ETH_GSTRING_LEN ,
mib - > name , ETH_GSTRING_LEN ) ;
}
}
EXPORT_SYMBOL_GPL ( rtl8366_get_strings ) ;
int rtl8366_get_sset_count ( struct dsa_switch * ds , int port , int sset )
{
struct realtek_smi * smi = ds - > priv ;
/* We only support SS_STATS */
if ( sset ! = ETH_SS_STATS )
return 0 ;
if ( port > = smi - > num_ports )
return - EINVAL ;
return smi - > num_mib_counters ;
}
EXPORT_SYMBOL_GPL ( rtl8366_get_sset_count ) ;
void rtl8366_get_ethtool_stats ( struct dsa_switch * ds , int port , uint64_t * data )
{
struct realtek_smi * smi = ds - > priv ;
int i ;
int ret ;
if ( port > = smi - > num_ports )
return ;
for ( i = 0 ; i < smi - > num_mib_counters ; i + + ) {
struct rtl8366_mib_counter * mib ;
u64 mibvalue = 0 ;
mib = & smi - > mib_counters [ i ] ;
ret = smi - > ops - > get_mib_counter ( smi , port , mib , & mibvalue ) ;
if ( ret ) {
dev_err ( smi - > dev , " error reading MIB counter %s \n " ,
mib - > name ) ;
}
data [ i ] = mibvalue ;
}
}
EXPORT_SYMBOL_GPL ( rtl8366_get_ethtool_stats ) ;