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