[PATCH 4/9] power: supply: photonicat-supply: Add Photonicat PMU battery and charger
From: Junhao Xie
Date: Fri Sep 06 2024 - 05:39:28 EST
Photonicat PMU supports battery and charger power supply.
The MCU only provides voltage meter.
Signed-off-by: Junhao Xie <bigfoot@xxxxxxxxxxx>
---
drivers/power/supply/Kconfig | 12 ++
drivers/power/supply/Makefile | 1 +
drivers/power/supply/photonicat-supply.c | 250 +++++++++++++++++++++++
3 files changed, 263 insertions(+)
create mode 100644 drivers/power/supply/photonicat-supply.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index bcfa63fb9f1e..4d2fcf568810 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -455,6 +455,18 @@ config CHARGER_PCF50633
help
Say Y to include support for NXP PCF50633 Main Battery Charger.
+config PHOTONICAT_POWER
+ tristate "Photonicat PMU power supply driver"
+ depends on MFD_PHOTONICAT_PMU
+ help
+ Photonicat PMU supports battery and charger power supply.
+ The MCU only provides voltage meter.
+
+ Say Y here to enable support for Photonicat PMU power supply.
+
+ This driver can also be built as a module. If so, the module will be
+ called photonicat-supply.
+
config BATTERY_RX51
tristate "Nokia RX-51 (N900) battery driver"
depends on TWL4030_MADC
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 8dcb41545317..81ada9bb6438 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_PHOTONICAT_POWER) += photonicat-supply.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
diff --git a/drivers/power/supply/photonicat-supply.c b/drivers/power/supply/photonicat-supply.c
new file mode 100644
index 000000000000..e6861a3904fe
--- /dev/null
+++ b/drivers/power/supply/photonicat-supply.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Junhao Xie <bigfoot@xxxxxxxxxxx>
+ */
+
+#include <linux/module.h>
+#include <linux/mfd/photonicat-pmu.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+enum pcat_supply_type {
+ PCAT_SUPPLY_BATTERY,
+ PCAT_SUPPLY_CHARGER,
+};
+
+struct pcat_supply {
+ struct device *dev;
+ struct pcat_pmu *pmu;
+ struct notifier_block nb;
+ struct power_supply *psy;
+ struct power_supply_desc desc;
+ struct power_supply_battery_info *bat_info;
+ enum pcat_supply_type type;
+ u16 supply_microvolt;
+ struct completion initial_report;
+};
+
+static int pcat_pmu_get_capacity(struct pcat_supply *supply)
+{
+ int uv;
+
+ if (supply->type != PCAT_SUPPLY_BATTERY)
+ return 0;
+ uv = supply->supply_microvolt * 1000;
+
+ return power_supply_batinfo_ocv2cap(supply->bat_info, uv, 20);
+}
+
+static int pcat_pmu_get_energy(struct pcat_supply *supply)
+{
+ int capacity;
+
+ if (supply->type != PCAT_SUPPLY_BATTERY)
+ return 0;
+ capacity = pcat_pmu_get_capacity(supply);
+ if (capacity < 0)
+ return 0;
+
+ return supply->bat_info->energy_full_design_uwh / 100 * capacity;
+}
+
+static int pcat_pmu_get_status(struct pcat_supply *supply)
+{
+ if (supply->type != PCAT_SUPPLY_BATTERY)
+ return 0;
+
+ if (pcat_pmu_get_capacity(supply) < 100) {
+ if (power_supply_am_i_supplied(supply->psy))
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ return POWER_SUPPLY_STATUS_FULL;
+}
+
+static int pcat_pmu_get_supply_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcat_supply *supply = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = pcat_pmu_get_capacity(supply);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = supply->supply_microvolt > 1000;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = supply->bat_info->energy_full_design_uwh;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ val->intval = pcat_pmu_get_energy(supply);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = pcat_pmu_get_status(supply);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = supply->bat_info->voltage_max_design_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = supply->bat_info->voltage_min_design_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = supply->supply_microvolt * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property pcat_charger_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property pcat_battery_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static int pcat_supply_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct pcat_supply *supply = container_of(nb, struct pcat_supply, nb);
+ struct pcat_data_cmd_status *status = pcat_data_get_data(data);
+
+ if (action != PCAT_CMD_STATUS_REPORT)
+ return NOTIFY_DONE;
+
+ switch (supply->type) {
+ case PCAT_SUPPLY_BATTERY:
+ supply->supply_microvolt = status->battery_microvolt;
+ break;
+ case PCAT_SUPPLY_CHARGER:
+ supply->supply_microvolt = status->charger_microvolt;
+ break;
+ }
+
+ complete(&supply->initial_report);
+
+ return NOTIFY_DONE;
+}
+
+static int pcat_supply_probe(struct platform_device *pdev)
+{
+ int ret;
+ const char *label;
+ const char *supply_type;
+ struct device *dev = &pdev->dev;
+ struct pcat_supply *supply;
+ struct power_supply_config psy_cfg = {};
+
+ supply = devm_kzalloc(dev, sizeof(*supply), GFP_KERNEL);
+ if (!supply)
+ return -ENOMEM;
+
+ supply->dev = dev;
+ supply->pmu = dev_get_drvdata(dev->parent);
+ supply->nb.notifier_call = pcat_supply_notify;
+ init_completion(&supply->initial_report);
+ psy_cfg.drv_data = supply;
+ psy_cfg.of_node = dev->of_node;
+ platform_set_drvdata(pdev, supply);
+
+ ret = of_property_read_string(dev->of_node, "type", &supply_type);
+ if (ret)
+ return dev_err_probe(dev, ret, "No supply type property\n");
+
+ ret = of_property_read_string(dev->of_node, "label", &label);
+ if (ret)
+ return dev_err_probe(dev, ret, "No label property\n");
+
+ if (!strcmp(supply_type, "battery")) {
+ supply->type = PCAT_SUPPLY_BATTERY;
+ supply->desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ supply->desc.properties = pcat_battery_props;
+ supply->desc.num_properties = ARRAY_SIZE(pcat_battery_props);
+ } else if (!strcmp(supply_type, "charger")) {
+ supply->type = PCAT_SUPPLY_CHARGER;
+ supply->desc.type = POWER_SUPPLY_TYPE_MAINS;
+ supply->desc.properties = pcat_charger_props;
+ supply->desc.num_properties = ARRAY_SIZE(pcat_charger_props);
+ } else
+ return dev_err_probe(dev, -EINVAL, "Unknown supply type %s\n",
+ supply_type);
+
+ ret = pcat_pmu_register_notify(supply->pmu, &supply->nb);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&supply->initial_report,
+ msecs_to_jiffies(3000))) {
+ ret = dev_err_probe(dev, -ETIMEDOUT,
+ "timeout waiting for initial report\n");
+ goto error;
+ }
+
+ dev_info(dev, "Voltage: %u mV\n", supply->supply_microvolt);
+
+ supply->desc.name = label;
+ supply->desc.get_property = pcat_pmu_get_supply_property;
+
+ supply->psy = devm_power_supply_register(dev, &supply->desc, &psy_cfg);
+ if (IS_ERR(supply->psy)) {
+ ret = PTR_ERR(supply->psy);
+ dev_err_probe(dev, ret, "Failed to register supply\n");
+ goto error;
+ }
+
+ if (supply->type == PCAT_SUPPLY_BATTERY) {
+ ret = power_supply_get_battery_info(supply->psy,
+ &supply->bat_info);
+ if (ret) {
+ dev_err_probe(dev, ret, "Unable to get battery info\n");
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ pcat_pmu_unregister_notify(supply->pmu, &supply->nb);
+ return ret;
+}
+
+static void pcat_supply_remove(struct platform_device *pdev)
+{
+ struct pcat_supply *supply = platform_get_drvdata(pdev);
+
+ pcat_pmu_unregister_notify(supply->pmu, &supply->nb);
+}
+
+static const struct of_device_id pcat_supply_dt_ids[] = {
+ { .compatible = "ariaboard,photonicat-pmu-supply", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pcat_supply_dt_ids);
+
+static struct platform_driver pcat_supply_driver = {
+ .driver = {
+ .name = "photonicat-supply",
+ .of_match_table = pcat_supply_dt_ids,
+ },
+ .probe = pcat_supply_probe,
+ .remove = pcat_supply_remove,
+};
+module_platform_driver(pcat_supply_driver);
+
+MODULE_AUTHOR("Junhao Xie <bigfoot@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("Photonicat PMU Power Supply");
+MODULE_LICENSE("GPL");
--
2.46.0