Message ID | E1f3II5-00077a-R3@rmk-PC.armlinux.org.uk |
---|---|
State | Accepted, archived |
Delegated to: | David Miller |
Headers | show |
Series | [v2] net: phy: marvell10g: add thermal hwmon device | expand |
From: Russell King <rmk+kernel@armlinux.org.uk> Date: Tue, 03 Apr 2018 10:31:45 +0100 > Add a thermal monitoring device for the Marvell 88x3310, which updates > once a second. We also need to hook into the suspend/resume mechanism > to ensure that the thermal monitoring is reconfigured when we resume. > > Suggested-by: Andrew Lunn <andrew@lunn.ch> > Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> > --- > v2: update to apply to net-next Applied, thanks for respinning.
On Tue, Apr 03, 2018 at 10:31:45AM +0100, Russell King wrote: > Add a thermal monitoring device for the Marvell 88x3310, which updates > once a second. We also need to hook into the suspend/resume mechanism > to ensure that the thermal monitoring is reconfigured when we resume. > > Suggested-by: Andrew Lunn <andrew@lunn.ch> > Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> > --- > v2: update to apply to net-next > > drivers/net/phy/marvell10g.c | 184 ++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 182 insertions(+), 2 deletions(-) > > diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c > index 8a0bd98fdec7..db9d66781da6 100644 > --- a/drivers/net/phy/marvell10g.c > +++ b/drivers/net/phy/marvell10g.c > @@ -21,8 +21,10 @@ > * If both the fiber and copper ports are connected, the first to gain > * link takes priority and the other port is completely locked out. > */ > -#include <linux/phy.h> > +#include <linux/ctype.h> > +#include <linux/hwmon.h> > #include <linux/marvell_phy.h> > +#include <linux/phy.h> > > enum { > MV_PCS_BASE_T = 0x0000, > @@ -40,6 +42,19 @@ enum { > */ > MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */ > MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */ > + > + /* Vendor2 MMD registers */ > + MV_V2_TEMP_CTRL = 0xf08a, > + MV_V2_TEMP_CTRL_MASK = 0xc000, > + MV_V2_TEMP_CTRL_SAMPLE = 0x0000, > + MV_V2_TEMP_CTRL_DISABLE = 0xc000, > + MV_V2_TEMP = 0xf08c, > + MV_V2_TEMP_UNKNOWN = 0x9600, /* unknown function */ > +}; > + > +struct mv3310_priv { > + struct device *hwmon_dev; > + char *hwmon_name; > }; > > static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, > @@ -60,17 +75,180 @@ static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, > return ret < 0 ? ret : 1; > } > > +#ifdef CONFIG_HWMON > +static umode_t mv3310_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + if (type == hwmon_chip && attr == hwmon_chip_update_interval) > + return 0444; > + if (type == hwmon_temp && attr == hwmon_temp_input) > + return 0444; > + return 0; > +} > + > +static int mv3310_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *value) > +{ > + struct phy_device *phydev = dev_get_drvdata(dev); > + int temp; > + > + if (type == hwmon_chip && attr == hwmon_chip_update_interval) { > + *value = MSEC_PER_SEC; The update_interval attribute is supposed to be used for setting an update interval in the chip. Having it return a constant doesn't really serve a useful purpose. Guenter > + return 0; > + } > + > + if (type == hwmon_temp && attr == hwmon_temp_input) { > + temp = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP); > + if (temp < 0) > + return temp; > + > + *value = ((temp & 0xff) - 75) * 1000; > + > + return 0; > + } > + > + return -EOPNOTSUPP; > +} > + > +static const struct hwmon_ops mv3310_hwmon_ops = { > + .is_visible = mv3310_hwmon_is_visible, > + .read = mv3310_hwmon_read, > +}; > + > +static u32 mv3310_hwmon_chip_config[] = { > + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, > + 0, > +}; > + > +static const struct hwmon_channel_info mv3310_hwmon_chip = { > + .type = hwmon_chip, > + .config = mv3310_hwmon_chip_config, > +}; > + > +static u32 mv3310_hwmon_temp_config[] = { > + HWMON_T_INPUT, > + 0, > +}; > + > +static const struct hwmon_channel_info mv3310_hwmon_temp = { > + .type = hwmon_temp, > + .config = mv3310_hwmon_temp_config, > +}; > + > +static const struct hwmon_channel_info *mv3310_hwmon_info[] = { > + &mv3310_hwmon_chip, > + &mv3310_hwmon_temp, > + NULL, > +}; > + > +static const struct hwmon_chip_info mv3310_hwmon_chip_info = { > + .ops = &mv3310_hwmon_ops, > + .info = mv3310_hwmon_info, > +}; > + > +static int mv3310_hwmon_config(struct phy_device *phydev, bool enable) > +{ > + u16 val; > + int ret; > + > + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP, > + MV_V2_TEMP_UNKNOWN); > + if (ret < 0) > + return ret; > + > + val = enable ? MV_V2_TEMP_CTRL_SAMPLE : MV_V2_TEMP_CTRL_DISABLE; > + ret = mv3310_modify(phydev, MDIO_MMD_VEND2, MV_V2_TEMP_CTRL, > + MV_V2_TEMP_CTRL_MASK, val); > + > + return ret < 0 ? ret : 0; > +} > + > +static void mv3310_hwmon_disable(void *data) > +{ > + struct phy_device *phydev = data; > + > + mv3310_hwmon_config(phydev, false); > +} > + > +static int mv3310_hwmon_probe(struct phy_device *phydev) > +{ > + struct device *dev = &phydev->mdio.dev; > + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); > + int i, j, ret; > + > + priv->hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); > + if (!priv->hwmon_name) > + return -ENODEV; > + > + for (i = j = 0; priv->hwmon_name[i]; i++) { > + if (isalnum(priv->hwmon_name[i])) { > + if (i != j) > + priv->hwmon_name[j] = priv->hwmon_name[i]; > + j++; > + } > + } > + priv->hwmon_name[j] = '\0'; > + > + ret = mv3310_hwmon_config(phydev, true); > + if (ret) > + return ret; > + > + ret = devm_add_action_or_reset(dev, mv3310_hwmon_disable, phydev); > + if (ret) > + return ret; > + > + priv->hwmon_dev = devm_hwmon_device_register_with_info(dev, > + priv->hwmon_name, phydev, > + &mv3310_hwmon_chip_info, NULL); > + > + return PTR_ERR_OR_ZERO(priv->hwmon_dev); > +} > +#else > +static inline int mv3310_hwmon_config(struct phy_device *phydev, bool enable) > +{ > + return 0; > +} > + > +static int mv3310_hwmon_probe(struct phy_device *phydev) > +{ > + return 0; > +} > +#endif > + > static int mv3310_probe(struct phy_device *phydev) > { > + struct mv3310_priv *priv; > u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; > + int ret; > > if (!phydev->is_c45 || > (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) > return -ENODEV; > > + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + dev_set_drvdata(&phydev->mdio.dev, priv); > + > + ret = mv3310_hwmon_probe(phydev); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int mv3310_suspend(struct phy_device *phydev) > +{ > return 0; > } > > +static int mv3310_resume(struct phy_device *phydev) > +{ > + return mv3310_hwmon_config(phydev, true); > +} > + > static int mv3310_config_init(struct phy_device *phydev) > { > __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; > @@ -367,9 +545,11 @@ static struct phy_driver mv3310_drivers[] = { > SUPPORTED_FIBRE | > SUPPORTED_10000baseT_Full | > SUPPORTED_Backplane, > - .probe = mv3310_probe, > .soft_reset = gen10g_no_soft_reset, > .config_init = mv3310_config_init, > + .probe = mv3310_probe, > + .suspend = mv3310_suspend, > + .resume = mv3310_resume, > .config_aneg = mv3310_config_aneg, > .aneg_done = mv3310_aneg_done, > .read_status = mv3310_read_status, > -- > 2.7.4 >
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index 8a0bd98fdec7..db9d66781da6 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -21,8 +21,10 @@ * If both the fiber and copper ports are connected, the first to gain * link takes priority and the other port is completely locked out. */ -#include <linux/phy.h> +#include <linux/ctype.h> +#include <linux/hwmon.h> #include <linux/marvell_phy.h> +#include <linux/phy.h> enum { MV_PCS_BASE_T = 0x0000, @@ -40,6 +42,19 @@ enum { */ MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */ MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */ + + /* Vendor2 MMD registers */ + MV_V2_TEMP_CTRL = 0xf08a, + MV_V2_TEMP_CTRL_MASK = 0xc000, + MV_V2_TEMP_CTRL_SAMPLE = 0x0000, + MV_V2_TEMP_CTRL_DISABLE = 0xc000, + MV_V2_TEMP = 0xf08c, + MV_V2_TEMP_UNKNOWN = 0x9600, /* unknown function */ +}; + +struct mv3310_priv { + struct device *hwmon_dev; + char *hwmon_name; }; static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, @@ -60,17 +75,180 @@ static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, return ret < 0 ? ret : 1; } +#ifdef CONFIG_HWMON +static umode_t mv3310_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return 0444; + if (type == hwmon_temp && attr == hwmon_temp_input) + return 0444; + return 0; +} + +static int mv3310_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + int temp; + + if (type == hwmon_chip && attr == hwmon_chip_update_interval) { + *value = MSEC_PER_SEC; + return 0; + } + + if (type == hwmon_temp && attr == hwmon_temp_input) { + temp = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP); + if (temp < 0) + return temp; + + *value = ((temp & 0xff) - 75) * 1000; + + return 0; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops mv3310_hwmon_ops = { + .is_visible = mv3310_hwmon_is_visible, + .read = mv3310_hwmon_read, +}; + +static u32 mv3310_hwmon_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0, +}; + +static const struct hwmon_channel_info mv3310_hwmon_chip = { + .type = hwmon_chip, + .config = mv3310_hwmon_chip_config, +}; + +static u32 mv3310_hwmon_temp_config[] = { + HWMON_T_INPUT, + 0, +}; + +static const struct hwmon_channel_info mv3310_hwmon_temp = { + .type = hwmon_temp, + .config = mv3310_hwmon_temp_config, +}; + +static const struct hwmon_channel_info *mv3310_hwmon_info[] = { + &mv3310_hwmon_chip, + &mv3310_hwmon_temp, + NULL, +}; + +static const struct hwmon_chip_info mv3310_hwmon_chip_info = { + .ops = &mv3310_hwmon_ops, + .info = mv3310_hwmon_info, +}; + +static int mv3310_hwmon_config(struct phy_device *phydev, bool enable) +{ + u16 val; + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP, + MV_V2_TEMP_UNKNOWN); + if (ret < 0) + return ret; + + val = enable ? MV_V2_TEMP_CTRL_SAMPLE : MV_V2_TEMP_CTRL_DISABLE; + ret = mv3310_modify(phydev, MDIO_MMD_VEND2, MV_V2_TEMP_CTRL, + MV_V2_TEMP_CTRL_MASK, val); + + return ret < 0 ? ret : 0; +} + +static void mv3310_hwmon_disable(void *data) +{ + struct phy_device *phydev = data; + + mv3310_hwmon_config(phydev, false); +} + +static int mv3310_hwmon_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); + int i, j, ret; + + priv->hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!priv->hwmon_name) + return -ENODEV; + + for (i = j = 0; priv->hwmon_name[i]; i++) { + if (isalnum(priv->hwmon_name[i])) { + if (i != j) + priv->hwmon_name[j] = priv->hwmon_name[i]; + j++; + } + } + priv->hwmon_name[j] = '\0'; + + ret = mv3310_hwmon_config(phydev, true); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, mv3310_hwmon_disable, phydev); + if (ret) + return ret; + + priv->hwmon_dev = devm_hwmon_device_register_with_info(dev, + priv->hwmon_name, phydev, + &mv3310_hwmon_chip_info, NULL); + + return PTR_ERR_OR_ZERO(priv->hwmon_dev); +} +#else +static inline int mv3310_hwmon_config(struct phy_device *phydev, bool enable) +{ + return 0; +} + +static int mv3310_hwmon_probe(struct phy_device *phydev) +{ + return 0; +} +#endif + static int mv3310_probe(struct phy_device *phydev) { + struct mv3310_priv *priv; u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + int ret; if (!phydev->is_c45 || (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) return -ENODEV; + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&phydev->mdio.dev, priv); + + ret = mv3310_hwmon_probe(phydev); + if (ret) + return ret; + + return 0; +} + +static int mv3310_suspend(struct phy_device *phydev) +{ return 0; } +static int mv3310_resume(struct phy_device *phydev) +{ + return mv3310_hwmon_config(phydev, true); +} + static int mv3310_config_init(struct phy_device *phydev) { __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; @@ -367,9 +545,11 @@ static struct phy_driver mv3310_drivers[] = { SUPPORTED_FIBRE | SUPPORTED_10000baseT_Full | SUPPORTED_Backplane, - .probe = mv3310_probe, .soft_reset = gen10g_no_soft_reset, .config_init = mv3310_config_init, + .probe = mv3310_probe, + .suspend = mv3310_suspend, + .resume = mv3310_resume, .config_aneg = mv3310_config_aneg, .aneg_done = mv3310_aneg_done, .read_status = mv3310_read_status,
Add a thermal monitoring device for the Marvell 88x3310, which updates once a second. We also need to hook into the suspend/resume mechanism to ensure that the thermal monitoring is reconfigured when we resume. Suggested-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> --- v2: update to apply to net-next drivers/net/phy/marvell10g.c | 184 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-)