aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2018-04-03 05:31:45 -0400
committerDavid S. Miller <davem@davemloft.net>2018-04-04 11:24:54 -0400
commit0d3ad8549c6327a8e0bad0fb785c00e07672f296 (patch)
tree919aa9917ef2e40964634ad08e580b63c212ab6d
parentbfacfb457b36911a10140b8cb3ce76a74883ac5a (diff)
net: phy: marvell10g: add thermal hwmon device
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> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/phy/marvell10g.c184
1 files changed, 182 insertions, 2 deletions
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index 9564916d2d7b..f77a2d9e7f9d 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -21,8 +21,10 @@
21 * If both the fiber and copper ports are connected, the first to gain 21 * If both the fiber and copper ports are connected, the first to gain
22 * link takes priority and the other port is completely locked out. 22 * link takes priority and the other port is completely locked out.
23 */ 23 */
24#include <linux/phy.h> 24#include <linux/ctype.h>
25#include <linux/hwmon.h>
25#include <linux/marvell_phy.h> 26#include <linux/marvell_phy.h>
27#include <linux/phy.h>
26 28
27enum { 29enum {
28 MV_PCS_BASE_T = 0x0000, 30 MV_PCS_BASE_T = 0x0000,
@@ -40,6 +42,19 @@ enum {
40 */ 42 */
41 MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */ 43 MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */
42 MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */ 44 MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */
45
46 /* Vendor2 MMD registers */
47 MV_V2_TEMP_CTRL = 0xf08a,
48 MV_V2_TEMP_CTRL_MASK = 0xc000,
49 MV_V2_TEMP_CTRL_SAMPLE = 0x0000,
50 MV_V2_TEMP_CTRL_DISABLE = 0xc000,
51 MV_V2_TEMP = 0xf08c,
52 MV_V2_TEMP_UNKNOWN = 0x9600, /* unknown function */
53};
54
55struct mv3310_priv {
56 struct device *hwmon_dev;
57 char *hwmon_name;
43}; 58};
44 59
45static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, 60static 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,
60 return ret < 0 ? ret : 1; 75 return ret < 0 ? ret : 1;
61} 76}
62 77
78#ifdef CONFIG_HWMON
79static umode_t mv3310_hwmon_is_visible(const void *data,
80 enum hwmon_sensor_types type,
81 u32 attr, int channel)
82{
83 if (type == hwmon_chip && attr == hwmon_chip_update_interval)
84 return 0444;
85 if (type == hwmon_temp && attr == hwmon_temp_input)
86 return 0444;
87 return 0;
88}
89
90static int mv3310_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
91 u32 attr, int channel, long *value)
92{
93 struct phy_device *phydev = dev_get_drvdata(dev);
94 int temp;
95
96 if (type == hwmon_chip && attr == hwmon_chip_update_interval) {
97 *value = MSEC_PER_SEC;
98 return 0;
99 }
100
101 if (type == hwmon_temp && attr == hwmon_temp_input) {
102 temp = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP);
103 if (temp < 0)
104 return temp;
105
106 *value = ((temp & 0xff) - 75) * 1000;
107
108 return 0;
109 }
110
111 return -EOPNOTSUPP;
112}
113
114static const struct hwmon_ops mv3310_hwmon_ops = {
115 .is_visible = mv3310_hwmon_is_visible,
116 .read = mv3310_hwmon_read,
117};
118
119static u32 mv3310_hwmon_chip_config[] = {
120 HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
121 0,
122};
123
124static const struct hwmon_channel_info mv3310_hwmon_chip = {
125 .type = hwmon_chip,
126 .config = mv3310_hwmon_chip_config,
127};
128
129static u32 mv3310_hwmon_temp_config[] = {
130 HWMON_T_INPUT,
131 0,
132};
133
134static const struct hwmon_channel_info mv3310_hwmon_temp = {
135 .type = hwmon_temp,
136 .config = mv3310_hwmon_temp_config,
137};
138
139static const struct hwmon_channel_info *mv3310_hwmon_info[] = {
140 &mv3310_hwmon_chip,
141 &mv3310_hwmon_temp,
142 NULL,
143};
144
145static const struct hwmon_chip_info mv3310_hwmon_chip_info = {
146 .ops = &mv3310_hwmon_ops,
147 .info = mv3310_hwmon_info,
148};
149
150static int mv3310_hwmon_config(struct phy_device *phydev, bool enable)
151{
152 u16 val;
153 int ret;
154
155 ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP,
156 MV_V2_TEMP_UNKNOWN);
157 if (ret < 0)
158 return ret;
159
160 val = enable ? MV_V2_TEMP_CTRL_SAMPLE : MV_V2_TEMP_CTRL_DISABLE;
161 ret = mv3310_modify(phydev, MDIO_MMD_VEND2, MV_V2_TEMP_CTRL,
162 MV_V2_TEMP_CTRL_MASK, val);
163
164 return ret < 0 ? ret : 0;
165}
166
167static void mv3310_hwmon_disable(void *data)
168{
169 struct phy_device *phydev = data;
170
171 mv3310_hwmon_config(phydev, false);
172}
173
174static int mv3310_hwmon_probe(struct phy_device *phydev)
175{
176 struct device *dev = &phydev->mdio.dev;
177 struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
178 int i, j, ret;
179
180 priv->hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
181 if (!priv->hwmon_name)
182 return -ENODEV;
183
184 for (i = j = 0; priv->hwmon_name[i]; i++) {
185 if (isalnum(priv->hwmon_name[i])) {
186 if (i != j)
187 priv->hwmon_name[j] = priv->hwmon_name[i];
188 j++;
189 }
190 }
191 priv->hwmon_name[j] = '\0';
192
193 ret = mv3310_hwmon_config(phydev, true);
194 if (ret)
195 return ret;
196
197 ret = devm_add_action_or_reset(dev, mv3310_hwmon_disable, phydev);
198 if (ret)
199 return ret;
200
201 priv->hwmon_dev = devm_hwmon_device_register_with_info(dev,
202 priv->hwmon_name, phydev,
203 &mv3310_hwmon_chip_info, NULL);
204
205 return PTR_ERR_OR_ZERO(priv->hwmon_dev);
206}
207#else
208static inline int mv3310_hwmon_config(struct phy_device *phydev, bool enable)
209{
210 return 0;
211}
212
213static int mv3310_hwmon_probe(struct phy_device *phydev)
214{
215 return 0;
216}
217#endif
218
63static int mv3310_probe(struct phy_device *phydev) 219static int mv3310_probe(struct phy_device *phydev)
64{ 220{
221 struct mv3310_priv *priv;
65 u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; 222 u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
223 int ret;
66 224
67 if (!phydev->is_c45 || 225 if (!phydev->is_c45 ||
68 (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) 226 (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
69 return -ENODEV; 227 return -ENODEV;
70 228
229 priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
230 if (!priv)
231 return -ENOMEM;
232
233 dev_set_drvdata(&phydev->mdio.dev, priv);
234
235 ret = mv3310_hwmon_probe(phydev);
236 if (ret)
237 return ret;
238
239 return 0;
240}
241
242static int mv3310_suspend(struct phy_device *phydev)
243{
71 return 0; 244 return 0;
72} 245}
73 246
247static int mv3310_resume(struct phy_device *phydev)
248{
249 return mv3310_hwmon_config(phydev, true);
250}
251
74static int mv3310_config_init(struct phy_device *phydev) 252static int mv3310_config_init(struct phy_device *phydev)
75{ 253{
76 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; 254 __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
@@ -367,9 +545,11 @@ static struct phy_driver mv3310_drivers[] = {
367 SUPPORTED_FIBRE | 545 SUPPORTED_FIBRE |
368 SUPPORTED_10000baseT_Full | 546 SUPPORTED_10000baseT_Full |
369 SUPPORTED_Backplane, 547 SUPPORTED_Backplane,
370 .probe = mv3310_probe,
371 .soft_reset = gen10g_no_soft_reset, 548 .soft_reset = gen10g_no_soft_reset,
372 .config_init = mv3310_config_init, 549 .config_init = mv3310_config_init,
550 .probe = mv3310_probe,
551 .suspend = mv3310_suspend,
552 .resume = mv3310_resume,
373 .config_aneg = mv3310_config_aneg, 553 .config_aneg = mv3310_config_aneg,
374 .aneg_done = mv3310_aneg_done, 554 .aneg_done = mv3310_aneg_done,
375 .read_status = mv3310_read_status, 555 .read_status = mv3310_read_status,