Merge branch 'stmmac-25gbps'

Michael Sit Wei Hong says:

====================
Enable 2.5Gbps speed for stmmac

Intel mGbE supports 2.5Gbps link speed by overclocking the clock rate
by 2.5 times to support 2.5Gbps link speed. In this mode, the serdes/PHY
operates at a serial baud rate of 3.125 Gbps and the PCS data path and
GMII interface of the MAC operate at 312.5 MHz instead of 125 MHz.
This is configured in the BIOS during boot up. The kernel driver is not able
access to modify the clock rate for 1Gbps/2.5G mode on the fly. The way to
determine the current 1G/2.5G mode is by reading a dedicated adhoc
register through mdio bus.

Changes:
v5 -> v6
 patch 1/3
 - Check if mdio_bus_data is populated to prevent NULL pointer dereferencing
   when accesing mdio_bus_data member

v4 -> v5
 patch 1/3
 - Rebase to latest code changes after Vladimir's code is merged and fix
   build warnings

v3 -> v4
 patch 1/3
 - Rebase to latest code and Initialize 'found' to 0 to avoid build warning

 patch 2/3
 - Fix indentation issue from v3

v2 -> v3
 patch 1/3
 -New patch added to restructure the code. enabling reading the dedicated
  adhoc register to determine link speed mode.

 patch 2/3
 -Restructure for 2.5G speed to use 2500BaseX configuration as the
  PHY interface.

 patch 3/3
 -Restructure to read serdes registers to set max_speed and configure to
  use 2500BaseX in 2.5G speeds.

v1 -> v2
 patch 1/2
 -Remove MAC supported link speed masking

 patch 2/2
 -Add supported link speed masking in the PCS

iperf3 and ping for 2.5Gbps and regression test on 10M/100M/1000Mbps
is done to prevent regresson issues.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2021-06-08 14:31:43 -07:00
commit 95848099a3
9 changed files with 171 additions and 30 deletions

View File

@ -102,6 +102,22 @@ static int intel_serdes_powerup(struct net_device *ndev, void *priv_data)
serdes_phy_addr = intel_priv->mdio_adhoc_addr; serdes_phy_addr = intel_priv->mdio_adhoc_addr;
/* Set the serdes rate and the PCLK rate */
data = mdiobus_read(priv->mii, serdes_phy_addr,
SERDES_GCR0);
data &= ~SERDES_RATE_MASK;
data &= ~SERDES_PCLK_MASK;
if (priv->plat->max_speed == 2500)
data |= SERDES_RATE_PCIE_GEN2 << SERDES_RATE_PCIE_SHIFT |
SERDES_PCLK_37p5MHZ << SERDES_PCLK_SHIFT;
else
data |= SERDES_RATE_PCIE_GEN1 << SERDES_RATE_PCIE_SHIFT |
SERDES_PCLK_70MHZ << SERDES_PCLK_SHIFT;
mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
/* assert clk_req */ /* assert clk_req */
data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0); data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
data |= SERDES_PLL_CLK; data |= SERDES_PLL_CLK;
@ -230,6 +246,32 @@ static void intel_serdes_powerdown(struct net_device *ndev, void *intel_data)
} }
} }
static void intel_speed_mode_2500(struct net_device *ndev, void *intel_data)
{
struct intel_priv_data *intel_priv = intel_data;
struct stmmac_priv *priv = netdev_priv(ndev);
int serdes_phy_addr = 0;
u32 data = 0;
serdes_phy_addr = intel_priv->mdio_adhoc_addr;
/* Determine the link speed mode: 2.5Gbps/1Gbps */
data = mdiobus_read(priv->mii, serdes_phy_addr,
SERDES_GCR);
if (((data & SERDES_LINK_MODE_MASK) >> SERDES_LINK_MODE_SHIFT) ==
SERDES_LINK_MODE_2G5) {
dev_info(priv->device, "Link Speed Mode: 2.5Gbps\n");
priv->plat->max_speed = 2500;
priv->plat->phy_interface = PHY_INTERFACE_MODE_2500BASEX;
priv->plat->mdio_bus_data->xpcs_an_inband = false;
} else {
priv->plat->max_speed = 1000;
priv->plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
priv->plat->mdio_bus_data->xpcs_an_inband = true;
}
}
/* Program PTP Clock Frequency for different variant of /* Program PTP Clock Frequency for different variant of
* Intel mGBE that has slightly different GPO mapping * Intel mGBE that has slightly different GPO mapping
*/ */
@ -586,7 +628,7 @@ static int ehl_sgmii_data(struct pci_dev *pdev,
{ {
plat->bus_id = 1; plat->bus_id = 1;
plat->phy_interface = PHY_INTERFACE_MODE_SGMII; plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
plat->speed_mode_2500 = intel_speed_mode_2500;
plat->serdes_powerup = intel_serdes_powerup; plat->serdes_powerup = intel_serdes_powerup;
plat->serdes_powerdown = intel_serdes_powerdown; plat->serdes_powerdown = intel_serdes_powerdown;
@ -639,6 +681,7 @@ static int ehl_pse0_sgmii1g_data(struct pci_dev *pdev,
struct plat_stmmacenet_data *plat) struct plat_stmmacenet_data *plat)
{ {
plat->phy_interface = PHY_INTERFACE_MODE_SGMII; plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
plat->speed_mode_2500 = intel_speed_mode_2500;
plat->serdes_powerup = intel_serdes_powerup; plat->serdes_powerup = intel_serdes_powerup;
plat->serdes_powerdown = intel_serdes_powerdown; plat->serdes_powerdown = intel_serdes_powerdown;
return ehl_pse0_common_data(pdev, plat); return ehl_pse0_common_data(pdev, plat);
@ -677,6 +720,7 @@ static int ehl_pse1_sgmii1g_data(struct pci_dev *pdev,
struct plat_stmmacenet_data *plat) struct plat_stmmacenet_data *plat)
{ {
plat->phy_interface = PHY_INTERFACE_MODE_SGMII; plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
plat->speed_mode_2500 = intel_speed_mode_2500;
plat->serdes_powerup = intel_serdes_powerup; plat->serdes_powerup = intel_serdes_powerup;
plat->serdes_powerdown = intel_serdes_powerdown; plat->serdes_powerdown = intel_serdes_powerdown;
return ehl_pse1_common_data(pdev, plat); return ehl_pse1_common_data(pdev, plat);
@ -711,6 +755,7 @@ static int tgl_sgmii_phy0_data(struct pci_dev *pdev,
{ {
plat->bus_id = 1; plat->bus_id = 1;
plat->phy_interface = PHY_INTERFACE_MODE_SGMII; plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
plat->speed_mode_2500 = intel_speed_mode_2500;
plat->serdes_powerup = intel_serdes_powerup; plat->serdes_powerup = intel_serdes_powerup;
plat->serdes_powerdown = intel_serdes_powerdown; plat->serdes_powerdown = intel_serdes_powerdown;
return tgl_common_data(pdev, plat); return tgl_common_data(pdev, plat);
@ -725,6 +770,7 @@ static int tgl_sgmii_phy1_data(struct pci_dev *pdev,
{ {
plat->bus_id = 2; plat->bus_id = 2;
plat->phy_interface = PHY_INTERFACE_MODE_SGMII; plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
plat->speed_mode_2500 = intel_speed_mode_2500;
plat->serdes_powerup = intel_serdes_powerup; plat->serdes_powerup = intel_serdes_powerup;
plat->serdes_powerdown = intel_serdes_powerdown; plat->serdes_powerdown = intel_serdes_powerdown;
return tgl_common_data(pdev, plat); return tgl_common_data(pdev, plat);

View File

@ -9,6 +9,7 @@
#define POLL_DELAY_US 8 #define POLL_DELAY_US 8
/* SERDES Register */ /* SERDES Register */
#define SERDES_GCR 0x0 /* Global Conguration */
#define SERDES_GSR0 0x5 /* Global Status Reg0 */ #define SERDES_GSR0 0x5 /* Global Status Reg0 */
#define SERDES_GCR0 0xb /* Global Configuration Reg0 */ #define SERDES_GCR0 0xb /* Global Configuration Reg0 */
@ -17,8 +18,20 @@
#define SERDES_PHY_RX_CLK BIT(1) /* PSE SGMII PHY rx clk */ #define SERDES_PHY_RX_CLK BIT(1) /* PSE SGMII PHY rx clk */
#define SERDES_RST BIT(2) /* Serdes Reset */ #define SERDES_RST BIT(2) /* Serdes Reset */
#define SERDES_PWR_ST_MASK GENMASK(6, 4) /* Serdes Power state*/ #define SERDES_PWR_ST_MASK GENMASK(6, 4) /* Serdes Power state*/
#define SERDES_RATE_MASK GENMASK(9, 8)
#define SERDES_PCLK_MASK GENMASK(14, 12) /* PCLK rate to PHY */
#define SERDES_LINK_MODE_MASK GENMASK(2, 1)
#define SERDES_LINK_MODE_SHIFT 1
#define SERDES_PWR_ST_SHIFT 4 #define SERDES_PWR_ST_SHIFT 4
#define SERDES_PWR_ST_P0 0x0 #define SERDES_PWR_ST_P0 0x0
#define SERDES_PWR_ST_P3 0x3 #define SERDES_PWR_ST_P3 0x3
#define SERDES_LINK_MODE_2G5 0x3
#define SERSED_LINK_MODE_1G 0x2
#define SERDES_PCLK_37p5MHZ 0x0
#define SERDES_PCLK_70MHZ 0x1
#define SERDES_RATE_PCIE_GEN1 0x0
#define SERDES_RATE_PCIE_GEN2 0x1
#define SERDES_RATE_PCIE_SHIFT 8
#define SERDES_PCLK_SHIFT 12
#endif /* __DWMAC_INTEL_H__ */ #endif /* __DWMAC_INTEL_H__ */

View File

@ -1358,6 +1358,7 @@ int dwmac4_setup(struct stmmac_priv *priv)
mac->link.speed10 = GMAC_CONFIG_PS; mac->link.speed10 = GMAC_CONFIG_PS;
mac->link.speed100 = GMAC_CONFIG_FES | GMAC_CONFIG_PS; mac->link.speed100 = GMAC_CONFIG_FES | GMAC_CONFIG_PS;
mac->link.speed1000 = 0; mac->link.speed1000 = 0;
mac->link.speed2500 = GMAC_CONFIG_FES;
mac->link.speed_mask = GMAC_CONFIG_FES | GMAC_CONFIG_PS; mac->link.speed_mask = GMAC_CONFIG_FES | GMAC_CONFIG_PS;
mac->mii.addr = GMAC_MDIO_ADDR; mac->mii.addr = GMAC_MDIO_ADDR;
mac->mii.data = GMAC_MDIO_DATA; mac->mii.data = GMAC_MDIO_DATA;

View File

@ -311,6 +311,7 @@ enum stmmac_state {
int stmmac_mdio_unregister(struct net_device *ndev); int stmmac_mdio_unregister(struct net_device *ndev);
int stmmac_mdio_register(struct net_device *ndev); int stmmac_mdio_register(struct net_device *ndev);
int stmmac_mdio_reset(struct mii_bus *mii); int stmmac_mdio_reset(struct mii_bus *mii);
int stmmac_xpcs_setup(struct mii_bus *mii);
void stmmac_set_ethtool_ops(struct net_device *netdev); void stmmac_set_ethtool_ops(struct net_device *netdev);
void stmmac_ptp_register(struct stmmac_priv *priv); void stmmac_ptp_register(struct stmmac_priv *priv);

View File

@ -931,6 +931,10 @@ static void stmmac_validate(struct phylink_config *config,
if ((max_speed > 0) && (max_speed < 1000)) { if ((max_speed > 0) && (max_speed < 1000)) {
phylink_set(mask, 1000baseT_Full); phylink_set(mask, 1000baseT_Full);
phylink_set(mask, 1000baseX_Full); phylink_set(mask, 1000baseX_Full);
} else if (priv->plat->has_gmac4) {
if (!max_speed || max_speed >= 2500)
phylink_set(mac_supported, 2500baseT_Full);
phylink_set(mac_supported, 2500baseX_Full);
} else if (priv->plat->has_xgmac) { } else if (priv->plat->has_xgmac) {
if (!max_speed || (max_speed >= 2500)) { if (!max_speed || (max_speed >= 2500)) {
phylink_set(mac_supported, 2500baseT_Full); phylink_set(mac_supported, 2500baseT_Full);
@ -6993,6 +6997,17 @@ int stmmac_dvr_probe(struct device *device,
} }
} }
if (priv->plat->speed_mode_2500)
priv->plat->speed_mode_2500(ndev, priv->plat->bsp_priv);
if (priv->plat->mdio_bus_data) {
if (priv->plat->mdio_bus_data->has_xpcs) {
ret = stmmac_xpcs_setup(priv->mii);
if (ret)
goto error_xpcs_setup;
}
}
ret = stmmac_phy_setup(priv); ret = stmmac_phy_setup(priv);
if (ret) { if (ret) {
netdev_err(ndev, "failed to setup phy (%d)\n", ret); netdev_err(ndev, "failed to setup phy (%d)\n", ret);
@ -7029,6 +7044,7 @@ error_serdes_powerup:
unregister_netdev(ndev); unregister_netdev(ndev);
error_netdev_register: error_netdev_register:
phylink_destroy(priv->phylink); phylink_destroy(priv->phylink);
error_xpcs_setup:
error_phy_setup: error_phy_setup:
if (priv->hw->pcs != STMMAC_PCS_TBI && if (priv->hw->pcs != STMMAC_PCS_TBI &&
priv->hw->pcs != STMMAC_PCS_RTBI) priv->hw->pcs != STMMAC_PCS_RTBI)

View File

@ -397,6 +397,41 @@ int stmmac_mdio_reset(struct mii_bus *bus)
return 0; return 0;
} }
int stmmac_xpcs_setup(struct mii_bus *bus)
{
int mode, addr;
struct net_device *ndev = bus->priv;
struct mdio_xpcs_args *xpcs;
struct stmmac_priv *priv;
struct mdio_device *mdiodev;
priv = netdev_priv(ndev);
mode = priv->plat->phy_interface;
/* Try to probe the XPCS by scanning all addresses. */
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
mdiodev = mdio_device_create(bus, addr);
if (IS_ERR(mdiodev))
continue;
xpcs = xpcs_create(mdiodev, mode);
if (IS_ERR_OR_NULL(xpcs)) {
mdio_device_free(mdiodev);
continue;
}
priv->hw->xpcs = xpcs;
break;
}
if (!priv->hw->xpcs) {
dev_warn(priv->device, "No xPCS found\n");
return -ENODEV;
}
return 0;
}
/** /**
* stmmac_mdio_register * stmmac_mdio_register
* @ndev: net device structure * @ndev: net device structure
@ -501,40 +536,11 @@ int stmmac_mdio_register(struct net_device *ndev)
goto no_phy_found; goto no_phy_found;
} }
/* Try to probe the XPCS by scanning all addresses. */
if (mdio_bus_data->has_xpcs) {
int mode = priv->plat->phy_interface;
struct mdio_device *mdiodev;
struct mdio_xpcs_args *xpcs;
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
mdiodev = mdio_device_create(new_bus, addr);
if (IS_ERR(mdiodev))
continue;
xpcs = xpcs_create(mdiodev, mode);
if (IS_ERR_OR_NULL(xpcs)) {
mdio_device_free(mdiodev);
continue;
}
priv->hw->xpcs = xpcs;
break;
}
if (!priv->hw->xpcs) {
dev_warn(dev, "No XPCS found\n");
err = -ENODEV;
goto no_xpcs_found;
}
}
bus_register_done: bus_register_done:
priv->mii = new_bus; priv->mii = new_bus;
return 0; return 0;
no_xpcs_found:
no_phy_found: no_phy_found:
mdiobus_unregister(new_bus); mdiobus_unregister(new_bus);
bus_register_fail: bus_register_fail:

View File

@ -57,9 +57,12 @@
/* Clause 37 Defines */ /* Clause 37 Defines */
/* VR MII MMD registers offsets */ /* VR MII MMD registers offsets */
#define DW_VR_MII_MMD_CTRL 0x0000
#define DW_VR_MII_DIG_CTRL1 0x8000 #define DW_VR_MII_DIG_CTRL1 0x8000
#define DW_VR_MII_AN_CTRL 0x8001 #define DW_VR_MII_AN_CTRL 0x8001
#define DW_VR_MII_AN_INTR_STS 0x8002 #define DW_VR_MII_AN_INTR_STS 0x8002
/* Enable 2.5G Mode */
#define DW_VR_MII_DIG_CTRL1_2G5_EN BIT(2)
/* EEE Mode Control Register */ /* EEE Mode Control Register */
#define DW_VR_MII_EEE_MCTRL0 0x8006 #define DW_VR_MII_EEE_MCTRL0 0x8006
#define DW_VR_MII_EEE_MCTRL1 0x800b #define DW_VR_MII_EEE_MCTRL1 0x800b
@ -86,6 +89,11 @@
#define DW_VR_MII_C37_ANSGM_SP_1000 0x2 #define DW_VR_MII_C37_ANSGM_SP_1000 0x2
#define DW_VR_MII_C37_ANSGM_SP_LNKSTS BIT(4) #define DW_VR_MII_C37_ANSGM_SP_LNKSTS BIT(4)
/* SR MII MMD Control defines */
#define AN_CL37_EN BIT(12) /* Enable Clause 37 auto-nego */
#define SGMII_SPEED_SS13 BIT(13) /* SGMII speed along with SS6 */
#define SGMII_SPEED_SS6 BIT(6) /* SGMII speed along with SS13 */
/* VR MII EEE Control 0 defines */ /* VR MII EEE Control 0 defines */
#define DW_VR_MII_EEE_LTX_EN BIT(0) /* LPI Tx Enable */ #define DW_VR_MII_EEE_LTX_EN BIT(0) /* LPI Tx Enable */
#define DW_VR_MII_EEE_LRX_EN BIT(1) /* LPI Rx Enable */ #define DW_VR_MII_EEE_LRX_EN BIT(1) /* LPI Rx Enable */
@ -161,6 +169,14 @@ static const int xpcs_sgmii_features[] = {
__ETHTOOL_LINK_MODE_MASK_NBITS, __ETHTOOL_LINK_MODE_MASK_NBITS,
}; };
static const int xpcs_2500basex_features[] = {
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
ETHTOOL_LINK_MODE_Autoneg_BIT,
ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
__ETHTOOL_LINK_MODE_MASK_NBITS,
};
static const phy_interface_t xpcs_usxgmii_interfaces[] = { static const phy_interface_t xpcs_usxgmii_interfaces[] = {
PHY_INTERFACE_MODE_USXGMII, PHY_INTERFACE_MODE_USXGMII,
}; };
@ -177,11 +193,17 @@ static const phy_interface_t xpcs_sgmii_interfaces[] = {
PHY_INTERFACE_MODE_SGMII, PHY_INTERFACE_MODE_SGMII,
}; };
static const phy_interface_t xpcs_2500basex_interfaces[] = {
PHY_INTERFACE_MODE_2500BASEX,
PHY_INTERFACE_MODE_MAX,
};
enum { enum {
DW_XPCS_USXGMII, DW_XPCS_USXGMII,
DW_XPCS_10GKR, DW_XPCS_10GKR,
DW_XPCS_XLGMII, DW_XPCS_XLGMII,
DW_XPCS_SGMII, DW_XPCS_SGMII,
DW_XPCS_2500BASEX,
DW_XPCS_INTERFACE_MAX, DW_XPCS_INTERFACE_MAX,
}; };
@ -306,6 +328,7 @@ static int xpcs_soft_reset(struct mdio_xpcs_args *xpcs,
dev = MDIO_MMD_PCS; dev = MDIO_MMD_PCS;
break; break;
case DW_AN_C37_SGMII: case DW_AN_C37_SGMII:
case DW_2500BASEX:
dev = MDIO_MMD_VEND2; dev = MDIO_MMD_VEND2;
break; break;
default: default:
@ -804,6 +827,28 @@ static int xpcs_config_aneg_c37_sgmii(struct mdio_xpcs_args *xpcs)
return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret); return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret);
} }
static int xpcs_config_2500basex(struct mdio_xpcs_args *xpcs)
{
int ret;
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1);
if (ret < 0)
return ret;
ret |= DW_VR_MII_DIG_CTRL1_2G5_EN;
ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret);
if (ret < 0)
return ret;
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL);
if (ret < 0)
return ret;
ret &= ~AN_CL37_EN;
ret |= SGMII_SPEED_SS6;
ret &= ~SGMII_SPEED_SS13;
return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, ret);
}
static int xpcs_do_config(struct mdio_xpcs_args *xpcs, static int xpcs_do_config(struct mdio_xpcs_args *xpcs,
phy_interface_t interface, unsigned int mode) phy_interface_t interface, unsigned int mode)
{ {
@ -827,6 +872,11 @@ static int xpcs_do_config(struct mdio_xpcs_args *xpcs,
if (ret) if (ret)
return ret; return ret;
break; break;
case DW_2500BASEX:
ret = xpcs_config_2500basex(xpcs);
if (ret)
return ret;
break;
default: default:
return -1; return -1;
} }
@ -1023,6 +1073,12 @@ static const struct xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
.an_mode = DW_AN_C37_SGMII, .an_mode = DW_AN_C37_SGMII,
}, },
[DW_XPCS_2500BASEX] = {
.supported = xpcs_2500basex_features,
.interface = xpcs_2500basex_interfaces,
.num_interfaces = ARRAY_SIZE(xpcs_2500basex_features),
.an_mode = DW_2500BASEX,
},
}; };
static const struct xpcs_id xpcs_id_list[] = { static const struct xpcs_id xpcs_id_list[] = {

View File

@ -13,6 +13,7 @@
/* AN mode */ /* AN mode */
#define DW_AN_C73 1 #define DW_AN_C73 1
#define DW_AN_C37_SGMII 2 #define DW_AN_C37_SGMII 2
#define DW_2500BASEX 3
struct xpcs_id; struct xpcs_id;

View File

@ -223,6 +223,7 @@ struct plat_stmmacenet_data {
void (*fix_mac_speed)(void *priv, unsigned int speed); void (*fix_mac_speed)(void *priv, unsigned int speed);
int (*serdes_powerup)(struct net_device *ndev, void *priv); int (*serdes_powerup)(struct net_device *ndev, void *priv);
void (*serdes_powerdown)(struct net_device *ndev, void *priv); void (*serdes_powerdown)(struct net_device *ndev, void *priv);
void (*speed_mode_2500)(struct net_device *ndev, void *priv);
void (*ptp_clk_freq_config)(void *priv); void (*ptp_clk_freq_config)(void *priv);
int (*init)(struct platform_device *pdev, void *priv); int (*init)(struct platform_device *pdev, void *priv);
void (*exit)(struct platform_device *pdev, void *priv); void (*exit)(struct platform_device *pdev, void *priv);