Skip to content

Commit c92ca13

Browse files
XECDesignpopcornmix
authored andcommitted
Add Raspberry Pi PoE+ HAT support
Signed-off-by: Serge Schneider <[email protected]>
1 parent 9212363 commit c92ca13

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

drivers/power/supply/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ config POWER_SUPPLY_HWMON
2828
Say 'Y' here if you want power supplies to
2929
have hwmon sysfs interface too.
3030

31+
config RPI_POE_POWER
32+
tristate "Raspberry Pi PoE+ HAT power supply driver"
33+
depends on RASPBERRYPI_FIRMWARE
34+
help
35+
Say Y here to enable support for Raspberry Pi PoE+ (Power over Ethernet
36+
Plus) HAT current measurement.
3137

3238
config PDA_POWER
3339
tristate "Generic PDA/phone power driver"

drivers/power/supply/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
99
obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
1010
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
1111

12+
obj-$(CONFIG_RPI_POE_POWER) += rpi_poe_power.o
1213
obj-$(CONFIG_PDA_POWER) += pda_power.o
1314
obj-$(CONFIG_APM_POWER) += apm_power.o
1415
obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o

drivers/power/supply/rpi_poe_power.c

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* rpi-poe-power.c - Raspberry Pi PoE+ HAT power supply driver.
4+
*
5+
* Copyright (C) 2019 Raspberry Pi (Trading) Ltd.
6+
* Based on axp20x_ac_power.c by Quentin Schulz <[email protected]>
7+
*
8+
* Author: Serge Schneider <[email protected]>
9+
*/
10+
11+
#include <linux/module.h>
12+
#include <linux/of.h>
13+
#include <linux/platform_device.h>
14+
#include <linux/power_supply.h>
15+
#include <soc/bcm2835/raspberrypi-firmware.h>
16+
17+
#define RPI_POE_ADC_REG 0x2
18+
#define RPI_POE_FLAG_REG 0x4
19+
20+
#define RPI_POE_FLAG_AT BIT(0)
21+
#define RPI_POE_FLAG_OC BIT(1)
22+
23+
#define RPI_POE_CURRENT_AF_MAX (2500 * 1000)
24+
#define RPI_POE_CURRENT_AT_MAX (5000 * 1000)
25+
26+
#define DRVNAME "rpi-poe-power-supply"
27+
28+
struct rpi_poe_power_supply_ctx {
29+
struct power_supply *supply;
30+
struct rpi_firmware *fw;
31+
};
32+
33+
struct fw_tag_data_s {
34+
u32 reg;
35+
u32 val;
36+
u32 ret;
37+
};
38+
39+
static int write_reg(struct rpi_firmware *fw, u32 reg, u32 *val)
40+
{
41+
struct fw_tag_data_s fw_tag_data = {
42+
.reg = reg,
43+
.val = *val
44+
};
45+
int ret;
46+
47+
ret = rpi_firmware_property(fw, RPI_FIRMWARE_SET_POE_HAT_VAL,
48+
&fw_tag_data, sizeof(fw_tag_data));
49+
if (ret)
50+
return ret;
51+
else if (fw_tag_data.ret)
52+
return -EIO;
53+
return 0;
54+
}
55+
56+
static int read_reg(struct rpi_firmware *fw, u32 reg, u32 *val)
57+
{
58+
struct fw_tag_data_s fw_tag_data = {
59+
.reg = reg,
60+
.val = *val
61+
};
62+
int ret;
63+
64+
ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_POE_HAT_VAL,
65+
&fw_tag_data, sizeof(fw_tag_data));
66+
if (ret)
67+
return ret;
68+
else if (fw_tag_data.ret)
69+
return -EIO;
70+
71+
*val = fw_tag_data.val;
72+
return 0;
73+
}
74+
75+
static int rpi_poe_power_supply_get_property(struct power_supply *psy,
76+
enum power_supply_property psp,
77+
union power_supply_propval *r_val)
78+
{
79+
struct rpi_poe_power_supply_ctx *ctx = power_supply_get_drvdata(psy);
80+
int ret;
81+
unsigned int val = 0;
82+
83+
switch (psp) {
84+
case POWER_SUPPLY_PROP_HEALTH:
85+
ret = read_reg(ctx->fw, RPI_POE_FLAG_REG, &val);
86+
if (ret)
87+
return ret;
88+
89+
if (val & RPI_POE_FLAG_OC) {
90+
r_val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
91+
val = RPI_POE_FLAG_OC;
92+
ret = write_reg(ctx->fw, RPI_POE_FLAG_REG, &val);
93+
if (ret)
94+
return ret;
95+
return 0;
96+
}
97+
98+
r_val->intval = POWER_SUPPLY_HEALTH_GOOD;
99+
return 0;
100+
101+
case POWER_SUPPLY_PROP_ONLINE:
102+
ret = read_reg(ctx->fw, RPI_POE_ADC_REG, &val);
103+
if (ret)
104+
return ret;
105+
106+
r_val->intval = (val > 5);
107+
return 0;
108+
109+
case POWER_SUPPLY_PROP_CURRENT_AVG:
110+
val = 50;
111+
ret = read_reg(ctx->fw, RPI_POE_ADC_REG, &val);
112+
if (ret)
113+
return ret;
114+
val = (val * 3300)/9821;
115+
r_val->intval = val * 1000;
116+
return 0;
117+
118+
case POWER_SUPPLY_PROP_CURRENT_NOW:
119+
ret = read_reg(ctx->fw, RPI_POE_ADC_REG, &val);
120+
if (ret)
121+
return ret;
122+
val = (val * 3300)/9821;
123+
r_val->intval = val * 1000;
124+
return 0;
125+
126+
case POWER_SUPPLY_PROP_CURRENT_MAX:
127+
ret = read_reg(ctx->fw, RPI_POE_FLAG_REG, &val);
128+
if (ret)
129+
return ret;
130+
131+
if (val & RPI_POE_FLAG_AT) {
132+
r_val->intval = RPI_POE_CURRENT_AT_MAX;
133+
return 0;
134+
}
135+
r_val->intval = RPI_POE_CURRENT_AF_MAX;
136+
return 0;
137+
138+
default:
139+
return -EINVAL;
140+
}
141+
142+
return -EINVAL;
143+
}
144+
145+
static enum power_supply_property rpi_poe_power_supply_properties[] = {
146+
POWER_SUPPLY_PROP_HEALTH,
147+
POWER_SUPPLY_PROP_ONLINE,
148+
POWER_SUPPLY_PROP_CURRENT_AVG,
149+
POWER_SUPPLY_PROP_CURRENT_NOW,
150+
POWER_SUPPLY_PROP_CURRENT_MAX,
151+
};
152+
153+
static const struct power_supply_desc rpi_poe_power_supply_desc = {
154+
.name = "rpi-poe",
155+
.type = POWER_SUPPLY_TYPE_MAINS,
156+
.properties = rpi_poe_power_supply_properties,
157+
.num_properties = ARRAY_SIZE(rpi_poe_power_supply_properties),
158+
.get_property = rpi_poe_power_supply_get_property,
159+
};
160+
161+
static int rpi_poe_power_supply_probe(struct platform_device *pdev)
162+
{
163+
struct power_supply_config psy_cfg = {};
164+
struct rpi_poe_power_supply_ctx *ctx;
165+
struct device_node *fw_node;
166+
u32 revision;
167+
168+
if (!of_device_is_available(pdev->dev.of_node))
169+
return -ENODEV;
170+
171+
fw_node = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
172+
if (!fw_node) {
173+
dev_err(&pdev->dev, "Missing firmware node\n");
174+
return -ENOENT;
175+
}
176+
177+
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
178+
if (!ctx)
179+
return -ENOMEM;
180+
181+
ctx->fw = rpi_firmware_get(fw_node);
182+
if (!ctx->fw)
183+
return -EPROBE_DEFER;
184+
if (rpi_firmware_property(ctx->fw,
185+
RPI_FIRMWARE_GET_FIRMWARE_REVISION,
186+
&revision, sizeof(revision))) {
187+
dev_err(&pdev->dev, "Failed to get firmware revision\n");
188+
return -ENOENT;
189+
}
190+
if (revision < 0x60af72e8) {
191+
dev_err(&pdev->dev, "Unsupported firmware\n");
192+
return -ENOENT;
193+
}
194+
platform_set_drvdata(pdev, ctx);
195+
196+
psy_cfg.of_node = pdev->dev.of_node;
197+
psy_cfg.drv_data = ctx;
198+
199+
ctx->supply = devm_power_supply_register(&pdev->dev,
200+
&rpi_poe_power_supply_desc,
201+
&psy_cfg);
202+
if (IS_ERR(ctx->supply))
203+
return PTR_ERR(ctx->supply);
204+
205+
return 0;
206+
}
207+
208+
static const struct of_device_id of_rpi_poe_power_supply_match[] = {
209+
{ .compatible = "raspberrypi,rpi-poe-power-supply", },
210+
{ /* sentinel */ }
211+
};
212+
MODULE_DEVICE_TABLE(of, of_rpi_poe_power_supply_match);
213+
214+
static struct platform_driver rpi_poe_power_supply_driver = {
215+
.probe = rpi_poe_power_supply_probe,
216+
.driver = {
217+
.name = DRVNAME,
218+
.of_match_table = of_rpi_poe_power_supply_match
219+
},
220+
};
221+
222+
module_platform_driver(rpi_poe_power_supply_driver);
223+
224+
MODULE_AUTHOR("Serge Schneider <[email protected]>");
225+
MODULE_ALIAS("platform:" DRVNAME);
226+
MODULE_DESCRIPTION("Raspberry Pi PoE+ HAT power supply driver");
227+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)