2014-07-03 18:28:38 +04:00
/*
* Copyright ( c ) 2014 Linaro Ltd .
* Copyright ( c ) 2014 Hisilicon Limited .
*
* 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 .
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# define SATA_PHY0_CTLL 0xa0
# define MPLL_MULTIPLIER_SHIFT 1
# define MPLL_MULTIPLIER_MASK 0xfe
# define MPLL_MULTIPLIER_50M 0x3c
# define MPLL_MULTIPLIER_100M 0x1e
# define PHY_RESET BIT(0)
# define REF_SSP_EN BIT(9)
# define SSC_EN BIT(10)
# define REF_USE_PAD BIT(23)
# define SATA_PORT_PHYCTL 0x174
# define SPEED_MODE_MASK 0x6f0000
# define HALF_RATE_SHIFT 16
# define PHY_CONFIG_SHIFT 18
# define GEN2_EN_SHIFT 21
# define SPEED_CTRL BIT(20)
# define SATA_PORT_PHYCTL1 0x148
# define AMPLITUDE_MASK 0x3ffffe
# define AMPLITUDE_GEN3 0x68
# define AMPLITUDE_GEN3_SHIFT 15
# define AMPLITUDE_GEN2 0x56
# define AMPLITUDE_GEN2_SHIFT 8
# define AMPLITUDE_GEN1 0x56
# define AMPLITUDE_GEN1_SHIFT 1
# define SATA_PORT_PHYCTL2 0x14c
# define PREEMPH_MASK 0x3ffff
# define PREEMPH_GEN3 0x20
# define PREEMPH_GEN3_SHIFT 12
# define PREEMPH_GEN2 0x15
# define PREEMPH_GEN2_SHIFT 6
# define PREEMPH_GEN1 0x5
# define PREEMPH_GEN1_SHIFT 0
struct hix5hd2_priv {
void __iomem * base ;
struct regmap * peri_ctrl ;
} ;
enum phy_speed_mode {
SPEED_MODE_GEN1 = 0 ,
SPEED_MODE_GEN2 = 1 ,
SPEED_MODE_GEN3 = 2 ,
} ;
static int hix5hd2_sata_phy_init ( struct phy * phy )
{
struct hix5hd2_priv * priv = phy_get_drvdata ( phy ) ;
u32 val , data [ 2 ] ;
int ret ;
if ( priv - > peri_ctrl ) {
ret = of_property_read_u32_array ( phy - > dev . of_node ,
" hisilicon,power-reg " ,
& data [ 0 ] , 2 ) ;
if ( ret ) {
dev_err ( & phy - > dev , " Fail read hisilicon,power-reg \n " ) ;
return ret ;
}
regmap_update_bits ( priv - > peri_ctrl , data [ 0 ] ,
BIT ( data [ 1 ] ) , BIT ( data [ 1 ] ) ) ;
}
/* reset phy */
val = readl_relaxed ( priv - > base + SATA_PHY0_CTLL ) ;
val & = ~ ( MPLL_MULTIPLIER_MASK | REF_USE_PAD ) ;
val | = MPLL_MULTIPLIER_50M < < MPLL_MULTIPLIER_SHIFT |
REF_SSP_EN | PHY_RESET ;
writel_relaxed ( val , priv - > base + SATA_PHY0_CTLL ) ;
msleep ( 20 ) ;
val & = ~ PHY_RESET ;
writel_relaxed ( val , priv - > base + SATA_PHY0_CTLL ) ;
val = readl_relaxed ( priv - > base + SATA_PORT_PHYCTL1 ) ;
val & = ~ AMPLITUDE_MASK ;
val | = AMPLITUDE_GEN3 < < AMPLITUDE_GEN3_SHIFT |
AMPLITUDE_GEN2 < < AMPLITUDE_GEN2_SHIFT |
AMPLITUDE_GEN1 < < AMPLITUDE_GEN1_SHIFT ;
writel_relaxed ( val , priv - > base + SATA_PORT_PHYCTL1 ) ;
val = readl_relaxed ( priv - > base + SATA_PORT_PHYCTL2 ) ;
val & = ~ PREEMPH_MASK ;
val | = PREEMPH_GEN3 < < PREEMPH_GEN3_SHIFT |
PREEMPH_GEN2 < < PREEMPH_GEN2_SHIFT |
PREEMPH_GEN1 < < PREEMPH_GEN1_SHIFT ;
writel_relaxed ( val , priv - > base + SATA_PORT_PHYCTL2 ) ;
/* ensure PHYCTRL setting takes effect */
val = readl_relaxed ( priv - > base + SATA_PORT_PHYCTL ) ;
val & = ~ SPEED_MODE_MASK ;
val | = SPEED_MODE_GEN1 < < HALF_RATE_SHIFT |
SPEED_MODE_GEN1 < < PHY_CONFIG_SHIFT |
SPEED_MODE_GEN1 < < GEN2_EN_SHIFT | SPEED_CTRL ;
writel_relaxed ( val , priv - > base + SATA_PORT_PHYCTL ) ;
msleep ( 20 ) ;
val & = ~ SPEED_MODE_MASK ;
val | = SPEED_MODE_GEN3 < < HALF_RATE_SHIFT |
SPEED_MODE_GEN3 < < PHY_CONFIG_SHIFT |
SPEED_MODE_GEN3 < < GEN2_EN_SHIFT | SPEED_CTRL ;
writel_relaxed ( val , priv - > base + SATA_PORT_PHYCTL ) ;
val & = ~ ( SPEED_MODE_MASK | SPEED_CTRL ) ;
val | = SPEED_MODE_GEN2 < < HALF_RATE_SHIFT |
SPEED_MODE_GEN2 < < PHY_CONFIG_SHIFT |
SPEED_MODE_GEN2 < < GEN2_EN_SHIFT ;
writel_relaxed ( val , priv - > base + SATA_PORT_PHYCTL ) ;
return 0 ;
}
static struct phy_ops hix5hd2_sata_phy_ops = {
. init = hix5hd2_sata_phy_init ,
. owner = THIS_MODULE ,
} ;
static int hix5hd2_sata_phy_probe ( struct platform_device * pdev )
{
struct phy_provider * phy_provider ;
struct device * dev = & pdev - > dev ;
struct resource * res ;
struct phy * phy ;
struct hix5hd2_priv * priv ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > base = devm_ioremap ( dev , res - > start , resource_size ( res ) ) ;
if ( ! priv - > base )
return - ENOMEM ;
priv - > peri_ctrl = syscon_regmap_lookup_by_phandle ( dev - > of_node ,
" hisilicon,peripheral-syscon " ) ;
if ( IS_ERR ( priv - > peri_ctrl ) )
priv - > peri_ctrl = NULL ;
2014-07-14 14:25:02 +04:00
phy = devm_phy_create ( dev , NULL , & hix5hd2_sata_phy_ops , NULL ) ;
2014-07-03 18:28:38 +04:00
if ( IS_ERR ( phy ) ) {
dev_err ( dev , " failed to create PHY \n " ) ;
return PTR_ERR ( phy ) ;
}
phy_set_drvdata ( phy , priv ) ;
phy_provider = devm_of_phy_provider_register ( dev , of_phy_simple_xlate ) ;
if ( IS_ERR ( phy_provider ) )
return PTR_ERR ( phy_provider ) ;
return 0 ;
}
static const struct of_device_id hix5hd2_sata_phy_of_match [ ] = {
{ . compatible = " hisilicon,hix5hd2-sata-phy " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , hix5hd2_sata_phy_of_match ) ;
static struct platform_driver hix5hd2_sata_phy_driver = {
. probe = hix5hd2_sata_phy_probe ,
. driver = {
. name = " hix5hd2-sata-phy " ,
. of_match_table = hix5hd2_sata_phy_of_match ,
}
} ;
module_platform_driver ( hix5hd2_sata_phy_driver ) ;
MODULE_AUTHOR ( " Jiancheng Xue <xuejiancheng@huawei.com> " ) ;
MODULE_DESCRIPTION ( " HISILICON HIX5HD2 SATA PHY driver " ) ;
MODULE_ALIAS ( " platform:hix5hd2-sata-phy " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;