mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
Add Raspberry Pi PoE+ HAT support
Signed-off-by: Serge Schneider <serge@raspberrypi.com> power: rpi-poe: Drop CURRENT_AVG as it is not hardware averaged As documented the _AVG parameters are meant to be hardware averaged, but the implementation for the PoE+ HAT was done in software in the firmware. Drop the property. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com> power: rpi-poe: Add option of being created by MFD or FW The firmware can only use I2C0 if the kernel isn't, therefore with libcamera and DRM using it the PoE HAT fan control needs to move to the kernel. Add the option for the driver to be created by the PoE HAT core MFD driver, and use the I2C regmap that provides to control fan functions. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com> power: rpi-poe: FIXUP DRIVER. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
This commit is contained in:
committed by
Dom Cobley
parent
4bd71f9926
commit
33afe2930d
@@ -28,6 +28,13 @@ config POWER_SUPPLY_HWMON
|
||||
Say 'Y' here if you want power supplies to
|
||||
have hwmon sysfs interface too.
|
||||
|
||||
config RPI_POE_POWER
|
||||
tristate "Raspberry Pi PoE+ HAT power supply driver"
|
||||
depends on RASPBERRYPI_FIRMWARE
|
||||
help
|
||||
Say Y here to enable support for Raspberry Pi PoE+ (Power over Ethernet
|
||||
Plus) HAT current measurement.
|
||||
|
||||
config APM_POWER
|
||||
tristate "APM emulation for class batteries"
|
||||
depends on APM_EMULATION
|
||||
|
||||
@@ -10,6 +10,7 @@ obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
|
||||
obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o
|
||||
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
|
||||
|
||||
obj-$(CONFIG_RPI_POE_POWER) += rpi_poe_power.o
|
||||
obj-$(CONFIG_APM_POWER) += apm_power.o
|
||||
obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o
|
||||
obj-$(CONFIG_IP5XXX_POWER) += ip5xxx_power.o
|
||||
|
||||
243
drivers/power/supply/rpi_poe_power.c
Normal file
243
drivers/power/supply/rpi_poe_power.c
Normal file
@@ -0,0 +1,243 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rpi-poe-power.c - Raspberry Pi PoE+ HAT power supply driver.
|
||||
*
|
||||
* Copyright (C) 2019 Raspberry Pi (Trading) Ltd.
|
||||
* Based on axp20x_ac_power.c by Quentin Schulz <quentin.schulz@free-electrons.com>
|
||||
*
|
||||
* Author: Serge Schneider <serge@raspberrypi.org>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <soc/bcm2835/raspberrypi-firmware.h>
|
||||
|
||||
#define RPI_POE_FW_BASE_REG 0x2
|
||||
|
||||
#define RPI_POE_ADC_REG 0x0
|
||||
#define RPI_POE_FLAG_REG 0x2
|
||||
|
||||
#define RPI_POE_FLAG_AT BIT(0)
|
||||
#define RPI_POE_FLAG_OC BIT(1)
|
||||
|
||||
#define RPI_POE_CURRENT_AF_MAX (2500 * 1000)
|
||||
#define RPI_POE_CURRENT_AT_MAX (5000 * 1000)
|
||||
|
||||
#define DRVNAME "rpi-poe-power-supply"
|
||||
|
||||
struct rpi_poe_power_supply_ctx {
|
||||
struct rpi_firmware *fw;
|
||||
|
||||
struct regmap *regmap;
|
||||
u32 offset;
|
||||
|
||||
struct power_supply *supply;
|
||||
};
|
||||
|
||||
struct fw_tag_data_s {
|
||||
u32 reg;
|
||||
u32 val;
|
||||
u32 ret;
|
||||
};
|
||||
|
||||
static int write_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val)
|
||||
{
|
||||
struct fw_tag_data_s fw_tag_data = {
|
||||
.reg = reg + RPI_POE_FW_BASE_REG,
|
||||
.val = *val
|
||||
};
|
||||
int ret;
|
||||
|
||||
if (ctx->fw) {
|
||||
ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_SET_POE_HAT_VAL,
|
||||
&fw_tag_data, sizeof(fw_tag_data));
|
||||
if (!ret && fw_tag_data.ret)
|
||||
ret = -EIO;
|
||||
} else {
|
||||
ret = regmap_write(ctx->regmap, ctx->offset + reg, *val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int read_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val)
|
||||
{
|
||||
struct fw_tag_data_s fw_tag_data = {
|
||||
.reg = reg + RPI_POE_FW_BASE_REG,
|
||||
.val = *val
|
||||
};
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
if (ctx->fw) {
|
||||
ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_GET_POE_HAT_VAL,
|
||||
&fw_tag_data, sizeof(fw_tag_data));
|
||||
if (!ret && fw_tag_data.ret)
|
||||
ret = -EIO;
|
||||
*val = fw_tag_data.val;
|
||||
} else {
|
||||
ret = regmap_read(ctx->regmap, ctx->offset + reg, &value);
|
||||
if (!ret) {
|
||||
*val = value;
|
||||
ret = regmap_read(ctx->regmap, ctx->offset + reg + 1, &value);
|
||||
*val |= value << 8;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rpi_poe_power_supply_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *r_val)
|
||||
{
|
||||
struct rpi_poe_power_supply_ctx *ctx = power_supply_get_drvdata(psy);
|
||||
int ret;
|
||||
unsigned int val = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
ret = read_reg(ctx, RPI_POE_FLAG_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & RPI_POE_FLAG_OC) {
|
||||
r_val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
val = RPI_POE_FLAG_OC;
|
||||
ret = write_reg(ctx, RPI_POE_FLAG_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r_val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
ret = read_reg(ctx, RPI_POE_ADC_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
r_val->intval = (val > 5);
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
ret = read_reg(ctx, RPI_POE_ADC_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
val = (val * 3300)/9821;
|
||||
r_val->intval = val * 1000;
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
ret = read_reg(ctx, RPI_POE_FLAG_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val & RPI_POE_FLAG_AT)
|
||||
r_val->intval = RPI_POE_CURRENT_AT_MAX;
|
||||
else
|
||||
r_val->intval = RPI_POE_CURRENT_AF_MAX;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static enum power_supply_property rpi_poe_power_supply_properties[] = {
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc rpi_poe_power_supply_desc = {
|
||||
.name = "rpi-poe",
|
||||
.type = POWER_SUPPLY_TYPE_MAINS,
|
||||
.properties = rpi_poe_power_supply_properties,
|
||||
.num_properties = ARRAY_SIZE(rpi_poe_power_supply_properties),
|
||||
.get_property = rpi_poe_power_supply_get_property,
|
||||
};
|
||||
|
||||
static int rpi_poe_power_supply_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct rpi_poe_power_supply_ctx *ctx;
|
||||
struct device_node *fw_node;
|
||||
u32 revision;
|
||||
|
||||
if (!fwnode_device_is_available(pdev->dev.fwnode))
|
||||
return -ENODEV;
|
||||
|
||||
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
if (pdev->dev.parent)
|
||||
ctx->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
|
||||
if (ctx->regmap) {
|
||||
if (device_property_read_u32(&pdev->dev, "reg", &ctx->offset))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
fw_node = of_find_compatible_node(NULL, NULL, "raspberrypi,bcm2835-firmware");
|
||||
if (!fw_node) {
|
||||
dev_err(&pdev->dev, "Missing firmware node\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ctx->fw = rpi_firmware_get(fw_node);
|
||||
if (!ctx->fw)
|
||||
return -EPROBE_DEFER;
|
||||
if (rpi_firmware_property(ctx->fw,
|
||||
RPI_FIRMWARE_GET_FIRMWARE_REVISION,
|
||||
&revision, sizeof(revision))) {
|
||||
dev_err(&pdev->dev, "Failed to get firmware revision\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
if (revision < 0x60af72e8) {
|
||||
dev_err(&pdev->dev, "Unsupported firmware\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ctx);
|
||||
|
||||
psy_cfg.fwnode = dev_fwnode(&pdev->dev);
|
||||
psy_cfg.drv_data = ctx;
|
||||
|
||||
ctx->supply = devm_power_supply_register(&pdev->dev,
|
||||
&rpi_poe_power_supply_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(ctx->supply))
|
||||
return PTR_ERR(ctx->supply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_rpi_poe_power_supply_match[] = {
|
||||
{ .compatible = "raspberrypi,rpi-poe-power-supply", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_rpi_poe_power_supply_match);
|
||||
|
||||
static struct platform_driver rpi_poe_power_supply_driver = {
|
||||
.probe = rpi_poe_power_supply_probe,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.of_match_table = of_rpi_poe_power_supply_match
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rpi_poe_power_supply_driver);
|
||||
|
||||
MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
|
||||
MODULE_ALIAS("platform:" DRVNAME);
|
||||
MODULE_DESCRIPTION("Raspberry Pi PoE+ HAT power supply driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user