2020-03-04 22:38:32 +01:00
// SPDX-License-Identifier: GPL-2.0
/* Qualcomm IPQ8064 MDIO interface driver
*
* Copyright ( C ) 2019 Christian Lamparter < chunkeey @ gmail . com >
* Copyright ( C ) 2020 Ansuel Smith < ansuelsmth @ gmail . com >
*/
# include <linux/delay.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of_mdio.h>
2021-05-14 23:03:50 +02:00
# include <linux/of_address.h>
2020-03-04 22:38:32 +01:00
# include <linux/platform_device.h>
2021-03-15 16:19:05 +05:30
# include <linux/regmap.h>
2020-03-04 22:38:32 +01:00
/* MII address register definitions */
2021-05-14 23:03:49 +02:00
# define MII_ADDR_REG_ADDR 0x10
# define MII_BUSY BIT(0)
# define MII_WRITE BIT(1)
# define MII_CLKRANGE(x) ((x) << 2)
# define MII_CLKRANGE_60_100M MII_CLKRANGE(0)
# define MII_CLKRANGE_100_150M MII_CLKRANGE(1)
# define MII_CLKRANGE_20_35M MII_CLKRANGE(2)
# define MII_CLKRANGE_35_60M MII_CLKRANGE(3)
# define MII_CLKRANGE_150_250M MII_CLKRANGE(4)
# define MII_CLKRANGE_250_300M MII_CLKRANGE(5)
2020-03-04 22:38:32 +01:00
# define MII_CLKRANGE_MASK GENMASK(4, 2)
# define MII_REG_SHIFT 6
# define MII_REG_MASK GENMASK(10, 6)
# define MII_ADDR_SHIFT 11
# define MII_ADDR_MASK GENMASK(15, 11)
2021-05-14 23:03:49 +02:00
# define MII_DATA_REG_ADDR 0x14
2020-03-04 22:38:32 +01:00
2021-05-14 23:03:49 +02:00
# define MII_MDIO_DELAY_USEC (1000)
# define MII_MDIO_RETRY_MSEC (10)
2020-03-04 22:38:32 +01:00
struct ipq8064_mdio {
struct regmap * base ; /* NSS_GMAC0_BASE */
} ;
static int
ipq8064_mdio_wait_busy ( struct ipq8064_mdio * priv )
{
u32 busy ;
return regmap_read_poll_timeout ( priv - > base , MII_ADDR_REG_ADDR , busy ,
! ( busy & MII_BUSY ) , MII_MDIO_DELAY_USEC ,
MII_MDIO_RETRY_MSEC * USEC_PER_MSEC ) ;
}
static int
ipq8064_mdio_read ( struct mii_bus * bus , int phy_addr , int reg_offset )
{
u32 miiaddr = MII_BUSY | MII_CLKRANGE_250_300M ;
struct ipq8064_mdio * priv = bus - > priv ;
u32 ret_val ;
int err ;
miiaddr | = ( ( phy_addr < < MII_ADDR_SHIFT ) & MII_ADDR_MASK ) |
( ( reg_offset < < MII_REG_SHIFT ) & MII_REG_MASK ) ;
regmap_write ( priv - > base , MII_ADDR_REG_ADDR , miiaddr ) ;
2021-05-14 23:03:51 +02:00
usleep_range ( 10 , 13 ) ;
2020-03-04 22:38:32 +01:00
err = ipq8064_mdio_wait_busy ( priv ) ;
if ( err )
return err ;
regmap_read ( priv - > base , MII_DATA_REG_ADDR , & ret_val ) ;
return ( int ) ret_val ;
}
static int
ipq8064_mdio_write ( struct mii_bus * bus , int phy_addr , int reg_offset , u16 data )
{
u32 miiaddr = MII_WRITE | MII_BUSY | MII_CLKRANGE_250_300M ;
struct ipq8064_mdio * priv = bus - > priv ;
regmap_write ( priv - > base , MII_DATA_REG_ADDR , data ) ;
miiaddr | = ( ( phy_addr < < MII_ADDR_SHIFT ) & MII_ADDR_MASK ) |
( ( reg_offset < < MII_REG_SHIFT ) & MII_REG_MASK ) ;
regmap_write ( priv - > base , MII_ADDR_REG_ADDR , miiaddr ) ;
2021-05-14 23:03:51 +02:00
/* For the specific reg 31 extra time is needed or the next
* read will produce garbage data .
*/
if ( reg_offset = = 31 )
usleep_range ( 30 , 43 ) ;
else
usleep_range ( 10 , 13 ) ;
2020-03-04 22:38:32 +01:00
return ipq8064_mdio_wait_busy ( priv ) ;
}
2021-05-14 23:03:50 +02:00
static const struct regmap_config ipq8064_mdio_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. can_multi_write = false ,
/* the mdio lock is used by any user of this mdio driver */
. disable_locking = true ,
. cache_type = REGCACHE_NONE ,
} ;
2020-03-04 22:38:32 +01:00
static int
ipq8064_mdio_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct ipq8064_mdio * priv ;
2021-05-14 23:03:50 +02:00
struct resource res ;
2020-03-04 22:38:32 +01:00
struct mii_bus * bus ;
2021-05-14 23:03:50 +02:00
void __iomem * base ;
2020-03-04 22:38:32 +01:00
int ret ;
2021-05-14 23:03:50 +02:00
if ( of_address_to_resource ( np , 0 , & res ) )
return - ENOMEM ;
2021-11-26 17:13:40 +08:00
base = devm_ioremap ( & pdev - > dev , res . start , resource_size ( & res ) ) ;
2021-05-14 23:03:50 +02:00
if ( ! base )
return - ENOMEM ;
2020-03-04 22:38:32 +01:00
bus = devm_mdiobus_alloc_size ( & pdev - > dev , sizeof ( * priv ) ) ;
if ( ! bus )
return - ENOMEM ;
bus - > name = " ipq8064_mdio_bus " ;
bus - > read = ipq8064_mdio_read ;
bus - > write = ipq8064_mdio_write ;
snprintf ( bus - > id , MII_BUS_ID_SIZE , " %s-mii " , dev_name ( & pdev - > dev ) ) ;
bus - > parent = & pdev - > dev ;
priv = bus - > priv ;
2021-05-14 23:03:50 +02:00
priv - > base = devm_regmap_init_mmio ( & pdev - > dev , base ,
& ipq8064_mdio_regmap_config ) ;
if ( IS_ERR ( priv - > base ) )
2020-03-04 22:38:32 +01:00
return PTR_ERR ( priv - > base ) ;
ret = of_mdiobus_register ( bus , np ) ;
if ( ret )
return ret ;
platform_set_drvdata ( pdev , bus ) ;
return 0 ;
}
static int
ipq8064_mdio_remove ( struct platform_device * pdev )
{
struct mii_bus * bus = platform_get_drvdata ( pdev ) ;
mdiobus_unregister ( bus ) ;
return 0 ;
}
static const struct of_device_id ipq8064_mdio_dt_ids [ ] = {
{ . compatible = " qcom,ipq8064-mdio " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , ipq8064_mdio_dt_ids ) ;
static struct platform_driver ipq8064_mdio_driver = {
. probe = ipq8064_mdio_probe ,
. remove = ipq8064_mdio_remove ,
. driver = {
. name = " ipq8064-mdio " ,
. of_match_table = ipq8064_mdio_dt_ids ,
} ,
} ;
module_platform_driver ( ipq8064_mdio_driver ) ;
MODULE_DESCRIPTION ( " Qualcomm IPQ8064 MDIO interface driver " ) ;
MODULE_AUTHOR ( " Christian Lamparter <chunkeey@gmail.com> " ) ;
MODULE_AUTHOR ( " Ansuel Smith <ansuelsmth@gmail.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;