You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

394 lines
9.9 KiB

#include <linux/platform_device.h>
#include "huanglong_phy.h"
#include "hleth.h"
#include "mdio.h"
#include "phy.h"
static const u32 phy_s28v200_fix_param[] = {
#include "festa.h"
};
#define CONFIG_FEPHY_TRIM
#ifdef CONFIG_FEPHY_TRIM
#define REG_LD_AM 0x3050
#define LD_AM_MASK GENMASK(4, 0)
#define REG_LDO_AM 0x3051
#define LDO_AM_MASK GENMASK(2, 0)
#define REG_R_TUNING 0x3052
#define R_TUNING_MASK GENMASK(5, 0)
#define REG_WR_DONE 0x3053
#define REG_DEF_ATE 0x3057
#define DEF_LD_AM 0x0f
#define DEF_LDO_AM 0x7
#define DEF_R_TUNING 0x15
static inline int hleth_phy_expanded_read(struct mii_bus *bus, int phyaddr,
u32 reg_addr)
{
int ret;
hleth_mdiobus_write(bus, phyaddr, MII_EXPMA, reg_addr);
ret = hleth_mdiobus_read(bus, phyaddr, MII_EXPMD);
return ret;
}
static inline int hleth_phy_expanded_write(struct mii_bus *bus, int phyaddr,
u32 reg_addr, u16 val)
{
int ret;
hleth_mdiobus_write(bus, phyaddr, MII_EXPMA, reg_addr);
ret = hleth_mdiobus_write(bus, phyaddr, MII_EXPMD, val);
return ret;
}
void hleth_use_default_trim(struct mii_bus *bus, int phyaddr)
{
unsigned short v;
int timeout = 3;
pr_info("FEPHY: No OTP data, use default ATE parameters to auto-trim!\n");
do {
msleep(250); /* 250:delay */
v = (unsigned short)hleth_phy_expanded_read(bus, phyaddr, REG_DEF_ATE);
v &= BIT(0);
} while (!v && --timeout);
if (!timeout)
pr_warn("FEPHY: fail to wait auto-trim finish!\n");
}
void hleth_config_festa_phy_trim(struct mii_bus *bus, int phyaddr,
u32 trim_params)
{
unsigned short ld_amptlitude;
unsigned short ldo_amptlitude;
unsigned short r_tuning_val;
unsigned short v;
int timeout = 3000; /* 3000:timeout */
ld_amptlitude = DEF_LD_AM;
ldo_amptlitude = DEF_LDO_AM;
r_tuning_val = DEF_R_TUNING;
if (!trim_params) {
hleth_use_default_trim(bus, phyaddr);
return;
}
ld_amptlitude = trim_params & LD_AM_MASK;
ldo_amptlitude = (trim_params >> 8) & LDO_AM_MASK; /* 8:right shift val */
r_tuning_val = (trim_params >> 16) & R_TUNING_MASK; /* 16:right shift val */
v = hleth_phy_expanded_read(bus, phyaddr, REG_LD_AM);
v = (v & ~LD_AM_MASK) | (ld_amptlitude & LD_AM_MASK);
hleth_phy_expanded_write(bus, phyaddr, REG_LD_AM, v);
v = hleth_phy_expanded_read(bus, phyaddr, REG_LDO_AM);
v = (v & ~LDO_AM_MASK) | (ldo_amptlitude & LDO_AM_MASK);
hleth_phy_expanded_write(bus, phyaddr, REG_LDO_AM, v);
v = hleth_phy_expanded_read(bus, phyaddr, REG_R_TUNING);
v = (v & ~R_TUNING_MASK) | (r_tuning_val & R_TUNING_MASK);
hleth_phy_expanded_write(bus, phyaddr, REG_R_TUNING, v);
v = hleth_phy_expanded_read(bus, phyaddr, REG_WR_DONE);
if (v & BIT(1))
pr_warn("FEPHY: invalid trim status.\n");
v = v | BIT(0);
hleth_phy_expanded_write(bus, phyaddr, REG_WR_DONE, v);
do {
usleep_range(100, 150); /* 100,150:delay zone */
v = hleth_phy_expanded_read(bus, phyaddr, REG_WR_DONE);
v &= BIT(1);
} while (!v && --timeout);
if (!timeout)
pr_warn("FEPHY: faile to wait trim finish!\n");
pr_info("FEPHY:addr=%d, la_am=0x%x, ldo_am=0x%x, r_tuning=0x%x\n",
phyaddr,
hleth_phy_expanded_read(bus, phyaddr, REG_LD_AM),
hleth_phy_expanded_read(bus, phyaddr, REG_LDO_AM),
hleth_phy_expanded_read(bus, phyaddr, REG_R_TUNING));
}
#endif
#ifdef CONFIG_FEPHY_TRIM
void hleth_fix_festa_phy_trim(struct mii_bus *bus, struct hleth_platdrv_data *pdata)
{
struct hleth_phy_param_s *phy_param = NULL;
int i;
int phyaddr;
if (bus == NULL || pdata == NULL)
return;
for (i = 0; i < HLETH_MAX_PORT; i++) {
phy_param = &pdata->hleth_phy_param[i];
if (phy_param == NULL)
continue;
if (!phy_param->isvalid || !phy_param->isinternal)
continue;
phyaddr = phy_param->phy_addr;
hleth_config_festa_phy_trim(bus, phyaddr,
phy_param->trim_params);
mdelay(5); /* 5:delay */
}
}
#else
void hleth_fix_festa_phy_trim(struct mii_bus *bus, struct hleth_platdrv_data *pdata)
{
msleep(300); /* 300:delay */
}
#endif
static int phy_expanded_write_bulk(struct phy_device *phy_dev,
const u32 reg_and_val[],
int count)
{
int i, v, ret = 0;
u32 reg_addr;
u16 val;
v = phy_read(phy_dev, MII_BMCR);
v = (unsigned int)v | BMCR_PDOWN;
phy_write(phy_dev, MII_BMCR, v);
for (i = 0; i < (2 * count); i += 2) { /* 2:operated value */
if ((i % 50) == 0) /* 50:operated value */
schedule();
reg_addr = reg_and_val[i];
val = (u16)reg_and_val[i + 1];
hleth_mdiobus_write_nodelay(phy_dev->mdio.bus,
phy_dev->mdio.addr,
MII_EXPMA, reg_addr);
ret = hleth_mdiobus_write_nodelay(phy_dev->mdio.bus,
phy_dev->mdio.addr,
MII_EXPMD, val);
}
v = phy_read(phy_dev, MII_BMCR);
v =(unsigned int)v & (~BMCR_PDOWN);
phy_write(phy_dev, MII_BMCR, v);
return ret;
}
static int hl_fephy_s28v200_fix(struct phy_device *phy_dev)
{
int count;
count = ARRAY_SIZE(phy_s28v200_fix_param);
if (count % 2) /* 2:operated value */
pr_warn("internal FEPHY fix register count is not right.\n");
count /= 2; /* 2:operated value */
phy_expanded_write_bulk(phy_dev, phy_s28v200_fix_param, count);
return 0;
}
static int ksz8051mnl_phy_fix(struct phy_device *phy_dev)
{
u32 v;
v = phy_read(phy_dev, 0x1F);
v |= (1 << 7); /* 7:set phy RMII 50MHz clk; */
phy_write(phy_dev, 0x1F, v);
v = phy_read(phy_dev, 0x16);
v |= (1 << 1); /* set phy RMII override; */
phy_write(phy_dev, 0x16, v);
return 0;
}
static int ksz8081rnb_phy_fix(struct phy_device *phy_dev)
{
u32 v;
v = phy_read(phy_dev, 0x1F);
v |= (1 << 7); /* 7:set phy RMII 50MHz clk; */
phy_write(phy_dev, 0x1F, v);
return 0;
}
void hleth_phy_register_fixups(void)
{
phy_register_fixup_for_uid(HUANGLONG_PHY_ID_FESTA_S28V200,
HUANGLONG_PHY_ID_MASK,
hl_fephy_s28v200_fix);
phy_register_fixup_for_uid(PHY_ID_KSZ8051MNL,
DEFAULT_PHY_MASK, ksz8051mnl_phy_fix);
phy_register_fixup_for_uid(PHY_ID_KSZ8081RNB,
DEFAULT_PHY_MASK, ksz8081rnb_phy_fix);
}
void hleth_phy_unregister_fixups(void)
{
phy_unregister_fixup_for_uid(HUANGLONG_PHY_ID_FESTA_S28V200,
HUANGLONG_PHY_ID_MASK);
phy_unregister_fixup_for_uid(PHY_ID_KSZ8051MNL, DEFAULT_PHY_MASK);
phy_unregister_fixup_for_uid(PHY_ID_KSZ8081RNB, DEFAULT_PHY_MASK);
}
static void hleth_internal_phy_clk_disable(void)
{
unsigned int val;
void *hleth_sys_regbase = ioremap_nocache(0xF8A22000, 0x1000);
/* FEPHY disable clock and keep reset */
val = readl(hleth_sys_regbase + HLETHPHY_SYSREG_REG);
val &= ~(HLETH_CLK_ENABLE);
val |= BIT(HLETH_SOFT_RESET_BIT);
writel(val, hleth_sys_regbase + HLETHPHY_SYSREG_REG);
iounmap(hleth_sys_regbase);
}
static void hleth_internal_phy_reset(struct hleth_phy_param_s *phy_param)
{
unsigned int val;
void *hleth_sys_regbase = ioremap_nocache(CRG_BASE, 0x1000);
void *hleth_fephy_base = ioremap_nocache(0xF8000000, 0x1000);
/* FEPHY enable clock */
val = readl(hleth_sys_regbase + HLETHPHY_SYSREG_REG);
val |= HLETH_CLK_ENABLE;
writel(val, hleth_sys_regbase + HLETHPHY_SYSREG_REG);
/* set FEPHY address */
val = readl(hleth_fephy_base + HLETH_FEPHY_ADDR);
val &= ~((MASK_PHY_ADDR) << HLETH_PHY_ADDR_BIT);
val |= ((phy_param->phy_addr & MASK_PHY_ADDR) << HLETH_PHY_ADDR_BIT);
writel(val, hleth_fephy_base + HLETH_FEPHY_ADDR);
/* FEPHY set reset */
val = readl(hleth_sys_regbase + HLETHPHY_SYSREG_REG);
val |= (1 << HLETH_SOFT_RESET_BIT);
writel(val, hleth_sys_regbase + HLETHPHY_SYSREG_REG);
usleep_range(10, 1000); /* 10,1000:delay zone */
/* FEPHY cancel reset */
val = readl(hleth_sys_regbase + HLETHPHY_SYSREG_REG);
val &= ~(1 << HLETH_SOFT_RESET_BIT);
writel(val, hleth_sys_regbase + HLETHPHY_SYSREG_REG);
msleep(20); /* 20:delay at least 15ms for MDIO operation */
iounmap(hleth_sys_regbase);
iounmap(hleth_fephy_base);
}
void hleth_gpio_reset(void __iomem *gpio_base, u32 gpio_bit)
{
u32 v;
#define RESET_DATA (1)
if (gpio_base == NULL)
return;
gpio_base = (void *)ioremap_nocache((uintptr_t)gpio_base, 0x1000);
/* config gpio[x] dir to output */
v = readb(gpio_base + 0x400);
v |= (1 << gpio_bit);
writeb(v, gpio_base + 0x400);
/* output 1--0--1 */
writeb(RESET_DATA << gpio_bit, gpio_base + (4 << gpio_bit));
msleep(20);
writeb((!RESET_DATA) << gpio_bit, gpio_base + (4 << gpio_bit));
msleep(20);
writeb(RESET_DATA << gpio_bit, gpio_base + (4 << gpio_bit));
msleep(20);
iounmap(gpio_base);
}
static void hleth_external_phy_reset(struct hleth_phy_param_s *phy_param)
{
unsigned int val;
void *hleth_sys_regbase = ioremap_nocache(0xF8A22000, 0x1000);
void *hleth_fephy_base = ioremap_nocache(0xF8A20000, 0x1000);
/************************************************/
/* reset external phy with default reset pin */
val = readl(hleth_sys_regbase + HLETH_FEPHY_RST_BASE);
val |= (1 << HLETH_FEPHY_RST_BIT);
writel(val, hleth_sys_regbase + HLETH_FEPHY_RST_BASE);
msleep(20); /* 20:delay */
/* then, cancel reset, and should delay 200ms */
val &= ~(1 << HLETH_FEPHY_RST_BIT);
writel(val, hleth_sys_regbase + HLETH_FEPHY_RST_BASE);
msleep(20); /* 20:delay */
val |= 1 << HLETH_FEPHY_RST_BIT;
writel(val, hleth_sys_regbase + HLETH_FEPHY_RST_BASE);
/************************************************/
/* reset external phy with gpio */
hleth_gpio_reset(phy_param->gpio_base, phy_param->gpio_bit);
/************************************************/
/* add some delay in case mdio cann't access now! */
msleep(30); /* 30:delay */
iounmap(hleth_sys_regbase);
iounmap(hleth_fephy_base);
}
void hleth_phy_reset(struct hleth_platdrv_data *pdata)
{
int i;
struct hleth_phy_param_s *phy_param = NULL;
if (pdata == NULL)
return;
for (i = 0; i < HLETH_MAX_PORT; i++) {
phy_param = &pdata->hleth_phy_param[i];
if (!phy_param->isvalid)
continue;
if (phy_param->isinternal)
hleth_internal_phy_reset(phy_param);
else
hleth_external_phy_reset(phy_param);
}
}
void hleth_phy_clk_disable(struct hleth_platdrv_data *pdata)
{
int i;
struct hleth_phy_param_s *phy_param = NULL;
if (pdata == NULL)
return;
for (i = 0; i < HLETH_MAX_PORT; i++) {
phy_param = &pdata->hleth_phy_param[i];
if (!phy_param->isvalid)
continue;
if (phy_param->isinternal)
hleth_internal_phy_clk_disable();
}
}