Skip to content

Commit fb470f7

Browse files
hkallweitdavem330
authored andcommitted
net: phy: aquantia: add hwmon support
This adds HWMON support for the temperature sensor and the related alarms on the 107/108/109 chips. This patch is based on work from Nikita and Andrew. I added: - support for changing alarm thresholds via sysfs - move HWMON code to a separate source file to improve maintainability - smaller changes like using IS_REACHABLE instead of ifdef (avoids problems if PHY driver is built in and HWMON is a module) v2: - remove struct aqr_priv - rename header file to aquantia.h v3: - add conditional compiling of aquantia_hwmon.c - improve converting sensor register values to/from long - add helper aqr_hwmon_test_bit Signed-off-by: Nikita Yushchenko <[email protected]> Signed-off-by: Andrew Lunn <[email protected]> Signed-off-by: Heiner Kallweit <[email protected]> Reviewed-by: Florian Fainelli <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent b4e6a10 commit fb470f7

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed

drivers/net/phy/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
4646

4747
obj-$(CONFIG_AMD_PHY) += amd.o
4848
aquantia-objs += aquantia_main.o
49+
ifdef CONFIG_HWMON
50+
aquantia-objs += aquantia_hwmon.o
51+
endif
4952
obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
5053
obj-$(CONFIG_ASIX_PHY) += asix.o
5154
obj-$(CONFIG_AT803X_PHY) += at803x.o

drivers/net/phy/aquantia.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* SPDX-License-Identifier: GPL-2.0
2+
* HWMON driver for Aquantia PHY
3+
*
4+
* Author: Nikita Yushchenko <[email protected]>
5+
* Author: Andrew Lunn <[email protected]>
6+
* Author: Heiner Kallweit <[email protected]>
7+
*/
8+
9+
#include <linux/device.h>
10+
#include <linux/phy.h>
11+
12+
#if IS_REACHABLE(CONFIG_HWMON)
13+
int aqr_hwmon_probe(struct phy_device *phydev);
14+
#else
15+
static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
16+
#endif

drivers/net/phy/aquantia_hwmon.c

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* HWMON driver for Aquantia PHY
3+
*
4+
* Author: Nikita Yushchenko <[email protected]>
5+
* Author: Andrew Lunn <[email protected]>
6+
* Author: Heiner Kallweit <[email protected]>
7+
*/
8+
9+
#include <linux/phy.h>
10+
#include <linux/device.h>
11+
#include <linux/ctype.h>
12+
#include <linux/hwmon.h>
13+
14+
#include "aquantia.h"
15+
16+
/* Vendor specific 1, MDIO_MMD_VEND2 */
17+
#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
18+
#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
19+
#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
20+
#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
21+
#define VEND1_THERMAL_STAT1 0xc820
22+
#define VEND1_THERMAL_STAT2 0xc821
23+
#define VEND1_THERMAL_STAT2_VALID BIT(0)
24+
#define VEND1_GENERAL_STAT1 0xc830
25+
#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
26+
#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
27+
#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
28+
#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
29+
30+
#if IS_REACHABLE(CONFIG_HWMON)
31+
32+
static umode_t aqr_hwmon_is_visible(const void *data,
33+
enum hwmon_sensor_types type,
34+
u32 attr, int channel)
35+
{
36+
if (type != hwmon_temp)
37+
return 0;
38+
39+
switch (attr) {
40+
case hwmon_temp_input:
41+
case hwmon_temp_min_alarm:
42+
case hwmon_temp_max_alarm:
43+
case hwmon_temp_lcrit_alarm:
44+
case hwmon_temp_crit_alarm:
45+
return 0444;
46+
case hwmon_temp_min:
47+
case hwmon_temp_max:
48+
case hwmon_temp_lcrit:
49+
case hwmon_temp_crit:
50+
return 0644;
51+
default:
52+
return 0;
53+
}
54+
}
55+
56+
static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
57+
{
58+
int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
59+
60+
if (temp < 0)
61+
return temp;
62+
63+
/* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
64+
*value = (s16)temp * 1000 / 256;
65+
66+
return 0;
67+
}
68+
69+
static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
70+
{
71+
int temp;
72+
73+
if (value >= 128000 || value < -128000)
74+
return -ERANGE;
75+
76+
temp = value * 256 / 1000;
77+
78+
/* temp is in s16 range and we're interested in lower 16 bits only */
79+
return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
80+
}
81+
82+
static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
83+
{
84+
int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
85+
86+
if (val < 0)
87+
return val;
88+
89+
return !!(val & bit);
90+
}
91+
92+
static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
93+
{
94+
int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
95+
96+
if (val < 0)
97+
return val;
98+
99+
*value = val;
100+
101+
return 0;
102+
}
103+
104+
static int aqr_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
105+
u32 attr, int channel, long *value)
106+
{
107+
struct phy_device *phydev = dev_get_drvdata(dev);
108+
int reg;
109+
110+
if (type != hwmon_temp)
111+
return -EOPNOTSUPP;
112+
113+
switch (attr) {
114+
case hwmon_temp_input:
115+
reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
116+
VEND1_THERMAL_STAT2_VALID);
117+
if (reg < 0)
118+
return reg;
119+
if (!reg)
120+
return -EBUSY;
121+
122+
return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
123+
124+
case hwmon_temp_lcrit:
125+
return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
126+
value);
127+
case hwmon_temp_min:
128+
return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
129+
value);
130+
case hwmon_temp_max:
131+
return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
132+
value);
133+
case hwmon_temp_crit:
134+
return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
135+
value);
136+
case hwmon_temp_lcrit_alarm:
137+
return aqr_hwmon_status1(phydev,
138+
VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
139+
value);
140+
case hwmon_temp_min_alarm:
141+
return aqr_hwmon_status1(phydev,
142+
VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
143+
value);
144+
case hwmon_temp_max_alarm:
145+
return aqr_hwmon_status1(phydev,
146+
VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
147+
value);
148+
case hwmon_temp_crit_alarm:
149+
return aqr_hwmon_status1(phydev,
150+
VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
151+
value);
152+
default:
153+
return -EOPNOTSUPP;
154+
}
155+
}
156+
157+
static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
158+
u32 attr, int channel, long value)
159+
{
160+
struct phy_device *phydev = dev_get_drvdata(dev);
161+
162+
if (type != hwmon_temp)
163+
return -EOPNOTSUPP;
164+
165+
switch (attr) {
166+
case hwmon_temp_lcrit:
167+
return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
168+
value);
169+
case hwmon_temp_min:
170+
return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
171+
value);
172+
case hwmon_temp_max:
173+
return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
174+
value);
175+
case hwmon_temp_crit:
176+
return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
177+
value);
178+
default:
179+
return -EOPNOTSUPP;
180+
}
181+
}
182+
183+
static const struct hwmon_ops aqr_hwmon_ops = {
184+
.is_visible = aqr_hwmon_is_visible,
185+
.read = aqr_hwmon_read,
186+
.write = aqr_hwmon_write,
187+
};
188+
189+
static u32 aqr_hwmon_chip_config[] = {
190+
HWMON_C_REGISTER_TZ,
191+
0,
192+
};
193+
194+
static const struct hwmon_channel_info aqr_hwmon_chip = {
195+
.type = hwmon_chip,
196+
.config = aqr_hwmon_chip_config,
197+
};
198+
199+
static u32 aqr_hwmon_temp_config[] = {
200+
HWMON_T_INPUT |
201+
HWMON_T_MAX | HWMON_T_MIN |
202+
HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
203+
HWMON_T_CRIT | HWMON_T_LCRIT |
204+
HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
205+
0,
206+
};
207+
208+
static const struct hwmon_channel_info aqr_hwmon_temp = {
209+
.type = hwmon_temp,
210+
.config = aqr_hwmon_temp_config,
211+
};
212+
213+
static const struct hwmon_channel_info *aqr_hwmon_info[] = {
214+
&aqr_hwmon_chip,
215+
&aqr_hwmon_temp,
216+
NULL,
217+
};
218+
219+
static const struct hwmon_chip_info aqr_hwmon_chip_info = {
220+
.ops = &aqr_hwmon_ops,
221+
.info = aqr_hwmon_info,
222+
};
223+
224+
int aqr_hwmon_probe(struct phy_device *phydev)
225+
{
226+
struct device *dev = &phydev->mdio.dev;
227+
struct device *hwmon_dev;
228+
char *hwmon_name;
229+
int i, j;
230+
231+
hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
232+
if (!hwmon_name)
233+
return -ENOMEM;
234+
235+
for (i = j = 0; hwmon_name[i]; i++) {
236+
if (isalnum(hwmon_name[i])) {
237+
if (i != j)
238+
hwmon_name[j] = hwmon_name[i];
239+
j++;
240+
}
241+
}
242+
hwmon_name[j] = '\0';
243+
244+
hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
245+
phydev, &aqr_hwmon_chip_info, NULL);
246+
247+
return PTR_ERR_OR_ZERO(hwmon_dev);
248+
}
249+
250+
#endif

drivers/net/phy/aquantia_main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <linux/delay.h>
1313
#include <linux/phy.h>
1414

15+
#include "aquantia.h"
16+
1517
#define PHY_ID_AQ1202 0x03a1b445
1618
#define PHY_ID_AQ2104 0x03a1b460
1719
#define PHY_ID_AQR105 0x03a1b4a2
@@ -231,6 +233,7 @@ static struct phy_driver aqr_driver[] = {
231233
.name = "Aquantia AQR107",
232234
.aneg_done = genphy_c45_aneg_done,
233235
.get_features = genphy_c45_pma_read_abilities,
236+
.probe = aqr_hwmon_probe,
234237
.config_aneg = aqr_config_aneg,
235238
.config_intr = aqr_config_intr,
236239
.ack_interrupt = aqr_ack_interrupt,
@@ -241,6 +244,7 @@ static struct phy_driver aqr_driver[] = {
241244
.name = "Aquantia AQCS109",
242245
.aneg_done = genphy_c45_aneg_done,
243246
.get_features = genphy_c45_pma_read_abilities,
247+
.probe = aqr_hwmon_probe,
244248
.config_init = aqcs109_config_init,
245249
.config_aneg = aqr_config_aneg,
246250
.config_intr = aqr_config_intr,

0 commit comments

Comments
 (0)