Merge branch 'cpsw-runtime-pm'

Grygorii Strashko says:

====================
drivers: net: cpsw: improve runtime pm

This series intended to improve runtime PM and allow CPSW to be
RPM suspended when all ethX netdevices are down.

To achieve above goal it is required to relax runtime PM constraints for
Davinci MDIO which blocks CPSW runtime PM now, because Davinci MDIO is always
powered on during probe and powered off only when it's going to be removed.
- Patches 6-11 implement PM runtime autosuspend for Davinci MDIO, but keep it
disabled by default, because Davinci MDIO is integrated in big set of TI devices
and not all of them verified to work correctly with RPM autosuspend enabled:
 expected to work on SoCs where MDIO is defined as part of CPSW in DT
 (cpsw.c DRA7/am57x, am437x, am335x)
The CPSW need to be fixed before RPM suspended can be allowed:
 - Patches 1-5 ensure that CPSW will not cause L3 errors while it is in RPM
   suspended state.

Davinci MDIO RPM autosuspend can be enabled through sysfs:
 echo 100 > /sys/devices/../48484000.ethernet/48485000.mdio/power/autosuspend_delay_ms

Patches 12 - 15: introduce new compatible string "ti,cpsw-mdio" which is used
then to enable RPM for am335x/am437x/dra7 SoCs.

Tested on am335x, am437x, am572x and k2g (on k2g with RPM disabled for Davinci MDIO)
These changes should not affect on errata i877 implementation on DRA7.

Power measurement on am335x GP EVM:
 Without this series:  547.60 mW total SoC power
 With this series + "ifconfig eth0 down": 477.32 mW Total Soc Power

Changes in v2:
- CPSW ethtool interface updated to use .begin()/.complete() callbacks
- kbuild failure fixed
- davinci_mdio DT updated with proper description of allowed compatible strings
  combinations

Link on v1:
 https://lkml.org/lkml/2016/6/15/362
====================

Reviewed-by: Mugunthan V N <mugunthanvnm@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2016-06-28 08:57:28 -04:00
commit 644afe2355
6 changed files with 182 additions and 77 deletions

View File

@ -2,7 +2,10 @@ TI SoC Davinci/Keystone2 MDIO Controller Device Tree Bindings
---------------------------------------------------
Required properties:
- compatible : Should be "ti,davinci_mdio" or "ti,keystone_mdio"
- compatible : Should be "ti,davinci_mdio"
and "ti,keystone_mdio" for Keystone 2 SoCs
and "ti,cpsw-mdio" for am335x, am472x, am57xx/dra7, dm814x SoCs
and "ti,am4372-mdio" for am472x SoC
- reg : physical base address and size of the davinci mdio
registers map
- bus_freq : Mdio Bus frequency

View File

@ -788,7 +788,7 @@
status = "disabled";
davinci_mdio: mdio@4a101000 {
compatible = "ti,davinci_mdio";
compatible = "ti,cpsw-mdio","ti,davinci_mdio";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "davinci_mdio";

View File

@ -635,7 +635,7 @@
syscon = <&scm_conf>;
davinci_mdio: mdio@4a101000 {
compatible = "ti,am4372-mdio","ti,davinci_mdio";
compatible = "ti,am4372-mdio","ti,cpsw-mdio","ti,davinci_mdio";
reg = <0x4a101000 0x100>;
#address-cells = <1>;
#size-cells = <0>;

View File

@ -1660,7 +1660,7 @@
status = "disabled";
davinci_mdio: mdio@48485000 {
compatible = "ti,davinci_mdio";
compatible = "ti,cpsw-mdio","ti,davinci_mdio";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "davinci_mdio";

View File

@ -1243,6 +1243,7 @@ static void cpsw_slave_stop(struct cpsw_slave *slave, struct cpsw_priv *priv)
slave->phy = NULL;
cpsw_ale_control_set(priv->ale, slave_port,
ALE_PORT_STATE, ALE_PORT_STATE_DISABLE);
soft_reset_slave(slave);
}
static int cpsw_ndo_open(struct net_device *ndev)
@ -1251,7 +1252,11 @@ static int cpsw_ndo_open(struct net_device *ndev)
int i, ret;
u32 reg;
pm_runtime_get_sync(&priv->pdev->dev);
ret = pm_runtime_get_sync(&priv->pdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&priv->pdev->dev);
return ret;
}
if (!cpsw_common_res_usage_state(priv))
cpsw_intr_disable(priv);
@ -1609,10 +1614,17 @@ static int cpsw_ndo_set_mac_address(struct net_device *ndev, void *p)
struct sockaddr *addr = (struct sockaddr *)p;
int flags = 0;
u16 vid = 0;
int ret;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
ret = pm_runtime_get_sync(&priv->pdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&priv->pdev->dev);
return ret;
}
if (priv->data.dual_emac) {
vid = priv->slaves[priv->emac_port].port_vlan;
flags = ALE_VLAN;
@ -1627,6 +1639,8 @@ static int cpsw_ndo_set_mac_address(struct net_device *ndev, void *p)
memcpy(ndev->dev_addr, priv->mac_addr, ETH_ALEN);
for_each_slave(priv, cpsw_set_slave_mac, priv);
pm_runtime_put(&priv->pdev->dev);
return 0;
}
@ -1691,10 +1705,17 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
__be16 proto, u16 vid)
{
struct cpsw_priv *priv = netdev_priv(ndev);
int ret;
if (vid == priv->data.default_vlan)
return 0;
ret = pm_runtime_get_sync(&priv->pdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&priv->pdev->dev);
return ret;
}
if (priv->data.dual_emac) {
/* In dual EMAC, reserved VLAN id should not be used for
* creating VLAN interfaces as this can break the dual
@ -1709,7 +1730,10 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
}
dev_info(priv->dev, "Adding vlanid %d to vlan filter\n", vid);
return cpsw_add_vlan_ale_entry(priv, vid);
ret = cpsw_add_vlan_ale_entry(priv, vid);
pm_runtime_put(&priv->pdev->dev);
return ret;
}
static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
@ -1721,6 +1745,12 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
if (vid == priv->data.default_vlan)
return 0;
ret = pm_runtime_get_sync(&priv->pdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&priv->pdev->dev);
return ret;
}
if (priv->data.dual_emac) {
int i;
@ -1740,8 +1770,10 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
if (ret != 0)
return ret;
return cpsw_ale_del_mcast(priv->ale, priv->ndev->broadcast,
0, ALE_VLAN, vid);
ret = cpsw_ale_del_mcast(priv->ale, priv->ndev->broadcast,
0, ALE_VLAN, vid);
pm_runtime_put(&priv->pdev->dev);
return ret;
}
static const struct net_device_ops cpsw_netdev_ops = {
@ -1900,10 +1932,33 @@ static int cpsw_set_pauseparam(struct net_device *ndev,
priv->tx_pause = pause->tx_pause ? true : false;
for_each_slave(priv, _cpsw_adjust_link, priv, &link);
return 0;
}
static int cpsw_ethtool_op_begin(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
int ret;
ret = pm_runtime_get_sync(&priv->pdev->dev);
if (ret < 0) {
cpsw_err(priv, drv, "ethtool begin failed %d\n", ret);
pm_runtime_put_noidle(&priv->pdev->dev);
}
return ret;
}
static void cpsw_ethtool_op_complete(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
int ret;
ret = pm_runtime_put(&priv->pdev->dev);
if (ret < 0)
cpsw_err(priv, drv, "ethtool complete failed %d\n", ret);
}
static const struct ethtool_ops cpsw_ethtool_ops = {
.get_drvinfo = cpsw_get_drvinfo,
.get_msglevel = cpsw_get_msglevel,
@ -1923,6 +1978,8 @@ static const struct ethtool_ops cpsw_ethtool_ops = {
.set_wol = cpsw_set_wol,
.get_regs_len = cpsw_get_regs_len,
.get_regs = cpsw_get_regs,
.begin = cpsw_ethtool_op_begin,
.complete = cpsw_ethtool_op_complete,
};
static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv,
@ -2311,7 +2368,11 @@ static int cpsw_probe(struct platform_device *pdev)
/* Need to enable clocks with runtime PM api to access module
* registers
*/
pm_runtime_get_sync(&pdev->dev);
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&pdev->dev);
goto clean_runtime_disable_ret;
}
priv->version = readl(&priv->regs->id_ver);
pm_runtime_put_sync(&pdev->dev);
@ -2548,16 +2609,12 @@ static int cpsw_suspend(struct device *dev)
for (i = 0; i < priv->data.slaves; i++) {
if (netif_running(priv->slaves[i].ndev))
cpsw_ndo_stop(priv->slaves[i].ndev);
soft_reset_slave(priv->slaves + i);
}
} else {
if (netif_running(ndev))
cpsw_ndo_stop(ndev);
for_each_slave(priv, soft_reset_slave);
}
pm_runtime_put_sync(&pdev->dev);
/* Select sleep pin state */
pinctrl_pm_select_sleep_state(&pdev->dev);
@ -2570,8 +2627,6 @@ static int cpsw_resume(struct device *dev)
struct net_device *ndev = platform_get_drvdata(pdev);
struct cpsw_priv *priv = netdev_priv(ndev);
pm_runtime_get_sync(&pdev->dev);
/* Select default pin state */
pinctrl_pm_select_default_state(&pdev->dev);

View File

@ -53,6 +53,10 @@
#define DEF_OUT_FREQ 2200000 /* 2.2 MHz */
struct davinci_mdio_of_param {
int autosuspend_delay_ms;
};
struct davinci_mdio_regs {
u32 version;
u32 control;
@ -90,19 +94,19 @@ static const struct mdio_platform_data default_pdata = {
struct davinci_mdio_data {
struct mdio_platform_data pdata;
struct davinci_mdio_regs __iomem *regs;
spinlock_t lock;
struct clk *clk;
struct device *dev;
struct mii_bus *bus;
bool suspended;
bool active_in_suspend;
unsigned long access_time; /* jiffies */
/* Indicates that driver shouldn't modify phy_mask in case
* if MDIO bus is registered from DT.
*/
bool skip_scan;
u32 clk_div;
};
static void __davinci_mdio_reset(struct davinci_mdio_data *data)
static void davinci_mdio_init_clk(struct davinci_mdio_data *data)
{
u32 mdio_in, div, mdio_out_khz, access_time;
@ -111,9 +115,7 @@ static void __davinci_mdio_reset(struct davinci_mdio_data *data)
if (div > CONTROL_MAX_DIV)
div = CONTROL_MAX_DIV;
/* set enable and clock divider */
__raw_writel(div | CONTROL_ENABLE, &data->regs->control);
data->clk_div = div;
/*
* One mdio transaction consists of:
* 32 bits of preamble
@ -134,12 +136,23 @@ static void __davinci_mdio_reset(struct davinci_mdio_data *data)
data->access_time = 1;
}
static void davinci_mdio_enable(struct davinci_mdio_data *data)
{
/* set enable and clock divider */
__raw_writel(data->clk_div | CONTROL_ENABLE, &data->regs->control);
}
static int davinci_mdio_reset(struct mii_bus *bus)
{
struct davinci_mdio_data *data = bus->priv;
u32 phy_mask, ver;
int ret;
__davinci_mdio_reset(data);
ret = pm_runtime_get_sync(data->dev);
if (ret < 0) {
pm_runtime_put_noidle(data->dev);
return ret;
}
/* wait for scan logic to settle */
msleep(PHY_MAX_ADDR * data->access_time);
@ -150,7 +163,7 @@ static int davinci_mdio_reset(struct mii_bus *bus)
(ver >> 8) & 0xff, ver & 0xff);
if (data->skip_scan)
return 0;
goto done;
/* get phy mask from the alive register */
phy_mask = __raw_readl(&data->regs->alive);
@ -165,6 +178,10 @@ static int davinci_mdio_reset(struct mii_bus *bus)
}
data->bus->phy_mask = phy_mask;
done:
pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return 0;
}
@ -190,7 +207,7 @@ static inline int wait_for_user_access(struct davinci_mdio_data *data)
* operation
*/
dev_warn(data->dev, "resetting idled controller\n");
__davinci_mdio_reset(data);
davinci_mdio_enable(data);
return -EAGAIN;
}
@ -225,11 +242,10 @@ static int davinci_mdio_read(struct mii_bus *bus, int phy_id, int phy_reg)
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
spin_lock(&data->lock);
if (data->suspended) {
spin_unlock(&data->lock);
return -ENODEV;
ret = pm_runtime_get_sync(data->dev);
if (ret < 0) {
pm_runtime_put_noidle(data->dev);
return ret;
}
reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |
@ -255,8 +271,8 @@ static int davinci_mdio_read(struct mii_bus *bus, int phy_id, int phy_reg)
break;
}
spin_unlock(&data->lock);
pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
}
@ -270,11 +286,10 @@ static int davinci_mdio_write(struct mii_bus *bus, int phy_id,
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
spin_lock(&data->lock);
if (data->suspended) {
spin_unlock(&data->lock);
return -ENODEV;
ret = pm_runtime_get_sync(data->dev);
if (ret < 0) {
pm_runtime_put_noidle(data->dev);
return ret;
}
reg = (USERACCESS_GO | USERACCESS_WRITE | (phy_reg << 21) |
@ -295,9 +310,10 @@ static int davinci_mdio_write(struct mii_bus *bus, int phy_id,
break;
}
spin_unlock(&data->lock);
pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return 0;
return ret;
}
#if IS_ENABLED(CONFIG_OF)
@ -320,6 +336,19 @@ static int davinci_mdio_probe_dt(struct mdio_platform_data *data,
}
#endif
#if IS_ENABLED(CONFIG_OF)
static const struct davinci_mdio_of_param of_cpsw_mdio_data = {
.autosuspend_delay_ms = 100,
};
static const struct of_device_id davinci_mdio_of_mtable[] = {
{ .compatible = "ti,davinci_mdio", },
{ .compatible = "ti,cpsw-mdio", .data = &of_cpsw_mdio_data},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, davinci_mdio_of_mtable);
#endif
static int davinci_mdio_probe(struct platform_device *pdev)
{
struct mdio_platform_data *pdata = dev_get_platdata(&pdev->dev);
@ -328,6 +357,7 @@ static int davinci_mdio_probe(struct platform_device *pdev)
struct resource *res;
struct phy_device *phy;
int ret, addr;
int autosuspend_delay_ms = -1;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@ -340,9 +370,22 @@ static int davinci_mdio_probe(struct platform_device *pdev)
}
if (dev->of_node) {
if (davinci_mdio_probe_dt(&data->pdata, pdev))
data->pdata = default_pdata;
const struct of_device_id *of_id;
ret = davinci_mdio_probe_dt(&data->pdata, pdev);
if (ret)
return ret;
snprintf(data->bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
of_id = of_match_device(davinci_mdio_of_mtable, &pdev->dev);
if (of_id) {
const struct davinci_mdio_of_param *of_mdio_data;
of_mdio_data = of_id->data;
if (of_mdio_data)
autosuspend_delay_ms =
of_mdio_data->autosuspend_delay_ms;
}
} else {
data->pdata = pdata ? (*pdata) : default_pdata;
snprintf(data->bus->id, MII_BUS_ID_SIZE, "%s-%x",
@ -356,26 +399,25 @@ static int davinci_mdio_probe(struct platform_device *pdev)
data->bus->parent = dev;
data->bus->priv = data;
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
data->clk = devm_clk_get(dev, "fck");
if (IS_ERR(data->clk)) {
dev_err(dev, "failed to get device clock\n");
ret = PTR_ERR(data->clk);
data->clk = NULL;
goto bail_out;
return PTR_ERR(data->clk);
}
dev_set_drvdata(dev, data);
data->dev = dev;
spin_lock_init(&data->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(data->regs)) {
ret = PTR_ERR(data->regs);
goto bail_out;
}
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
davinci_mdio_init_clk(data);
pm_runtime_set_autosuspend_delay(&pdev->dev, autosuspend_delay_ms);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
/* register the mii bus
* Create PHYs from DT only in case if PHY child nodes are explicitly
@ -404,9 +446,8 @@ static int davinci_mdio_probe(struct platform_device *pdev)
return 0;
bail_out:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
@ -417,29 +458,47 @@ static int davinci_mdio_remove(struct platform_device *pdev)
if (data->bus)
mdiobus_unregister(data->bus);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int davinci_mdio_suspend(struct device *dev)
#ifdef CONFIG_PM
static int davinci_mdio_runtime_suspend(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
u32 ctrl;
spin_lock(&data->lock);
/* shutdown the scan state machine */
ctrl = __raw_readl(&data->regs->control);
ctrl &= ~CONTROL_ENABLE;
__raw_writel(ctrl, &data->regs->control);
wait_for_idle(data);
data->suspended = true;
spin_unlock(&data->lock);
pm_runtime_put_sync(data->dev);
return 0;
}
static int davinci_mdio_runtime_resume(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
davinci_mdio_enable(data);
return 0;
}
#endif
#ifdef CONFIG_PM_SLEEP
static int davinci_mdio_suspend(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
int ret = 0;
data->active_in_suspend = !pm_runtime_status_suspended(dev);
if (data->active_in_suspend)
ret = pm_runtime_force_suspend(dev);
if (ret < 0)
return ret;
/* Select sleep pin state */
pinctrl_pm_select_sleep_state(dev);
@ -454,31 +513,19 @@ static int davinci_mdio_resume(struct device *dev)
/* Select default pin state */
pinctrl_pm_select_default_state(dev);
pm_runtime_get_sync(data->dev);
spin_lock(&data->lock);
/* restart the scan state machine */
__davinci_mdio_reset(data);
data->suspended = false;
spin_unlock(&data->lock);
if (data->active_in_suspend)
pm_runtime_force_resume(dev);
return 0;
}
#endif
static const struct dev_pm_ops davinci_mdio_pm_ops = {
SET_RUNTIME_PM_OPS(davinci_mdio_runtime_suspend,
davinci_mdio_runtime_resume, NULL)
SET_LATE_SYSTEM_SLEEP_PM_OPS(davinci_mdio_suspend, davinci_mdio_resume)
};
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id davinci_mdio_of_mtable[] = {
{ .compatible = "ti,davinci_mdio", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, davinci_mdio_of_mtable);
#endif
static struct platform_driver davinci_mdio_driver = {
.driver = {
.name = "davinci_mdio",