firmware: Enable LicheePi 4A firmware drivers 88/311588/3
authorMichal Wilczynski <m.wilczynski@samsung.com>
Wed, 22 May 2024 12:34:05 +0000 (14:34 +0200)
committerMichal Wilczynski <m.wilczynski@samsung.com>
Mon, 27 May 2024 13:08:23 +0000 (15:08 +0200)
Ported from vendor kernel [1].

[1] https://gitee.com/thead-yocto/kernel.git

Change-Id: I6339a711e54502ac8c094d451c104ba31aa586e6
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
drivers/firmware/Kconfig
drivers/firmware/Makefile
drivers/firmware/thead/Kconfig [new file with mode: 0644]
drivers/firmware/thead/Makefile [new file with mode: 0644]
drivers/firmware/thead/light_aon.c [new file with mode: 0644]
drivers/firmware/thead/light_aon_misc.c [new file with mode: 0644]
drivers/firmware/thead/light_aon_pd.c [new file with mode: 0644]
drivers/firmware/thead/light_aon_test.c [new file with mode: 0644]

index b59e3041fd627585c4662f35c1fc34103df9730d..3d091439c28da5f2acc52e58dd74ade7e24a26d7 100644 (file)
@@ -314,5 +314,6 @@ source "drivers/firmware/psci/Kconfig"
 source "drivers/firmware/smccc/Kconfig"
 source "drivers/firmware/tegra/Kconfig"
 source "drivers/firmware/xilinx/Kconfig"
+source "drivers/firmware/thead/Kconfig"
 
 endmenu
index 28fcddcd688fc2fd0f977df39126bf251808b970..a817fe549c9fd10c881e2275057f94d7d3b9d17b 100644 (file)
@@ -38,3 +38,4 @@ obj-y                         += psci/
 obj-y                          += smccc/
 obj-y                          += tegra/
 obj-y                          += xilinx/
+obj-y                          += thead/
diff --git a/drivers/firmware/thead/Kconfig b/drivers/firmware/thead/Kconfig
new file mode 100644 (file)
index 0000000..ad5b82d
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config LIGHT_AON
+       bool "Thead Light Aon Protocol driver"
+       depends on THEAD_LIGHT_MBOX
+       default y
+       help
+         Thead light Aon is a low-level system function which runs a dedicated
+         thead riscv E902 core to provide power, clock and resource management.
+
+         This driver manages the IPC interface between host cpu liks thead
+         and the Aon firmware running on thead riscv E902 core.
+
+config LIGHT_AON_PD
+       bool "Thead Light Aon Power Domain driver"
+       depends on LIGHT_AON
+       select PM_GENERIC_DOMAINS if PM
+       help
+         The Aon power domain virtual driver.
diff --git a/drivers/firmware/thead/Makefile b/drivers/firmware/thead/Makefile
new file mode 100644 (file)
index 0000000..6bd2afe
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_LIGHT_AON)        += light_aon.o light_aon_misc.o light_aon_test.o
+obj-$(CONFIG_LIGHT_AON_PD) += light_aon_pd.o
diff --git a/drivers/firmware/thead/light_aon.c b/drivers/firmware/thead/light_aon.c
new file mode 100644 (file)
index 0000000..5af7de8
--- /dev/null
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+#include <linux/err.h>
+#include <linux/firmware/thead/ipc.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+/* wait for response for 3000ms instead of 300ms (fix me pls)*/
+#define MAX_RX_TIMEOUT         (msecs_to_jiffies(3000))
+#define MAX_TX_TIMEOUT         (msecs_to_jiffies(500))
+
+struct light_aon_chan {
+       struct light_aon_ipc *aon_ipc;
+
+       struct mbox_client cl;
+       struct mbox_chan *ch;
+       struct completion tx_done;
+};
+
+struct light_aon_ipc {
+       struct light_aon_chan chans;
+       struct device *dev;
+       struct mutex lock;
+       struct completion done;
+       u32 *msg;
+};
+
+/*
+ * This type is used to indicate error response for most functions.
+ */
+enum light_aon_error_codes {
+       LIGHT_AON_ERR_NONE = 0, /* Success */
+       LIGHT_AON_ERR_VERSION = 1,      /* Incompatible API version */
+       LIGHT_AON_ERR_CONFIG = 2,       /* Configuration error */
+       LIGHT_AON_ERR_PARM = 3, /* Bad parameter */
+       LIGHT_AON_ERR_NOACCESS = 4,     /* Permission error (no access) */
+       LIGHT_AON_ERR_LOCKED = 5,       /* Permission error (locked) */
+       LIGHT_AON_ERR_UNAVAILABLE = 6,  /* Unavailable (out of resources) */
+       LIGHT_AON_ERR_NOTFOUND = 7,     /* Not found */
+       LIGHT_AON_ERR_NOPOWER = 8,      /* No power */
+       LIGHT_AON_ERR_IPC = 9,          /* Generic IPC error */
+       LIGHT_AON_ERR_BUSY = 10,        /* Resource is currently busy/active */
+       LIGHT_AON_ERR_FAIL = 11,        /* General I/O failure */
+       LIGHT_AON_ERR_LAST
+};
+
+static int light_aon_linux_errmap[LIGHT_AON_ERR_LAST] = {
+       0,       /* LIGHT_AON_ERR_NONE */
+       -EINVAL, /* LIGHT_AON_ERR_VERSION */
+       -EINVAL, /* LIGHT_AON_ERR_CONFIG */
+       -EINVAL, /* LIGHT_AON_ERR_PARM */
+       -EACCES, /* LIGHT_AON_ERR_NOACCESS */
+       -EACCES, /* LIGHT_AON_ERR_LOCKED */
+       -ERANGE, /* LIGHT_AON_ERR_UNAVAILABLE */
+       -EEXIST, /* LIGHT_AON_ERR_NOTFOUND */
+       -EPERM,  /* LIGHT_AON_ERR_NOPOWER */
+       -EPIPE,  /* LIGHT_AON_ERR_IPC */
+       -EBUSY,  /* LIGHT_AON_ERR_BUSY */
+       -EIO,    /* LIGHT_AON_ERR_FAIL */
+};
+
+static struct light_aon_ipc *light_aon_ipc_handle;
+
+static inline int light_aon_to_linux_errno(int errno)
+{
+       if (errno >= LIGHT_AON_ERR_NONE && errno < LIGHT_AON_ERR_LAST)
+               return light_aon_linux_errmap[errno];
+       return -EIO;
+}
+
+/*
+ * Get the default handle used by SCU
+ */
+int light_aon_get_handle(struct light_aon_ipc **ipc)
+{
+       if (!light_aon_ipc_handle)
+               return -EPROBE_DEFER;
+
+       *ipc = light_aon_ipc_handle;
+       return 0;
+}
+EXPORT_SYMBOL(light_aon_get_handle);
+
+static void light_aon_tx_done(struct mbox_client *cl, void *mssg, int r)
+{
+       struct light_aon_chan *aon_chan = container_of(cl, struct light_aon_chan, cl);
+
+       complete(&aon_chan->tx_done);
+}
+
+static void light_aon_rx_callback(struct mbox_client *c, void *msg)
+{
+       struct light_aon_chan *aon_chan = container_of(c, struct light_aon_chan, cl);
+       struct light_aon_ipc *aon_ipc = aon_chan->aon_ipc;
+
+       memcpy(aon_ipc->msg, msg, LIGHT_AON_RPC_MSG_NUM * sizeof(u32));
+       dev_dbg(aon_ipc->dev, "msg head: 0x%x\n", *((u32 *)msg));
+       complete(&aon_ipc->done);
+}
+
+static int light_aon_ipc_write(struct light_aon_ipc *aon_ipc, void *msg)
+{
+       struct light_aon_rpc_msg_hdr *hdr = msg;
+       struct light_aon_chan *aon_chan;
+       u32 *data = msg;
+       int ret;
+
+       /* check size, currently it requires 7 MSG in one transfer */
+       if (hdr->size != LIGHT_AON_RPC_MSG_NUM)
+               return -EINVAL;
+
+       dev_dbg(aon_ipc->dev, "RPC SVC %u FUNC %u SIZE %u\n", hdr->svc,
+               hdr->func, hdr->size);
+
+       aon_chan = &aon_ipc->chans;
+
+       if (!wait_for_completion_timeout(&aon_chan->tx_done,
+                                        MAX_TX_TIMEOUT)) {
+               dev_err(aon_ipc->dev, "tx_done timeout\n");
+               return -ETIMEDOUT;
+       }
+       reinit_completion(&aon_chan->tx_done);
+
+       ret = mbox_send_message(aon_chan->ch, data);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/*
+ * RPC command/response
+ */
+int light_aon_call_rpc(struct light_aon_ipc *aon_ipc, void *msg, bool have_resp)
+{
+       struct light_aon_rpc_msg_hdr *hdr;
+       int ret;
+
+       if (WARN_ON(!aon_ipc || !msg))
+               return -EINVAL;
+
+       mutex_lock(&aon_ipc->lock);
+       reinit_completion(&aon_ipc->done);
+
+       if (have_resp)
+               aon_ipc->msg = msg;
+
+       ret = light_aon_ipc_write(aon_ipc, msg);
+       if (ret < 0) {
+               dev_err(aon_ipc->dev, "RPC send msg failed: %d\n", ret);
+               goto out;
+       }
+
+       if (have_resp) {
+               if (!wait_for_completion_timeout(&aon_ipc->done,
+                                                MAX_RX_TIMEOUT)) {
+                       dev_err(aon_ipc->dev, "RPC send msg timeout\n");
+                       mutex_unlock(&aon_ipc->lock);
+                       return -ETIMEDOUT;
+               }
+
+               /* response status is stored in hdr->func field */
+               hdr = msg;
+               ret = hdr->func;
+       }
+
+out:
+       mutex_unlock(&aon_ipc->lock);
+
+       dev_dbg(aon_ipc->dev, "RPC SVC done\n");
+
+       return light_aon_to_linux_errno(ret);
+}
+EXPORT_SYMBOL(light_aon_call_rpc);
+
+static int light_aon_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct light_aon_ipc *aon_ipc;
+       struct light_aon_chan *aon_chan;
+       struct mbox_client *cl;
+       int ret;
+
+       aon_ipc = devm_kzalloc(dev, sizeof(*aon_ipc), GFP_KERNEL);
+       if (!aon_ipc)
+               return -ENOMEM;
+
+       aon_chan = &aon_ipc->chans;
+       cl = &aon_chan->cl;
+       cl->dev = dev;
+       cl->tx_block = false;
+       cl->knows_txdone = true;
+       cl->rx_callback = light_aon_rx_callback;
+
+       /* Initial tx_done completion as "done" */
+       cl->tx_done = light_aon_tx_done;
+       init_completion(&aon_chan->tx_done);
+       complete(&aon_chan->tx_done);
+
+       aon_chan->aon_ipc = aon_ipc;
+       aon_chan->ch = mbox_request_channel_byname(cl, "aon");
+       if (IS_ERR(aon_chan->ch)) {
+               ret = PTR_ERR(aon_chan->ch);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Failed to request aon mbox chan ret %d\n", ret);
+               return ret;
+       }
+
+       dev_dbg(dev, "request thead mbox chan: aon\n");
+
+       aon_ipc->dev = dev;
+       mutex_init(&aon_ipc->lock);
+       init_completion(&aon_ipc->done);
+
+       light_aon_ipc_handle = aon_ipc;
+
+       return devm_of_platform_populate(dev);
+}
+
+static const struct of_device_id light_aon_match[] = {
+       { .compatible = "thead,light-aon", },
+       { /* Sentinel */ }
+};
+
+static int __maybe_unused light_aon_resume_noirq(struct device *dev)
+{
+       struct light_aon_chan *aon_chan;
+       int ret;
+
+       aon_chan = &light_aon_ipc_handle->chans;
+
+       complete(&aon_chan->tx_done);
+       return 0;
+}
+
+static const struct dev_pm_ops light_aon_pm_ops = {
+       SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL,
+                                     light_aon_resume_noirq)
+};
+static struct platform_driver light_aon_driver = {
+       .driver = {
+               .name = "light-aon",
+               .of_match_table = light_aon_match,
+               .pm = &light_aon_pm_ops,
+       },
+       .probe = light_aon_probe,
+};
+builtin_platform_driver(light_aon_driver);
+
+MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
+MODULE_DESCRIPTION("Thead Light firmware protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/thead/light_aon_misc.c b/drivers/firmware/thead/light_aon_misc.c
new file mode 100644 (file)
index 0000000..3fb689f
--- /dev/null
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+#include <linux/firmware/thead/ipc.h>
+
+struct light_aon_msg_req_misc_set_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 ctrl;
+       u32 val;
+       u16 resource;
+       u16 reserved[7];
+} __packed __aligned(4);
+
+struct light_aon_msg_req_misc_get_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 ctrl;
+       u16 resource;
+       u16 reserved[9];
+} __packed __aligned(4);
+
+struct light_aon_msg_resp_misc_get_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 val;
+       u32 reserved[5];
+} __packed __aligned(4);
+
+int light_aon_misc_set_control(struct light_aon_ipc *ipc, u16 resource,
+                           u32 ctrl, u32 val)
+{
+       struct light_aon_msg_req_misc_set_ctrl msg;
+       struct light_aon_rpc_msg_hdr *hdr = &msg.hdr;
+
+       hdr->ver = LIGHT_AON_RPC_VERSION;
+       hdr->svc = (uint8_t)LIGHT_AON_RPC_SVC_MISC;
+       hdr->func = (uint8_t)LIGHT_AON_MISC_FUNC_SET_CONTROL;
+       hdr->size = LIGHT_AON_RPC_MSG_NUM;
+
+       msg.ctrl = ctrl;
+       msg.val = val;
+       msg.resource = resource;
+
+       return light_aon_call_rpc(ipc, &msg, true);
+}
+EXPORT_SYMBOL(light_aon_misc_set_control);
+
+int light_aon_misc_get_control(struct light_aon_ipc *ipc, u16 resource,
+                           u32 ctrl, u32 *val)
+{
+       struct light_aon_msg_req_misc_get_ctrl msg;
+       struct light_aon_msg_resp_misc_get_ctrl *resp;
+       struct light_aon_rpc_msg_hdr *hdr = &msg.hdr;
+       int ret;
+
+       hdr->ver = LIGHT_AON_RPC_VERSION;
+       hdr->svc = (uint8_t)LIGHT_AON_RPC_SVC_MISC;
+       hdr->func = (uint8_t)LIGHT_AON_MISC_FUNC_GET_CONTROL;
+       hdr->size = LIGHT_AON_RPC_MSG_NUM;
+
+       msg.ctrl = ctrl;
+       msg.resource = resource;
+
+       ret = light_aon_call_rpc(ipc, &msg, true);
+       if (ret)
+               return ret;
+
+       resp = (struct light_aon_msg_resp_misc_get_ctrl *)&msg;
+       if (val != NULL)
+               *val = resp->val;
+
+       return 0;
+}
+EXPORT_SYMBOL(light_aon_misc_get_control);
diff --git a/drivers/firmware/thead/light_aon_pd.c b/drivers/firmware/thead/light_aon_pd.c
new file mode 100644 (file)
index 0000000..3bef67d
--- /dev/null
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+#include <dt-bindings/firmware/thead/rsrc.h>
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/firmware/thead/ipc.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_domain.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+struct light_aon_msg_req_set_resource_power_mode {
+       struct light_aon_rpc_msg_hdr hdr;
+       u16 resource;
+       u16 mode;
+       u16 reserved[10];
+} __packed __aligned(4);
+
+#define LIGHT_AONU_PD_NAME_SIZE 20
+#define LIGHT_AONU_PD_STATE_NAME_SIZE 10
+
+struct light_aon_pm_domain {
+       struct generic_pm_domain pd;
+       char name[LIGHT_AONU_PD_NAME_SIZE];
+       u16 rsrc;
+};
+
+struct light_aon_pd_range {
+       char *name;
+       u32 rsrc;
+       u8 num;
+
+       /* add domain index */
+       bool postfix;
+       u8 start_from;
+};
+
+struct light_aon_pd_soc {
+       const struct light_aon_pd_range *pd_ranges;
+       u8 num_ranges;
+};
+
+static const struct light_aon_pd_range light_aon_pd_ranges[] = {
+       /* AUDIO SS */
+       { "audio", LIGHT_AON_AUDIO_PD, 1, false, 0 },
+       { "vdec", LIGHT_AON_VDEC_PD, 1, false, 0},
+       { "npu", LIGHT_AON_NPU_PD, 1, false, 0},
+       { "venc", LIGHT_AON_VENC_PD, 1, false, 0},
+       { "gpu", LIGHT_AON_GPU_PD, 1, false, 0},
+       { "dsp0", LIGHT_AON_DSP0_PD, 1, false, 0},
+       { "dsp1", LIGHT_AON_DSP1_PD, 1, false, 0},
+       {},
+};
+
+static const struct light_aon_pd_soc light_aon_pd = {
+       .pd_ranges = light_aon_pd_ranges,
+       .num_ranges = ARRAY_SIZE(light_aon_pd_ranges),
+};
+
+static struct light_aon_ipc *pm_ipc_handle;
+static struct dentry *pd_debugfs_root;
+struct dentry *pd_pde;
+struct genpd_onecell_data *genpd_data;
+
+static inline struct light_aon_pm_domain *to_light_aon_pd(struct generic_pm_domain *genpd)
+{
+       return container_of(genpd, struct light_aon_pm_domain, pd);
+}
+
+static int light_aon_pd_power(struct generic_pm_domain *domain, bool power_on)
+{
+       struct light_aon_msg_req_set_resource_power_mode msg;
+       struct light_aon_rpc_msg_hdr *hdr = &msg.hdr;
+       struct light_aon_pm_domain *pd;
+       int ret;
+
+       pd = to_light_aon_pd(domain);
+
+       hdr->ver = LIGHT_AON_RPC_VERSION;
+       hdr->svc = LIGHT_AON_RPC_SVC_PM;
+       hdr->func = LIGHT_AON_PM_FUNC_SET_RESOURCE_POWER_MODE;
+       hdr->size = LIGHT_AON_RPC_MSG_NUM;
+
+       msg.resource = pd->rsrc;
+       msg.mode = power_on ? LIGHT_AON_PM_PW_MODE_ON : LIGHT_AON_PM_PW_MODE_OFF;
+
+       ret = light_aon_call_rpc(pm_ipc_handle, &msg, true);
+       if (ret)
+               dev_err(&domain->dev, "failed to power %s resource %d ret %d\n",
+                       power_on ? "up" : "off", pd->rsrc, ret);
+
+       return ret;
+}
+
+static int light_aon_pd_power_on(struct generic_pm_domain *domain)
+{
+       return light_aon_pd_power(domain, true);
+}
+
+static int light_aon_pd_power_off(struct generic_pm_domain *domain)
+{
+       return light_aon_pd_power(domain, false);
+}
+
+static struct generic_pm_domain *light_aon_pd_xlate(struct of_phandle_args *spec,
+                                                 void *data)
+{
+       struct generic_pm_domain *domain = ERR_PTR(-ENOENT);
+       struct genpd_onecell_data *pd_data = data;
+       unsigned int i;
+
+       for (i = 0; i < pd_data->num_domains; i++) {
+               struct light_aon_pm_domain *aon_pd;
+
+               aon_pd = to_light_aon_pd(pd_data->domains[i]);
+               if (aon_pd->rsrc == spec->args[0]) {
+                       domain = &aon_pd->pd;
+                       break;
+               }
+       }
+
+       return domain;
+}
+
+static struct light_aon_pm_domain *
+light_aon_add_pm_domain(struct device *dev, int idx,
+                     const struct light_aon_pd_range *pd_ranges)
+{
+       struct light_aon_pm_domain *aon_pd;
+       int ret;
+
+       aon_pd = devm_kzalloc(dev, sizeof(*aon_pd), GFP_KERNEL);
+       if (!aon_pd)
+               return ERR_PTR(-ENOMEM);
+
+       aon_pd->rsrc = pd_ranges->rsrc + idx;
+       aon_pd->pd.power_off = light_aon_pd_power_off;
+       aon_pd->pd.power_on = light_aon_pd_power_on;
+
+       if (pd_ranges->postfix)
+               snprintf(aon_pd->name, sizeof(aon_pd->name),
+                        "%s%i", pd_ranges->name, pd_ranges->start_from + idx);
+       else
+               snprintf(aon_pd->name, sizeof(aon_pd->name),
+                        "%s", pd_ranges->name);
+
+       aon_pd->pd.name = aon_pd->name;
+
+#if 0
+       if (aon_pd->rsrc >= LIGHT_AON_R_LAST) {
+               dev_warn(dev, "invalid pd %s rsrc id %d found",
+                        aon_pd->name, aon_pd->rsrc);
+
+               devm_kfree(dev, aon_pd);
+               return NULL;
+       }
+#endif
+
+       ret = pm_genpd_init(&aon_pd->pd, NULL, true);
+       if (ret) {
+               dev_warn(dev, "failed to init pd %s rsrc id %d",
+                        aon_pd->name, aon_pd->rsrc);
+               devm_kfree(dev, aon_pd);
+               return NULL;
+       }
+
+       return aon_pd;
+}
+
+static int light_aon_init_pm_domains(struct device *dev,
+                                   const struct light_aon_pd_soc *pd_soc)
+{
+       const struct light_aon_pd_range *pd_ranges = pd_soc->pd_ranges;
+       struct generic_pm_domain **domains;
+       struct genpd_onecell_data *pd_data;
+       struct light_aon_pm_domain *aon_pd;
+       u32 count = 0;
+       int i, j;
+
+       for (i = 0; i < pd_soc->num_ranges; i++)
+               count += pd_ranges[i].num;
+
+       domains = devm_kcalloc(dev, count, sizeof(*domains), GFP_KERNEL);
+       if (!domains)
+               return -ENOMEM;
+
+       pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL);
+       if (!pd_data)
+               return -ENOMEM;
+
+       count = 0;
+       for (i = 0; i < pd_soc->num_ranges; i++) {
+               for (j = 0; j < pd_ranges[i].num; j++) {
+                       aon_pd = light_aon_add_pm_domain(dev, j, &pd_ranges[i]);
+                       if (IS_ERR_OR_NULL(aon_pd))
+                               continue;
+
+                       domains[count++] = &aon_pd->pd;
+                       dev_dbg(dev, "added power domain %s\n", aon_pd->pd.name);
+               }
+       }
+
+       pd_data->domains = domains;
+       pd_data->num_domains = count;
+       pd_data->xlate = light_aon_pd_xlate;
+       genpd_data = pd_data;
+
+       of_genpd_add_provider_onecell(dev->of_node, pd_data);
+
+       return 0;
+}
+
+static char *pd_get_user_string(const char __user *userbuf, size_t userlen)
+{
+       char *buffer;
+
+       buffer = vmalloc(userlen + 1);
+       if (!buffer)
+               return ERR_PTR(-ENOMEM);
+
+       if (copy_from_user(buffer, userbuf, userlen) != 0) {
+               vfree(buffer);
+               return ERR_PTR(-EFAULT);
+       }
+
+       /* got the string, now strip linefeed. */
+       if (buffer[userlen - 1] == '\n')
+               buffer[userlen -1] = '\0';
+       else
+               buffer[userlen] = '\0';
+
+       pr_debug("buffer = %s\n", buffer);
+
+       return buffer;
+}
+
+static ssize_t light_power_domain_write(struct file *file,
+                                       const char __user *userbuf,
+                                       size_t userlen, loff_t *ppos)
+{
+       char *buffer, *start, *end;
+       struct seq_file *m = (struct seq_file *)file->private_data;
+       struct genpd_onecell_data *aon_pds_data = m->private;
+       struct generic_pm_domain *hitted_pm_genpd;
+       char pd_name[LIGHT_AONU_PD_NAME_SIZE];
+       char pd_state[LIGHT_AONU_PD_STATE_NAME_SIZE];
+       int idx, ret;
+       size_t origin_len = userlen;
+
+       buffer = pd_get_user_string(userbuf, userlen);
+       if (IS_ERR(buffer))
+               return PTR_ERR(buffer);
+
+       start = skip_spaces(buffer);
+       end = start;
+       while(!isspace(*end) && *end != '\0')
+               end++;
+
+       *end = '\0';
+       strcpy(pd_name, start);
+       pr_debug("power domain name: %s\n", pd_name);
+
+       /* find the target power domain */
+       for (idx = 0; idx < aon_pds_data->num_domains; idx++) {
+               struct generic_pm_domain *domain = aon_pds_data->domains[idx];
+               pr_debug("generic pm domain name: %s, pd_name: %s, ret = %d\n",
+                               domain->name, pd_name, strcmp(pd_name, domain->name));
+               if (strcmp(pd_name, domain->name))
+                       continue;
+               else {
+                       hitted_pm_genpd = aon_pds_data->domains[idx];
+                       pr_debug("target pm power domain-%s found, index: %d\n",
+                                       hitted_pm_genpd->name, idx);
+                       break;
+               }
+       }
+
+       if (idx >= aon_pds_data->num_domains) {
+               pr_err("no taget power domain-%s found, idx = %d, total pd numbers = %d\n",
+                               pd_name, idx, aon_pds_data->num_domains);
+               userlen = -EINVAL;
+               goto out;
+       }
+
+       if (!hitted_pm_genpd->power_on && !hitted_pm_genpd->power_off) {
+               pr_err("no power operations registered for power domain-%s\n", pd_name);
+               userlen = -EINVAL;
+               goto out;
+       }
+
+       end = end + 1;
+       start = skip_spaces(end);
+       end = start;
+       while(!isspace(*end) && *end != '\0')
+               end++;
+
+       *end = '\0';
+       strcpy(pd_state, start);
+       pr_debug("power domain target state: %s\n", pd_state);
+
+       if (!strcmp(pd_state, "on")) {
+               ret = hitted_pm_genpd->power_on(hitted_pm_genpd);
+               if (ret) {
+                       userlen = ret;
+                       goto out;
+               }
+       } else if (!strcmp(pd_state, "off")) {
+               ret = hitted_pm_genpd->power_off(hitted_pm_genpd);
+               if (ret) {
+                       userlen = ret;
+                       goto out;
+               }
+       } else {
+               pr_err("invalid power domain target state, not 'on' or 'off'\n");
+               userlen = -EINVAL;
+               goto out;
+       }
+
+out:
+       memset(buffer, 0, origin_len);
+       vfree(buffer);
+
+       return userlen;
+}
+
+static int light_power_domain_show(struct seq_file *m, void *v)
+{
+       struct genpd_onecell_data *pd_data = m->private;
+       u32 count = pd_data->num_domains;
+       int idx;
+
+       seq_puts(m, "[Power domain name list]: ");
+       for(idx = 0; idx < count; idx++)
+               seq_printf(m, "%s ", pd_data->domains[idx]->name);
+       seq_printf(m, "\n");
+       seq_puts(m, "[Power on  domain usage]: echo power_name on  > domain\n");
+       seq_puts(m, "[Power off domain usage]: echo power_name off > domain\n");
+
+       return 0;
+}
+
+static int light_power_domain_open(struct inode *inode, struct file *file)
+{
+       struct genpd_onecell_data *pd_data = inode->i_private;
+
+       return single_open(file, light_power_domain_show, pd_data);
+}
+
+static const struct file_operations light_power_domain_fops = {
+       .owner  = THIS_MODULE,
+       .write  = light_power_domain_write,
+       .read   = seq_read,
+       .open   = light_power_domain_open,
+       .llseek = generic_file_llseek,
+};
+
+static void pd_debugfs_init(struct genpd_onecell_data *aon_pds_data)
+{
+       umode_t mode = S_IRUSR | S_IWUSR | S_IFREG;
+
+       pd_debugfs_root = debugfs_create_dir("power_domain", NULL);
+       if (!pd_debugfs_root || IS_ERR(pd_debugfs_root))
+               return;
+
+       pd_pde = debugfs_create_file("domain", mode, pd_debugfs_root, (void *)aon_pds_data, &light_power_domain_fops);
+
+       pr_info("succeed to create power domain debugfs direntry\n");
+}
+
+static int light_aon_pd_probe(struct platform_device *pdev)
+{
+       const struct light_aon_pd_soc *pd_soc;
+       int ret;
+
+       ret = light_aon_get_handle(&pm_ipc_handle);
+       if (ret)
+               return ret;
+
+       pd_soc = of_device_get_match_data(&pdev->dev);
+       if (!pd_soc)
+               return -ENODEV;
+
+       ret = light_aon_init_pm_domains(&pdev->dev, pd_soc);
+       if (ret)
+               return ret;
+
+       pd_debugfs_init(genpd_data);
+
+       return 0;
+}
+
+static const struct of_device_id light_aon_pd_match[] = {
+       { .compatible = "thead,light-aon-pd", &light_aon_pd},
+       { /* sentinel */ }
+};
+
+static struct platform_driver light_aon_pd_driver = {
+       .driver = {
+               .name = "light-aon-pd",
+               .of_match_table = light_aon_pd_match,
+       },
+       .probe = light_aon_pd_probe,
+};
+builtin_platform_driver(light_aon_pd_driver);
+
+MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
+MODULE_DESCRIPTION("Thead Light firmware protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/thead/light_aon_test.c b/drivers/firmware/thead/light_aon_test.c
new file mode 100644 (file)
index 0000000..1720254
--- /dev/null
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/firmware/thead/ipc.h>
+
+#define MBOX_MAX_MSG_LEN               28
+
+static struct dentry *root_debugfs_dir;
+
+struct light_aon_msg_req_misc_set_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 ctrl;
+       u32 val;
+       u16 resource;
+       u16 reserved[7];
+} __packed __aligned(4);
+
+struct light_aon_msg_req_misc_get_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 ctrl;
+       u16 resource;
+       u16 reserved[9];
+} __packed __aligned(4);
+
+struct light_aon_msg_resp_misc_get_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 val;
+       u32 reserved[5];
+} __packed __aligned(4);
+
+struct light_aon_device {
+       struct device           *dev;
+       char                    *test_buf;
+       struct light_aon_ipc    *ipc_handle;
+};
+
+static ssize_t light_aon_test_buf_write(struct file *filp,
+                                     const char __user *userbuf,
+                                     size_t count, loff_t *ppos)
+{
+       struct light_aon_device *tdev = filp->private_data;
+       int ret;
+
+       if (count > MBOX_MAX_MSG_LEN)
+               count = MBOX_MAX_MSG_LEN;
+
+       ret = copy_from_user(tdev->test_buf, userbuf, count);
+       if (ret) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       ret = light_aon_misc_set_control(tdev->ipc_handle, 0x1, 0x2, 0x3);
+       ret |= light_aon_misc_set_control(tdev->ipc_handle, 0x11, 0x12, 0x13);
+       ret |= light_aon_misc_set_control(tdev->ipc_handle, 0x21, 0x22, 0x23);
+       ret |= light_aon_misc_set_control(tdev->ipc_handle, 0x31, 0x32, 0x33);
+       if (ret)
+               dev_err(tdev->dev, "failed to set control\n");
+
+       //print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_NONE, 16, 1, tdev->test_buf, MBOX_MAX_MSG_LEN, true);
+
+out:
+       return ret < 0 ? ret : count;
+}
+
+static ssize_t light_aon_test_buf_read(struct file *filp,
+                                     char __user *userbuf,
+                                     size_t count, loff_t *ppos)
+{
+       struct light_aon_device *tdev = filp->private_data;
+
+       //print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_NONE, 16, 1, tdev->test_buf, MBOX_MAX_MSG_LEN, true);
+       memset(tdev->test_buf, 0, MBOX_MAX_MSG_LEN);
+
+       return MBOX_MAX_MSG_LEN;
+}
+
+static const struct file_operations light_aon_test_buf_ops = {
+       .write  = light_aon_test_buf_write,
+       .read   = light_aon_test_buf_read,
+       .open   = simple_open,
+       .llseek = generic_file_llseek,
+};
+
+static int light_aon_add_debugfs(struct platform_device *pdev, struct light_aon_device *tdev)
+{
+       root_debugfs_dir = debugfs_create_dir("light_aon",NULL);
+       if (!root_debugfs_dir) {
+               dev_err(&pdev->dev, "Failed to create light_aon_test debugfs\n");
+               return -EINVAL;
+       }
+
+       debugfs_create_file("test", 0600, root_debugfs_dir, tdev, &light_aon_test_buf_ops);
+       return 0;
+}
+
+static int light_aon_probe(struct platform_device *pdev)
+{
+       struct light_aon_device *tdev;
+       int ret;
+
+       tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
+       if (!tdev)
+               return -ENOMEM;
+
+       tdev->dev = &pdev->dev;
+       platform_set_drvdata(pdev, tdev);
+
+       tdev->test_buf = devm_kzalloc(&pdev->dev, MBOX_MAX_MSG_LEN, GFP_KERNEL);
+       if (!tdev->test_buf)
+               return -ENOMEM;
+
+       ret = light_aon_get_handle(&(tdev->ipc_handle));
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get ipc_handle\n");
+               return ret;
+       }
+
+       ret = light_aon_add_debugfs(pdev, tdev);
+       if (ret)
+               return ret;
+
+       dev_info(&pdev->dev, "Successfully registered\n");
+
+       return 0;
+}
+
+static int light_aon_remove(struct platform_device *pdev)
+{
+       debugfs_remove_recursive(root_debugfs_dir);
+       return 0;
+}
+
+static const struct of_device_id light_aon_match[] = {
+       { .compatible = "thead,light-aon-test" },
+       {},
+};
+
+static struct platform_driver light_aon_driver = {
+       .driver = {
+               .name = "thead,light-aon-test",
+               .of_match_table = light_aon_match,
+       },
+       .probe  = light_aon_probe,
+       .remove = light_aon_remove,
+};
+module_platform_driver(light_aon_driver);
+
+MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
+MODULE_DESCRIPTION("Thead Light firmware protocol test driver");
+MODULE_LICENSE("GPL v2");