2018-11-20 15:55:05 -08:00
// SPDX-License-Identifier: GPL-2.0
2017-05-31 20:19:19 +00:00
/*
* Microchip switch driver main logic
*
2019-02-22 16:36:47 -08:00
* Copyright ( C ) 2017 - 2019 Microchip Technology Inc .
2017-05-31 20:19:19 +00:00
*/
# include <linux/delay.h>
# include <linux/export.h>
2018-12-10 14:43:06 +01:00
# include <linux/gpio/consumer.h>
2017-05-31 20:19:19 +00:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_data/microchip-ksz.h>
# include <linux/phy.h>
# include <linux/etherdevice.h>
# include <linux/if_bridge.h>
2018-11-20 15:55:09 -08:00
# include <linux/of_net.h>
2017-05-31 20:19:19 +00:00
# include <net/dsa.h>
# include <net/switchdev.h>
# include "ksz_priv.h"
2019-02-22 16:36:51 -08:00
void ksz_port_cleanup ( struct ksz_device * dev , int port )
{
/* Common code for port cleanup. */
mutex_lock ( & dev - > dev_mutex ) ;
dev - > on_ports & = ~ ( 1 < < port ) ;
dev - > live_ports & = ~ ( 1 < < port ) ;
mutex_unlock ( & dev - > dev_mutex ) ;
}
EXPORT_SYMBOL_GPL ( ksz_port_cleanup ) ;
2018-11-20 15:55:09 -08:00
void ksz_update_port_member ( struct ksz_device * dev , int port )
2017-05-31 20:19:19 +00:00
{
2018-11-20 15:55:09 -08:00
struct ksz_port * p ;
2017-05-31 20:19:19 +00:00
int i ;
2018-11-20 15:55:09 -08:00
for ( i = 0 ; i < dev - > port_cnt ; i + + ) {
if ( i = = port | | i = = dev - > cpu_port )
continue ;
p = & dev - > ports [ i ] ;
if ( ! ( dev - > member & ( 1 < < i ) ) )
continue ;
/* Port is a member of the bridge and is forwarding. */
if ( p - > stp_state = = BR_STATE_FORWARDING & &
p - > member ! = dev - > member )
dev - > dev_ops - > cfg_port_member ( dev , i , dev - > member ) ;
2017-05-31 20:19:19 +00:00
}
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_update_port_member ) ;
2017-05-31 20:19:19 +00:00
2019-02-22 16:36:48 -08:00
static void port_r_cnt ( struct ksz_device * dev , int port )
{
struct ksz_port_mib * mib = & dev - > ports [ port ] . mib ;
u64 * dropped ;
/* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
while ( mib - > cnt_ptr < dev - > reg_mib_cnt ) {
dev - > dev_ops - > r_mib_cnt ( dev , port , mib - > cnt_ptr ,
& mib - > counters [ mib - > cnt_ptr ] ) ;
+ + mib - > cnt_ptr ;
}
/* last one in storage */
dropped = & mib - > counters [ dev - > mib_cnt ] ;
/* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
while ( mib - > cnt_ptr < dev - > mib_cnt ) {
dev - > dev_ops - > r_mib_pkt ( dev , port , mib - > cnt_ptr ,
dropped , & mib - > counters [ mib - > cnt_ptr ] ) ;
+ + mib - > cnt_ptr ;
}
mib - > cnt_ptr = 0 ;
}
static void ksz_mib_read_work ( struct work_struct * work )
{
struct ksz_device * dev = container_of ( work , struct ksz_device ,
mib_read ) ;
struct ksz_port_mib * mib ;
struct ksz_port * p ;
int i ;
for ( i = 0 ; i < dev - > mib_port_cnt ; i + + ) {
p = & dev - > ports [ i ] ;
mib = & p - > mib ;
mutex_lock ( & mib - > cnt_mutex ) ;
/* Only read MIB counters when the port is told to do.
* If not , read only dropped counters when link is not up .
*/
if ( ! p - > read ) {
const struct dsa_port * dp = dsa_to_port ( dev - > ds , i ) ;
if ( ! netif_carrier_ok ( dp - > slave ) )
mib - > cnt_ptr = dev - > reg_mib_cnt ;
}
port_r_cnt ( dev , i ) ;
p - > read = false ;
mutex_unlock ( & mib - > cnt_mutex ) ;
}
}
static void mib_monitor ( struct timer_list * t )
{
struct ksz_device * dev = from_timer ( dev , t , mib_read_timer ) ;
mod_timer ( & dev - > mib_read_timer , jiffies + dev - > mib_read_interval ) ;
schedule_work ( & dev - > mib_read ) ;
}
void ksz_init_mib_timer ( struct ksz_device * dev )
{
int i ;
/* Read MIB counters every 30 seconds to avoid overflow. */
dev - > mib_read_interval = msecs_to_jiffies ( 30000 ) ;
INIT_WORK ( & dev - > mib_read , ksz_mib_read_work ) ;
timer_setup ( & dev - > mib_read_timer , mib_monitor , 0 ) ;
for ( i = 0 ; i < dev - > mib_port_cnt ; i + + )
dev - > dev_ops - > port_init_cnt ( dev , i ) ;
/* Start the timer 2 seconds later. */
dev - > mib_read_timer . expires = jiffies + msecs_to_jiffies ( 2000 ) ;
add_timer ( & dev - > mib_read_timer ) ;
}
EXPORT_SYMBOL_GPL ( ksz_init_mib_timer ) ;
2018-11-20 15:55:09 -08:00
int ksz_phy_read16 ( struct dsa_switch * ds , int addr , int reg )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
u16 val = 0xffff ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
dev - > dev_ops - > r_phy ( dev , addr , reg , & val ) ;
2017-05-31 20:19:19 +00:00
return val ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_phy_read16 ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_phy_write16 ( struct dsa_switch * ds , int addr , int reg , u16 val )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
dev - > dev_ops - > w_phy ( dev , addr , reg , val ) ;
2017-05-31 20:19:19 +00:00
return 0 ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_phy_write16 ) ;
2017-05-31 20:19:19 +00:00
2019-02-22 16:36:49 -08:00
void ksz_adjust_link ( struct dsa_switch * ds , int port ,
struct phy_device * phydev )
{
struct ksz_device * dev = ds - > priv ;
struct ksz_port * p = & dev - > ports [ port ] ;
/* Read all MIB counters when the link is going down. */
if ( ! phydev - > link ) {
p - > read = true ;
schedule_work ( & dev - > mib_read ) ;
}
2019-02-22 16:36:51 -08:00
mutex_lock ( & dev - > dev_mutex ) ;
if ( ! phydev - > link )
dev - > live_ports & = ~ ( 1 < < port ) ;
else
/* Remember which port is connected and active. */
dev - > live_ports | = ( 1 < < port ) & dev - > on_ports ;
mutex_unlock ( & dev - > dev_mutex ) ;
2019-02-22 16:36:49 -08:00
}
EXPORT_SYMBOL_GPL ( ksz_adjust_link ) ;
2018-11-20 15:55:09 -08:00
int ksz_sset_count ( struct dsa_switch * ds , int port , int sset )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-04-25 12:12:50 -07:00
if ( sset ! = ETH_SS_STATS )
return 0 ;
2018-11-20 15:55:09 -08:00
return dev - > mib_cnt ;
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_sset_count ) ;
2017-05-31 20:19:19 +00:00
2019-02-22 16:36:48 -08:00
void ksz_get_ethtool_stats ( struct dsa_switch * ds , int port , uint64_t * buf )
{
const struct dsa_port * dp = dsa_to_port ( ds , port ) ;
struct ksz_device * dev = ds - > priv ;
struct ksz_port_mib * mib ;
mib = & dev - > ports [ port ] . mib ;
mutex_lock ( & mib - > cnt_mutex ) ;
/* Only read dropped counters if no link. */
if ( ! netif_carrier_ok ( dp - > slave ) )
mib - > cnt_ptr = dev - > reg_mib_cnt ;
port_r_cnt ( dev , port ) ;
memcpy ( buf , mib - > counters , dev - > mib_cnt * sizeof ( u64 ) ) ;
mutex_unlock ( & mib - > cnt_mutex ) ;
}
EXPORT_SYMBOL_GPL ( ksz_get_ethtool_stats ) ;
2018-11-20 15:55:09 -08:00
int ksz_port_bridge_join ( struct dsa_switch * ds , int port ,
struct net_device * br )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2019-02-22 16:36:51 -08:00
mutex_lock ( & dev - > dev_mutex ) ;
2018-11-20 15:55:09 -08:00
dev - > br_member | = ( 1 < < port ) ;
2019-02-22 16:36:51 -08:00
mutex_unlock ( & dev - > dev_mutex ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
/* port_stp_state_set() will be called after to put the port in
* appropriate state so there is no need to do anything .
*/
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
return 0 ;
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_bridge_join ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
void ksz_port_bridge_leave ( struct dsa_switch * ds , int port ,
struct net_device * br )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2019-02-22 16:36:51 -08:00
mutex_lock ( & dev - > dev_mutex ) ;
2018-11-20 15:55:09 -08:00
dev - > br_member & = ~ ( 1 < < port ) ;
dev - > member & = ~ ( 1 < < port ) ;
2019-02-22 16:36:51 -08:00
mutex_unlock ( & dev - > dev_mutex ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
/* port_stp_state_set() will be called after to put the port in
* forwarding state so there is no need to do anything .
*/
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_bridge_leave ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
void ksz_port_fast_age ( struct dsa_switch * ds , int port )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
dev - > dev_ops - > flush_dyn_mac_table ( dev , port ) ;
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_fast_age ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_port_vlan_prepare ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
2017-05-31 20:19:19 +00:00
{
/* nothing needed */
return 0 ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_vlan_prepare ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_port_fdb_dump ( struct dsa_switch * ds , int port , dsa_fdb_dump_cb_t * cb ,
void * data )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2017-08-06 16:15:40 +03:00
int ret = 0 ;
2018-11-20 15:55:09 -08:00
u16 i = 0 ;
u16 entries = 0 ;
u8 timestamp = 0 ;
u8 fid ;
u8 member ;
2017-05-31 20:19:19 +00:00
struct alu_struct alu ;
do {
2018-11-20 15:55:09 -08:00
alu . is_static = false ;
ret = dev - > dev_ops - > r_dyn_mac_table ( dev , i , alu . mac , & fid ,
& member , & timestamp ,
& entries ) ;
if ( ! ret & & ( member & BIT ( port ) ) ) {
2017-08-06 16:15:49 +03:00
ret = cb ( alu . mac , alu . fid , alu . is_static , data ) ;
2017-05-31 20:19:19 +00:00
if ( ret )
2018-11-20 15:55:09 -08:00
break ;
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
i + + ;
} while ( i < entries ) ;
if ( i > = entries )
ret = 0 ;
2017-05-31 20:19:19 +00:00
return ret ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_fdb_dump ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_port_mdb_prepare ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_mdb * mdb )
2017-05-31 20:19:19 +00:00
{
/* nothing to do */
return 0 ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_mdb_prepare ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
void ksz_port_mdb_add ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_mdb * mdb )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
struct alu_struct alu ;
2017-05-31 20:19:19 +00:00
int index ;
2018-11-20 15:55:09 -08:00
int empty = 0 ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
alu . port_forward = 0 ;
2017-05-31 20:19:19 +00:00
for ( index = 0 ; index < dev - > num_statics ; index + + ) {
2018-11-20 15:55:09 -08:00
if ( ! dev - > dev_ops - > r_sta_mac_table ( dev , index , & alu ) ) {
/* Found one already in static MAC table. */
if ( ! memcmp ( alu . mac , mdb - > addr , ETH_ALEN ) & &
alu . fid = = mdb - > vid )
2017-05-31 20:19:19 +00:00
break ;
2018-11-20 15:55:09 -08:00
/* Remember the first empty entry. */
} else if ( ! empty ) {
empty = index + 1 ;
2017-05-31 20:19:19 +00:00
}
}
/* no available entry */
2018-11-20 15:55:09 -08:00
if ( index = = dev - > num_statics & & ! empty )
return ;
2017-05-31 20:19:19 +00:00
/* add entry */
2018-11-20 15:55:09 -08:00
if ( index = = dev - > num_statics ) {
index = empty - 1 ;
memset ( & alu , 0 , sizeof ( alu ) ) ;
memcpy ( alu . mac , mdb - > addr , ETH_ALEN ) ;
alu . is_static = true ;
}
alu . port_forward | = BIT ( port ) ;
if ( mdb - > vid ) {
alu . is_use_fid = true ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
/* Need a way to map VID to FID. */
alu . fid = mdb - > vid ;
}
dev - > dev_ops - > w_sta_mac_table ( dev , index , & alu ) ;
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_mdb_add ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_port_mdb_del ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_mdb * mdb )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
struct alu_struct alu ;
2017-05-31 20:19:19 +00:00
int index ;
int ret = 0 ;
for ( index = 0 ; index < dev - > num_statics ; index + + ) {
2018-11-20 15:55:09 -08:00
if ( ! dev - > dev_ops - > r_sta_mac_table ( dev , index , & alu ) ) {
/* Found one already in static MAC table. */
if ( ! memcmp ( alu . mac , mdb - > addr , ETH_ALEN ) & &
alu . fid = = mdb - > vid )
2017-05-31 20:19:19 +00:00
break ;
}
}
/* no available entry */
2018-11-20 15:55:09 -08:00
if ( index = = dev - > num_statics )
2017-05-31 20:19:19 +00:00
goto exit ;
/* clear port */
2018-11-20 15:55:09 -08:00
alu . port_forward & = ~ BIT ( port ) ;
if ( ! alu . port_forward )
alu . is_static = false ;
dev - > dev_ops - > w_sta_mac_table ( dev , index , & alu ) ;
2017-05-31 20:19:19 +00:00
exit :
return ret ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_port_mdb_del ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
int ksz_enable_port ( struct dsa_switch * ds , int port , struct phy_device * phy )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
/* setup slave port */
dev - > dev_ops - > port_setup ( dev , port , false ) ;
2019-02-22 16:36:47 -08:00
dev - > dev_ops - > phy_setup ( dev , port , phy ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
/* port_stp_state_set() will be called after to enable the port so
* there is no need to do anything .
*/
2017-05-31 20:19:19 +00:00
return 0 ;
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_enable_port ) ;
2017-05-31 20:19:19 +00:00
2019-02-24 20:44:43 +01:00
void ksz_disable_port ( struct dsa_switch * ds , int port )
2017-05-31 20:19:19 +00:00
{
struct ksz_device * dev = ds - > priv ;
2018-11-20 15:55:09 -08:00
dev - > on_ports & = ~ ( 1 < < port ) ;
dev - > live_ports & = ~ ( 1 < < port ) ;
2017-05-31 20:19:19 +00:00
2018-11-20 15:55:09 -08:00
/* port_stp_state_set() will be called after to disable the port so
* there is no need to do anything .
*/
2017-05-31 20:19:19 +00:00
}
2018-11-20 15:55:09 -08:00
EXPORT_SYMBOL_GPL ( ksz_disable_port ) ;
2017-05-31 20:19:19 +00:00
struct ksz_device * ksz_switch_alloc ( struct device * base ,
const struct ksz_io_ops * ops ,
void * priv )
{
struct dsa_switch * ds ;
struct ksz_device * swdev ;
ds = dsa_switch_alloc ( base , DSA_MAX_PORTS ) ;
if ( ! ds )
return NULL ;
swdev = devm_kzalloc ( base , sizeof ( * swdev ) , GFP_KERNEL ) ;
if ( ! swdev )
return NULL ;
ds - > priv = swdev ;
swdev - > dev = base ;
swdev - > ds = ds ;
swdev - > priv = priv ;
swdev - > ops = ops ;
return swdev ;
}
EXPORT_SYMBOL ( ksz_switch_alloc ) ;
2018-11-20 15:55:09 -08:00
int ksz_switch_register ( struct ksz_device * dev ,
const struct ksz_dev_ops * ops )
2017-05-31 20:19:19 +00:00
{
int ret ;
if ( dev - > pdata )
dev - > chip_id = dev - > pdata - > chip_id ;
2018-12-10 14:43:06 +01:00
dev - > reset_gpio = devm_gpiod_get_optional ( dev - > dev , " reset " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( dev - > reset_gpio ) )
return PTR_ERR ( dev - > reset_gpio ) ;
if ( dev - > reset_gpio ) {
gpiod_set_value ( dev - > reset_gpio , 1 ) ;
mdelay ( 10 ) ;
gpiod_set_value ( dev - > reset_gpio , 0 ) ;
}
2019-02-22 16:36:51 -08:00
mutex_init ( & dev - > dev_mutex ) ;
2018-11-02 19:23:41 -07:00
mutex_init ( & dev - > reg_mutex ) ;
mutex_init ( & dev - > stats_mutex ) ;
mutex_init ( & dev - > alu_mutex ) ;
mutex_init ( & dev - > vlan_mutex ) ;
2018-11-20 15:55:09 -08:00
dev - > dev_ops = ops ;
if ( dev - > dev_ops - > detect ( dev ) )
2017-05-31 20:19:19 +00:00
return - EINVAL ;
2018-11-20 15:55:09 -08:00
ret = dev - > dev_ops - > init ( dev ) ;
2017-05-31 20:19:19 +00:00
if ( ret )
return ret ;
2019-02-28 19:57:24 -08:00
/* Host port interface will be self detected, or specifically set in
* device tree .
*/
2018-11-20 15:55:09 -08:00
if ( dev - > dev - > of_node ) {
ret = of_get_phy_mode ( dev - > dev - > of_node ) ;
if ( ret > = 0 )
dev - > interface = ret ;
}
ret = dsa_register_switch ( dev - > ds ) ;
if ( ret ) {
dev - > dev_ops - > exit ( dev ) ;
return ret ;
}
return 0 ;
2017-05-31 20:19:19 +00:00
}
EXPORT_SYMBOL ( ksz_switch_register ) ;
void ksz_switch_remove ( struct ksz_device * dev )
{
2019-02-22 16:36:48 -08:00
/* timer started */
if ( dev - > mib_read_timer . expires ) {
del_timer_sync ( & dev - > mib_read_timer ) ;
flush_work ( & dev - > mib_read ) ;
}
2018-11-20 15:55:09 -08:00
dev - > dev_ops - > exit ( dev ) ;
2017-05-31 20:19:19 +00:00
dsa_unregister_switch ( dev - > ds ) ;
2018-12-10 14:43:06 +01:00
if ( dev - > reset_gpio )
gpiod_set_value ( dev - > reset_gpio , 1 ) ;
2017-05-31 20:19:19 +00:00
}
EXPORT_SYMBOL ( ksz_switch_remove ) ;
MODULE_AUTHOR ( " Woojung Huh <Woojung.Huh@microchip.com> " ) ;
MODULE_DESCRIPTION ( " Microchip KSZ Series Switch DSA Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;