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>
source "drivers/firmware/smccc/Kconfig"
source "drivers/firmware/tegra/Kconfig"
source "drivers/firmware/xilinx/Kconfig"
+source "drivers/firmware/thead/Kconfig"
endmenu
obj-y += smccc/
obj-y += tegra/
obj-y += xilinx/
+obj-y += thead/
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+// 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");
--- /dev/null
+// 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);
--- /dev/null
+// 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");
--- /dev/null
+// 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");