mirror of
https://github.com/revyos/thead-kernel.git
synced 2026-06-21 09:12:26 +02:00
1000 lines
29 KiB
C
1000 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include "stmmac_platform.h"
|
|
|
|
/* clock registers */
|
|
#define GMAC_CLK_CFG0 0x00
|
|
#define GMAC_CLK_CFG1 0x04
|
|
#define GMAC_CLK_CFG2 0x08
|
|
#define GMAC_CLK_CFG3 0x0C
|
|
#define GMAC_CLK_CFG4 0x10
|
|
#define GMAC_CLK_CFG5 0x14
|
|
#define GMAC_CLK_CFG6 0x18
|
|
|
|
/* phy interface */
|
|
#define DWMAC_PHYIF_MII_GMII 0
|
|
#define DWMAC_PHYIF_RGMII 1
|
|
#define DWMAC_PHYIF_RMII 4
|
|
/* register bit fields, bit[3]: reserved, bit[2:0]: phy interface */
|
|
#define DWMAC_PHYIF_MASK 0x7
|
|
#define DWMAC_PHYIF_BIT_WIDTH 4
|
|
|
|
/* TXCLK direction, 1:input, 0:output */
|
|
#define TXCLK_DIR_OUTPUT 0
|
|
#define TXCLK_DIR_INPUT 1
|
|
|
|
#define GMAC_CLK_PLLOUT_250M 250000000
|
|
#define GMAC_GMII_RGMII_RATE 125000000
|
|
#define GMAC_MII_RATE 25000000
|
|
/* clock divider for speed */
|
|
#define GMAC_CLKDIV_125M (GMAC_CLK_PLLOUT_250M / GMAC_GMII_RGMII_RATE)
|
|
#define GMAC_CLKDIV_25M (GMAC_CLK_PLLOUT_250M / GMAC_MII_RATE)
|
|
#define GMAC_PTP_CLK_RATE 50000000 //50MHz
|
|
|
|
struct thead_dwmac_ops {
|
|
void (*set_clk_source)(struct plat_stmmacenet_data *plat_dat);
|
|
void (*set_clk_pll)(struct plat_stmmacenet_data *plat_dat);
|
|
void (*set_clk_div)(struct plat_stmmacenet_data *plat_dat, unsigned int speed);
|
|
void (*enable_clk)(struct plat_stmmacenet_data *plat_dat);
|
|
void (*set_ptp_div)(struct plat_stmmacenet_data *plat_dat,unsigned int ptp_clk_rate);
|
|
};
|
|
|
|
struct thead_dwmac_priv_data {
|
|
int id;
|
|
struct device *dev;
|
|
void __iomem *phy_if_reg;
|
|
void __iomem *txclk_dir_reg;
|
|
void __iomem *gmac_clk_reg;
|
|
phy_interface_t interface;
|
|
struct clk *gmac_pll_clk;
|
|
unsigned long gmac_pll_clk_freq;
|
|
struct clk *gmac_axi_aclk;
|
|
struct clk *gmac_axi_pclk;
|
|
const struct thead_dwmac_ops *ops;
|
|
struct plat_stmmacenet_data *plat_dat;
|
|
};
|
|
|
|
#define pm_debug dev_dbg // for suspend/resume interface debug info show,replace to dev_info
|
|
|
|
/* set GMAC PHY interface, 0:MII/GMII, 1:RGMII, 4:RMII */
|
|
static void thead_dwmac_set_phy_if(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
void __iomem *phy_if_reg = thead_plat_dat->phy_if_reg;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
//int devid = thead_plat_dat->id;
|
|
unsigned int phyif = PHY_INTERFACE_MODE_MII;
|
|
uint32_t reg;
|
|
|
|
if (phy_if_reg == NULL)
|
|
return;
|
|
|
|
switch (interface)
|
|
{
|
|
case PHY_INTERFACE_MODE_MII:
|
|
case PHY_INTERFACE_MODE_GMII:
|
|
phyif = DWMAC_PHYIF_MII_GMII;
|
|
break;
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
phyif = DWMAC_PHYIF_RGMII;
|
|
break;
|
|
case PHY_INTERFACE_MODE_RMII:
|
|
phyif = DWMAC_PHYIF_RMII;
|
|
break;
|
|
default:
|
|
dev_err(dev, "phy interface %d not supported\n", interface);
|
|
return;
|
|
};
|
|
|
|
reg = readl(phy_if_reg);
|
|
//This reg defined bit not related to devid
|
|
reg &= ~(DWMAC_PHYIF_MASK );
|
|
reg |= (phyif & DWMAC_PHYIF_MASK) ;
|
|
dev_info(dev,"set phy_if_reg val 0x%x \n",reg);
|
|
writel(reg, phy_if_reg);
|
|
}
|
|
|
|
/*
|
|
* set GMAC TXCLK direction
|
|
* MII : TXCLK is input
|
|
* GMII/RGMII : TXCLK is output
|
|
*/
|
|
static void thead_dwmac_set_txclk_dir(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
void __iomem *txclk_dir_reg = thead_plat_dat->txclk_dir_reg;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
unsigned int txclk_dir = TXCLK_DIR_INPUT;
|
|
|
|
if (txclk_dir_reg == NULL)
|
|
return;
|
|
|
|
switch (interface)
|
|
{
|
|
case PHY_INTERFACE_MODE_MII:
|
|
case PHY_INTERFACE_MODE_RMII:
|
|
txclk_dir = TXCLK_DIR_INPUT;
|
|
break;
|
|
case PHY_INTERFACE_MODE_GMII:
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
txclk_dir = TXCLK_DIR_OUTPUT;
|
|
break;
|
|
default:
|
|
dev_err(dev, "phy interface %d not supported\n", interface);
|
|
return;
|
|
};
|
|
|
|
writel(txclk_dir, txclk_dir_reg);
|
|
}
|
|
|
|
static void thead_dwmac_ice_set_clk_source(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
uint32_t reg;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG0);
|
|
|
|
/* RX clock source */
|
|
reg |= BIT(7); /* gmac_rx_clk_sel: extern pin */
|
|
|
|
/* TX clock source */
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
reg |= BIT(1); /* gmac_tx_clk_sel: extern pin */
|
|
reg &= ~BIT(2); /* gmac_tx_clk_gbit_sel: u_tx_clk_mux */
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII) {
|
|
reg &= ~BIT(5); /* gmac_tx_clk_out_sel: GMAC PLL */
|
|
reg |= BIT(2); /* gmac_tx_clk_gbit_sel: GMAC PLL */
|
|
} else if (interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
reg &= ~BIT(5); /* gmac_tx_clk_out_sel: GMAC PLL */
|
|
reg |= BIT(2); /* gmac_tx_clk_gbit_sel: GMAC PLL */
|
|
} else {
|
|
dev_err(dev, "phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG0);
|
|
}
|
|
|
|
|
|
/* set clock source */
|
|
static void thead_dwmac_set_clock_delay(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
unsigned int delay;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
if (of_property_read_u32(dev->of_node, "rx-clk-delay",
|
|
&delay) == 0) {
|
|
/* RX clk delay */
|
|
writel(delay, gmac_clk_reg + GMAC_CLK_CFG1);
|
|
pr_info("RX clk delay: 0x%X\n", delay);
|
|
}
|
|
|
|
if (of_property_read_u32(dev->of_node, "tx-clk-delay",
|
|
&delay) == 0) {
|
|
/* TX clk delay */
|
|
writel(delay, gmac_clk_reg + GMAC_CLK_CFG2);
|
|
pr_info("TX clk delay: 0x%X\n", delay);
|
|
}
|
|
}
|
|
|
|
/* set gmac pll divider (u_pll_clk_div) to get 250MHz clock */
|
|
static void thead_dwmac_ice_set_pll(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
unsigned int src_freq = thead_plat_dat->gmac_pll_clk_freq;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
unsigned int reg;
|
|
unsigned int div;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
/* for mii, no internal pll is used */
|
|
return;
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
|
|
/* check clock */
|
|
if ((src_freq == 0) || (src_freq % GMAC_CLK_PLLOUT_250M != 0)) {
|
|
pr_err("error! invalid gmac pll freq %d\n", src_freq);
|
|
return;
|
|
}
|
|
div = src_freq / GMAC_CLK_PLLOUT_250M;
|
|
|
|
/* disable pll_clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG3);
|
|
reg &= ~BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
|
|
/* modify divider */
|
|
writel(div, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
|
|
/* enable pll_clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG3);
|
|
reg |= BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
} else {
|
|
pr_err("phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* set gmac speed */
|
|
static void thead_dwmac_ice_set_clk_div(struct plat_stmmacenet_data *plat_dat, unsigned int speed)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
unsigned int reg;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
/* For MII, no internal PLL is used */
|
|
return;
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
|
|
/* disable gtx_clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG4);
|
|
reg &= ~BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG4);
|
|
|
|
/*
|
|
* modify divider
|
|
*/
|
|
/* gtx_clk_div */
|
|
if (speed == SPEED_1000) {
|
|
writel(GMAC_CLKDIV_125M, gmac_clk_reg + GMAC_CLK_CFG4);
|
|
} else if (speed == SPEED_100) {
|
|
writel(GMAC_CLKDIV_25M, gmac_clk_reg + GMAC_CLK_CFG4);
|
|
} else {
|
|
writel(GMAC_CLKDIV_25M / 10, gmac_clk_reg + GMAC_CLK_CFG4);
|
|
}
|
|
|
|
/* enable gtx_clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG4);
|
|
reg |= BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG4);
|
|
} else {
|
|
pr_err("phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void thead_dwmac_light_set_clk_div(struct plat_stmmacenet_data *plat_dat, unsigned int speed)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
unsigned long src_freq = thead_plat_dat->gmac_pll_clk_freq;
|
|
unsigned int div;
|
|
unsigned int reg;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
/* For MII, no internal PLL is used */
|
|
return;
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
|
|
/* check clock */
|
|
if ((src_freq == 0) || (src_freq % GMAC_GMII_RGMII_RATE != 0 ||
|
|
src_freq % GMAC_MII_RATE != 0)) {
|
|
pr_err("error! invalid gmac pll freq %lu\n", src_freq);
|
|
return;
|
|
}
|
|
|
|
/* disable gtx_clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG3);
|
|
reg &= ~BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
|
|
/*
|
|
* modify divider
|
|
*/
|
|
/* gtx_clk_div */
|
|
if (speed == SPEED_1000)
|
|
div = src_freq / GMAC_GMII_RGMII_RATE;
|
|
else if (speed == SPEED_100)
|
|
div = src_freq / GMAC_MII_RATE;
|
|
else
|
|
div = (src_freq * 10) / GMAC_MII_RATE;
|
|
writel(div, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
|
|
/* enable gtx_clk_div */
|
|
reg = div | BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG3);
|
|
} else {
|
|
pr_err("phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void thead_dwmac_light_set_ptp_clk_div(struct plat_stmmacenet_data *plat_dat,unsigned int ptp_clk_rate)
|
|
{
|
|
unsigned int div;
|
|
unsigned int reg;
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
unsigned long src_freq = thead_plat_dat->gmac_pll_clk_freq;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
if(!ptp_clk_rate || !src_freq)
|
|
{
|
|
pr_warn("invalid gmac pll freq %lu or ptp_clk_rate %d\n", src_freq,ptp_clk_rate);
|
|
return;
|
|
}
|
|
/* disable clk_div */
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG5);
|
|
reg &= ~BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG5);
|
|
|
|
div = src_freq / ptp_clk_rate;
|
|
writel(div,gmac_clk_reg + GMAC_CLK_CFG5);
|
|
|
|
/* enable clk_div */
|
|
reg = div | BIT(31);
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG5);
|
|
return ;
|
|
}
|
|
|
|
/* enable gmac clock */
|
|
static void thead_dwmac_ice_enable_clk(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
unsigned int reg;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG0);
|
|
|
|
/* enable gmac_hclk */
|
|
reg |= BIT(14);
|
|
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
reg |= BIT(8); /* enable gmac_rx_clk */
|
|
reg |= BIT(3); /* enable gmac_tx_clk */
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII) {
|
|
reg |= BIT(8); /* enable gmac_rx_clk */
|
|
reg |= BIT(3); /* enable gmac_tx_clk */
|
|
reg |= BIT(6); /* enable gmac_tx_clk_out */
|
|
} else if (interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
reg |= BIT(8); /* enable gmac_rx_clk */
|
|
reg |= BIT(3); /* enable gmac_tx_clk */
|
|
reg |= BIT(6); /* enable gmac_tx_clk_out */
|
|
reg |= BIT(9); /* enable gmac_rx_clk_n */
|
|
reg |= BIT(4); /* enable gmac_tx_clk_n */
|
|
} else {
|
|
dev_err(dev, "phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG0);
|
|
}
|
|
|
|
static void thead_dwmac_light_enable_clk(struct plat_stmmacenet_data *plat_dat)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = plat_dat->bsp_priv;
|
|
phy_interface_t interface = thead_plat_dat->interface;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
struct device *dev = thead_plat_dat->dev;
|
|
unsigned int reg = 0;
|
|
|
|
if (gmac_clk_reg == NULL)
|
|
return;
|
|
|
|
/* use internal pll */
|
|
writel(BIT(0), gmac_clk_reg + GMAC_CLK_CFG6);
|
|
|
|
if (interface == PHY_INTERFACE_MODE_MII) {
|
|
reg |= BIT(4); /* enable gmac_rx_clk */
|
|
reg |= BIT(1); /* enable gmac_tx_clk */
|
|
} else if (interface == PHY_INTERFACE_MODE_GMII) {
|
|
reg |= BIT(4); /* enable gmac_rx_clk */
|
|
reg |= BIT(1); /* enable gmac_tx_clk */
|
|
reg |= BIT(4); /* enable gmac_tx_clk_out */
|
|
} else if (interface == PHY_INTERFACE_MODE_RGMII
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_ID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_RXID
|
|
|| interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
reg |= BIT(4); /* enable gmac_rx_clk */
|
|
reg |= BIT(1); /* enable gmac_tx_clk */
|
|
reg |= BIT(3); /* enable gmac_tx_clk_out */
|
|
reg |= BIT(5); /* enable gmac_rx_clk_n */
|
|
reg |= BIT(2); /* enable gmac_tx_clk_n */
|
|
} else {
|
|
dev_err(dev, "phy interface %d not supported\n", interface);
|
|
return;
|
|
}
|
|
|
|
reg |= BIT(6); /* ephy ref clk */
|
|
writel(reg, gmac_clk_reg + GMAC_CLK_CFG0);
|
|
}
|
|
static int thead_dwmac_init(struct platform_device *pdev, void *bsp_priv)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
//struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct resource *res;
|
|
void __iomem *ptr;
|
|
//struct clk *clktmp;
|
|
//int ret;
|
|
|
|
thead_plat_dat->id = of_alias_get_id(np, "ethernet");
|
|
if (thead_plat_dat->id < 0) {
|
|
thead_plat_dat->id = 0;
|
|
}
|
|
dev_info(dev, "id: %d\n", thead_plat_dat->id);
|
|
|
|
thead_plat_dat->interface = device_get_phy_mode(&pdev->dev);
|
|
if (thead_plat_dat->interface < 0)
|
|
return -ENODEV;
|
|
dev_info(dev, "phy interface: %d\n", thead_plat_dat->interface);
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_if_reg");
|
|
if ((res != NULL) && (resource_type(res) == IORESOURCE_MEM)) {
|
|
ptr = devm_ioremap(dev, res->start, resource_size(res));
|
|
if (!ptr) {
|
|
dev_err(dev, "phy interface register not exist, skipped it\n");
|
|
} else {
|
|
thead_plat_dat->phy_if_reg = ptr;
|
|
}
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txclk_dir_reg");
|
|
ptr = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(ptr)) {
|
|
dev_err(dev, "txclk_dir register not exist, skipped it\n");
|
|
} else {
|
|
thead_plat_dat->txclk_dir_reg = ptr;
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "clk_mgr_reg");
|
|
ptr = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(ptr)) {
|
|
dev_err(dev, "gmac_clk register not exist, skipped it\n");
|
|
} else {
|
|
thead_plat_dat->gmac_clk_reg = ptr;
|
|
}
|
|
#if 0
|
|
/* get gmac pll clk */
|
|
clktmp = devm_clk_get(dev, "gmac_pll_clk");
|
|
if (IS_ERR(clktmp)) {
|
|
dev_err(dev, "gmac_pll_clk not exist, skipped it\n");
|
|
} else {
|
|
thead_plat_dat->gmac_pll_clk = clktmp;
|
|
|
|
ret = clk_prepare_enable(thead_plat_dat->gmac_pll_clk);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to enable clk 'gmac_pll_clk'\n");
|
|
return -1;
|
|
}
|
|
|
|
thead_plat_dat->gmac_pll_clk_freq =
|
|
clk_get_rate(thead_plat_dat->gmac_pll_clk);
|
|
}
|
|
|
|
|
|
thead_dwmac_set_phy_if(plat_dat);
|
|
thead_dwmac_set_txclk_dir(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_source)
|
|
thead_plat_dat->ops->set_clk_source(plat_dat);
|
|
|
|
thead_dwmac_set_clock_delay(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_pll)
|
|
thead_plat_dat->ops->set_clk_pll(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_div)
|
|
thead_plat_dat->ops->set_clk_div(plat_dat, SPEED_1000);
|
|
|
|
if (thead_plat_dat->ops->enable_clk)
|
|
thead_plat_dat->ops->enable_clk(plat_dat);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void thead_dwmac_fix_speed(void *bsp_priv, unsigned int speed)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
|
|
|
|
if (thead_plat_dat->ops->set_clk_div)
|
|
thead_plat_dat->ops->set_clk_div(plat_dat, speed);
|
|
}
|
|
|
|
/**
|
|
* dwmac1000_validate_mcast_bins - validates the number of Multicast filter bins
|
|
* @mcast_bins: Multicast filtering bins
|
|
* Description:
|
|
* this function validates the number of Multicast filtering bins specified
|
|
* by the configuration through the device tree. The Synopsys GMAC supports
|
|
* 64 bins, 128 bins, or 256 bins. "bins" refer to the division of CRC
|
|
* number space. 64 bins correspond to 6 bits of the CRC, 128 corresponds
|
|
* to 7 bits, and 256 refers to 8 bits of the CRC. Any other setting is
|
|
* invalid and will cause the filtering algorithm to use Multicast
|
|
* promiscuous mode.
|
|
*/
|
|
static int dwmac1000_validate_mcast_bins(int mcast_bins)
|
|
{
|
|
int x = mcast_bins;
|
|
|
|
switch (x) {
|
|
case HASH_TABLE_SIZE:
|
|
case 128:
|
|
case 256:
|
|
break;
|
|
default:
|
|
x = 0;
|
|
pr_info("Hash table entries set to unexpected value %d",
|
|
mcast_bins);
|
|
break;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
/**
|
|
* dwmac1000_validate_ucast_entries - validate the Unicast address entries
|
|
* @ucast_entries: number of Unicast address entries
|
|
* Description:
|
|
* This function validates the number of Unicast address entries supported
|
|
* by a particular Synopsys 10/100/1000 controller. The Synopsys controller
|
|
* supports 1..32, 64, or 128 Unicast filter entries for it's Unicast filter
|
|
* logic. This function validates a valid, supported configuration is
|
|
* selected, and defaults to 1 Unicast address if an unsupported
|
|
* configuration is selected.
|
|
*/
|
|
static int dwmac1000_validate_ucast_entries(int ucast_entries)
|
|
{
|
|
int x = ucast_entries;
|
|
|
|
switch (x) {
|
|
case 1 ... 32:
|
|
case 64:
|
|
case 128:
|
|
break;
|
|
default:
|
|
x = 1;
|
|
pr_info("Unicast table entries set to unexpected value %d\n",
|
|
ucast_entries);
|
|
break;
|
|
}
|
|
return x;
|
|
}
|
|
static void __maybe_unused thead_dwmac_dump_priv_reg(struct platform_device *pdev, void *bsp_priv)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
struct device *dev = &pdev->dev;
|
|
void __iomem *gmac_clk_reg = thead_plat_dat->gmac_clk_reg;
|
|
unsigned int reg = 0;
|
|
int i;
|
|
dev_info(dev,"dump gmac_clk_reg %p\n",gmac_clk_reg);
|
|
if(gmac_clk_reg == NULL)
|
|
return ;
|
|
for(i=0; i< 0x1c; i+=4)
|
|
{
|
|
reg = readl(gmac_clk_reg + GMAC_CLK_CFG0+i);
|
|
pr_info("%08x ",reg);
|
|
}
|
|
pr_info("\n");
|
|
reg = readl(thead_plat_dat->phy_if_reg);
|
|
pr_info("phy_if_reg %08x ",reg);
|
|
reg = readl(thead_plat_dat->txclk_dir_reg);
|
|
pr_info("txclk_dir_reg %08x ",reg);
|
|
}
|
|
|
|
int thead_dwmac_clk_enable(struct platform_device *pdev, void *bsp_priv)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
ret = clk_prepare_enable(thead_plat_dat->gmac_pll_clk);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to enable clk 'gmac_pll_clk'\n");
|
|
return -1;
|
|
}
|
|
ret = clk_prepare_enable(thead_plat_dat->gmac_axi_aclk);
|
|
if (ret) {
|
|
clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
|
|
dev_err(dev, "Failed to enable clk 'gmac_axi_aclk'\n");
|
|
return -1;
|
|
}
|
|
ret = clk_prepare_enable(thead_plat_dat->gmac_axi_pclk);
|
|
if (ret) {
|
|
clk_disable_unprepare(thead_plat_dat->gmac_axi_aclk);
|
|
clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
|
|
dev_err(dev, "Failed to enable clk 'gmac_axi_pclk'\n");
|
|
return -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int thead_dwmac_clk_init(struct platform_device *pdev, void *bsp_priv)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
struct device *dev = &pdev->dev;
|
|
struct plat_stmmacenet_data *plat_dat = thead_plat_dat->plat_dat;
|
|
int ret = 0;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
|
|
thead_dwmac_set_phy_if(plat_dat);
|
|
thead_dwmac_set_txclk_dir(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_source)
|
|
thead_plat_dat->ops->set_clk_source(plat_dat);
|
|
|
|
thead_dwmac_set_clock_delay(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_pll)
|
|
thead_plat_dat->ops->set_clk_pll(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_clk_div)
|
|
thead_plat_dat->ops->set_clk_div(plat_dat, SPEED_1000);
|
|
|
|
if (thead_plat_dat->ops->enable_clk)
|
|
thead_plat_dat->ops->enable_clk(plat_dat);
|
|
|
|
if (thead_plat_dat->ops->set_ptp_div)
|
|
thead_plat_dat->ops->set_ptp_div(plat_dat,plat_dat->clk_ptp_rate);
|
|
//thead_dwmac_dump_priv_reg(pdev,bsp_priv);
|
|
return ret;
|
|
}
|
|
void thead_dwmac_clk_disable(struct platform_device *pdev, void *bsp_priv)
|
|
{
|
|
struct thead_dwmac_priv_data *thead_plat_dat = bsp_priv;
|
|
struct device *dev = &pdev->dev;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
//thead_dwmac_dump_priv_reg(pdev,bsp_priv);
|
|
|
|
clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
|
|
clk_disable_unprepare(thead_plat_dat->gmac_axi_aclk);
|
|
clk_disable_unprepare(thead_plat_dat->gmac_pll_clk);
|
|
|
|
return ;
|
|
}
|
|
|
|
static int thead_dwmac_probe(struct platform_device *pdev)
|
|
{
|
|
struct plat_stmmacenet_data *plat_dat;
|
|
struct stmmac_resources stmmac_res;
|
|
struct thead_dwmac_priv_data *thead_plat_dat;
|
|
struct device *dev = &pdev->dev;
|
|
const struct thead_dwmac_ops *data;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret;
|
|
|
|
thead_plat_dat = devm_kzalloc(dev, sizeof(*thead_plat_dat), GFP_KERNEL);
|
|
if (thead_plat_dat == NULL) {
|
|
dev_err(&pdev->dev, "allocate memory failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = stmmac_get_platform_resources(pdev, &stmmac_res);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (pdev->dev.of_node) {
|
|
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
|
|
if (IS_ERR(plat_dat)) {
|
|
dev_err(&pdev->dev, "dt configuration failed\n");
|
|
return PTR_ERR(plat_dat);
|
|
}
|
|
|
|
data = of_device_get_match_data(&pdev->dev);
|
|
if (!data) {
|
|
dev_err(&pdev->dev, "failed to get match data\n");
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
thead_plat_dat->ops = data;
|
|
} else {
|
|
plat_dat = dev_get_platdata(&pdev->dev);
|
|
if (!plat_dat) {
|
|
dev_err(&pdev->dev, "no platform data provided\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set default value for multicast hash bins */
|
|
plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
|
|
|
|
/* Set default value for unicast filter entries */
|
|
plat_dat->unicast_filter_entries = 1;
|
|
}
|
|
|
|
|
|
|
|
/* populate bsp private data */
|
|
thead_plat_dat->dev = &pdev->dev;
|
|
plat_dat->bsp_priv = thead_plat_dat;
|
|
plat_dat->fix_mac_speed = thead_dwmac_fix_speed;
|
|
plat_dat->init = thead_dwmac_clk_init;
|
|
of_property_read_u32(np, "max-frame-size", &plat_dat->maxmtu);
|
|
of_property_read_u32(np, "snps,multicast-filter-bins",
|
|
&plat_dat->multicast_filter_bins);
|
|
of_property_read_u32(np, "snps,perfect-filter-entries",
|
|
&plat_dat->unicast_filter_entries);
|
|
plat_dat->unicast_filter_entries = dwmac1000_validate_ucast_entries(
|
|
plat_dat->unicast_filter_entries);
|
|
plat_dat->multicast_filter_bins = dwmac1000_validate_mcast_bins(
|
|
plat_dat->multicast_filter_bins);
|
|
plat_dat->has_gmac = 1;
|
|
plat_dat->pmt = 1;
|
|
thead_plat_dat->plat_dat = plat_dat;
|
|
|
|
/* get gmac pll clk */
|
|
thead_plat_dat->gmac_pll_clk = devm_clk_get(dev, "gmac_pll_clk");
|
|
if (IS_ERR(thead_plat_dat->gmac_pll_clk)) {
|
|
dev_err(dev, "gmac_pll_clk not exist, dts error\n");
|
|
goto err_remove_config_dt;
|
|
}
|
|
|
|
thead_plat_dat->gmac_axi_aclk = devm_clk_get(dev, "axi_aclk");
|
|
if (IS_ERR(thead_plat_dat->gmac_axi_aclk)) {
|
|
dev_err(dev, "gmac axi_aclk not exist, skipped it\n");
|
|
}
|
|
thead_plat_dat->gmac_axi_pclk = devm_clk_get(dev, "axi_pclk");
|
|
if (IS_ERR(thead_plat_dat->gmac_axi_pclk)) {
|
|
dev_err(dev, "gmac axi_pclk not exist, skipped it\n");
|
|
}
|
|
|
|
|
|
thead_plat_dat->gmac_pll_clk_freq =
|
|
clk_get_rate(thead_plat_dat->gmac_pll_clk);
|
|
dev_info(dev,"get_rate gmac_pll_clk_freq %ld \n",thead_plat_dat->gmac_pll_clk_freq);
|
|
|
|
ret = thead_dwmac_init(pdev, plat_dat->bsp_priv);
|
|
if (ret)
|
|
goto err_remove_config_dt;
|
|
|
|
plat_dat->clk_ptp_rate = GMAC_PTP_CLK_RATE;
|
|
|
|
ret = thead_dwmac_clk_enable(pdev, plat_dat->bsp_priv);
|
|
if (ret)
|
|
goto err_remove_config_dt;
|
|
|
|
/* Custom initialisation (if needed) -- init clks*/
|
|
if (plat_dat->init) {
|
|
ret = plat_dat->init(pdev, plat_dat->bsp_priv);
|
|
if (ret)
|
|
goto err_exit;
|
|
}
|
|
|
|
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
|
|
if (ret)
|
|
goto err_exit;
|
|
|
|
return 0;
|
|
|
|
err_exit:
|
|
if (plat_dat->exit)
|
|
plat_dat->exit(pdev, plat_dat->bsp_priv);
|
|
thead_dwmac_clk_disable(pdev, plat_dat->bsp_priv);
|
|
err_remove_config_dt:
|
|
if (pdev->dev.of_node)
|
|
stmmac_remove_config_dt(pdev, plat_dat);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* stmmac_pltfr_suspend
|
|
* @dev: device pointer
|
|
* Description: this function is invoked when suspend the driver and it direcly
|
|
* call the main suspend function and then, if required, on some platform, it
|
|
* can call an exit helper.
|
|
*/
|
|
static int __maybe_unused thead_dwmac_suspend(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
ret = stmmac_suspend(dev);
|
|
if (priv->plat->exit)
|
|
priv->plat->exit(pdev, priv->plat->bsp_priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* thead_dwmac_resume
|
|
* @dev: device pointer
|
|
* Description: this function is invoked when resume the driver before calling
|
|
* the main resume function, on some platforms, it can call own init helper
|
|
* if required.
|
|
*/
|
|
static int __maybe_unused thead_dwmac_resume(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
|
|
if (!netif_running(ndev))
|
|
return 0;
|
|
|
|
pm_runtime_get_sync(dev);
|
|
if (priv->plat->init)
|
|
priv->plat->init(pdev, priv->plat->bsp_priv);
|
|
pm_runtime_put(dev);
|
|
|
|
return stmmac_resume(dev);
|
|
}
|
|
|
|
static int __maybe_unused thead_dwmac_runtime_suspend(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
stmmac_bus_clks_config(priv, false);
|
|
thead_dwmac_clk_disable(pdev, priv->plat->bsp_priv);
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused thead_dwmac_runtime_resume(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
int ret;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
ret = stmmac_bus_clks_config(priv, true);
|
|
if(ret)
|
|
return ret;
|
|
ret = thead_dwmac_clk_enable(pdev, priv->plat->bsp_priv);
|
|
if(ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused thead_dwmac_noirq_suspend(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
int ret;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
if (!netif_running(ndev))
|
|
return 0;
|
|
|
|
if (!device_may_wakeup(priv->device) || !priv->plat->pmt) {
|
|
/* Disable clock in case of PWM is off */
|
|
clk_disable_unprepare(priv->plat->clk_ptp_ref);
|
|
|
|
ret = pm_runtime_force_suspend(dev);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused thead_dwmac_noirq_resume(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
int ret;
|
|
pm_debug(dev,"enter %s()\n",__func__);
|
|
if (!netif_running(ndev))
|
|
return 0;
|
|
|
|
if (!device_may_wakeup(priv->device) || !priv->plat->pmt) {
|
|
/* enable the clk previously disabled */
|
|
ret = pm_runtime_force_resume(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
|
|
if (ret < 0) {
|
|
netdev_warn(priv->dev,
|
|
"failed to enable PTP reference clock: %pe\n",
|
|
ERR_PTR(ret));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*similar with stmmac_pltfr_pm_ops,but clks enable/disable add this drv need */
|
|
const struct dev_pm_ops thead_dwmac_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(thead_dwmac_suspend, thead_dwmac_resume)
|
|
SET_RUNTIME_PM_OPS(thead_dwmac_runtime_suspend, thead_dwmac_runtime_resume, NULL)
|
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(thead_dwmac_noirq_suspend, thead_dwmac_noirq_resume)
|
|
};
|
|
|
|
|
|
static struct thead_dwmac_ops thead_ice_dwmac_data = {
|
|
.set_clk_source = thead_dwmac_ice_set_clk_source,
|
|
.set_clk_pll = thead_dwmac_ice_set_pll,
|
|
.set_clk_div = thead_dwmac_ice_set_clk_div,
|
|
.enable_clk = thead_dwmac_ice_enable_clk,
|
|
};
|
|
|
|
static struct thead_dwmac_ops thead_light_dwmac_data = {
|
|
.set_clk_div = thead_dwmac_light_set_clk_div,
|
|
.enable_clk = thead_dwmac_light_enable_clk,
|
|
.set_ptp_div = thead_dwmac_light_set_ptp_clk_div,
|
|
};
|
|
|
|
static const struct of_device_id thead_dwmac_match[] = {
|
|
{ .compatible = "thead,ice-dwmac", .data = &thead_ice_dwmac_data },
|
|
{ .compatible = "thead,light-dwmac", .data = &thead_light_dwmac_data },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, thead_dwmac_match);
|
|
|
|
static struct platform_driver thead_dwmac_driver = {
|
|
.probe = thead_dwmac_probe,
|
|
.remove = stmmac_pltfr_remove,
|
|
.driver = {
|
|
.name = "light_dwmac_eth",
|
|
.pm = &thead_dwmac_pm_ops,
|
|
.of_match_table = of_match_ptr(thead_dwmac_match),
|
|
},
|
|
};
|
|
module_platform_driver(thead_dwmac_driver);
|
|
|
|
MODULE_AUTHOR("THEAD");
|
|
MODULE_DESCRIPTION("T-HEAD dwmac driver");
|
|
MODULE_LICENSE("GPL v2");
|