456 lines
12 KiB
C
456 lines
12 KiB
C
|
/*
|
||
|
* eqos_ape_ioctl.c -- EQOS APE Clock Synchronization driver IO control
|
||
|
*
|
||
|
* Copyright (c) 2015-2021 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 received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/tegra_pm_domains.h>
|
||
|
#include <linux/clk/tegra.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <uapi/misc/eqos_ape_ioctl.h>
|
||
|
#include "eqos_ape_global.h"
|
||
|
|
||
|
#define ONE_MILLION (1000000)
|
||
|
#define ONE_BILLION (1000000000)
|
||
|
#define DEFAULT_N_INT (0)
|
||
|
#define DEFAULT_N_FRACT (0)
|
||
|
#define DEFAULT_N_MODULO (0)
|
||
|
|
||
|
static struct rate_to_time_period g_rate;
|
||
|
static int eqos_ape_ioctl_major;
|
||
|
static struct cdev eqos_ape_ioctl_cdev;
|
||
|
static struct device *dev_eqos_ape;
|
||
|
static struct class *eqos_ape_ioctl_class;
|
||
|
struct eqos_drvdata *eqos_ape_drv_data;
|
||
|
|
||
|
static void sync_snapshot(void)
|
||
|
{
|
||
|
struct eqos_drvdata *data = eqos_ape_drv_data;
|
||
|
|
||
|
/* trigger and store snapsnot */
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_3_0_ENABLE |
|
||
|
AMISC_APE_TSC_CTRL_3_0_TRIGGER),
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
data->ape_sec_snap = amisc_readl(AMISC_APE_SNAP_TSC_SEC_0);
|
||
|
data->ape_ns_snap = amisc_readl(AMISC_APE_SNAP_TSC_NS_0);
|
||
|
data->eavb_sec_snap = amisc_readl(AMISC_EAVB_SNAP_TSC_SEC_0);
|
||
|
data->eavb_ns_snap = amisc_readl(AMISC_EAVB_SNAP_TSC_NS_0);
|
||
|
}
|
||
|
|
||
|
static long eqos_ape_hw_ioctl(struct file *file,
|
||
|
unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
struct eqos_ape_cmd __user *_eqos_ape = (struct eqos_ape_cmd *)arg;
|
||
|
struct eqos_ape_cmd eqos_ape;
|
||
|
struct eqos_ape_sync_cmd __user *_eqos_ape_sync =
|
||
|
(struct eqos_ape_sync_cmd *)arg;
|
||
|
struct eqos_ape_sync_cmd eqos_ape_sync;
|
||
|
struct rate_to_time_period __user *_rate_info =
|
||
|
(struct rate_to_time_period *)arg;
|
||
|
struct rate_to_time_period rate_info = {0, 0, 0, 0};
|
||
|
u64 ape_sec_snap_prev = 0, ape_ns_snap_prev = 0;
|
||
|
u64 eavb_sec_snap_prev = 0, eavb_ns_snap_prev = 0;
|
||
|
struct eqos_drvdata *data = eqos_ape_drv_data;
|
||
|
struct device *dev = &data->pdev->dev;
|
||
|
u64 num = 0;
|
||
|
u64 den = 0;
|
||
|
int cur_rate;
|
||
|
int new_rate;
|
||
|
int set_rate;
|
||
|
|
||
|
switch (cmd) {
|
||
|
case EQOS_APE_AMISC_INIT:
|
||
|
amisc_idle_disable();
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_3_0_ENABLE,
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
|
||
|
if (!(arg && copy_from_user(&rate_info,
|
||
|
_rate_info,
|
||
|
sizeof(rate_info)))) {
|
||
|
g_rate.n_modulo = rate_info.n_modulo;
|
||
|
g_rate.n_fract = rate_info.n_fract;
|
||
|
g_rate.n_int = rate_info.n_int;
|
||
|
amisc_writel(
|
||
|
(AMISC_APE_TSC_CTRL_NMODULE_0_0_MASK
|
||
|
(rate_info.n_modulo) |
|
||
|
AMISC_APE_TSC_CTRL_NFRACT_0_0_MASK
|
||
|
(rate_info.n_fract)),
|
||
|
AMISC_APE_TSC_CTRL_0_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_NINT_1_0_MASK
|
||
|
(rate_info.n_int),
|
||
|
AMISC_APE_TSC_CTRL_1_0);
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_3_0_ENABLE |
|
||
|
AMISC_APE_TSC_CTRL_3_0_COPY),
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
|
||
|
ape_sec_snap_prev = amisc_readl(AMISC_APE_RT_TSC_SEC_0);
|
||
|
ape_ns_snap_prev = amisc_readl(AMISC_APE_RT_TSC_NS_0);
|
||
|
dev_dbg(dev, "APE Time Sec %lld NSec %lld\n",
|
||
|
ape_sec_snap_prev, ape_ns_snap_prev);
|
||
|
dev_dbg(dev, "APE Time Sec %llx NSec %llx\n",
|
||
|
ape_sec_snap_prev, ape_ns_snap_prev);
|
||
|
} else {
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case EQOS_APE_AMISC_FREQ_SYNC:
|
||
|
if (data->first_sync) {
|
||
|
sync_snapshot();
|
||
|
data->first_sync = 0;
|
||
|
dev_dbg(dev, "EAVB sec %lld nsec %lld\n",
|
||
|
data->eavb_sec_snap, data->eavb_ns_snap);
|
||
|
dev_dbg(dev, "APE sec %lld nsec %lld\n",
|
||
|
data->ape_sec_snap, data->ape_ns_snap);
|
||
|
break;
|
||
|
}
|
||
|
if (arg && copy_from_user(&eqos_ape_sync,
|
||
|
_eqos_ape_sync,
|
||
|
sizeof(eqos_ape_sync)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
|
||
|
|
||
|
/* Store previous timestamp*/
|
||
|
eavb_sec_snap_prev = data->eavb_sec_snap;
|
||
|
eavb_ns_snap_prev = data->eavb_ns_snap;
|
||
|
ape_sec_snap_prev = data->ape_sec_snap;
|
||
|
ape_ns_snap_prev = data->ape_ns_snap;
|
||
|
|
||
|
sync_snapshot();
|
||
|
/* TODO: implementation for clock change logic */
|
||
|
den = (data->ape_sec_snap - ape_sec_snap_prev) * ONE_BILLION
|
||
|
+ (data->ape_ns_snap - ape_ns_snap_prev);
|
||
|
num = (data->eavb_sec_snap - eavb_sec_snap_prev) * ONE_BILLION
|
||
|
+ (data->eavb_ns_snap - eavb_ns_snap_prev);
|
||
|
|
||
|
eqos_ape_sync.drift_num = num;
|
||
|
eqos_ape_sync.drift_den = den;
|
||
|
|
||
|
dev_dbg(dev, "num %lld den %lld\n", num, den);
|
||
|
if (copy_to_user((struct eqos_ape_sync_cmd *)_eqos_ape_sync,
|
||
|
&eqos_ape_sync,
|
||
|
sizeof(eqos_ape_sync)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
break;
|
||
|
case EQOS_APE_TEST_FREQ_ADJ:
|
||
|
|
||
|
if (!arg || copy_from_user(&eqos_ape,
|
||
|
_eqos_ape,
|
||
|
sizeof(eqos_ape)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
dev_dbg(dev, "Applied freq adj %d\n", eqos_ape.ppm);
|
||
|
cur_rate = amisc_plla_get_rate();
|
||
|
dev_dbg(dev, "current rate %d\n", cur_rate);
|
||
|
|
||
|
num = ONE_MILLION + eqos_ape.ppm;
|
||
|
den = ONE_MILLION;
|
||
|
|
||
|
new_rate = (int)((num * cur_rate)/den);
|
||
|
|
||
|
dev_dbg(dev, "new rate %d\n", new_rate);
|
||
|
amisc_plla_set_rate(new_rate);
|
||
|
|
||
|
set_rate = amisc_plla_get_rate();
|
||
|
dev_dbg(dev, "applied rate %d\n", set_rate);
|
||
|
|
||
|
break;
|
||
|
case EQOS_APE_AMISC_PHASE_SYNC:
|
||
|
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_3_0_ENABLE |
|
||
|
AMISC_APE_TSC_CTRL_3_0_COPY),
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
data->first_sync = 1;
|
||
|
break;
|
||
|
|
||
|
case EQOS_APE_AMISC_DEINIT:
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_NMODULE_0_0_MASK(0) |
|
||
|
AMISC_APE_TSC_CTRL_NFRACT_0_0_MASK(0)),
|
||
|
AMISC_APE_TSC_CTRL_0_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_NINT_1_0_MASK(DEFAULT_N_INT),
|
||
|
AMISC_APE_TSC_CTRL_1_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_3_0_DISABLE,
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
amisc_idle_enable();
|
||
|
amisc_plla_set_rate(data->pll_a_clk_rate);
|
||
|
data->first_sync = 1;
|
||
|
break;
|
||
|
|
||
|
case EQOS_APE_AMISC_GET_RATE:
|
||
|
/* configuring n_fract/n_modulo based on the rate table*/
|
||
|
rate_info.rate = amisc_ape_get_rate();
|
||
|
if (copy_to_user((struct rate_to_time_period *)_rate_info,
|
||
|
&rate_info,
|
||
|
sizeof(rate_info)))
|
||
|
return -EFAULT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_ioctl_open(struct inode *inp, struct file *filep)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = pm_runtime_get_sync(&eqos_ape_drv_data->pdev->dev);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
dev_dbg(&eqos_ape_drv_data->pdev->dev, "eqos ape opened\n");
|
||
|
amisc_idle_enable();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_ioctl_release(struct inode *inp, struct file *filep)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
amisc_idle_disable();
|
||
|
ret = pm_runtime_put_sync(&eqos_ape_drv_data->pdev->dev);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&eqos_ape_drv_data->pdev->dev, "pm_runtime_put_sync failed\n");
|
||
|
return ret;
|
||
|
}
|
||
|
dev_dbg(&eqos_ape_drv_data->pdev->dev, "eqos ape closed\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations eqos_ape_ioctl_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = eqos_ape_ioctl_open,
|
||
|
.release = eqos_ape_ioctl_release,
|
||
|
.unlocked_ioctl = eqos_ape_hw_ioctl,
|
||
|
};
|
||
|
|
||
|
static void eqos_ape_ioctl_cleanup(void)
|
||
|
{
|
||
|
|
||
|
amisc_clk_deinit();
|
||
|
cdev_del(&eqos_ape_ioctl_cdev);
|
||
|
device_destroy(eqos_ape_ioctl_class, MKDEV(eqos_ape_ioctl_major, 0));
|
||
|
|
||
|
if (eqos_ape_ioctl_class)
|
||
|
class_destroy(eqos_ape_ioctl_class);
|
||
|
|
||
|
unregister_chrdev_region(
|
||
|
MKDEV(eqos_ape_ioctl_major, 0), 1);
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_init(void)
|
||
|
{
|
||
|
struct device *dev = &eqos_ape_drv_data->pdev->dev;
|
||
|
dev_t eqos_ape_ioctl_dev;
|
||
|
int ret = -ENODEV;
|
||
|
int result;
|
||
|
|
||
|
|
||
|
result = alloc_chrdev_region(&eqos_ape_ioctl_dev, 0,
|
||
|
1, "eqos_ape_hw");
|
||
|
if (result < 0)
|
||
|
goto fail_err;
|
||
|
|
||
|
eqos_ape_ioctl_major = MAJOR(eqos_ape_ioctl_dev);
|
||
|
cdev_init(&eqos_ape_ioctl_cdev, &eqos_ape_ioctl_fops);
|
||
|
eqos_ape_ioctl_cdev.owner = THIS_MODULE;
|
||
|
eqos_ape_ioctl_cdev.ops = &eqos_ape_ioctl_fops;
|
||
|
|
||
|
result = cdev_add(&eqos_ape_ioctl_cdev, eqos_ape_ioctl_dev, 1);
|
||
|
if (result < 0)
|
||
|
goto fail_chrdev;
|
||
|
|
||
|
eqos_ape_ioctl_class = class_create(THIS_MODULE, "eqos_ape_hw");
|
||
|
if (IS_ERR(eqos_ape_ioctl_class)) {
|
||
|
pr_err(KERN_ERR "eqos_ape_hwdep: device class file already in use.\n");
|
||
|
eqos_ape_ioctl_cleanup();
|
||
|
return PTR_ERR(eqos_ape_ioctl_class);
|
||
|
}
|
||
|
|
||
|
dev_eqos_ape = device_create(eqos_ape_ioctl_class, NULL,
|
||
|
MKDEV(eqos_ape_ioctl_major, 0),
|
||
|
NULL, "%s", "eqos_ape_hw");
|
||
|
|
||
|
dev_dbg(dev, "eqos ape init\n");
|
||
|
return 0;
|
||
|
|
||
|
fail_chrdev:
|
||
|
unregister_chrdev_region(eqos_ape_ioctl_dev, 1);
|
||
|
|
||
|
fail_err:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void eqos_ape_exit(void)
|
||
|
{
|
||
|
eqos_ape_ioctl_cleanup();
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct eqos_drvdata *drv_data;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct resource *res = NULL;
|
||
|
void __iomem *base = NULL;
|
||
|
int ret = 0;
|
||
|
int iter;
|
||
|
|
||
|
|
||
|
drv_data = devm_kzalloc(dev, sizeof(*drv_data),
|
||
|
GFP_KERNEL);
|
||
|
drv_data->first_sync = 1;
|
||
|
dev_set_drvdata(dev, drv_data);
|
||
|
|
||
|
platform_set_drvdata(pdev, drv_data);
|
||
|
drv_data->pdev = pdev;
|
||
|
|
||
|
drv_data->base_regs = devm_kzalloc(dev, sizeof(void *),
|
||
|
GFP_KERNEL);
|
||
|
if (!drv_data->base_regs) {
|
||
|
ret = -ENOMEM;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
for (iter = 0; iter < AMISC_MAX_REG; iter++) {
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, iter);
|
||
|
if (!res) {
|
||
|
dev_err(dev,
|
||
|
"Failed to get resource with ID %d\n",
|
||
|
iter);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
base = devm_ioremap_nocache(dev,
|
||
|
res->start,
|
||
|
resource_size(res));
|
||
|
if (IS_ERR_OR_NULL(base)) {
|
||
|
dev_err(dev, "Failed to iomap resource reg[%d]\n",
|
||
|
iter);
|
||
|
ret = PTR_ERR(base);
|
||
|
goto out;
|
||
|
}
|
||
|
drv_data->base_regs[iter] = base;
|
||
|
}
|
||
|
eqos_ape_drv_data = drv_data;
|
||
|
eqos_ape_init();
|
||
|
|
||
|
pm_runtime_enable(&eqos_ape_drv_data->pdev->dev);
|
||
|
amisc_clk_init();
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
eqos_ape_exit();
|
||
|
|
||
|
pm_runtime_disable(&eqos_ape_drv_data->pdev->dev);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PM_SLEEP
|
||
|
static int eqos_ape_suspend(struct device *dev)
|
||
|
{
|
||
|
struct eqos_drvdata *data = eqos_ape_drv_data;
|
||
|
|
||
|
if (pm_runtime_status_suspended(dev))
|
||
|
return 0;
|
||
|
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_NMODULE_0_0_MASK(0) |
|
||
|
AMISC_APE_TSC_CTRL_NFRACT_0_0_MASK(0)),
|
||
|
AMISC_APE_TSC_CTRL_0_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_NINT_1_0_MASK(DEFAULT_N_INT),
|
||
|
AMISC_APE_TSC_CTRL_1_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_3_0_DISABLE,
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
amisc_idle_enable();
|
||
|
amisc_plla_set_rate(data->pll_a_clk_rate);
|
||
|
data->first_sync = 1;
|
||
|
|
||
|
pm_runtime_put_sync(dev);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int eqos_ape_resume(struct device *dev)
|
||
|
{
|
||
|
if (pm_runtime_status_suspended(dev))
|
||
|
return 0;
|
||
|
|
||
|
pm_runtime_get_sync(dev);
|
||
|
|
||
|
amisc_idle_disable();
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_3_0_ENABLE,
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
|
||
|
amisc_writel(
|
||
|
(AMISC_APE_TSC_CTRL_NMODULE_0_0_MASK
|
||
|
(g_rate.n_modulo) |
|
||
|
AMISC_APE_TSC_CTRL_NFRACT_0_0_MASK
|
||
|
(g_rate.n_fract)),
|
||
|
AMISC_APE_TSC_CTRL_0_0);
|
||
|
amisc_writel(AMISC_APE_TSC_CTRL_NINT_1_0_MASK
|
||
|
(g_rate.n_int),
|
||
|
AMISC_APE_TSC_CTRL_1_0);
|
||
|
amisc_writel((AMISC_APE_TSC_CTRL_3_0_ENABLE |
|
||
|
AMISC_APE_TSC_CTRL_3_0_COPY),
|
||
|
AMISC_APE_TSC_CTRL_3_0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
static const struct dev_pm_ops eqos_ape_pm_ops = {
|
||
|
SET_SYSTEM_SLEEP_PM_OPS(eqos_ape_suspend, eqos_ape_resume)
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id eqos_ape_of_match[] = {
|
||
|
{ .compatible = "nvidia,tegra18x-eqos-ape", .data = NULL, },
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
static struct platform_driver eqos_ape_driver = {
|
||
|
.driver = {
|
||
|
.name = "eqos_ape",
|
||
|
.owner = THIS_MODULE,
|
||
|
.pm = &eqos_ape_pm_ops,
|
||
|
.of_match_table = of_match_ptr(eqos_ape_of_match),
|
||
|
},
|
||
|
.probe = eqos_ape_probe,
|
||
|
.remove = eqos_ape_remove,
|
||
|
};
|
||
|
|
||
|
static int __init eqos_ape_modinit(void)
|
||
|
{
|
||
|
return platform_driver_register(&eqos_ape_driver);
|
||
|
}
|
||
|
module_init(eqos_ape_modinit);
|
||
|
|
||
|
static void __exit eqos_ape_modexit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&eqos_ape_driver);
|
||
|
}
|
||
|
module_exit(eqos_ape_modexit);
|
||
|
|
||
|
MODULE_AUTHOR("Sidharth R V <svarier@nvidia.com>");
|
||
|
MODULE_DESCRIPTION("EQOS APE driver IO control of AMISC");
|
||
|
MODULE_LICENSE("GPL");
|