blob: ceb8b8d5c948200dc7a4d4653aed3d948d2b51d4 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
struct meson_timestamp_dev {
void __iomem *base;
struct miscdevice timestamp;
};
static u64 meson_timestamp_hw_get(void __iomem *vaddr)
{
u64 low = readl_relaxed(vaddr);
u64 high = readl_relaxed(vaddr + 4);
// If low 32bit flipped, read timestamp again.
u64 low2 = readl_relaxed(vaddr);
if (low > low2) {
low = low2;
high = readl_relaxed(vaddr + 4);
}
return (high << 32) + low;
}
static int meson_timestamp_open(struct inode *inp, struct file *file)
{
if ((file->f_mode & FMODE_READ) == 0)
return -EINVAL;
if (file->f_mode & FMODE_WRITE)
return -EINVAL;
return 0;
}
static ssize_t meson_timestamp_read(struct file *file, char __user *buf,
size_t size, loff_t *offp)
{
struct miscdevice *c;
struct meson_timestamp_dev *tdev;
u64 counter;
int ret;
c = (struct miscdevice *)file->private_data;
tdev = container_of(c, struct meson_timestamp_dev, timestamp);
counter = meson_timestamp_hw_get(tdev->base);
ret = put_user(counter, (uint64_t __user *)buf);
if (ret)
return -EFAULT;
return sizeof(uint64_t);
}
static int meson_timestamp_release(struct inode *inp, struct file *file)
{
return 0;
}
static const struct file_operations meson_timestamp_fops = {
.owner = THIS_MODULE,
.open = meson_timestamp_open,
.read = meson_timestamp_read,
.release = meson_timestamp_release,
};
static ssize_t time_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct miscdevice *c = dev_get_drvdata(dev);
struct meson_timestamp_dev *tdev;
tdev = container_of(c, struct meson_timestamp_dev, timestamp);
return sprintf(buf, "%llu\n", meson_timestamp_hw_get(tdev->base));
}
static DEVICE_ATTR_RO(time);
static struct attribute *meson_timestamp_attrs[] = {
&dev_attr_time.attr,
NULL
};
static const struct attribute_group meson_timestamp_group = {
.attrs = meson_timestamp_attrs,
};
static const struct attribute_group *meson_timestamp_groups[] = {
&meson_timestamp_group,
NULL
};
static int meson_timestamp_dev_register(struct meson_timestamp_dev *tdev)
{
tdev->timestamp.minor = MISC_DYNAMIC_MINOR;
tdev->timestamp.name = "timestamp";
tdev->timestamp.fops = &meson_timestamp_fops;
tdev->timestamp.groups = meson_timestamp_groups;
return misc_register(&tdev->timestamp);
}
static int meson_timestamp_probe(struct platform_device *pdev)
{
struct meson_timestamp_dev *tdev;
struct resource *res;
tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
if (!tdev)
return -ENOMEM;
platform_set_drvdata(pdev, tdev);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no mem resource.\n");
return -EINVAL;
}
tdev->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tdev->base))
return PTR_ERR(tdev->base);
pr_info("Kernel TE entry: %llu\n", meson_timestamp_hw_get(tdev->base));
return meson_timestamp_dev_register(tdev);
}
static int meson_timestamp_remove(struct platform_device *pdev)
{
struct meson_timestamp_dev *tdev = platform_get_drvdata(pdev);
misc_deregister(&tdev->timestamp);
return 0;
}
static const struct of_device_id meson_timestamp_of_match[] = {
{
.compatible = "amlogic, meson-soc-timestamp",
},
{ /* sentinel */ }
}
MODULE_DEVICE_TABLE(of, meson_timestamp_of_match);
static struct platform_driver meson_timestamp_driver = {
.probe = meson_timestamp_probe,
.remove = meson_timestamp_remove,
.driver = {
.name = "meson_soc_timestamp",
.of_match_table = meson_timestamp_of_match,
},
};
module_platform_driver(meson_timestamp_driver);