[PATCH 1/9] mfd: Add driver for Photonicat power management MCU

From: Junhao Xie
Date: Fri Sep 06 2024 - 05:39:06 EST


Add a driver for Photonicat power management MCU, which
provides battery and charger power supply, real-time clock,
watchdog, hardware shutdown.

This driver implementes core MFD/serdev device as well as
communication subroutines necessary for commanding the device.

Signed-off-by: Junhao Xie <bigfoot@xxxxxxxxxxx>
---
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/photonicat-pmu.c | 501 +++++++++++++++++++++++++++++
include/linux/mfd/photonicat-pmu.h | 86 +++++
4 files changed, 601 insertions(+)
create mode 100644 drivers/mfd/photonicat-pmu.c
create mode 100644 include/linux/mfd/photonicat-pmu.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index bc8be2e593b6..5fd339aa0f9a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1135,6 +1135,19 @@ config MFD_PM8XXX
Say M here if you want to include support for PM8xxx chips as a
module. This will build a module called "pm8xxx-core".

+config MFD_PHOTONICAT_PMU
+ tristate "Photonicat Power Management MCU"
+ depends on OF
+ depends on SERIAL_DEV_BUS
+ select CRC16
+ select MFD_CORE
+ help
+ Driver for the Power Management MCU in the Ariaboard Photonicat,
+ which provides battery and charger power supply, real-time clock,
+ watchdog, hardware shutdown.
+
+ Say M or Y here to include this support.
+
config MFD_QCOM_RPM
tristate "Qualcomm Resource Power Manager (RPM)"
depends on ARCH_QCOM && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 02b651cd7535..25872850f216 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -223,6 +223,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o
obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o
obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o
obj-$(CONFIG_MFD_PALMAS) += palmas.o
+obj-$(CONFIG_MFD_PHOTONICAT_PMU) += photonicat-pmu.o
obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o
obj-$(CONFIG_MFD_NTXEC) += ntxec.o
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
diff --git a/drivers/mfd/photonicat-pmu.c b/drivers/mfd/photonicat-pmu.c
new file mode 100644
index 000000000000..e6bbaf256c50
--- /dev/null
+++ b/drivers/mfd/photonicat-pmu.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Junhao Xie <bigfoot@xxxxxxxxxxx>
+ */
+
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/crc16.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mfd/photonicat-pmu.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/serdev.h>
+#include <linux/spinlock.h>
+
+#define PCAT_ADDR_CPU(id) ((id & 0x7F))
+#define PCAT_ADDR_PMU(id) ((id & 0x7F) | 0x80)
+#define PCAT_ADDR_CPU_ALL 0x80
+#define PCAT_ADDR_PMU_ALL 0xFE
+#define PCAT_ADDR_ALL 0xFF
+
+#define PCAT_MAGIC_HEAD 0xA5
+#define PCAT_MAGIC_END 0x5A
+
+struct pcat_data_head {
+ u8 magic_head;
+ u8 source;
+ u8 dest;
+ u16 frame_id;
+ u16 length;
+ u16 command;
+} __packed;
+
+struct pcat_data_foot {
+ u8 need_ack;
+ u16 crc16;
+ u8 magic_end;
+} __packed;
+
+struct pcat_data {
+ struct pcat_pmu *pmu;
+ struct pcat_data_head *head;
+ struct pcat_data_foot *foot;
+ void *data;
+ size_t size;
+};
+
+struct pcat_request {
+ struct pcat_pmu *pmu;
+ u16 frame_id;
+ struct completion received;
+ struct pcat_request_request {
+ u16 cmd;
+ u16 want;
+ const void *data;
+ size_t size;
+ } request;
+ struct pcat_request_reply {
+ struct pcat_data_head head;
+ struct pcat_data_foot foot;
+ void *data;
+ size_t size;
+ } reply;
+};
+
+struct pcat_pmu {
+ struct device *dev;
+ struct serdev_device *serdev;
+ atomic_t frame;
+ char buffer[8192];
+ size_t length;
+ struct pcat_request *reply;
+ spinlock_t bus_lock;
+ struct mutex reply_lock;
+ struct mutex status_lock;
+ struct completion first_status;
+ struct blocking_notifier_head notifier_list;
+ u8 local_id;
+ u8 remote_id;
+};
+
+void *pcat_data_get_data(struct pcat_data *data)
+{
+ if (!data)
+ return NULL;
+ return data->data;
+}
+EXPORT_SYMBOL_GPL(pcat_data_get_data);
+
+static int pcat_pmu_raw_write(struct pcat_pmu *pmu, u16 frame_id,
+ enum pcat_pmu_cmd cmd, bool need_ack,
+ const void *data, size_t len)
+{
+ int ret;
+ struct pcat_data_head head;
+ struct pcat_data_foot foot;
+
+ head.magic_head = PCAT_MAGIC_HEAD;
+ head.source = PCAT_ADDR_CPU(pmu->local_id);
+ head.dest = PCAT_ADDR_PMU(pmu->remote_id);
+ head.frame_id = frame_id;
+ head.length = len + sizeof(struct pcat_data_foot) - 1;
+ head.command = cmd;
+
+ ret = serdev_device_write_buf(pmu->serdev, (u8 *)&head, sizeof(head));
+ if (ret < 0) {
+ dev_err(pmu->dev, "failed to write frame head: %d", ret);
+ return ret;
+ }
+
+ ret = serdev_device_write_buf(pmu->serdev, data, len);
+ if (ret < 0) {
+ dev_err(pmu->dev, "failed to write frame body: %d", ret);
+ return ret;
+ }
+
+ foot.need_ack = need_ack;
+ foot.crc16 = 0;
+ foot.magic_end = PCAT_MAGIC_END;
+ foot.crc16 = crc16(0xFFFF, (u8 *)&head + 1, sizeof(head) - 1);
+ foot.crc16 = crc16(foot.crc16, data, len);
+ foot.crc16 = crc16(foot.crc16, (u8 *)&foot, 1);
+
+ ret = serdev_device_write_buf(pmu->serdev, (u8 *)&foot, sizeof(foot));
+ if (ret < 0)
+ dev_err(pmu->dev, "failed to send frame foot: %d", ret);
+
+ return ret;
+}
+
+int pcat_pmu_send(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ const void *data, size_t len)
+{
+ u16 frame_id = atomic_inc_return(&pmu->frame);
+
+ return pcat_pmu_raw_write(pmu, frame_id, cmd, false, data, len);
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_send);
+
+int pcat_pmu_execute(struct pcat_request *request)
+{
+ int ret = 0, retries = 0;
+ unsigned long flags;
+ struct pcat_pmu *pmu = request->pmu;
+ struct pcat_request_request *req = &request->request;
+ struct pcat_request_reply *reply = &request->reply;
+
+ init_completion(&request->received);
+ memset(reply, 0, sizeof(request->reply));
+
+ mutex_lock(&pmu->reply_lock);
+ if (request->frame_id == 0)
+ request->frame_id = atomic_inc_return(&pmu->frame);
+ pmu->reply = request;
+ mutex_unlock(&pmu->reply_lock);
+
+ if (req->want == 0)
+ req->want = req->cmd + 1;
+
+ dev_dbg(pmu->dev, "frame 0x%04X execute cmd 0x%02X\n",
+ request->frame_id, req->cmd);
+
+ while (1) {
+ spin_lock_irqsave(&pmu->bus_lock, flags);
+ ret = pcat_pmu_raw_write(pmu, request->frame_id, req->cmd,
+ true, req->data, req->size);
+ spin_unlock_irqrestore(&pmu->bus_lock, flags);
+ if (ret < 0) {
+ dev_err(pmu->dev,
+ "frame 0x%04X write 0x%02X cmd failed: %d\n",
+ request->frame_id, req->cmd, ret);
+ goto fail;
+ }
+ dev_dbg(pmu->dev, "frame 0x%04X waiting response for 0x%02X\n",
+ request->frame_id, req->cmd);
+ if (!wait_for_completion_timeout(&request->received, HZ)) {
+ if (retries < 3) {
+ retries++;
+ continue;
+ } else {
+ dev_warn(pmu->dev,
+ "frame 0x%04X cmd 0x%02X timeout\n",
+ request->frame_id, req->cmd);
+ ret = -ETIMEDOUT;
+ goto fail;
+ }
+ }
+ break;
+ }
+ dev_dbg(pmu->dev, "frame 0x%04X got response 0x%02X\n",
+ request->frame_id, reply->head.command);
+
+ return 0;
+fail:
+ mutex_lock(&pmu->reply_lock);
+ pmu->reply = NULL;
+ mutex_unlock(&pmu->reply_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_execute);
+
+int pcat_pmu_write_data(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ const void *data, size_t size)
+{
+ int ret;
+ struct pcat_request request = {
+ .pmu = pmu,
+ .request.cmd = cmd,
+ .request.data = data,
+ .request.size = size,
+ };
+ ret = pcat_pmu_execute(&request);
+ if (request.reply.data)
+ devm_kfree(pmu->dev, request.reply.data);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_write_data);
+
+int pcat_pmu_read_string(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ char *str, size_t len)
+{
+ int ret;
+ struct pcat_request request = {
+ .pmu = pmu,
+ .request.cmd = cmd,
+ };
+ memset(str, 0, len);
+ ret = pcat_pmu_execute(&request);
+ if (request.reply.data) {
+ memcpy(str, request.reply.data,
+ min(len - 1, request.reply.size));
+ devm_kfree(pmu->dev, request.reply.data);
+ };
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_read_string);
+
+int pcat_pmu_write_u8(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd, u8 v)
+{
+ return pcat_pmu_write_data(pmu, cmd, &v, sizeof(v));
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_write_u8);
+
+static bool pcat_process_reply(struct pcat_data *p)
+{
+ bool processed = false;
+ struct pcat_pmu *pmu = p->pmu;
+ struct device *dev = pmu->dev;
+ struct pcat_request *request;
+ struct pcat_request_request *req;
+ struct pcat_request_reply *reply;
+
+ mutex_lock(&pmu->reply_lock);
+ request = pmu->reply;
+ if (!request)
+ goto skip;
+
+ req = &request->request;
+ reply = &request->reply;
+ if (request->frame_id != p->head->frame_id) {
+ dev_dbg_ratelimited(dev, "skip mismatch frame %04X != %04X",
+ request->frame_id, p->head->frame_id);
+ goto skip;
+ }
+
+ if (req->want == 0)
+ req->want = req->cmd + 1;
+
+ if (req->want != p->head->command) {
+ dev_dbg_ratelimited(
+ dev, "frame %04X skip mismatch command %02X != %02X",
+ request->frame_id, req->want, p->head->command);
+ goto skip;
+ }
+
+ if (completion_done(&request->received)) {
+ dev_dbg_ratelimited(dev, "frame %04X skip done completion",
+ request->frame_id);
+ goto skip;
+ }
+
+ memcpy(&reply->head, p->head, sizeof(struct pcat_data_head));
+ memcpy(&reply->foot, p->foot, sizeof(struct pcat_data_foot));
+ if (p->data && p->size > 0) {
+ reply->data = devm_kzalloc(pmu->dev, p->size + 1, GFP_KERNEL);
+ if (pmu->reply->reply.data) {
+ memcpy(reply->data, p->data, p->size);
+ reply->size = p->size;
+ }
+ }
+
+ complete(&request->received);
+ pmu->reply = NULL;
+ processed = true;
+
+skip:
+ mutex_unlock(&pmu->reply_lock);
+ return processed;
+}
+
+static int pcat_process_data(struct pcat_pmu *pmu, const u8 *data, size_t len)
+{
+ int ret;
+ u16 crc16_calc;
+ size_t data_size = 0;
+ struct pcat_data frame;
+
+ memset(&frame, 0, sizeof(frame));
+ frame.pmu = pmu;
+ if (len < sizeof(struct pcat_data_head)) {
+ dev_dbg_ratelimited(pmu->dev, "head too small %zu < %zu\n", len,
+ sizeof(struct pcat_data_head));
+ return -EAGAIN;
+ }
+
+ frame.head = (struct pcat_data_head *)data;
+ if (frame.head->magic_head != PCAT_MAGIC_HEAD) {
+ dev_dbg_ratelimited(pmu->dev, "bad head magic %02X\n",
+ frame.head->magic_head);
+ return -EBADMSG;
+ }
+
+ if (frame.head->source != PCAT_ADDR_PMU(pmu->remote_id)) {
+ dev_dbg_ratelimited(pmu->dev, "unknown data source %02X\n",
+ frame.head->source);
+ return 0;
+ }
+ if (frame.head->dest != PCAT_ADDR_CPU(pmu->local_id) &&
+ frame.head->dest != PCAT_ADDR_CPU_ALL &&
+ frame.head->dest != PCAT_ADDR_ALL) {
+ dev_dbg_ratelimited(pmu->dev, "not data destination %02X\n",
+ frame.head->dest);
+ return 0;
+ }
+ if (frame.head->length < sizeof(struct pcat_data_foot) - 1 ||
+ frame.head->length >= U16_MAX - 4) {
+ dev_dbg_ratelimited(pmu->dev, "invalid length %d\n",
+ frame.head->length);
+ return -EBADMSG;
+ }
+ data_size = sizeof(struct pcat_data_head) + frame.head->length + 1;
+ if (data_size > len) {
+ dev_dbg_ratelimited(pmu->dev, "data too small %zu > %zu\n",
+ data_size, len);
+ return -EAGAIN;
+ }
+ frame.data = (u8 *)data + sizeof(struct pcat_data_head);
+ frame.size = frame.head->length + 1 - sizeof(struct pcat_data_foot);
+ frame.foot = (struct pcat_data_foot *)(data + frame.size +
+ sizeof(struct pcat_data_head));
+ if (frame.foot->magic_end != PCAT_MAGIC_END) {
+ dev_dbg_ratelimited(pmu->dev, "bad foot magic %02X\n",
+ frame.foot->magic_end);
+ return -EBADMSG;
+ }
+ crc16_calc = crc16(0xFFFF, data + 1, frame.head->length + 6);
+ if (frame.foot->crc16 != crc16_calc) {
+ dev_warn_ratelimited(pmu->dev, "crc16 mismatch %04X != %04X\n",
+ frame.foot->crc16, crc16_calc);
+ return -EBADMSG;
+ }
+
+ if (pcat_process_reply(&frame))
+ return 0;
+
+ ret = blocking_notifier_call_chain(&pmu->notifier_list,
+ frame.head->command, &frame);
+ if (ret == NOTIFY_DONE && frame.foot->need_ack) {
+ pcat_pmu_raw_write(pmu, frame.head->frame_id,
+ frame.head->command + 1, false, NULL, 0);
+ }
+
+ return 0;
+}
+
+static size_t pcat_pmu_receive_buf(struct serdev_device *serdev,
+ const unsigned char *buf, size_t size)
+{
+ struct device *dev = &serdev->dev;
+ struct pcat_pmu *pmu = dev_get_drvdata(dev);
+ size_t processed = size;
+ int ret;
+ size_t new_len = pmu->length + size;
+
+ if (!pmu || !buf || size <= 0)
+ return 0;
+ if (new_len > sizeof(pmu->buffer)) {
+ new_len = sizeof(pmu->buffer);
+ processed = new_len - pmu->length;
+ }
+ if (pmu->length)
+ dev_dbg(pmu->dev, "got remaining message at %zu size %zu (%zu)",
+ pmu->length, processed, new_len);
+ memcpy(pmu->buffer + pmu->length, buf, processed);
+ pmu->length = new_len;
+ ret = pcat_process_data(pmu, pmu->buffer, pmu->length);
+ if (ret != -EAGAIN)
+ pmu->length = 0;
+ else
+ dev_dbg(pmu->dev, "got partial message %zu", pmu->length);
+ return processed;
+}
+
+static const struct serdev_device_ops pcat_pmu_serdev_device_ops = {
+ .receive_buf = pcat_pmu_receive_buf,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+int pcat_pmu_register_notify(struct pcat_pmu *pmu, struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&pmu->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_register_notify);
+
+void pcat_pmu_unregister_notify(struct pcat_pmu *pmu, struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&pmu->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(pcat_pmu_unregister_notify);
+
+static int pcat_pmu_probe(struct serdev_device *serdev)
+{
+ int ret;
+ u32 baudrate;
+ u32 address;
+ char buffer[64];
+ struct pcat_pmu *pmu = NULL;
+ struct device *dev = &serdev->dev;
+
+ pmu = devm_kzalloc(dev, sizeof(struct pcat_pmu), GFP_KERNEL);
+ if (!pmu)
+ return -ENOMEM;
+ pmu->dev = dev;
+ pmu->serdev = serdev;
+ spin_lock_init(&pmu->bus_lock);
+ mutex_init(&pmu->reply_lock);
+ init_completion(&pmu->first_status);
+
+ if (of_property_read_u32(dev->of_node, "current-speed", &baudrate))
+ baudrate = 115200;
+
+ if (of_property_read_u32(dev->of_node, "local-address", &address))
+ address = 1;
+ pmu->local_id = address;
+
+ if (of_property_read_u32(dev->of_node, "remote-address", &address))
+ address = 1;
+ pmu->remote_id = address;
+
+ serdev_device_set_client_ops(serdev, &pcat_pmu_serdev_device_ops);
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to open serdev\n");
+
+ serdev_device_set_baudrate(serdev, baudrate);
+ serdev_device_set_flow_control(serdev, false);
+ serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ dev_set_drvdata(dev, pmu);
+
+ /* Disable watchdog on boot */
+ pcat_pmu_write_data(pmu, PCAT_CMD_WATCHDOG_TIMEOUT_SET,
+ (u8[]){ 60, 60, 0 }, 3);
+
+ /* Read hardware version */
+ pcat_pmu_read_string(pmu, PCAT_CMD_PMU_HW_VERSION_GET,
+ buffer, sizeof(buffer));
+ if (buffer[0])
+ dev_info(dev, "PMU Hardware version: %s\n", buffer);
+
+ /* Read firmware version */
+ pcat_pmu_read_string(pmu, PCAT_CMD_PMU_FW_VERSION_GET,
+ buffer, sizeof(buffer));
+ if (buffer[0])
+ dev_info(dev, "PMU Firmware version: %s\n", buffer);
+
+ return devm_of_platform_populate(dev);
+}
+
+static const struct of_device_id pcat_pmu_dt_ids[] = {
+ { .compatible = "ariaboard,photonicat-pmu", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pcat_pmu_dt_ids);
+
+static struct serdev_device_driver pcat_pmu_driver = {
+ .driver = {
+ .name = "photonicat-pmu",
+ .of_match_table = pcat_pmu_dt_ids,
+ },
+ .probe = pcat_pmu_probe,
+};
+module_serdev_device_driver(pcat_pmu_driver);
+
+MODULE_ALIAS("platform:photonicat-pmu");
+MODULE_AUTHOR("Junhao Xie <bigfoot@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("Photonicat Power Management Unit");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/photonicat-pmu.h b/include/linux/mfd/photonicat-pmu.h
new file mode 100644
index 000000000000..1bada5efd83a
--- /dev/null
+++ b/include/linux/mfd/photonicat-pmu.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 Junhao Xie <bigfoot@xxxxxxxxxxx>
+ */
+
+#ifndef _PHOTONICAT_PMU_H
+#define _PHOTONICAT_PMU_H
+
+#include <linux/notifier.h>
+#include <linux/types.h>
+
+struct pcat_data;
+struct pcat_pmu;
+struct pcat_request;
+
+struct pcat_data_cmd_date_time {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 minute;
+ u8 second;
+} __packed;
+
+struct pcat_data_cmd_led_setup {
+ u16 on_time;
+ u16 down_time;
+ u16 repeat;
+} __packed;
+
+struct pcat_data_cmd_status {
+ u16 battery_microvolt;
+ u16 charger_microvolt;
+ u16 gpio_input;
+ u16 gpio_output;
+ struct pcat_data_cmd_date_time time;
+ u16 reserved;
+ u8 temp;
+} __packed;
+
+enum pcat_pmu_cmd {
+ PCAT_CMD_HEARTBEAT = 0x01,
+ PCAT_CMD_HEARTBEAT_ACK = 0x02,
+ PCAT_CMD_PMU_HW_VERSION_GET = 0x03,
+ PCAT_CMD_PMU_HW_VERSION_GET_ACK = 0x04,
+ PCAT_CMD_PMU_FW_VERSION_GET = 0x05,
+ PCAT_CMD_PMU_FW_VERSION_GET_ACK = 0x06,
+ PCAT_CMD_STATUS_REPORT = 0x07,
+ PCAT_CMD_STATUS_REPORT_ACK = 0x08,
+ PCAT_CMD_DATE_TIME_SYNC = 0x09,
+ PCAT_CMD_DATE_TIME_SYNC_ACK = 0x0A,
+ PCAT_CMD_SCHEDULE_STARTUP_TIME_SET = 0x0B,
+ PCAT_CMD_SCHEDULE_STARTUP_TIME_SET_ACK = 0x0C,
+ PCAT_CMD_PMU_REQUEST_SHUTDOWN = 0x0D,
+ PCAT_CMD_PMU_REQUEST_SHUTDOWN_ACK = 0x0E,
+ PCAT_CMD_HOST_REQUEST_SHUTDOWN = 0x0F,
+ PCAT_CMD_HOST_REQUEST_SHUTDOWN_ACK = 0x10,
+ PCAT_CMD_PMU_REQUEST_FACTORY_RESET = 0x11,
+ PCAT_CMD_PMU_REQUEST_FACTORY_RESET_ACK = 0x12,
+ PCAT_CMD_WATCHDOG_TIMEOUT_SET = 0x13,
+ PCAT_CMD_WATCHDOG_TIMEOUT_SET_ACK = 0x14,
+ PCAT_CMD_CHARGER_ON_AUTO_START = 0x15,
+ PCAT_CMD_CHARGER_ON_AUTO_START_ACK = 0x16,
+ PCAT_CMD_VOLTAGE_THRESHOLD_SET = 0x17,
+ PCAT_CMD_VOLTAGE_THRESHOLD_SET_ACK = 0x18,
+ PCAT_CMD_NET_STATUS_LED_SETUP = 0x19,
+ PCAT_CMD_NET_STATUS_LED_SETUP_ACK = 0x1A,
+ PCAT_CMD_POWER_ON_EVENT_GET = 0x1B,
+ PCAT_CMD_POWER_ON_EVENT_GET_ACK = 0x1C,
+};
+
+void *pcat_data_get_data(struct pcat_data *data);
+int pcat_pmu_send(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ const void *data, size_t len);
+int pcat_pmu_execute(struct pcat_request *request);
+int pcat_pmu_write_data(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ const void *data, size_t size);
+int pcat_pmu_read_string(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd,
+ char *str, size_t len);
+int pcat_pmu_write_u8(struct pcat_pmu *pmu, enum pcat_pmu_cmd cmd, u8 value);
+int pcat_pmu_register_notify(struct pcat_pmu *pmu,
+ struct notifier_block *nb);
+void pcat_pmu_unregister_notify(struct pcat_pmu *pmu,
+ struct notifier_block *nb);
+
+#endif
--
2.46.0