diff options
author | Russell King <rmk+kernel@armlinux.org.uk> | 2018-04-03 05:31:45 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-04-04 11:24:54 -0400 |
commit | 0d3ad8549c6327a8e0bad0fb785c00e07672f296 (patch) | |
tree | 919aa9917ef2e40964634ad08e580b63c212ab6d | |
parent | bfacfb457b36911a10140b8cb3ce76a74883ac5a (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.c | 184 |
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 | ||
27 | enum { | 29 | enum { |
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 | |||
55 | struct mv3310_priv { | ||
56 | struct device *hwmon_dev; | ||
57 | char *hwmon_name; | ||
43 | }; | 58 | }; |
44 | 59 | ||
45 | static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, | 60 | 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, | |||
60 | return ret < 0 ? ret : 1; | 75 | return ret < 0 ? ret : 1; |
61 | } | 76 | } |
62 | 77 | ||
78 | #ifdef CONFIG_HWMON | ||
79 | static 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 | |||
90 | static 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 | |||
114 | static const struct hwmon_ops mv3310_hwmon_ops = { | ||
115 | .is_visible = mv3310_hwmon_is_visible, | ||
116 | .read = mv3310_hwmon_read, | ||
117 | }; | ||
118 | |||
119 | static u32 mv3310_hwmon_chip_config[] = { | ||
120 | HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, | ||
121 | 0, | ||
122 | }; | ||
123 | |||
124 | static const struct hwmon_channel_info mv3310_hwmon_chip = { | ||
125 | .type = hwmon_chip, | ||
126 | .config = mv3310_hwmon_chip_config, | ||
127 | }; | ||
128 | |||
129 | static u32 mv3310_hwmon_temp_config[] = { | ||
130 | HWMON_T_INPUT, | ||
131 | 0, | ||
132 | }; | ||
133 | |||
134 | static const struct hwmon_channel_info mv3310_hwmon_temp = { | ||
135 | .type = hwmon_temp, | ||
136 | .config = mv3310_hwmon_temp_config, | ||
137 | }; | ||
138 | |||
139 | static const struct hwmon_channel_info *mv3310_hwmon_info[] = { | ||
140 | &mv3310_hwmon_chip, | ||
141 | &mv3310_hwmon_temp, | ||
142 | NULL, | ||
143 | }; | ||
144 | |||
145 | static const struct hwmon_chip_info mv3310_hwmon_chip_info = { | ||
146 | .ops = &mv3310_hwmon_ops, | ||
147 | .info = mv3310_hwmon_info, | ||
148 | }; | ||
149 | |||
150 | static 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 | |||
167 | static void mv3310_hwmon_disable(void *data) | ||
168 | { | ||
169 | struct phy_device *phydev = data; | ||
170 | |||
171 | mv3310_hwmon_config(phydev, false); | ||
172 | } | ||
173 | |||
174 | static 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 | ||
208 | static inline int mv3310_hwmon_config(struct phy_device *phydev, bool enable) | ||
209 | { | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static int mv3310_hwmon_probe(struct phy_device *phydev) | ||
214 | { | ||
215 | return 0; | ||
216 | } | ||
217 | #endif | ||
218 | |||
63 | static int mv3310_probe(struct phy_device *phydev) | 219 | static 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 | |||
242 | static int mv3310_suspend(struct phy_device *phydev) | ||
243 | { | ||
71 | return 0; | 244 | return 0; |
72 | } | 245 | } |
73 | 246 | ||
247 | static int mv3310_resume(struct phy_device *phydev) | ||
248 | { | ||
249 | return mv3310_hwmon_config(phydev, true); | ||
250 | } | ||
251 | |||
74 | static int mv3310_config_init(struct phy_device *phydev) | 252 | static 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, |