--- /dev/null
+/* Copyright (c) 2015,2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ */
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/uaccess.h>
+
+#include "rpm_stats.h"
+
+#define RPM_RAIL_BUF_LEN 600
+
+#define SNPRINTF(buf, size, format, ...) \
+{ \
+ if (size > 0) { \
+ int ret; \
+ ret = snprintf(buf, size, format, ## __VA_ARGS__); \
+ if (ret > size) { \
+ buf += size; \
+ size = 0; \
+ } else { \
+ buf += ret; \
+ size -= ret; \
+ } \
+ } \
+}
+
+#define NAMELEN (sizeof(uint32_t)+1)
+
+struct msm_rpm_rail_stats_platform_data {
+ phys_addr_t phys_addr_base;
+ u32 phys_size;
+};
+
+struct msm_rpm_rail_corner {
+ uint64_t time;
+ uint32_t corner;
+ uint32_t reserved;
+};
+
+struct msm_rpm_rail_type {
+ uint32_t rail;
+ uint32_t num_corners;
+ uint32_t current_corner;
+ uint32_t last_entered;
+};
+
+struct msm_rpm_rail_stats {
+ uint32_t num_rails;
+ uint32_t reserved;
+};
+
+struct msm_rpm_rail_stats_private_data {
+ void __iomem *reg_base;
+ u32 len;
+ char buf[RPM_RAIL_BUF_LEN];
+ struct msm_rpm_rail_stats_platform_data *platform_data;
+};
+
+int msm_rpm_rail_stats_file_close(struct inode *inode, struct file *file)
+{
+ struct msm_rpm_rail_stats_private_data *private = file->private_data;
+
+ if (private->reg_base)
+ iounmap(private->reg_base);
+ kfree(file->private_data);
+
+ return 0;
+}
+
+static int msm_rpm_rail_corner_copy(void __iomem **base, char **buf,
+ int count)
+{
+ struct msm_rpm_rail_corner rc;
+ char corner[NAMELEN];
+
+ memset(&rc, 0, sizeof(rc));
+ memcpy_fromio(&rc, *base, sizeof(rc));
+
+ corner[NAMELEN - 1] = '\0';
+ memcpy(corner, &rc.corner, NAMELEN - 1);
+ SNPRINTF(*buf, count, "\t\tcorner:%-5s time:%-16llu\n",
+ corner, rc.time);
+
+ *base += sizeof(rc);
+
+ return count;
+}
+
+static int msm_rpm_rail_type_copy(void __iomem **base, char **buf, int count)
+{
+ struct msm_rpm_rail_type rt;
+ char rail[NAMELEN];
+ int i;
+
+ memset(&rt, 0, sizeof(rt));
+ memcpy_fromio(&rt, *base, sizeof(rt));
+
+ rail[NAMELEN - 1] = '\0';
+ memcpy(rail, &rt.rail, NAMELEN - 1);
+ SNPRINTF(*buf, count,
+ "\trail:%-2s num_corners:%-2u current_corner:%-2u last_entered:%-8u\n",
+ rail, rt.num_corners, rt.current_corner, rt.last_entered);
+
+ *base += sizeof(rt);
+
+ for (i = 0; i < rt.num_corners; i++)
+ count = msm_rpm_rail_corner_copy(base, buf, count);
+
+ return count;
+}
+
+static int msm_rpm_rail_stats_copy(
+ struct msm_rpm_rail_stats_private_data *prvdata)
+{
+ struct msm_rpm_rail_stats rs;
+ void __iomem *base = prvdata->reg_base;
+ char *buf = prvdata->buf;
+ int count = RPM_RAIL_BUF_LEN;
+ int i;
+
+ memset(&rs, 0, sizeof(rs));
+ memcpy_fromio(&rs, base, sizeof(rs));
+
+ SNPRINTF(buf, count, "Number of Rails:%u\n", rs.num_rails);
+
+ base = prvdata->reg_base + sizeof(rs);
+
+ for (i = 0; i < rs.num_rails; i++)
+ count = msm_rpm_rail_type_copy(&base, &buf, count);
+
+ return RPM_RAIL_BUF_LEN - count;
+}
+
+static ssize_t msm_rpm_rail_stats_file_read(struct file *file,
+ char __user *bufu, size_t count, loff_t *ppos)
+{
+ struct msm_rpm_rail_stats_private_data *prvdata =
+ file->private_data;
+ struct msm_rpm_rail_stats_platform_data *pdata;
+
+ if (!prvdata)
+ return -EINVAL;
+
+ if (!prvdata->platform_data)
+ return -EINVAL;
+
+ if (!bufu || count == 0)
+ return -EINVAL;
+
+ pdata = prvdata->platform_data;
+
+ if (*ppos <= pdata->phys_size) {
+ prvdata->len = msm_rpm_rail_stats_copy(prvdata);
+ *ppos = 0;
+ }
+
+ return simple_read_from_buffer(bufu, count, ppos,
+ prvdata->buf, prvdata->len);
+}
+
+static int msm_rpm_rail_stats_file_open(struct inode *inode,
+ struct file *file)
+{
+ struct msm_rpm_rail_stats_private_data *prvdata;
+ struct msm_rpm_rail_stats_platform_data *pdata = inode->i_private;
+
+ file->private_data =
+ kzalloc(sizeof(struct msm_rpm_rail_stats_private_data),
+ GFP_KERNEL);
+
+ if (!file->private_data)
+ return -ENOMEM;
+ prvdata = file->private_data;
+
+ prvdata->reg_base = ioremap(pdata->phys_addr_base,
+ pdata->phys_size);
+ if (!prvdata->reg_base) {
+ kfree(file->private_data);
+ prvdata = NULL;
+ pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n",
+ __func__, &pdata->phys_addr_base,
+ pdata->phys_size);
+ return -EBUSY;
+ }
+
+ prvdata->len = 0;
+ prvdata->platform_data = pdata;
+ return 0;
+}
+
+
+static const struct file_operations msm_rpm_rail_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = msm_rpm_rail_stats_file_open,
+ .read = msm_rpm_rail_stats_file_read,
+ .release = msm_rpm_rail_stats_file_close,
+ .llseek = no_llseek,
+};
+
+static int msm_rpm_rail_stats_probe(struct platform_device *pdev)
+{
+ struct dentry *dent;
+ struct msm_rpm_rail_stats_platform_data *pdata;
+ struct resource *res;
+ struct resource *offset;
+ struct device_node *node;
+ uint32_t offset_addr;
+ void __iomem *phys_ptr;
+
+ if (!pdev)
+ return -EINVAL;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "phys_addr_base");
+ if (!res)
+ return -EINVAL;
+
+ offset = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "offset_addr");
+ if (!offset)
+ return -EINVAL;
+
+ phys_ptr = ioremap_nocache(offset->start, SZ_4);
+ if (!phys_ptr) {
+ dev_err(&pdev->dev, "%s: Failed to ioremap address.\n",
+ __func__);
+ return -ENODEV;
+ }
+ offset_addr = readl_relaxed(phys_ptr);
+ iounmap(phys_ptr);
+
+ if (!offset_addr) {
+ dev_err(&pdev->dev, "%s: RPM Rail Stats not available: Exit\n",
+ __func__);
+ return 0;
+ }
+
+ node = pdev->dev.of_node;
+
+ if (pdev->dev.platform_data) {
+ pdata = pdev->dev.platform_data;
+ if (!pdata)
+ return -ENOMEM;
+ } else if (node) {
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ } else {
+ dev_err(&pdev->dev, "%s: pdata is not available: Exit\n",
+ __func__);
+ return 0;
+ }
+
+ pdata->phys_addr_base = res->start + offset_addr;
+ pdata->phys_size = resource_size(res);
+
+ dent = debugfs_create_file("rpm_rail_stats", S_IRUGO, NULL,
+ pdata, &msm_rpm_rail_stats_fops);
+
+ if (!dent) {
+ dev_err(&pdev->dev, "%s: ERROR debugfs_create_file failed\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, dent);
+ return 0;
+}
+
+static int msm_rpm_rail_stats_remove(struct platform_device *pdev)
+{
+ struct dentry *dent = platform_get_drvdata(pdev);
+
+ debugfs_remove(dent);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static const struct of_device_id rpm_rail_table[] = {
+ {.compatible = "qcom,rpm-rail-stats"},
+ {},
+};
+
+static struct platform_driver msm_rpm_rail_stats_driver = {
+ .probe = msm_rpm_rail_stats_probe,
+ .remove = msm_rpm_rail_stats_remove,
+ .driver = {
+ .name = "msm_rpm_rail_stats",
+ .owner = THIS_MODULE,
+ .of_match_table = rpm_rail_table,
+ },
+};
+
+static int __init msm_rpm_rail_stats_init(void)
+{
+ return platform_driver_register(&msm_rpm_rail_stats_driver);
+}
+
+static void __exit msm_rpm_rail_stats_exit(void)
+{
+ platform_driver_unregister(&msm_rpm_rail_stats_driver);
+}
+
+module_init(msm_rpm_rail_stats_init);
+module_exit(msm_rpm_rail_stats_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM RPM rail Statistics driver");
+MODULE_ALIAS("platform:msm_rail_stat_log");