2010-04-29 06:12:41 +00:00
/*
* drivers / net / phy / micrel . c
*
* Driver for Micrel PHYs
*
* Author : David J . Choi
*
2013-01-23 14:05:15 +00:00
* Copyright ( c ) 2010 - 2013 Micrel , Inc .
2014-11-19 12:59:23 +01:00
* Copyright ( c ) 2014 Johan Hovold < johan @ kernel . org >
2010-04-29 06:12:41 +00:00
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
2013-01-23 14:05:15 +00:00
* Support : Micrel Phys :
* Giga phys : ksz9021 , ksz9031
* 100 / 10 Phys : ksz8001 , ksz8721 , ksz8737 , ksz8041
* ksz8021 , ksz8031 , ksz8051 ,
* ksz8081 , ksz8091 ,
* ksz8061 ,
* Switch : ksz8873 , ksz886x
2010-04-29 06:12:41 +00:00
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/phy.h>
2011-02-14 02:05:33 +00:00
# include <linux/micrel_phy.h>
2013-08-21 01:46:12 +00:00
# include <linux/of.h>
2014-10-10 09:48:05 +02:00
# include <linux/clk.h>
2010-04-29 06:12:41 +00:00
2012-09-23 16:58:49 +00:00
/* Operation Mode Strap Override */
# define MII_KSZPHY_OMSO 0x16
2014-11-11 20:00:09 +01:00
# define KSZPHY_OMSO_B_CAST_OFF BIT(9)
2015-02-13 21:35:33 +01:00
# define KSZPHY_OMSO_NAND_TREE_ON BIT(5)
2014-11-11 20:00:09 +01:00
# define KSZPHY_OMSO_RMII_OVERRIDE BIT(1)
# define KSZPHY_OMSO_MII_OVERRIDE BIT(0)
2012-09-23 16:58:49 +00:00
2010-06-28 15:23:41 +00:00
/* general Interrupt control/status reg in vendor specific block. */
# define MII_KSZPHY_INTCS 0x1B
2014-11-11 20:00:09 +01:00
# define KSZPHY_INTCS_JABBER BIT(15)
# define KSZPHY_INTCS_RECEIVE_ERR BIT(14)
# define KSZPHY_INTCS_PAGE_RECEIVE BIT(13)
# define KSZPHY_INTCS_PARELLEL BIT(12)
# define KSZPHY_INTCS_LINK_PARTNER_ACK BIT(11)
# define KSZPHY_INTCS_LINK_DOWN BIT(10)
# define KSZPHY_INTCS_REMOTE_FAULT BIT(9)
# define KSZPHY_INTCS_LINK_UP BIT(8)
2010-06-28 15:23:41 +00:00
# define KSZPHY_INTCS_ALL (KSZPHY_INTCS_LINK_UP |\
KSZPHY_INTCS_LINK_DOWN )
2014-11-11 20:00:14 +01:00
/* PHY Control 1 */
# define MII_KSZPHY_CTRL_1 0x1e
/* PHY Control 2 / PHY Control (if no PHY Control 1) */
# define MII_KSZPHY_CTRL_2 0x1f
# define MII_KSZPHY_CTRL MII_KSZPHY_CTRL_2
2010-06-28 15:23:41 +00:00
/* bitmap of PHY register to set interrupt mode */
2014-11-11 20:00:09 +01:00
# define KSZPHY_CTRL_INT_ACTIVE_HIGH BIT(9)
2014-11-19 12:59:18 +01:00
# define KSZPHY_RMII_REF_CLK_SEL BIT(7)
2010-06-28 15:23:41 +00:00
2013-08-21 01:46:12 +00:00
/* Write/read to/from extended registers */
# define MII_KSZPHY_EXTREG 0x0b
# define KSZPHY_EXTREG_WRITE 0x8000
# define MII_KSZPHY_EXTREG_WRITE 0x0c
# define MII_KSZPHY_EXTREG_READ 0x0d
/* Extended registers */
# define MII_KSZPHY_CLK_CONTROL_PAD_SKEW 0x104
# define MII_KSZPHY_RX_DATA_PAD_SKEW 0x105
# define MII_KSZPHY_TX_DATA_PAD_SKEW 0x106
# define PS_TO_REG 200
2015-12-30 16:28:27 +01:00
struct kszphy_hw_stat {
const char * string ;
u8 reg ;
u8 bits ;
} ;
static struct kszphy_hw_stat kszphy_hw_stats [ ] = {
{ " phy_receive_errors " , 21 , 16 } ,
{ " phy_idle_errors " , 10 , 8 } ,
} ;
2014-11-19 12:59:15 +01:00
struct kszphy_type {
u32 led_mode_reg ;
2014-11-19 12:59:22 +01:00
u16 interrupt_level_mask ;
2014-11-19 12:59:17 +01:00
bool has_broadcast_disable ;
2015-02-13 21:35:33 +01:00
bool has_nand_tree_disable ;
2014-11-19 12:59:18 +01:00
bool has_rmii_ref_clk_sel ;
2014-11-19 12:59:15 +01:00
} ;
struct kszphy_priv {
const struct kszphy_type * type ;
2014-11-19 12:59:16 +01:00
int led_mode ;
2014-11-19 12:59:18 +01:00
bool rmii_ref_clk_sel ;
bool rmii_ref_clk_sel_val ;
2015-12-30 16:28:27 +01:00
u64 stats [ ARRAY_SIZE ( kszphy_hw_stats ) ] ;
2014-11-19 12:59:15 +01:00
} ;
static const struct kszphy_type ksz8021_type = {
. led_mode_reg = MII_KSZPHY_CTRL_2 ,
2014-12-23 12:59:17 +01:00
. has_broadcast_disable = true ,
2015-02-13 21:35:33 +01:00
. has_nand_tree_disable = true ,
2014-11-19 12:59:18 +01:00
. has_rmii_ref_clk_sel = true ,
2014-11-19 12:59:15 +01:00
} ;
static const struct kszphy_type ksz8041_type = {
. led_mode_reg = MII_KSZPHY_CTRL_1 ,
} ;
static const struct kszphy_type ksz8051_type = {
. led_mode_reg = MII_KSZPHY_CTRL_2 ,
2015-02-13 21:35:33 +01:00
. has_nand_tree_disable = true ,
2014-11-19 12:59:15 +01:00
} ;
static const struct kszphy_type ksz8081_type = {
. led_mode_reg = MII_KSZPHY_CTRL_2 ,
2014-11-19 12:59:17 +01:00
. has_broadcast_disable = true ,
2015-02-13 21:35:33 +01:00
. has_nand_tree_disable = true ,
2014-11-19 12:59:19 +01:00
. has_rmii_ref_clk_sel = true ,
2014-11-19 12:59:15 +01:00
} ;
2014-11-19 12:59:22 +01:00
static const struct kszphy_type ks8737_type = {
. interrupt_level_mask = BIT ( 14 ) ,
} ;
static const struct kszphy_type ksz9021_type = {
. interrupt_level_mask = BIT ( 14 ) ,
} ;
2013-08-21 01:46:12 +00:00
static int kszphy_extended_write ( struct phy_device * phydev ,
2013-12-17 21:38:11 -08:00
u32 regnum , u16 val )
2013-08-21 01:46:12 +00:00
{
phy_write ( phydev , MII_KSZPHY_EXTREG , KSZPHY_EXTREG_WRITE | regnum ) ;
return phy_write ( phydev , MII_KSZPHY_EXTREG_WRITE , val ) ;
}
static int kszphy_extended_read ( struct phy_device * phydev ,
2013-12-17 21:38:11 -08:00
u32 regnum )
2013-08-21 01:46:12 +00:00
{
phy_write ( phydev , MII_KSZPHY_EXTREG , regnum ) ;
return phy_read ( phydev , MII_KSZPHY_EXTREG_READ ) ;
}
2010-06-28 15:23:41 +00:00
static int kszphy_ack_interrupt ( struct phy_device * phydev )
{
/* bit[7..0] int status, which is a read and clear register. */
int rc ;
rc = phy_read ( phydev , MII_KSZPHY_INTCS ) ;
return ( rc < 0 ) ? rc : 0 ;
}
static int kszphy_config_intr ( struct phy_device * phydev )
{
2014-11-19 12:59:22 +01:00
const struct kszphy_type * type = phydev - > drv - > driver_data ;
int temp ;
u16 mask ;
2010-06-28 15:23:41 +00:00
2014-11-19 12:59:22 +01:00
if ( type & & type - > interrupt_level_mask )
mask = type - > interrupt_level_mask ;
else
mask = KSZPHY_CTRL_INT_ACTIVE_HIGH ;
2010-06-28 15:23:41 +00:00
/* set the interrupt pin active low */
temp = phy_read ( phydev , MII_KSZPHY_CTRL ) ;
2014-11-11 20:00:08 +01:00
if ( temp < 0 )
return temp ;
2014-11-19 12:59:22 +01:00
temp & = ~ mask ;
2010-06-28 15:23:41 +00:00
phy_write ( phydev , MII_KSZPHY_CTRL , temp ) ;
2014-11-19 12:59:22 +01:00
/* enable / disable interrupts */
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED )
temp = KSZPHY_INTCS_ALL ;
else
temp = 0 ;
2010-06-28 15:23:41 +00:00
2014-11-19 12:59:22 +01:00
return phy_write ( phydev , MII_KSZPHY_INTCS , temp ) ;
2010-06-28 15:23:41 +00:00
}
2010-04-29 06:12:41 +00:00
2014-11-19 12:59:18 +01:00
static int kszphy_rmii_clk_sel ( struct phy_device * phydev , bool val )
{
int ctrl ;
ctrl = phy_read ( phydev , MII_KSZPHY_CTRL ) ;
if ( ctrl < 0 )
return ctrl ;
if ( val )
ctrl | = KSZPHY_RMII_REF_CLK_SEL ;
else
ctrl & = ~ KSZPHY_RMII_REF_CLK_SEL ;
return phy_write ( phydev , MII_KSZPHY_CTRL , ctrl ) ;
}
2014-11-19 12:59:16 +01:00
static int kszphy_setup_led ( struct phy_device * phydev , u32 reg , int val )
2014-02-26 11:48:00 +00:00
{
2014-11-11 20:00:14 +01:00
int rc , temp , shift ;
2014-11-11 20:00:12 +01:00
2014-11-11 20:00:14 +01:00
switch ( reg ) {
case MII_KSZPHY_CTRL_1 :
shift = 14 ;
break ;
case MII_KSZPHY_CTRL_2 :
shift = 4 ;
break ;
default :
return - EINVAL ;
}
2014-02-26 11:48:00 +00:00
temp = phy_read ( phydev , reg ) ;
2014-11-11 20:00:13 +01:00
if ( temp < 0 ) {
rc = temp ;
goto out ;
}
2014-02-26 11:48:00 +00:00
2014-03-19 02:58:16 +03:00
temp & = ~ ( 3 < < shift ) ;
2014-02-26 11:48:00 +00:00
temp | = val < < shift ;
rc = phy_write ( phydev , reg , temp ) ;
2014-11-11 20:00:13 +01:00
out :
if ( rc < 0 )
2016-01-06 20:11:09 +01:00
phydev_err ( phydev , " failed to set led mode \n " ) ;
2014-02-26 11:48:00 +00:00
2014-11-11 20:00:13 +01:00
return rc ;
2014-02-26 11:48:00 +00:00
}
2014-11-11 20:00:10 +01:00
/* Disable PHY address 0 as the broadcast address, so that it can be used as a
* unique ( non - broadcast ) address on a shared bus .
*/
static int kszphy_broadcast_disable ( struct phy_device * phydev )
{
int ret ;
ret = phy_read ( phydev , MII_KSZPHY_OMSO ) ;
if ( ret < 0 )
goto out ;
ret = phy_write ( phydev , MII_KSZPHY_OMSO , ret | KSZPHY_OMSO_B_CAST_OFF ) ;
out :
if ( ret )
2016-01-06 20:11:09 +01:00
phydev_err ( phydev , " failed to disable broadcast address \n " ) ;
2014-11-11 20:00:10 +01:00
return ret ;
}
2015-02-13 21:35:33 +01:00
static int kszphy_nand_tree_disable ( struct phy_device * phydev )
{
int ret ;
ret = phy_read ( phydev , MII_KSZPHY_OMSO ) ;
if ( ret < 0 )
goto out ;
if ( ! ( ret & KSZPHY_OMSO_NAND_TREE_ON ) )
return 0 ;
ret = phy_write ( phydev , MII_KSZPHY_OMSO ,
ret & ~ KSZPHY_OMSO_NAND_TREE_ON ) ;
out :
if ( ret )
2016-01-06 20:11:09 +01:00
phydev_err ( phydev , " failed to disable NAND tree mode \n " ) ;
2015-02-13 21:35:33 +01:00
return ret ;
}
2010-04-29 06:12:41 +00:00
static int kszphy_config_init ( struct phy_device * phydev )
{
2014-11-19 12:59:15 +01:00
struct kszphy_priv * priv = phydev - > priv ;
const struct kszphy_type * type ;
2014-11-19 12:59:18 +01:00
int ret ;
2010-04-29 06:12:41 +00:00
2014-11-19 12:59:15 +01:00
if ( ! priv )
return 0 ;
type = priv - > type ;
2014-11-19 12:59:17 +01:00
if ( type - > has_broadcast_disable )
kszphy_broadcast_disable ( phydev ) ;
2015-02-13 21:35:33 +01:00
if ( type - > has_nand_tree_disable )
kszphy_nand_tree_disable ( phydev ) ;
2014-11-19 12:59:18 +01:00
if ( priv - > rmii_ref_clk_sel ) {
ret = kszphy_rmii_clk_sel ( phydev , priv - > rmii_ref_clk_sel_val ) ;
if ( ret ) {
2016-01-06 20:11:09 +01:00
phydev_err ( phydev ,
" failed to set rmii reference clock \n " ) ;
2014-11-19 12:59:18 +01:00
return ret ;
}
}
2014-11-19 12:59:16 +01:00
if ( priv - > led_mode > = 0 )
kszphy_setup_led ( phydev , type - > led_mode_reg , priv - > led_mode ) ;
2014-11-19 12:59:15 +01:00
return 0 ;
2014-02-26 11:48:00 +00:00
}
2013-08-21 01:46:12 +00:00
static int ksz9021_load_values_from_of ( struct phy_device * phydev ,
2015-06-05 18:00:24 -05:00
const struct device_node * of_node ,
u16 reg ,
const char * field1 , const char * field2 ,
const char * field3 , const char * field4 )
2013-08-21 01:46:12 +00:00
{
int val1 = - 1 ;
int val2 = - 2 ;
int val3 = - 3 ;
int val4 = - 4 ;
int newval ;
int matches = 0 ;
if ( ! of_property_read_u32 ( of_node , field1 , & val1 ) )
matches + + ;
if ( ! of_property_read_u32 ( of_node , field2 , & val2 ) )
matches + + ;
if ( ! of_property_read_u32 ( of_node , field3 , & val3 ) )
matches + + ;
if ( ! of_property_read_u32 ( of_node , field4 , & val4 ) )
matches + + ;
if ( ! matches )
return 0 ;
if ( matches < 4 )
newval = kszphy_extended_read ( phydev , reg ) ;
else
newval = 0 ;
if ( val1 ! = - 1 )
newval = ( ( newval & 0xfff0 ) | ( ( val1 / PS_TO_REG ) & 0xf ) < < 0 ) ;
2014-04-22 15:01:04 +02:00
if ( val2 ! = - 2 )
2013-08-21 01:46:12 +00:00
newval = ( ( newval & 0xff0f ) | ( ( val2 / PS_TO_REG ) & 0xf ) < < 4 ) ;
2014-04-22 15:01:04 +02:00
if ( val3 ! = - 3 )
2013-08-21 01:46:12 +00:00
newval = ( ( newval & 0xf0ff ) | ( ( val3 / PS_TO_REG ) & 0xf ) < < 8 ) ;
2014-04-22 15:01:04 +02:00
if ( val4 ! = - 4 )
2013-08-21 01:46:12 +00:00
newval = ( ( newval & 0x0fff ) | ( ( val4 / PS_TO_REG ) & 0xf ) < < 12 ) ;
return kszphy_extended_write ( phydev , reg , newval ) ;
}
static int ksz9021_config_init ( struct phy_device * phydev )
{
2016-01-06 20:11:16 +01:00
const struct device * dev = & phydev - > mdio . dev ;
2015-06-05 18:00:24 -05:00
const struct device_node * of_node = dev - > of_node ;
2015-12-09 19:56:31 +01:00
const struct device * dev_walker ;
/* The Micrel driver has a deprecated option to place phy OF
* properties in the MAC node . Walk up the tree of devices to
* find a device with an OF node .
*/
2016-01-06 20:11:16 +01:00
dev_walker = & phydev - > mdio . dev ;
2015-12-09 19:56:31 +01:00
do {
of_node = dev_walker - > of_node ;
dev_walker = dev_walker - > parent ;
} while ( ! of_node & & dev_walker ) ;
2013-08-21 01:46:12 +00:00
if ( of_node ) {
ksz9021_load_values_from_of ( phydev , of_node ,
MII_KSZPHY_CLK_CONTROL_PAD_SKEW ,
" txen-skew-ps " , " txc-skew-ps " ,
" rxdv-skew-ps " , " rxc-skew-ps " ) ;
ksz9021_load_values_from_of ( phydev , of_node ,
MII_KSZPHY_RX_DATA_PAD_SKEW ,
" rxd0-skew-ps " , " rxd1-skew-ps " ,
" rxd2-skew-ps " , " rxd3-skew-ps " ) ;
ksz9021_load_values_from_of ( phydev , of_node ,
MII_KSZPHY_TX_DATA_PAD_SKEW ,
" txd0-skew-ps " , " txd1-skew-ps " ,
" txd2-skew-ps " , " txd3-skew-ps " ) ;
}
return 0 ;
}
2014-05-06 09:40:17 +02:00
# define MII_KSZ9031RN_MMD_CTRL_REG 0x0d
# define MII_KSZ9031RN_MMD_REGDATA_REG 0x0e
# define OP_DATA 1
# define KSZ9031_PS_TO_REG 60
/* Extended registers */
2015-06-05 18:00:26 -05:00
/* MMD Address 0x0 */
# define MII_KSZ9031RN_FLP_BURST_TX_LO 3
# define MII_KSZ9031RN_FLP_BURST_TX_HI 4
2015-06-05 18:00:25 -05:00
/* MMD Address 0x2 */
2014-05-06 09:40:17 +02:00
# define MII_KSZ9031RN_CONTROL_PAD_SKEW 4
# define MII_KSZ9031RN_RX_DATA_PAD_SKEW 5
# define MII_KSZ9031RN_TX_DATA_PAD_SKEW 6
# define MII_KSZ9031RN_CLK_PAD_SKEW 8
static int ksz9031_extended_write ( struct phy_device * phydev ,
u8 mode , u32 dev_addr , u32 regnum , u16 val )
{
phy_write ( phydev , MII_KSZ9031RN_MMD_CTRL_REG , dev_addr ) ;
phy_write ( phydev , MII_KSZ9031RN_MMD_REGDATA_REG , regnum ) ;
phy_write ( phydev , MII_KSZ9031RN_MMD_CTRL_REG , ( mode < < 14 ) | dev_addr ) ;
return phy_write ( phydev , MII_KSZ9031RN_MMD_REGDATA_REG , val ) ;
}
static int ksz9031_extended_read ( struct phy_device * phydev ,
u8 mode , u32 dev_addr , u32 regnum )
{
phy_write ( phydev , MII_KSZ9031RN_MMD_CTRL_REG , dev_addr ) ;
phy_write ( phydev , MII_KSZ9031RN_MMD_REGDATA_REG , regnum ) ;
phy_write ( phydev , MII_KSZ9031RN_MMD_CTRL_REG , ( mode < < 14 ) | dev_addr ) ;
return phy_read ( phydev , MII_KSZ9031RN_MMD_REGDATA_REG ) ;
}
static int ksz9031_of_load_skew_values ( struct phy_device * phydev ,
2015-06-05 18:00:24 -05:00
const struct device_node * of_node ,
2014-05-06 09:40:17 +02:00
u16 reg , size_t field_sz ,
2015-06-05 18:00:24 -05:00
const char * field [ ] , u8 numfields )
2014-05-06 09:40:17 +02:00
{
int val [ 4 ] = { - 1 , - 2 , - 3 , - 4 } ;
int matches = 0 ;
u16 mask ;
u16 maxval ;
u16 newval ;
int i ;
for ( i = 0 ; i < numfields ; i + + )
if ( ! of_property_read_u32 ( of_node , field [ i ] , val + i ) )
matches + + ;
if ( ! matches )
return 0 ;
if ( matches < numfields )
newval = ksz9031_extended_read ( phydev , OP_DATA , 2 , reg ) ;
else
newval = 0 ;
maxval = ( field_sz = = 4 ) ? 0xf : 0x1f ;
for ( i = 0 ; i < numfields ; i + + )
if ( val [ i ] ! = - ( i + 1 ) ) {
mask = 0xffff ;
mask ^ = maxval < < ( field_sz * i ) ;
newval = ( newval & mask ) |
( ( ( val [ i ] / KSZ9031_PS_TO_REG ) & maxval )
< < ( field_sz * i ) ) ;
}
return ksz9031_extended_write ( phydev , OP_DATA , 2 , reg , newval ) ;
}
2015-06-05 18:00:26 -05:00
static int ksz9031_center_flp_timing ( struct phy_device * phydev )
{
int result ;
/* Center KSZ9031RNX FLP timing at 16ms. */
result = ksz9031_extended_write ( phydev , OP_DATA , 0 ,
MII_KSZ9031RN_FLP_BURST_TX_HI , 0x0006 ) ;
result = ksz9031_extended_write ( phydev , OP_DATA , 0 ,
MII_KSZ9031RN_FLP_BURST_TX_LO , 0x1A80 ) ;
if ( result )
return result ;
return genphy_restart_aneg ( phydev ) ;
}
2014-05-06 09:40:17 +02:00
static int ksz9031_config_init ( struct phy_device * phydev )
{
2016-01-06 20:11:16 +01:00
const struct device * dev = & phydev - > mdio . dev ;
2015-06-05 18:00:24 -05:00
const struct device_node * of_node = dev - > of_node ;
static const char * clk_skews [ 2 ] = { " rxc-skew-ps " , " txc-skew-ps " } ;
static const char * rx_data_skews [ 4 ] = {
2014-05-06 09:40:17 +02:00
" rxd0-skew-ps " , " rxd1-skew-ps " ,
" rxd2-skew-ps " , " rxd3-skew-ps "
} ;
2015-06-05 18:00:24 -05:00
static const char * tx_data_skews [ 4 ] = {
2014-05-06 09:40:17 +02:00
" txd0-skew-ps " , " txd1-skew-ps " ,
" txd2-skew-ps " , " txd3-skew-ps "
} ;
2015-06-05 18:00:24 -05:00
static const char * control_skews [ 2 ] = { " txen-skew-ps " , " rxdv-skew-ps " } ;
2016-01-07 09:31:15 +01:00
const struct device * dev_walker ;
2014-05-06 09:40:17 +02:00
2016-01-07 09:31:15 +01:00
/* The Micrel driver has a deprecated option to place phy OF
* properties in the MAC node . Walk up the tree of devices to
* find a device with an OF node .
*/
2016-01-11 23:55:43 -05:00
dev_walker = & phydev - > mdio . dev ;
2016-01-07 09:31:15 +01:00
do {
of_node = dev_walker - > of_node ;
dev_walker = dev_walker - > parent ;
} while ( ! of_node & & dev_walker ) ;
2014-05-06 09:40:17 +02:00
if ( of_node ) {
ksz9031_of_load_skew_values ( phydev , of_node ,
MII_KSZ9031RN_CLK_PAD_SKEW , 5 ,
clk_skews , 2 ) ;
ksz9031_of_load_skew_values ( phydev , of_node ,
MII_KSZ9031RN_CONTROL_PAD_SKEW , 4 ,
control_skews , 2 ) ;
ksz9031_of_load_skew_values ( phydev , of_node ,
MII_KSZ9031RN_RX_DATA_PAD_SKEW , 4 ,
rx_data_skews , 4 ) ;
ksz9031_of_load_skew_values ( phydev , of_node ,
MII_KSZ9031RN_TX_DATA_PAD_SKEW , 4 ,
tx_data_skews , 4 ) ;
}
2015-06-05 18:00:26 -05:00
return ksz9031_center_flp_timing ( phydev ) ;
2014-05-06 09:40:17 +02:00
}
2012-11-21 05:38:07 +00:00
# define KSZ8873MLL_GLOBAL_CONTROL_4 0x06
2014-11-11 20:00:09 +01:00
# define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX BIT(6)
# define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED BIT(4)
2013-08-06 17:29:35 +09:00
static int ksz8873mll_read_status ( struct phy_device * phydev )
2012-11-21 05:38:07 +00:00
{
int regval ;
/* dummy read */
regval = phy_read ( phydev , KSZ8873MLL_GLOBAL_CONTROL_4 ) ;
regval = phy_read ( phydev , KSZ8873MLL_GLOBAL_CONTROL_4 ) ;
if ( regval & KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX )
phydev - > duplex = DUPLEX_HALF ;
else
phydev - > duplex = DUPLEX_FULL ;
if ( regval & KSZ8873MLL_GLOBAL_CONTROL_4_SPEED )
phydev - > speed = SPEED_10 ;
else
phydev - > speed = SPEED_100 ;
phydev - > link = 1 ;
phydev - > pause = phydev - > asym_pause = 0 ;
return 0 ;
}
2015-10-21 14:17:04 -05:00
static int ksz9031_read_status ( struct phy_device * phydev )
{
int err ;
int regval ;
err = genphy_read_status ( phydev ) ;
if ( err )
return err ;
/* Make sure the PHY is not broken. Read idle error count,
* and reset the PHY if it is maxed out .
*/
regval = phy_read ( phydev , MII_STAT1000 ) ;
if ( ( regval & 0xFF ) = = 0xFF ) {
phy_init_hw ( phydev ) ;
phydev - > link = 0 ;
}
return 0 ;
}
2012-11-21 05:38:07 +00:00
static int ksz8873mll_config_aneg ( struct phy_device * phydev )
{
return 0 ;
}
2014-07-29 15:19:58 -05:00
/* This routine returns -1 as an indication to the caller that the
* Micrel ksz9021 10 / 100 / 1000 PHY does not support standard IEEE
* MMD extended PHY registers .
*/
static int
ksz9021_rd_mmd_phyreg ( struct phy_device * phydev , int ptrad , int devnum ,
int regnum )
{
return - 1 ;
}
/* This routine does nothing since the Micrel ksz9021 does not support
* standard IEEE MMD extended PHY registers .
*/
static void
ksz9021_wr_mmd_phyreg ( struct phy_device * phydev , int ptrad , int devnum ,
int regnum , u32 val )
{
}
2015-12-30 16:28:27 +01:00
static int kszphy_get_sset_count ( struct phy_device * phydev )
{
return ARRAY_SIZE ( kszphy_hw_stats ) ;
}
static void kszphy_get_strings ( struct phy_device * phydev , u8 * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( kszphy_hw_stats ) ; i + + ) {
memcpy ( data + i * ETH_GSTRING_LEN ,
kszphy_hw_stats [ i ] . string , ETH_GSTRING_LEN ) ;
}
}
# ifndef UINT64_MAX
# define UINT64_MAX (u64)(~((u64)0))
# endif
static u64 kszphy_get_stat ( struct phy_device * phydev , int i )
{
struct kszphy_hw_stat stat = kszphy_hw_stats [ i ] ;
struct kszphy_priv * priv = phydev - > priv ;
u64 val ;
val = phy_read ( phydev , stat . reg ) ;
if ( val < 0 ) {
val = UINT64_MAX ;
} else {
val = val & ( ( 1 < < stat . bits ) - 1 ) ;
priv - > stats [ i ] + = val ;
val = priv - > stats [ i ] ;
}
return val ;
}
static void kszphy_get_stats ( struct phy_device * phydev ,
struct ethtool_stats * stats , u64 * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( kszphy_hw_stats ) ; i + + )
data [ i ] = kszphy_get_stat ( phydev , i ) ;
}
2016-02-26 19:18:22 +01:00
static int kszphy_resume ( struct phy_device * phydev )
{
int value ;
mutex_lock ( & phydev - > lock ) ;
value = phy_read ( phydev , MII_BMCR ) ;
phy_write ( phydev , MII_BMCR , value & ~ BMCR_PDOWN ) ;
kszphy_config_intr ( phydev ) ;
mutex_unlock ( & phydev - > lock ) ;
return 0 ;
}
2014-11-19 12:59:15 +01:00
static int kszphy_probe ( struct phy_device * phydev )
{
const struct kszphy_type * type = phydev - > drv - > driver_data ;
2016-01-06 20:11:16 +01:00
const struct device_node * np = phydev - > mdio . dev . of_node ;
2014-11-19 12:59:15 +01:00
struct kszphy_priv * priv ;
2014-11-19 12:59:18 +01:00
struct clk * clk ;
2014-11-19 12:59:16 +01:00
int ret ;
2014-11-19 12:59:15 +01:00
2016-01-06 20:11:16 +01:00
priv = devm_kzalloc ( & phydev - > mdio . dev , sizeof ( * priv ) , GFP_KERNEL ) ;
2014-11-19 12:59:15 +01:00
if ( ! priv )
return - ENOMEM ;
phydev - > priv = priv ;
priv - > type = type ;
2014-11-19 12:59:16 +01:00
if ( type - > led_mode_reg ) {
ret = of_property_read_u32 ( np , " micrel,led-mode " ,
& priv - > led_mode ) ;
if ( ret )
priv - > led_mode = - 1 ;
if ( priv - > led_mode > 3 ) {
2016-01-06 20:11:09 +01:00
phydev_err ( phydev , " invalid led mode: 0x%02x \n " ,
priv - > led_mode ) ;
2014-11-19 12:59:16 +01:00
priv - > led_mode = - 1 ;
}
} else {
priv - > led_mode = - 1 ;
}
2016-01-06 20:11:16 +01:00
clk = devm_clk_get ( & phydev - > mdio . dev , " rmii-ref " ) ;
2015-05-12 09:43:14 +02:00
/* NOTE: clk may be NULL if building without CONFIG_HAVE_CLK */
if ( ! IS_ERR_OR_NULL ( clk ) ) {
2014-10-10 09:48:05 +02:00
unsigned long rate = clk_get_rate ( clk ) ;
2014-11-19 12:59:19 +01:00
bool rmii_ref_clk_sel_25_mhz ;
2014-10-10 09:48:05 +02:00
2014-11-19 12:59:18 +01:00
priv - > rmii_ref_clk_sel = type - > has_rmii_ref_clk_sel ;
2014-11-19 12:59:19 +01:00
rmii_ref_clk_sel_25_mhz = of_property_read_bool ( np ,
" micrel,rmii-reference-clock-select-25-mhz " ) ;
2014-11-19 12:59:18 +01:00
2014-10-10 09:48:05 +02:00
if ( rate > 24500000 & & rate < 25500000 ) {
2014-11-19 12:59:19 +01:00
priv - > rmii_ref_clk_sel_val = rmii_ref_clk_sel_25_mhz ;
2014-10-10 09:48:05 +02:00
} else if ( rate > 49500000 & & rate < 50500000 ) {
2014-11-19 12:59:19 +01:00
priv - > rmii_ref_clk_sel_val = ! rmii_ref_clk_sel_25_mhz ;
2014-10-10 09:48:05 +02:00
} else {
2016-01-06 20:11:09 +01:00
phydev_err ( phydev , " Clock rate out of range: %ld \n " ,
rate ) ;
2014-10-10 09:48:05 +02:00
return - EINVAL ;
}
}
2014-11-19 12:59:18 +01:00
/* Support legacy board-file configuration */
if ( phydev - > dev_flags & MICREL_PHY_50MHZ_CLK ) {
priv - > rmii_ref_clk_sel = true ;
priv - > rmii_ref_clk_sel_val = true ;
}
return 0 ;
2014-10-10 09:48:05 +02:00
}
2012-07-04 05:44:34 +00:00
static struct phy_driver ksphy_driver [ ] = {
{
2010-06-28 15:23:41 +00:00
. phy_id = PHY_ID_KS8737 ,
. phy_id_mask = 0x00fffff0 ,
. name = " Micrel KS8737 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:22 +01:00
. driver_data = & ks8737_type ,
2010-06-28 15:23:41 +00:00
. config_init = kszphy_config_init ,
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
2014-11-19 12:59:22 +01:00
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-09-23 16:58:49 +00:00
} , {
. phy_id = PHY_ID_KSZ8021 ,
. phy_id_mask = 0x00ffffff ,
2013-01-23 14:05:15 +00:00
. name = " Micrel KSZ8021 or KSZ8031 " ,
2012-09-23 16:58:49 +00:00
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause |
SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8021_type ,
2014-11-19 12:59:18 +01:00
. probe = kszphy_probe ,
2014-12-23 12:59:17 +01:00
. config_init = kszphy_config_init ,
2012-09-23 16:58:49 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2013-03-10 22:50:02 +00:00
} , {
. phy_id = PHY_ID_KSZ8031 ,
. phy_id_mask = 0x00ffffff ,
. name = " Micrel KSZ8031 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause |
SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8021_type ,
2014-11-19 12:59:18 +01:00
. probe = kszphy_probe ,
2014-12-23 12:59:17 +01:00
. config_init = kszphy_config_init ,
2013-03-10 22:50:02 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2012-09-23 16:58:50 +00:00
. phy_id = PHY_ID_KSZ8041 ,
2010-06-28 15:23:41 +00:00
. phy_id_mask = 0x00fffff0 ,
2012-09-23 16:58:50 +00:00
. name = " Micrel KSZ8041 " ,
2010-06-28 15:23:41 +00:00
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8041_type ,
. probe = kszphy_probe ,
. config_init = kszphy_config_init ,
2010-06-28 15:23:41 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2013-12-10 02:20:41 +03:00
} , {
. phy_id = PHY_ID_KSZ8041RNLI ,
. phy_id_mask = 0x00fffff0 ,
. name = " Micrel KSZ8041RNLI " ,
. features = PHY_BASIC_FEATURES |
SUPPORTED_Pause | SUPPORTED_Asym_Pause ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8041_type ,
. probe = kszphy_probe ,
. config_init = kszphy_config_init ,
2013-12-10 02:20:41 +03:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-12-10 02:20:41 +03:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2012-09-23 16:58:50 +00:00
. phy_id = PHY_ID_KSZ8051 ,
2010-04-29 06:12:41 +00:00
. phy_id_mask = 0x00fffff0 ,
2012-09-23 16:58:50 +00:00
. name = " Micrel KSZ8051 " ,
2010-06-28 15:23:41 +00:00
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8051_type ,
. probe = kszphy_probe ,
2014-11-19 12:59:18 +01:00
. config_init = kszphy_config_init ,
2010-04-29 06:12:41 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2010-06-28 15:23:41 +00:00
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2012-09-23 16:58:50 +00:00
. phy_id = PHY_ID_KSZ8001 ,
. name = " Micrel KSZ8001 or KS8721 " ,
2012-06-17 22:52:09 +00:00
. phy_id_mask = 0x00ffffff ,
2010-06-28 15:23:41 +00:00
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8041_type ,
. probe = kszphy_probe ,
. config_init = kszphy_config_init ,
2010-04-29 06:12:41 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2010-06-28 15:23:41 +00:00
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2013-01-23 14:05:15 +00:00
} , {
. phy_id = PHY_ID_KSZ8081 ,
. name = " Micrel KSZ8081 or KSZ8091 " ,
. phy_id_mask = 0x00fffff0 ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:15 +01:00
. driver_data = & ksz8081_type ,
. probe = kszphy_probe ,
2014-11-19 12:59:17 +01:00
. config_init = kszphy_config_init ,
2013-01-23 14:05:15 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
2016-02-26 19:18:22 +01:00
. resume = kszphy_resume ,
2013-01-23 14:05:15 +00:00
} , {
. phy_id = PHY_ID_KSZ8061 ,
. name = " Micrel KSZ8061 " ,
. phy_id_mask = 0x00fffff0 ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
. config_init = kszphy_config_init ,
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. ack_interrupt = kszphy_ack_interrupt ,
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2010-04-29 06:12:41 +00:00
. phy_id = PHY_ID_KSZ9021 ,
2012-06-17 22:52:09 +00:00
. phy_id_mask = 0x000ffffe ,
2010-04-29 06:12:41 +00:00
. name = " Micrel KSZ9021 Gigabit PHY " ,
2013-02-28 08:45:22 +00:00
. features = ( PHY_GBIT_FEATURES | SUPPORTED_Pause ) ,
2010-06-28 15:23:41 +00:00
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:22 +01:00
. driver_data = & ksz9021_type ,
2013-08-21 01:46:12 +00:00
. config_init = ksz9021_config_init ,
2010-04-29 06:12:41 +00:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2010-06-28 15:23:41 +00:00
. ack_interrupt = kszphy_ack_interrupt ,
2014-11-19 12:59:22 +01:00
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2014-07-29 15:19:58 -05:00
. read_mmd_indirect = ksz9021_rd_mmd_phyreg ,
. write_mmd_indirect = ksz9021_wr_mmd_phyreg ,
2013-01-23 14:05:15 +00:00
} , {
. phy_id = PHY_ID_KSZ9031 ,
. phy_id_mask = 0x00fffff0 ,
. name = " Micrel KSZ9031 Gigabit PHY " ,
2014-09-15 12:06:33 +02:00
. features = ( PHY_GBIT_FEATURES | SUPPORTED_Pause ) ,
2013-01-23 14:05:15 +00:00
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
2014-11-19 12:59:22 +01:00
. driver_data = & ksz9021_type ,
2014-05-06 09:40:17 +02:00
. config_init = ksz9031_config_init ,
2013-01-23 14:05:15 +00:00
. config_aneg = genphy_config_aneg ,
2015-10-21 14:17:04 -05:00
. read_status = ksz9031_read_status ,
2013-01-23 14:05:15 +00:00
. ack_interrupt = kszphy_ack_interrupt ,
2014-11-19 12:59:22 +01:00
. config_intr = kszphy_config_intr ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-11-21 05:38:07 +00:00
} , {
. phy_id = PHY_ID_KSZ8873MLL ,
. phy_id_mask = 0x00fffff0 ,
. name = " Micrel KSZ8873MLL Switch " ,
. features = ( SUPPORTED_Pause | SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_MAGICANEG ,
. config_init = kszphy_config_init ,
. config_aneg = ksz8873mll_config_aneg ,
. read_status = ksz8873mll_read_status ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2013-01-23 14:05:15 +00:00
} , {
. phy_id = PHY_ID_KSZ886X ,
. phy_id_mask = 0x00fffff0 ,
. name = " Micrel KSZ886X Switch " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause ) ,
. flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT ,
. config_init = kszphy_config_init ,
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2015-12-30 16:28:27 +01:00
. get_sset_count = kszphy_get_sset_count ,
. get_strings = kszphy_get_strings ,
. get_stats = kszphy_get_stats ,
2013-09-19 19:40:48 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} } ;
2010-04-29 06:12:41 +00:00
2014-11-11 19:45:59 +01:00
module_phy_driver ( ksphy_driver ) ;
2010-04-29 06:12:41 +00:00
MODULE_DESCRIPTION ( " Micrel PHY driver " ) ;
MODULE_AUTHOR ( " David J. Choi " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-05-03 15:48:29 -07:00
2010-10-03 23:43:32 +00:00
static struct mdio_device_id __maybe_unused micrel_tbl [ ] = {
2012-06-17 22:52:09 +00:00
{ PHY_ID_KSZ9021 , 0x000ffffe } ,
2013-01-23 14:05:15 +00:00
{ PHY_ID_KSZ9031 , 0x00fffff0 } ,
2012-09-23 16:58:50 +00:00
{ PHY_ID_KSZ8001 , 0x00ffffff } ,
2010-06-28 15:23:41 +00:00
{ PHY_ID_KS8737 , 0x00fffff0 } ,
2012-09-23 16:58:49 +00:00
{ PHY_ID_KSZ8021 , 0x00ffffff } ,
2013-03-10 22:50:02 +00:00
{ PHY_ID_KSZ8031 , 0x00ffffff } ,
2012-09-23 16:58:50 +00:00
{ PHY_ID_KSZ8041 , 0x00fffff0 } ,
{ PHY_ID_KSZ8051 , 0x00fffff0 } ,
2013-01-23 14:05:15 +00:00
{ PHY_ID_KSZ8061 , 0x00fffff0 } ,
{ PHY_ID_KSZ8081 , 0x00fffff0 } ,
2012-11-21 05:38:07 +00:00
{ PHY_ID_KSZ8873MLL , 0x00fffff0 } ,
2013-01-23 14:05:15 +00:00
{ PHY_ID_KSZ886X , 0x00fffff0 } ,
2010-05-03 15:48:29 -07:00
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , micrel_tbl ) ;