/* * Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have eeceived a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aon-ivc-dbg-messages.h" #define AON_REQUEST_MASK 0xF #define AON_REQUESTS_TOTAL (AON_REQUEST_TYPE_MAX + 1) #define AON_PSTATES_TOTAL 7 #define AON_PSTATE_MASK (0x7F << AON_REQUESTS_TOTAL) #define AON_STATE(req) ((req & AON_PSTATE_MASK) >> AON_REQUESTS_TOTAL) #define TX_BLOCK_PERIOD 20 #define AON_ROOT 0 #define AON_PM 1 #define AON_MODS 2 #define AON_STATS 3 #define IVC_DBG_CH_FRAME_SIZE 128 #define MODS_DEFAULT_VAL 0xFFFF #define PSTATE_NAME_MAX_LEN 15 static struct device *aondev; struct tegra_aondbg { struct device *dev; struct mbox_client cl; struct mbox_chan *mbox; struct dentry *aon_root; }; struct aon_dbgfs_node { char *name; u32 id; u8 pdr_id; mode_t mode; struct completion *wait_on; const struct file_operations *fops; char data[IVC_DBG_CH_FRAME_SIZE]; }; struct dbgfs_dir { const char *name; struct dentry *dir; struct dbgfs_dir *parent; }; static struct dbgfs_dir aon_dbgfs_dirs[] = { {.name = "aon", .parent = NULL}, {.name = "aon_pm", .parent = &aon_dbgfs_dirs[AON_ROOT]}, {.name = "aon_mods", .parent = &aon_dbgfs_dirs[AON_ROOT]}, {.name = "stats", .parent = &aon_dbgfs_dirs[AON_PM]} }; static const char *const pstates[] = {"idle", "standby", "dormant", "deepidle", "deepstandby", "deepdormant", "active"}; static u32 thresholds[AON_PSTATES_TOTAL]; static u32 mods_result = MODS_DEFAULT_VAL; static unsigned int completion_timeout = 50; static DEFINE_MUTEX(aon_mutex); static DEFINE_SPINLOCK(mods); static DEFINE_SPINLOCK(completion); static struct aon_dbgfs_node aon_nodes[]; static void set_mods_result(u32 result) { spin_lock(&mods); mods_result = result; spin_unlock(&mods); } static unsigned int get_mods_result(void) { u32 val; spin_lock(&mods); val = mods_result; spin_unlock(&mods); return val; } static void set_completion_timeout(unsigned int timeout) { spin_lock(&completion); completion_timeout = timeout; spin_unlock(&completion); } static unsigned int get_completion_timeout(void) { unsigned int val; spin_lock(&completion); val = completion_timeout; spin_unlock(&completion); return val; } static struct aon_dbg_response *aon_create_ivc_dbg_req(u32 request, u32 flag, u32 data) { struct tegra_aondbg *aondbg; struct aon_dbg_request req; struct aon_dbg_response *resp; struct tegra_aon_mbox_msg msg; int ret = 0; uint16_t pstate = 0; unsigned int timeout = 0; if (aondev == NULL) return (void *)-EPROBE_DEFER; aondbg = dev_get_drvdata(aondev); if (aondbg == NULL) { dev_err(aondev, "Control data for IPC with SPE is NULL\n"); return (void *)-EINVAL; } pstate = (request > AON_REQUEST_TYPE_MAX) ? __builtin_ctz(AON_STATE(request)) : 0; req.req_type = request & AON_REQUEST_MASK; switch (req.req_type) { case AON_PM_TARGET_POWER_STATE: req.data.pm_xfer.type.pstate.flag = flag; req.data.pm_xfer.type.pstate.state = (flag) ? data : 0; break; case AON_PM_THRESHOLD: req.data.pm_xfer.type.threshold.flag = flag; req.data.pm_xfer.type.threshold.state = pstate; req.data.pm_xfer.type.threshold.val = data; break; case AON_PM_WAKE_TIMEOUT: req.data.pm_xfer.type.wake_tout.flag = flag; req.data.pm_xfer.type.wake_tout.timeout = (flag) ? data : 0; break; case AON_PM_DISABLE_PLLAON: req.data.pm_xfer.type.disable_pllaon.flag = flag; req.data.pm_xfer.type.disable_pllaon.disable = data; break; case AON_MODS_LOOPS_TEST: req.data.mods_xfer.loops = data; break; case AON_PM_VDD_RTC_RETENTION: req.data.pm_xfer.type.retention.flag = flag; req.data.pm_xfer.type.retention.enable = (flag) ? data : 0; break; case AON_PM_SC8_COUNT: case AON_MODS_CRC: case AON_PM_STATE_TIME: case AON_PM_STATE_COUNT: case AON_PING_TEST: break; default: dev_err(aondev, "Invalid aon dbg request\n"); return ERR_PTR(-EINVAL); } msg.length = sizeof(struct aon_dbg_request); msg.data = (void *)&req; ret = mbox_send_message(aondbg->mbox, (void *)&msg); if (ret < 0) { dev_err(aondev, "mbox_send_message failed\n"); return ERR_PTR(ret); } timeout = get_completion_timeout(); ret = wait_for_completion_timeout(aon_nodes[req.req_type].wait_on, msecs_to_jiffies(timeout)); if (!ret) { dev_err(aondev, "No response\n"); return ERR_PTR(-ETIMEDOUT); } resp = (void *)aon_nodes[req.req_type].data; if (resp->resp_type > AON_REQUEST_TYPE_MAX) { dev_err(aondev, "Invalid aon dbg response\n"); return ERR_PTR(-EIO); } if (resp->status != AON_DBG_STATUS_OK) { dev_err(aondev, "Request failed\n"); return ERR_PTR(-EIO); } return resp; } int tegra_aon_get_pllaon_state(void) { int ret = -EINVAL; u64 val; struct aon_dbg_response *resp = NULL; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(AON_PM_DISABLE_PLLAON, READ, 0); if (IS_ERR(resp)) { ret = PTR_ERR(resp); mutex_unlock(&aon_mutex); return ret; } if (resp->resp_type == AON_PM_DISABLE_PLLAON) { val = resp->data.pm_xfer.type.disable_pllaon.disable; ret = (val == 1) ? 1 : 0; } mutex_unlock(&aon_mutex); return ret; } EXPORT_SYMBOL(tegra_aon_get_pllaon_state); int tegra_aon_set_pllaon_state(bool enable) { int ret = 0; u64 val = enable ? 0 : 1; struct aon_dbg_response *resp = NULL; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(AON_PM_DISABLE_PLLAON, WRITE, val); if (IS_ERR(resp)) ret = PTR_ERR(resp); mutex_unlock(&aon_mutex); return ret; } EXPORT_SYMBOL(tegra_aon_set_pllaon_state); static ssize_t aon_pm_target_power_state_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { char buf[PSTATE_NAME_MAX_LEN]; char *name; struct aon_dbg_response *resp; int i; if (count >= sizeof(buf)) return -EINVAL; if (strncpy_from_user(buf, user_buf, count) <= 0) return -EFAULT; buf[count] = '\0'; name = strim(buf); for (i = 0; i < ARRAY_SIZE(pstates); i++) { if (!strcmp(name, pstates[i])) break; } if (i >= ARRAY_SIZE(pstates)) return -EINVAL; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(AON_PM_TARGET_POWER_STATE, WRITE, i); mutex_unlock(&aon_mutex); if (IS_ERR(resp)) return PTR_ERR(resp); return count; } static int aon_pm_target_power_state_show(struct seq_file *file, void *data) { struct aon_dbg_response *resp; int pstate_index; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)file->private, READ, 0); if (IS_ERR(resp)) { mutex_unlock(&aon_mutex); return PTR_ERR(resp); } pstate_index = resp->data.pm_xfer.type.pstate.state; if (pstate_index < AON_PSTATES_TOTAL) { seq_printf(file, "%s\n", pstates[pstate_index]); } else { dev_err(aondev, "invalid pstate in response\n"); } mutex_unlock(&aon_mutex); return 0; } static int aon_pm_target_power_state_open(struct inode *inode, struct file *file) { return single_open(file, aon_pm_target_power_state_show, inode->i_private); } static const struct file_operations aon_pm_target_power_state_fops = { .open = aon_pm_target_power_state_open, .llseek = seq_lseek, .read = seq_read, .write = aon_pm_target_power_state_write, .release = single_release }; static int aon_pm_show(void *data, u64 *val) { struct aon_dbg_response *resp; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)data, READ, 0); if (IS_ERR(resp)) { mutex_unlock(&aon_mutex); return PTR_ERR(resp); } switch (resp->resp_type) { case AON_PM_WAKE_TIMEOUT: *val = resp->data.pm_xfer.type.wake_tout.timeout; break; case AON_PM_THRESHOLD: *val = resp->data.pm_xfer.type.threshold.val; break; case AON_PM_VDD_RTC_RETENTION: *val = resp->data.pm_xfer.type.retention.enable; break; case AON_PM_DISABLE_PLLAON: *val = resp->data.pm_xfer.type.disable_pllaon.disable; break; case AON_PM_SC8_COUNT: *val = resp->data.pm_xfer.type.sc8_count.count; break; default: dev_err(aondev, "Invalid pm response\n"); break; } mutex_unlock(&aon_mutex); return 0; } static int aon_pm_store(void *data, u64 val) { struct aon_dbg_response *resp; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)data, WRITE, val); mutex_unlock(&aon_mutex); if (IS_ERR(resp)) return PTR_ERR(resp); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_pm_threshold_fops, aon_pm_show, aon_pm_store, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(aon_pm_wake_timeout_fops, aon_pm_show, aon_pm_store, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(aon_pm_disable_pllaon_fops, aon_pm_show, aon_pm_store, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(aon_pm_retention_fops, aon_pm_show, aon_pm_store, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(aon_pm_sc8_count_fops, aon_pm_show, NULL, "%lld\n"); static int aon_pm_state_times_show(struct seq_file *file, void *data) { struct aon_dbg_response *resp; int i, len; u64 *arr; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)file->private, READ, 0); if (IS_ERR(resp)) { mutex_unlock(&aon_mutex); return PTR_ERR(resp); } len = ARRAY_SIZE(resp->data.pm_xfer.type.state_times.state_durations); arr = resp->data.pm_xfer.type.state_times.state_durations; for (i = 0; i < len; i++) seq_printf(file, "%s: %llu\n", pstates[i], arr[i]); mutex_unlock(&aon_mutex); return 0; } static int aon_pm_state_times_open(struct inode *inode, struct file *file) { return single_open(file, aon_pm_state_times_show, inode->i_private); } static const struct file_operations aon_pm_state_times_fops = { .open = aon_pm_state_times_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int aon_pm_state_counts_show(struct seq_file *file, void *data) { struct aon_dbg_response *resp; int i, len; u32 *arr; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)file->private, READ, 0); if (IS_ERR(resp)) { mutex_unlock(&aon_mutex); return PTR_ERR(resp); } len = ARRAY_SIZE(resp->data.pm_xfer.type.state_counts.state_entry_counts); arr = resp->data.pm_xfer.type.state_counts.state_entry_counts; for (i = 0; i < len; i++) seq_printf(file, "%s: %u\n", pstates[i], arr[i]); mutex_unlock(&aon_mutex); return 0; } static int aon_pm_state_counts_open(struct inode *inode, struct file *file) { return single_open(file, aon_pm_state_counts_show, inode->i_private); } static const struct file_operations aon_pm_state_counts_fops = { .open = aon_pm_state_counts_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static ssize_t __aon_do_ping(u32 context, char **data) { struct aon_dbg_response *resp; int ret = 0; *data = NULL; resp = aon_create_ivc_dbg_req(context, READ, 0); if (IS_ERR(resp)) ret = PTR_ERR(resp); else *data = resp->data.ping_xfer.data; return ret; } static int aon_ping_show(struct seq_file *file, void *param) { char *data; int ret; mutex_lock(&aon_mutex); ret = __aon_do_ping(*(u32 *)file->private, &data); if (ret >= 0) seq_printf(file, "%s\n", data); mutex_unlock(&aon_mutex); return ret; } static ssize_t aon_version_show(struct device *dev, char *buf, size_t size) { char *data; int ret = 0; mutex_lock(&aon_mutex); ret = __aon_do_ping(AON_PING_TEST, &data); if (ret < 0) ret = snprintf(buf, size, "error retrieving version: %d", ret); else ret = snprintf(buf, size, "%s", data ? data : "unavailable"); mutex_unlock(&aon_mutex); return ret; } static int aon_ping_open(struct inode *inode, struct file *file) { return single_open(file, aon_ping_show, inode->i_private); } static const struct file_operations aon_ping_fops = { .open = aon_ping_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int aon_mods_loops_store(void *data, u64 val) { struct aon_dbg_response *resp; int ret = 0; mutex_lock(&aon_mutex); set_mods_result(MODS_DEFAULT_VAL); resp = aon_create_ivc_dbg_req(*(u32 *)data, WRITE, val); if (IS_ERR(resp)) ret = PTR_ERR(resp); else set_mods_result(resp->status); mutex_unlock(&aon_mutex); return ret; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_loops_fops, NULL, aon_mods_loops_store, "%lld\n"); static int aon_mods_result_show(void *data, u64 *val) { *val = get_mods_result(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_result_fops, aon_mods_result_show, NULL, "%lld\n"); static int aon_mods_crc_show(void *data, u64 *val) { struct aon_dbg_response *resp; int ret = 0; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)data, READ, 0); if (IS_ERR(resp)) ret = PTR_ERR(resp); else *val = resp->data.crc_xfer.crc; mutex_unlock(&aon_mutex); return ret; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_crc_fops, aon_mods_crc_show, NULL, "%llx\n"); static int aon_timeout_show(void *data, u64 *val) { *val = get_completion_timeout(); return 0; } static int aon_timeout_store(void *data, u64 val) { set_completion_timeout(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_timeout_fops, aon_timeout_show, aon_timeout_store, "%lld\n"); static struct aon_dbgfs_node aon_nodes[] = { {.name = "target_power_state", .id = AON_PM_TARGET_POWER_STATE, .pdr_id = AON_PM, .mode = S_IRUGO | S_IWUSR, .fops = &aon_pm_target_power_state_fops,}, {.name = "threshold", .id = AON_PM_THRESHOLD, .pdr_id = AON_PM, .mode = S_IRUGO | S_IWUSR, .fops = &aon_pm_threshold_fops,}, {.name = "wake_timeout", .id = AON_PM_WAKE_TIMEOUT, .pdr_id = AON_PM, .mode = S_IRUGO | S_IWUSR, .fops = &aon_pm_wake_timeout_fops,}, {.name = "disable_pllaon", .id = AON_PM_DISABLE_PLLAON, .pdr_id = AON_PM, .mode = S_IRUGO | S_IWUSR, .fops = &aon_pm_disable_pllaon_fops,}, {.name = "state_times", .id = AON_PM_STATE_TIME, .pdr_id = AON_STATS, .mode = S_IRUGO, .fops = &aon_pm_state_times_fops,}, {.name = "state_counts", .id = AON_PM_STATE_COUNT, .pdr_id = AON_STATS, .mode = S_IRUGO, .fops = &aon_pm_state_counts_fops,}, {.name = "loops", .id = AON_MODS_LOOPS_TEST, .pdr_id = AON_MODS, .mode = S_IWUSR, .fops = &aon_mods_loops_fops, }, {.name = "result", .id = AON_MODS_RESULT, .pdr_id = AON_MODS, .mode = S_IRUGO , .fops = &aon_mods_result_fops,}, {.name = "crc", .id = AON_MODS_CRC, .pdr_id = AON_MODS, .mode = S_IRUGO , .fops = &aon_mods_crc_fops,}, {.name = "ping", .id = AON_PING_TEST, .pdr_id = AON_ROOT, .mode = S_IRUGO | S_IWUSR, .fops = &aon_ping_fops,}, {.name = "enable_retention", .id = AON_PM_VDD_RTC_RETENTION, .pdr_id = AON_PM, .mode = S_IRUGO | S_IWUSR, .fops = &aon_pm_retention_fops,}, {.name = "sc8_count", .id = AON_PM_SC8_COUNT, .pdr_id = AON_STATS, .mode = S_IRUGO, .fops = &aon_pm_sc8_count_fops,}, {.name = "completion_timeout", .pdr_id = AON_ROOT, .mode = S_IRUGO | S_IWUSR, .fops = &aon_timeout_fops,}, }; static void tegra_aondbg_recv_msg(struct mbox_client *cl, void *rx_msg) { struct tegra_aon_mbox_msg *msg; struct aon_dbg_response *resp; msg = (struct tegra_aon_mbox_msg *)rx_msg; resp = (void *)msg->data; if (resp->resp_type > AON_REQUEST_TYPE_MAX) { dev_err(aondev, "Multiple request types in 1 response\n"); return; } memcpy(aon_nodes[resp->resp_type].data, msg->data, IVC_DBG_CH_FRAME_SIZE); complete(aon_nodes[resp->resp_type].wait_on); } static int aon_pm_dbg_init(struct tegra_aondbg *aon) { struct dentry *d; struct dentry *td; int i, j; d = debugfs_create_dir(aon_dbgfs_dirs[0].name, NULL); if (IS_ERR_OR_NULL(d)) goto clean; aon_dbgfs_dirs[0].dir = d; aon->aon_root = d; for (i = 1; i < ARRAY_SIZE(aon_dbgfs_dirs); i++) { d = debugfs_create_dir(aon_dbgfs_dirs[i].name, aon_dbgfs_dirs[i].parent->dir); if (IS_ERR_OR_NULL(d)) goto clean; aon_dbgfs_dirs[i].dir = d; } for (i = 0; i < ARRAY_SIZE(aon_nodes); i++) { if (strcmp(aon_nodes[i].name, "threshold")) { d = debugfs_create_file(aon_nodes[i].name, aon_nodes[i].mode, aon_dbgfs_dirs[aon_nodes[i].pdr_id].dir, &aon_nodes[i].id, aon_nodes[i].fops); if (IS_ERR_OR_NULL(d)) goto clean; continue; } d = debugfs_create_dir(aon_nodes[i].name, aon_dbgfs_dirs[aon_nodes[i].pdr_id].dir); if (IS_ERR_OR_NULL(d)) goto clean; /* * We only need to loop till off by one as we don't need * active state as a separate node in threshold directory. */ for (j = 0; j < ARRAY_SIZE(pstates) - 1; j++) { thresholds[j] = aon_nodes[i].id | BIT(j + AON_REQUESTS_TOTAL); td = debugfs_create_file(pstates[j], aon_nodes[i].mode, d, &thresholds[j], aon_nodes[i].fops); if (IS_ERR_OR_NULL(d)) { d = td; goto clean; } } } return 0; clean: debugfs_remove_recursive(aon->aon_root); return PTR_ERR(d); } #define NV(p) "nvidia," p static int tegra_aondbg_probe(struct platform_device *pdev) { struct tegra_aondbg *aondbg; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; int i; int ret; aondev = &pdev->dev; dev_dbg(dev, "aondbg driver probe()\n"); if (!np) { dev_err(dev, "tegra-aondbg: DT data required.\n"); return -EINVAL; } aondbg = devm_kzalloc(&pdev->dev, sizeof(*aondbg), GFP_KERNEL); if (!aondbg) return -ENOMEM; dev_set_drvdata(&pdev->dev, aondbg); aondbg->dev = &pdev->dev; aondbg->cl.dev = &pdev->dev; aondbg->cl.tx_block = true; aondbg->cl.tx_tout = TX_BLOCK_PERIOD; aondbg->cl.knows_txdone = false; aondbg->cl.rx_callback = tegra_aondbg_recv_msg; aondbg->mbox = mbox_request_channel(&aondbg->cl, 0); if (IS_ERR(aondbg->mbox)) { ret = PTR_ERR(aondbg->mbox); if (ret != -EPROBE_DEFER) dev_warn(&pdev->dev, "can't get mailbox channel (%d)\n", ret); return ret; } dev_dbg(dev, "aondbg->mbox = %p\n", aondbg->mbox); for (i = 0; i < ARRAY_SIZE(aon_nodes); i++) { aon_nodes[i].wait_on = devm_kzalloc(&pdev->dev, sizeof(struct completion), GFP_KERNEL); if (!aon_nodes[i].wait_on) { dev_err(dev, "out of memory.\n"); return -ENOMEM; } init_completion(aon_nodes[i].wait_on); } ret = aon_pm_dbg_init(aondbg); if (ret) { dev_err(dev, "failed to create debugfs nodes.\n"); return ret; } dev_info(dev, "aondbg driver probe() OK\n"); devm_tegrafw_register(&pdev->dev, "spe", TFW_NORMAL, aon_version_show, NULL); return 0; } static int tegra_aondbg_remove(struct platform_device *pdev) { struct tegra_aondbg *aondbg; aondbg = dev_get_drvdata(&pdev->dev); mbox_free_channel(aondbg->mbox); debugfs_remove_recursive(aondbg->aon_root); return 0; } static const struct of_device_id tegra_aondbg_of_match[] = { { .compatible = "nvidia,tegra186-aondbg", }, {}, }; MODULE_DEVICE_TABLE(of, tegra_aondbg_of_match); static struct platform_driver tegra_aondbg_driver = { .driver = { .name = "tegra186-aondbg", .owner = THIS_MODULE, .of_match_table = of_match_ptr(tegra_aondbg_of_match), }, .probe = tegra_aondbg_probe, .remove = tegra_aondbg_remove, }; module_platform_driver(tegra_aondbg_driver); MODULE_DESCRIPTION("AON DBG driver"); MODULE_AUTHOR("NVIDIA"); MODULE_LICENSE("GPL v2");