2020-11-03 08:10:56 +01:00
// SPDX-License-Identifier: (GPL-2.0 or MIT)
/*
* DSA driver for :
* Hirschmann Hellcreek TSN switch .
*
* Copyright ( C ) 2019 , 2020 Linutronix GmbH
* Author Kurt Kanzenbach < kurt @ linutronix . de >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_mdio.h>
# include <linux/platform_device.h>
# include <linux/bitops.h>
# include <linux/if_bridge.h>
# include <linux/if_vlan.h>
# include <linux/etherdevice.h>
# include <linux/random.h>
# include <linux/iopoll.h>
# include <linux/mutex.h>
# include <linux/delay.h>
# include <net/dsa.h>
# include "hellcreek.h"
2020-11-03 08:10:57 +01:00
# include "hellcreek_ptp.h"
2020-11-03 08:10:58 +01:00
# include "hellcreek_hwtstamp.h"
2020-11-03 08:10:56 +01:00
static const struct hellcreek_counter hellcreek_counter [ ] = {
{ 0x00 , " RxFiltered " , } ,
{ 0x01 , " RxOctets1k " , } ,
{ 0x02 , " RxVTAG " , } ,
{ 0x03 , " RxL2BAD " , } ,
{ 0x04 , " RxOverloadDrop " , } ,
{ 0x05 , " RxUC " , } ,
{ 0x06 , " RxMC " , } ,
{ 0x07 , " RxBC " , } ,
{ 0x08 , " RxRS<64 " , } ,
{ 0x09 , " RxRS64 " , } ,
{ 0x0a , " RxRS65_127 " , } ,
{ 0x0b , " RxRS128_255 " , } ,
{ 0x0c , " RxRS256_511 " , } ,
{ 0x0d , " RxRS512_1023 " , } ,
{ 0x0e , " RxRS1024_1518 " , } ,
{ 0x0f , " RxRS>1518 " , } ,
{ 0x10 , " TxTailDropQueue0 " , } ,
{ 0x11 , " TxTailDropQueue1 " , } ,
{ 0x12 , " TxTailDropQueue2 " , } ,
{ 0x13 , " TxTailDropQueue3 " , } ,
{ 0x14 , " TxTailDropQueue4 " , } ,
{ 0x15 , " TxTailDropQueue5 " , } ,
{ 0x16 , " TxTailDropQueue6 " , } ,
{ 0x17 , " TxTailDropQueue7 " , } ,
{ 0x18 , " RxTrafficClass0 " , } ,
{ 0x19 , " RxTrafficClass1 " , } ,
{ 0x1a , " RxTrafficClass2 " , } ,
{ 0x1b , " RxTrafficClass3 " , } ,
{ 0x1c , " RxTrafficClass4 " , } ,
{ 0x1d , " RxTrafficClass5 " , } ,
{ 0x1e , " RxTrafficClass6 " , } ,
{ 0x1f , " RxTrafficClass7 " , } ,
{ 0x21 , " TxOctets1k " , } ,
{ 0x22 , " TxVTAG " , } ,
{ 0x23 , " TxL2BAD " , } ,
{ 0x25 , " TxUC " , } ,
{ 0x26 , " TxMC " , } ,
{ 0x27 , " TxBC " , } ,
{ 0x28 , " TxTS<64 " , } ,
{ 0x29 , " TxTS64 " , } ,
{ 0x2a , " TxTS65_127 " , } ,
{ 0x2b , " TxTS128_255 " , } ,
{ 0x2c , " TxTS256_511 " , } ,
{ 0x2d , " TxTS512_1023 " , } ,
{ 0x2e , " TxTS1024_1518 " , } ,
{ 0x2f , " TxTS>1518 " , } ,
{ 0x30 , " TxTrafficClassOverrun0 " , } ,
{ 0x31 , " TxTrafficClassOverrun1 " , } ,
{ 0x32 , " TxTrafficClassOverrun2 " , } ,
{ 0x33 , " TxTrafficClassOverrun3 " , } ,
{ 0x34 , " TxTrafficClassOverrun4 " , } ,
{ 0x35 , " TxTrafficClassOverrun5 " , } ,
{ 0x36 , " TxTrafficClassOverrun6 " , } ,
{ 0x37 , " TxTrafficClassOverrun7 " , } ,
{ 0x38 , " TxTrafficClass0 " , } ,
{ 0x39 , " TxTrafficClass1 " , } ,
{ 0x3a , " TxTrafficClass2 " , } ,
{ 0x3b , " TxTrafficClass3 " , } ,
{ 0x3c , " TxTrafficClass4 " , } ,
{ 0x3d , " TxTrafficClass5 " , } ,
{ 0x3e , " TxTrafficClass6 " , } ,
{ 0x3f , " TxTrafficClass7 " , } ,
} ;
static u16 hellcreek_read ( struct hellcreek * hellcreek , unsigned int offset )
{
return readw ( hellcreek - > base + offset ) ;
}
static u16 hellcreek_read_ctrl ( struct hellcreek * hellcreek )
{
return readw ( hellcreek - > base + HR_CTRL_C ) ;
}
static u16 hellcreek_read_stat ( struct hellcreek * hellcreek )
{
return readw ( hellcreek - > base + HR_SWSTAT ) ;
}
static void hellcreek_write ( struct hellcreek * hellcreek , u16 data ,
unsigned int offset )
{
writew ( data , hellcreek - > base + offset ) ;
}
static void hellcreek_select_port ( struct hellcreek * hellcreek , int port )
{
u16 val = port < < HR_PSEL_PTWSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_PSEL ) ;
}
static void hellcreek_select_prio ( struct hellcreek * hellcreek , int prio )
{
u16 val = prio < < HR_PSEL_PRTCWSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_PSEL ) ;
}
static void hellcreek_select_counter ( struct hellcreek * hellcreek , int counter )
{
u16 val = counter < < HR_CSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_CSEL ) ;
/* Data sheet states to wait at least 20 internal clock cycles */
ndelay ( 200 ) ;
}
static void hellcreek_select_vlan ( struct hellcreek * hellcreek , int vid ,
bool pvid )
{
u16 val = 0 ;
/* Set pvid bit first */
if ( pvid )
val | = HR_VIDCFG_PVID ;
hellcreek_write ( hellcreek , val , HR_VIDCFG ) ;
/* Set vlan */
val | = vid < < HR_VIDCFG_VID_SHIFT ;
hellcreek_write ( hellcreek , val , HR_VIDCFG ) ;
}
static int hellcreek_wait_until_ready ( struct hellcreek * hellcreek )
{
u16 val ;
/* Wait up to 1ms, although 3 us should be enough */
return readx_poll_timeout ( hellcreek_read_ctrl , hellcreek ,
val , val & HR_CTRL_C_READY ,
3 , 1000 ) ;
}
static int hellcreek_wait_until_transitioned ( struct hellcreek * hellcreek )
{
u16 val ;
return readx_poll_timeout_atomic ( hellcreek_read_ctrl , hellcreek ,
val , ! ( val & HR_CTRL_C_TRANSITION ) ,
1 , 1000 ) ;
}
static int hellcreek_wait_fdb_ready ( struct hellcreek * hellcreek )
{
u16 val ;
return readx_poll_timeout_atomic ( hellcreek_read_stat , hellcreek ,
val , ! ( val & HR_SWSTAT_BUSY ) ,
1 , 1000 ) ;
}
static int hellcreek_detect ( struct hellcreek * hellcreek )
{
u16 id , rel_low , rel_high , date_low , date_high , tgd_ver ;
u8 tgd_maj , tgd_min ;
u32 rel , date ;
id = hellcreek_read ( hellcreek , HR_MODID_C ) ;
rel_low = hellcreek_read ( hellcreek , HR_REL_L_C ) ;
rel_high = hellcreek_read ( hellcreek , HR_REL_H_C ) ;
date_low = hellcreek_read ( hellcreek , HR_BLD_L_C ) ;
date_high = hellcreek_read ( hellcreek , HR_BLD_H_C ) ;
tgd_ver = hellcreek_read ( hellcreek , TR_TGDVER ) ;
if ( id ! = hellcreek - > pdata - > module_id )
return - ENODEV ;
rel = rel_low | ( rel_high < < 16 ) ;
date = date_low | ( date_high < < 16 ) ;
tgd_maj = ( tgd_ver & TR_TGDVER_REV_MAJ_MASK ) > > TR_TGDVER_REV_MAJ_SHIFT ;
tgd_min = ( tgd_ver & TR_TGDVER_REV_MIN_MASK ) > > TR_TGDVER_REV_MIN_SHIFT ;
dev_info ( hellcreek - > dev , " Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x \n " ,
id , rel , date , tgd_maj , tgd_min ) ;
return 0 ;
}
static void hellcreek_feature_detect ( struct hellcreek * hellcreek )
{
u16 features ;
features = hellcreek_read ( hellcreek , HR_FEABITS0 ) ;
/* Currently we only detect the size of the FDB table */
hellcreek - > fdb_entries = ( ( features & HR_FEABITS0_FDBBINS_MASK ) > >
HR_FEABITS0_FDBBINS_SHIFT ) * 32 ;
dev_info ( hellcreek - > dev , " Feature detect: FDB entries=%zu \n " ,
hellcreek - > fdb_entries ) ;
}
static enum dsa_tag_protocol hellcreek_get_tag_protocol ( struct dsa_switch * ds ,
int port ,
enum dsa_tag_protocol mp )
{
return DSA_TAG_PROTO_HELLCREEK ;
}
static int hellcreek_port_enable ( struct dsa_switch * ds , int port ,
struct phy_device * phy )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Enable port %d \n " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
val | = HR_PTCFG_ADMIN_EN ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return 0 ;
}
static void hellcreek_port_disable ( struct dsa_switch * ds , int port )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Disable port %d \n " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
val & = ~ HR_PTCFG_ADMIN_EN ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_get_strings ( struct dsa_switch * ds , int port ,
u32 stringset , uint8_t * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( hellcreek_counter ) ; + + i ) {
const struct hellcreek_counter * counter = & hellcreek_counter [ i ] ;
strlcpy ( data + i * ETH_GSTRING_LEN ,
counter - > name , ETH_GSTRING_LEN ) ;
}
}
static int hellcreek_get_sset_count ( struct dsa_switch * ds , int port , int sset )
{
if ( sset ! = ETH_SS_STATS )
return 0 ;
return ARRAY_SIZE ( hellcreek_counter ) ;
}
static void hellcreek_get_ethtool_stats ( struct dsa_switch * ds , int port ,
uint64_t * data )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
int i ;
hellcreek_port = & hellcreek - > ports [ port ] ;
for ( i = 0 ; i < ARRAY_SIZE ( hellcreek_counter ) ; + + i ) {
const struct hellcreek_counter * counter = & hellcreek_counter [ i ] ;
u8 offset = counter - > offset + port * 64 ;
u16 high , low ;
u64 value = 0 ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_counter ( hellcreek , offset ) ;
/* The registers are locked internally by selecting the
* counter . So low and high can be read without reading high
* again .
*/
high = hellcreek_read ( hellcreek , HR_CRDH ) ;
low = hellcreek_read ( hellcreek , HR_CRDL ) ;
value = ( high < < 16 ) | low ;
hellcreek_port - > counter_values [ i ] + = value ;
data [ i ] = hellcreek_port - > counter_values [ i ] ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
}
static u16 hellcreek_private_vid ( int port )
{
return VLAN_N_VID - port + 1 ;
}
static int hellcreek_vlan_prepare ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
struct hellcreek * hellcreek = ds - > priv ;
int i ;
dev_dbg ( hellcreek - > dev , " VLAN prepare for port %d \n " , port ) ;
/* Restriction: Make sure that nobody uses the "private" VLANs. These
* VLANs are internally used by the driver to ensure port
* separation . Thus , they cannot be used by someone else .
*/
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
const u16 restricted_vid = hellcreek_private_vid ( i ) ;
u16 vid ;
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
for ( vid = vlan - > vid_begin ; vid < = vlan - > vid_end ; + + vid )
if ( vid = = restricted_vid )
return - EBUSY ;
}
return 0 ;
}
static void hellcreek_select_vlan_params ( struct hellcreek * hellcreek , int port ,
int * shift , int * mask )
{
switch ( port ) {
case 0 :
* shift = HR_VIDMBRCFG_P0MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P0MBR_MASK ;
break ;
case 1 :
* shift = HR_VIDMBRCFG_P1MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P1MBR_MASK ;
break ;
case 2 :
* shift = HR_VIDMBRCFG_P2MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P2MBR_MASK ;
break ;
case 3 :
* shift = HR_VIDMBRCFG_P3MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P3MBR_MASK ;
break ;
default :
* shift = * mask = 0 ;
dev_err ( hellcreek - > dev , " Unknown port %d selected! \n " , port ) ;
}
}
static void hellcreek_apply_vlan ( struct hellcreek * hellcreek , int port , u16 vid ,
bool pvid , bool untagged )
{
int shift , mask ;
u16 val ;
dev_dbg ( hellcreek - > dev , " Apply VLAN: port=%d vid=%u pvid=%d untagged=%d " ,
port , vid , pvid , untagged ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_select_vlan ( hellcreek , vid , pvid ) ;
/* Setup port vlan membership */
hellcreek_select_vlan_params ( hellcreek , port , & shift , & mask ) ;
val = hellcreek - > vidmbrcfg [ vid ] ;
val & = ~ mask ;
if ( untagged )
val | = HELLCREEK_VLAN_UNTAGGED_MEMBER < < shift ;
else
val | = HELLCREEK_VLAN_TAGGED_MEMBER < < shift ;
hellcreek_write ( hellcreek , val , HR_VIDMBRCFG ) ;
hellcreek - > vidmbrcfg [ vid ] = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_unapply_vlan ( struct hellcreek * hellcreek , int port ,
u16 vid )
{
int shift , mask ;
u16 val ;
dev_dbg ( hellcreek - > dev , " Unapply VLAN: port=%d vid=%u \n " , port , vid ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_vlan ( hellcreek , vid , 0 ) ;
/* Setup port vlan membership */
hellcreek_select_vlan_params ( hellcreek , port , & shift , & mask ) ;
val = hellcreek - > vidmbrcfg [ vid ] ;
val & = ~ mask ;
val | = HELLCREEK_VLAN_NO_MEMBER < < shift ;
hellcreek_write ( hellcreek , val , HR_VIDMBRCFG ) ;
hellcreek - > vidmbrcfg [ vid ] = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_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 hellcreek * hellcreek = ds - > priv ;
u16 vid ;
dev_dbg ( hellcreek - > dev , " Add VLANs (%d -- %d) on port %d, %s, %s \n " ,
vlan - > vid_begin , vlan - > vid_end , port ,
untagged ? " untagged " : " tagged " ,
pvid ? " PVID " : " no PVID " ) ;
for ( vid = vlan - > vid_begin ; vid < = vlan - > vid_end ; + + vid )
hellcreek_apply_vlan ( hellcreek , port , vid , pvid , untagged ) ;
}
static int hellcreek_vlan_del ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
struct hellcreek * hellcreek = ds - > priv ;
u16 vid ;
dev_dbg ( hellcreek - > dev , " Remove VLANs (%d -- %d) on port %d \n " ,
vlan - > vid_begin , vlan - > vid_end , port ) ;
for ( vid = vlan - > vid_begin ; vid < = vlan - > vid_end ; + + vid )
hellcreek_unapply_vlan ( hellcreek , port , vid ) ;
return 0 ;
}
static void hellcreek_port_stp_state_set ( struct dsa_switch * ds , int port ,
u8 state )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
const char * new_state ;
u16 val ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_port = & hellcreek - > ports [ port ] ;
val = hellcreek_port - > ptcfg ;
switch ( state ) {
case BR_STATE_DISABLED :
new_state = " DISABLED " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_BLOCKING :
new_state = " BLOCKING " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_LISTENING :
new_state = " LISTENING " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_LEARNING :
new_state = " LEARNING " ;
val | = HR_PTCFG_BLOCKED ;
val | = HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_FORWARDING :
new_state = " FORWARDING " ;
val & = ~ HR_PTCFG_BLOCKED ;
val | = HR_PTCFG_LEARNING_EN ;
break ;
default :
new_state = " UNKNOWN " ;
}
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
dev_dbg ( hellcreek - > dev , " Configured STP state for port %d: %s \n " ,
port , new_state ) ;
}
static void hellcreek_setup_ingressflt ( struct hellcreek * hellcreek , int port ,
bool enable )
{
struct hellcreek_port * hellcreek_port = & hellcreek - > ports [ port ] ;
u16 ptcfg ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ptcfg = hellcreek_port - > ptcfg ;
if ( enable )
ptcfg | = HR_PTCFG_INGRESSFLT ;
else
ptcfg & = ~ HR_PTCFG_INGRESSFLT ;
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
hellcreek_port - > ptcfg = ptcfg ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_setup_vlan_awareness ( struct hellcreek * hellcreek ,
bool enable )
{
u16 swcfg ;
mutex_lock ( & hellcreek - > reg_lock ) ;
swcfg = hellcreek - > swcfg ;
if ( enable )
swcfg | = HR_SWCFG_VLAN_UNAWARE ;
else
swcfg & = ~ HR_SWCFG_VLAN_UNAWARE ;
hellcreek_write ( hellcreek , swcfg , HR_SWCFG ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */
static void hellcreek_setup_vlan_membership ( struct dsa_switch * ds , int port ,
bool enabled )
{
const u16 vid = hellcreek_private_vid ( port ) ;
int upstream = dsa_upstream_port ( ds , port ) ;
struct hellcreek * hellcreek = ds - > priv ;
/* Apply vid to port as egress untagged and port vlan id */
if ( enabled )
hellcreek_apply_vlan ( hellcreek , port , vid , true , true ) ;
else
hellcreek_unapply_vlan ( hellcreek , port , vid ) ;
/* Apply vid to cpu port as well */
if ( enabled )
hellcreek_apply_vlan ( hellcreek , upstream , vid , false , true ) ;
else
hellcreek_unapply_vlan ( hellcreek , upstream , vid ) ;
}
static int hellcreek_port_bridge_join ( struct dsa_switch * ds , int port ,
struct net_device * br )
{
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " Port %d joins a bridge \n " , port ) ;
/* When joining a vlan_filtering bridge, keep the switch VLAN aware */
if ( ! ds - > vlan_filtering )
hellcreek_setup_vlan_awareness ( hellcreek , false ) ;
/* Drop private vlans */
hellcreek_setup_vlan_membership ( ds , port , false ) ;
return 0 ;
}
static void hellcreek_port_bridge_leave ( struct dsa_switch * ds , int port ,
struct net_device * br )
{
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " Port %d leaves a bridge \n " , port ) ;
/* Enable VLAN awareness */
hellcreek_setup_vlan_awareness ( hellcreek , true ) ;
/* Enable private vlans */
hellcreek_setup_vlan_membership ( ds , port , true ) ;
}
static int __hellcreek_fdb_add ( struct hellcreek * hellcreek ,
const struct hellcreek_fdb_entry * entry )
{
u16 meta = 0 ;
dev_dbg ( hellcreek - > dev , " Add static FDB entry: MAC=%pM, MASK=0x%02x, "
" OBT=%d, REPRIO_EN=%d, PRIO=%d \n " , entry - > mac , entry - > portmask ,
entry - > is_obt , entry - > reprio_en , entry - > reprio_tc ) ;
/* Add mac address */
hellcreek_write ( hellcreek , entry - > mac [ 1 ] | ( entry - > mac [ 0 ] < < 8 ) , HR_FDBWDH ) ;
hellcreek_write ( hellcreek , entry - > mac [ 3 ] | ( entry - > mac [ 2 ] < < 8 ) , HR_FDBWDM ) ;
hellcreek_write ( hellcreek , entry - > mac [ 5 ] | ( entry - > mac [ 4 ] < < 8 ) , HR_FDBWDL ) ;
/* Meta data */
meta | = entry - > portmask < < HR_FDBWRM0_PORTMASK_SHIFT ;
if ( entry - > is_obt )
meta | = HR_FDBWRM0_OBT ;
if ( entry - > reprio_en ) {
meta | = HR_FDBWRM0_REPRIO_EN ;
meta | = entry - > reprio_tc < < HR_FDBWRM0_REPRIO_TC_SHIFT ;
}
hellcreek_write ( hellcreek , meta , HR_FDBWRM0 ) ;
/* Commit */
hellcreek_write ( hellcreek , 0x00 , HR_FDBWRCMD ) ;
/* Wait until done */
return hellcreek_wait_fdb_ready ( hellcreek ) ;
}
static int __hellcreek_fdb_del ( struct hellcreek * hellcreek ,
const struct hellcreek_fdb_entry * entry )
{
dev_dbg ( hellcreek - > dev , " Delete FDB entry: MAC=%pM! \n " , entry - > mac ) ;
/* Delete by matching idx */
hellcreek_write ( hellcreek , entry - > idx | HR_FDBWRCMD_FDBDEL , HR_FDBWRCMD ) ;
/* Wait until done */
return hellcreek_wait_fdb_ready ( hellcreek ) ;
}
/* Retrieve the index of a FDB entry by mac address. Currently we search through
* the complete table in hardware . If that ' s too slow , we might have to cache
* the complete FDB table in software .
*/
static int hellcreek_fdb_get ( struct hellcreek * hellcreek ,
const unsigned char * dest ,
struct hellcreek_fdb_entry * entry )
{
size_t i ;
/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
* should reset the internal pointer . But , that doesn ' t work . The vendor
* suggested a subsequent write as workaround . Same for HR_FDBRDH below .
*/
hellcreek_read ( hellcreek , HR_FDBMAX ) ;
hellcreek_write ( hellcreek , 0x00 , HR_FDBMAX ) ;
/* We have to read the complete table, because the switch/driver might
* enter new entries anywhere .
*/
for ( i = 0 ; i < hellcreek - > fdb_entries ; + + i ) {
unsigned char addr [ ETH_ALEN ] ;
u16 meta , mac ;
meta = hellcreek_read ( hellcreek , HR_FDBMDRD ) ;
mac = hellcreek_read ( hellcreek , HR_FDBRDL ) ;
addr [ 5 ] = mac & 0xff ;
addr [ 4 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDM ) ;
addr [ 3 ] = mac & 0xff ;
addr [ 2 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDH ) ;
addr [ 1 ] = mac & 0xff ;
addr [ 0 ] = ( mac & 0xff00 ) > > 8 ;
/* Force next entry */
hellcreek_write ( hellcreek , 0x00 , HR_FDBRDH ) ;
if ( memcmp ( addr , dest , ETH_ALEN ) )
continue ;
/* Match found */
entry - > idx = i ;
entry - > portmask = ( meta & HR_FDBMDRD_PORTMASK_MASK ) > >
HR_FDBMDRD_PORTMASK_SHIFT ;
entry - > age = ( meta & HR_FDBMDRD_AGE_MASK ) > >
HR_FDBMDRD_AGE_SHIFT ;
entry - > is_obt = ! ! ( meta & HR_FDBMDRD_OBT ) ;
entry - > pass_blocked = ! ! ( meta & HR_FDBMDRD_PASS_BLOCKED ) ;
entry - > is_static = ! ! ( meta & HR_FDBMDRD_STATIC ) ;
entry - > reprio_tc = ( meta & HR_FDBMDRD_REPRIO_TC_MASK ) > >
HR_FDBMDRD_REPRIO_TC_SHIFT ;
entry - > reprio_en = ! ! ( meta & HR_FDBMDRD_REPRIO_EN ) ;
memcpy ( entry - > mac , addr , sizeof ( addr ) ) ;
return 0 ;
}
return - ENOENT ;
}
static int hellcreek_fdb_add ( struct dsa_switch * ds , int port ,
const unsigned char * addr , u16 vid )
{
struct hellcreek_fdb_entry entry = { 0 } ;
struct hellcreek * hellcreek = ds - > priv ;
int ret ;
dev_dbg ( hellcreek - > dev , " Add FDB entry for MAC=%pM \n " , addr ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ret = hellcreek_fdb_get ( hellcreek , addr , & entry ) ;
if ( ret ) {
/* Not found */
memcpy ( entry . mac , addr , sizeof ( entry . mac ) ) ;
entry . portmask = BIT ( port ) ;
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
} else {
/* Found */
ret = __hellcreek_fdb_del ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to delete FDB entry! \n " ) ;
goto out ;
}
entry . portmask | = BIT ( port ) ;
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
}
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static int hellcreek_fdb_del ( struct dsa_switch * ds , int port ,
const unsigned char * addr , u16 vid )
{
struct hellcreek_fdb_entry entry = { 0 } ;
struct hellcreek * hellcreek = ds - > priv ;
int ret ;
dev_dbg ( hellcreek - > dev , " Delete FDB entry for MAC=%pM \n " , addr ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ret = hellcreek_fdb_get ( hellcreek , addr , & entry ) ;
if ( ret ) {
/* Not found */
dev_err ( hellcreek - > dev , " FDB entry for deletion not found! \n " ) ;
} else {
/* Found */
ret = __hellcreek_fdb_del ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to delete FDB entry! \n " ) ;
goto out ;
}
entry . portmask & = ~ BIT ( port ) ;
if ( entry . portmask ! = 0x00 ) {
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
}
}
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static int hellcreek_fdb_dump ( struct dsa_switch * ds , int port ,
dsa_fdb_dump_cb_t * cb , void * data )
{
struct hellcreek * hellcreek = ds - > priv ;
u16 entries ;
size_t i ;
mutex_lock ( & hellcreek - > reg_lock ) ;
/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
* should reset the internal pointer . But , that doesn ' t work . The vendor
* suggested a subsequent write as workaround . Same for HR_FDBRDH below .
*/
entries = hellcreek_read ( hellcreek , HR_FDBMAX ) ;
hellcreek_write ( hellcreek , 0x00 , HR_FDBMAX ) ;
dev_dbg ( hellcreek - > dev , " FDB dump for port %d, entries=%d! \n " , port , entries ) ;
/* Read table */
for ( i = 0 ; i < hellcreek - > fdb_entries ; + + i ) {
unsigned char null_addr [ ETH_ALEN ] = { 0 } ;
struct hellcreek_fdb_entry entry = { 0 } ;
u16 meta , mac ;
meta = hellcreek_read ( hellcreek , HR_FDBMDRD ) ;
mac = hellcreek_read ( hellcreek , HR_FDBRDL ) ;
entry . mac [ 5 ] = mac & 0xff ;
entry . mac [ 4 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDM ) ;
entry . mac [ 3 ] = mac & 0xff ;
entry . mac [ 2 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDH ) ;
entry . mac [ 1 ] = mac & 0xff ;
entry . mac [ 0 ] = ( mac & 0xff00 ) > > 8 ;
/* Force next entry */
hellcreek_write ( hellcreek , 0x00 , HR_FDBRDH ) ;
/* Check valid */
if ( ! memcmp ( entry . mac , null_addr , ETH_ALEN ) )
continue ;
entry . portmask = ( meta & HR_FDBMDRD_PORTMASK_MASK ) > >
HR_FDBMDRD_PORTMASK_SHIFT ;
entry . is_static = ! ! ( meta & HR_FDBMDRD_STATIC ) ;
/* Check port mask */
if ( ! ( entry . portmask & BIT ( port ) ) )
continue ;
cb ( entry . mac , 0 , entry . is_static , data ) ;
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
return 0 ;
}
static int hellcreek_vlan_filtering ( struct dsa_switch * ds , int port ,
bool vlan_filtering ,
struct switchdev_trans * trans )
{
struct hellcreek * hellcreek = ds - > priv ;
if ( switchdev_trans_ph_prepare ( trans ) )
return 0 ;
dev_dbg ( hellcreek - > dev , " %s VLAN filtering on port %d \n " ,
vlan_filtering ? " Enable " : " Disable " , port ) ;
/* Configure port to drop packages with not known vids */
hellcreek_setup_ingressflt ( hellcreek , port , vlan_filtering ) ;
/* Enable VLAN awareness on the switch. This save due to
* ds - > vlan_filtering_is_global .
*/
hellcreek_setup_vlan_awareness ( hellcreek , vlan_filtering ) ;
return 0 ;
}
static int hellcreek_enable_ip_core ( struct hellcreek * hellcreek )
{
int ret ;
u16 val ;
mutex_lock ( & hellcreek - > reg_lock ) ;
val = hellcreek_read ( hellcreek , HR_CTRL_C ) ;
val | = HR_CTRL_C_ENABLE ;
hellcreek_write ( hellcreek , val , HR_CTRL_C ) ;
ret = hellcreek_wait_until_transitioned ( hellcreek ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static void hellcreek_setup_cpu_and_tunnel_port ( struct hellcreek * hellcreek )
{
struct hellcreek_port * tunnel_port = & hellcreek - > ports [ TUNNEL_PORT ] ;
struct hellcreek_port * cpu_port = & hellcreek - > ports [ CPU_PORT ] ;
u16 ptcfg = 0 ;
ptcfg | = HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , CPU_PORT ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
hellcreek_select_port ( hellcreek , TUNNEL_PORT ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
cpu_port - > ptcfg = ptcfg ;
tunnel_port - > ptcfg = ptcfg ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_setup_tc_identity_mapping ( struct hellcreek * hellcreek )
{
int i ;
/* The switch has multiple egress queues per port. The queue is selected
* via the PCP field in the VLAN header . The switch internally deals
* with traffic classes instead of PCP values and this mapping is
* configurable .
*
* The default mapping is ( PCP - TC ) :
* 7 - 7
* 6 - 6
* 5 - 5
* 4 - 4
* 3 - 3
* 2 - 1
* 1 - 0
* 0 - 2
*
* The default should be an identity mapping .
*/
for ( i = 0 ; i < 8 ; + + i ) {
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_prio ( hellcreek , i ) ;
hellcreek_write ( hellcreek ,
i < < HR_PRTCCFG_PCP_TC_MAP_SHIFT ,
HR_PRTCCFG ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
}
2020-11-03 08:10:57 +01:00
static int hellcreek_setup_fdb ( struct hellcreek * hellcreek )
{
static struct hellcreek_fdb_entry ptp = {
/* MAC: 01-1B-19-00-00-00 */
. mac = { 0x01 , 0x1b , 0x19 , 0x00 , 0x00 , 0x00 } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 0 ,
. is_static = 1 ,
. reprio_tc = 6 , /* TC: 6 as per IEEE 802.1AS */
. reprio_en = 1 ,
} ;
static struct hellcreek_fdb_entry p2p = {
/* MAC: 01-80-C2-00-00-0E */
. mac = { 0x01 , 0x80 , 0xc2 , 0x00 , 0x00 , 0x0e } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 0 ,
. is_static = 1 ,
. reprio_tc = 6 , /* TC: 6 as per IEEE 802.1AS */
. reprio_en = 1 ,
} ;
int ret ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ret = __hellcreek_fdb_add ( hellcreek , & ptp ) ;
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & p2p ) ;
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
2020-11-03 08:10:56 +01:00
static int hellcreek_setup ( struct dsa_switch * ds )
{
struct hellcreek * hellcreek = ds - > priv ;
u16 swcfg = 0 ;
int ret , i ;
dev_dbg ( hellcreek - > dev , " Set up the switch \n " ) ;
/* Let's go */
ret = hellcreek_enable_ip_core ( hellcreek ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to enable IP core! \n " ) ;
return ret ;
}
/* Enable CPU/Tunnel ports */
hellcreek_setup_cpu_and_tunnel_port ( hellcreek ) ;
/* Switch config: Keep defaults, enable FDB aging and learning and tag
* each frame from / to cpu port for DSA tagging . Also enable the length
* aware shaping mode . This eliminates the need for Qbv guard bands .
*/
swcfg | = HR_SWCFG_FDBAGE_EN |
HR_SWCFG_FDBLRN_EN |
HR_SWCFG_ALWAYS_OBT |
( HR_SWCFG_LAS_ON < < HR_SWCFG_LAS_MODE_SHIFT ) ;
hellcreek - > swcfg = swcfg ;
hellcreek_write ( hellcreek , swcfg , HR_SWCFG ) ;
/* Initial vlan membership to reflect port separation */
for ( i = 0 ; i < ds - > num_ports ; + + i ) {
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
hellcreek_setup_vlan_membership ( ds , i , true ) ;
}
/* Configure PCP <-> TC mapping */
hellcreek_setup_tc_identity_mapping ( hellcreek ) ;
/* Allow VLAN configurations while not filtering which is the default
* for new DSA drivers .
*/
ds - > configure_vlan_while_not_filtering = true ;
/* The VLAN awareness is a global switch setting. Therefore, mixed vlan
* filtering setups are not supported .
*/
ds - > vlan_filtering_is_global = true ;
2020-11-03 08:10:57 +01:00
/* Intercept _all_ PTP multicast traffic */
ret = hellcreek_setup_fdb ( hellcreek ) ;
if ( ret ) {
dev_err ( hellcreek - > dev ,
" Failed to insert static PTP FDB entries \n " ) ;
return ret ;
}
2020-11-03 08:10:56 +01:00
return 0 ;
}
static void hellcreek_phylink_validate ( struct dsa_switch * ds , int port ,
unsigned long * supported ,
struct phylink_link_state * state )
{
__ETHTOOL_DECLARE_LINK_MODE_MASK ( mask ) = { 0 , } ;
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " Phylink validate for port %d \n " , port ) ;
/* The MAC settings are a hardware configuration option and cannot be
* changed at run time or by strapping . Therefore the attached PHYs
* should be programmed to only advertise settings which are supported
* by the hardware .
*/
if ( hellcreek - > pdata - > is_100_mbits )
phylink_set ( mask , 100 baseT_Full ) ;
else
phylink_set ( mask , 1000 baseT_Full ) ;
bitmap_and ( supported , supported , mask ,
__ETHTOOL_LINK_MODE_MASK_NBITS ) ;
bitmap_and ( state - > advertising , state - > advertising , mask ,
__ETHTOOL_LINK_MODE_MASK_NBITS ) ;
}
static int
hellcreek_port_prechangeupper ( struct dsa_switch * ds , int port ,
struct netdev_notifier_changeupper_info * info )
{
struct hellcreek * hellcreek = ds - > priv ;
bool used = true ;
int ret = - EBUSY ;
u16 vid ;
int i ;
dev_dbg ( hellcreek - > dev , " Pre change upper for port %d \n " , port ) ;
/*
* Deny VLAN devices on top of lan ports with the same VLAN ids , because
* it breaks the port separation due to the private VLANs . Example :
*
* lan0 .100 * and * lan1 .100 cannot be used in parallel . However , lan0 .99
* and lan1 .100 works .
*/
if ( ! is_vlan_dev ( info - > upper_dev ) )
return 0 ;
vid = vlan_dev_vlan_id ( info - > upper_dev ) ;
/* For all ports, check bitmaps */
mutex_lock ( & hellcreek - > vlan_lock ) ;
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
if ( port = = i )
continue ;
used = used & & test_bit ( vid , hellcreek - > ports [ i ] . vlan_dev_bitmap ) ;
}
if ( used )
goto out ;
/* Update bitmap */
set_bit ( vid , hellcreek - > ports [ port ] . vlan_dev_bitmap ) ;
ret = 0 ;
out :
mutex_unlock ( & hellcreek - > vlan_lock ) ;
return ret ;
}
static const struct dsa_switch_ops hellcreek_ds_ops = {
. get_ethtool_stats = hellcreek_get_ethtool_stats ,
. get_sset_count = hellcreek_get_sset_count ,
. get_strings = hellcreek_get_strings ,
. get_tag_protocol = hellcreek_get_tag_protocol ,
2020-11-03 08:10:58 +01:00
. get_ts_info = hellcreek_get_ts_info ,
2020-11-03 08:10:56 +01:00
. phylink_validate = hellcreek_phylink_validate ,
. port_bridge_join = hellcreek_port_bridge_join ,
. port_bridge_leave = hellcreek_port_bridge_leave ,
. port_disable = hellcreek_port_disable ,
. port_enable = hellcreek_port_enable ,
. port_fdb_add = hellcreek_fdb_add ,
. port_fdb_del = hellcreek_fdb_del ,
. port_fdb_dump = hellcreek_fdb_dump ,
2020-11-03 08:10:58 +01:00
. port_hwtstamp_set = hellcreek_port_hwtstamp_set ,
. port_hwtstamp_get = hellcreek_port_hwtstamp_get ,
2020-11-03 08:10:56 +01:00
. port_prechangeupper = hellcreek_port_prechangeupper ,
2020-11-03 08:10:58 +01:00
. port_rxtstamp = hellcreek_port_rxtstamp ,
2020-11-03 08:10:56 +01:00
. port_stp_state_set = hellcreek_port_stp_state_set ,
2020-11-03 08:10:58 +01:00
. port_txtstamp = hellcreek_port_txtstamp ,
2020-11-03 08:10:56 +01:00
. port_vlan_add = hellcreek_vlan_add ,
. port_vlan_del = hellcreek_vlan_del ,
. port_vlan_filtering = hellcreek_vlan_filtering ,
. port_vlan_prepare = hellcreek_vlan_prepare ,
. setup = hellcreek_setup ,
} ;
static int hellcreek_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct hellcreek * hellcreek ;
struct resource * res ;
int ret , i ;
hellcreek = devm_kzalloc ( dev , sizeof ( * hellcreek ) , GFP_KERNEL ) ;
if ( ! hellcreek )
return - ENOMEM ;
hellcreek - > vidmbrcfg = devm_kcalloc ( dev , VLAN_N_VID ,
sizeof ( * hellcreek - > vidmbrcfg ) ,
GFP_KERNEL ) ;
if ( ! hellcreek - > vidmbrcfg )
return - ENOMEM ;
hellcreek - > pdata = of_device_get_match_data ( dev ) ;
hellcreek - > ports = devm_kcalloc ( dev , hellcreek - > pdata - > num_ports ,
sizeof ( * hellcreek - > ports ) ,
GFP_KERNEL ) ;
if ( ! hellcreek - > ports )
return - ENOMEM ;
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
struct hellcreek_port * port = & hellcreek - > ports [ i ] ;
port - > counter_values =
devm_kcalloc ( dev ,
ARRAY_SIZE ( hellcreek_counter ) ,
sizeof ( * port - > counter_values ) ,
GFP_KERNEL ) ;
if ( ! port - > counter_values )
return - ENOMEM ;
port - > vlan_dev_bitmap =
devm_kcalloc ( dev ,
BITS_TO_LONGS ( VLAN_N_VID ) ,
sizeof ( unsigned long ) ,
GFP_KERNEL ) ;
if ( ! port - > vlan_dev_bitmap )
return - ENOMEM ;
port - > hellcreek = hellcreek ;
port - > port = i ;
}
mutex_init ( & hellcreek - > reg_lock ) ;
mutex_init ( & hellcreek - > vlan_lock ) ;
2020-11-03 08:10:57 +01:00
mutex_init ( & hellcreek - > ptp_lock ) ;
2020-11-03 08:10:56 +01:00
hellcreek - > dev = dev ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " tsn " ) ;
if ( ! res ) {
dev_err ( dev , " No memory region provided! \n " ) ;
return - ENODEV ;
}
hellcreek - > base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( hellcreek - > base ) ) {
dev_err ( dev , " No memory available! \n " ) ;
return PTR_ERR ( hellcreek - > base ) ;
}
2020-11-03 08:10:57 +01:00
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " ptp " ) ;
if ( ! res ) {
dev_err ( dev , " No PTP memory region provided! \n " ) ;
return - ENODEV ;
}
hellcreek - > ptp_base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( hellcreek - > ptp_base ) ) {
dev_err ( dev , " No memory available! \n " ) ;
return PTR_ERR ( hellcreek - > ptp_base ) ;
}
2020-11-03 08:10:56 +01:00
ret = hellcreek_detect ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " No (known) chip found! \n " ) ;
return ret ;
}
ret = hellcreek_wait_until_ready ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Switch didn't become ready! \n " ) ;
return ret ;
}
hellcreek_feature_detect ( hellcreek ) ;
hellcreek - > ds = devm_kzalloc ( dev , sizeof ( * hellcreek - > ds ) , GFP_KERNEL ) ;
if ( ! hellcreek - > ds )
return - ENOMEM ;
hellcreek - > ds - > dev = dev ;
hellcreek - > ds - > priv = hellcreek ;
hellcreek - > ds - > ops = & hellcreek_ds_ops ;
hellcreek - > ds - > num_ports = hellcreek - > pdata - > num_ports ;
hellcreek - > ds - > num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES ;
ret = dsa_register_switch ( hellcreek - > ds ) ;
if ( ret ) {
dev_err ( dev , " Unable to register switch \n " ) ;
return ret ;
}
2020-11-03 08:10:57 +01:00
ret = hellcreek_ptp_setup ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Failed to setup PTP! \n " ) ;
goto err_ptp_setup ;
}
2020-11-03 08:10:58 +01:00
ret = hellcreek_hwtstamp_setup ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Failed to setup hardware timestamping! \n " ) ;
goto err_tstamp_setup ;
}
2020-11-03 08:10:56 +01:00
platform_set_drvdata ( pdev , hellcreek ) ;
return 0 ;
2020-11-03 08:10:57 +01:00
2020-11-03 08:10:58 +01:00
err_tstamp_setup :
hellcreek_ptp_free ( hellcreek ) ;
2020-11-03 08:10:57 +01:00
err_ptp_setup :
dsa_unregister_switch ( hellcreek - > ds ) ;
return ret ;
2020-11-03 08:10:56 +01:00
}
static int hellcreek_remove ( struct platform_device * pdev )
{
struct hellcreek * hellcreek = platform_get_drvdata ( pdev ) ;
2020-11-03 08:10:58 +01:00
hellcreek_hwtstamp_free ( hellcreek ) ;
2020-11-03 08:10:57 +01:00
hellcreek_ptp_free ( hellcreek ) ;
2020-11-03 08:10:56 +01:00
dsa_unregister_switch ( hellcreek - > ds ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
static const struct hellcreek_platform_data de1soc_r1_pdata = {
. num_ports = 4 ,
. is_100_mbits = 1 ,
. qbv_support = 1 ,
. qbv_on_cpu_port = 1 ,
. qbu_support = 0 ,
. module_id = 0x4c30 ,
} ;
static const struct of_device_id hellcreek_of_match [ ] = {
{
. compatible = " hirschmann,hellcreek-de1soc-r1 " ,
. data = & de1soc_r1_pdata ,
} ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , hellcreek_of_match ) ;
static struct platform_driver hellcreek_driver = {
. probe = hellcreek_probe ,
. remove = hellcreek_remove ,
. driver = {
. name = " hellcreek " ,
. of_match_table = hellcreek_of_match ,
} ,
} ;
module_platform_driver ( hellcreek_driver ) ;
MODULE_AUTHOR ( " Kurt Kanzenbach <kurt@linutronix.de> " ) ;
MODULE_DESCRIPTION ( " Hirschmann Hellcreek driver " ) ;
MODULE_LICENSE ( " Dual MIT/GPL " ) ;