summaryrefslogtreecommitdiffstats
path: root/drivers/ptp/ptp_vmw.c
blob: 7ec90359428abfd92d39dc1251da05ca704a551b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
 * Copyright (C) 2020 VMware, Inc., Palo Alto, CA., USA
 *
 * PTP clock driver for VMware precision clock virtual device.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ptp_clock_kernel.h>
#include <asm/hypervisor.h>
#include <asm/vmware.h>

#define VMWARE_MAGIC 0x564D5868
#define VMWARE_CMD_PCLK(nr) ((nr << 16) | 97)
#define VMWARE_CMD_PCLK_GETTIME VMWARE_CMD_PCLK(0)

static struct acpi_device *ptp_vmw_acpi_device;
static struct ptp_clock *ptp_vmw_clock;


static int ptp_vmw_pclk_read(u64 *ns)
{
	u32 ret, nsec_hi, nsec_lo, unused1, unused2, unused3;

	asm volatile (VMWARE_HYPERCALL :
		"=a"(ret), "=b"(nsec_hi), "=c"(nsec_lo), "=d"(unused1),
		"=S"(unused2), "=D"(unused3) :
		"a"(VMWARE_MAGIC), "b"(0),
		"c"(VMWARE_CMD_PCLK_GETTIME), "d"(0) :
		"memory");

	if (ret == 0)
		*ns = ((u64)nsec_hi << 32) | nsec_lo;
	return ret;
}

/*
 * PTP clock ops.
 */

static int ptp_vmw_adjtime(struct ptp_clock_info *info, s64 delta)
{
	return -EOPNOTSUPP;
}

static int ptp_vmw_adjfine(struct ptp_clock_info *info, long delta)
{
	return -EOPNOTSUPP;
}

static int ptp_vmw_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
{
	u64 ns;

	if (ptp_vmw_pclk_read(&ns) != 0)
		return -EIO;
	*ts = ns_to_timespec64(ns);
	return 0;
}

static int ptp_vmw_settime(struct ptp_clock_info *info,
			  const struct timespec64 *ts)
{
	return -EOPNOTSUPP;
}

static int ptp_vmw_enable(struct ptp_clock_info *info,
			 struct ptp_clock_request *request, int on)
{
	return -EOPNOTSUPP;
}

static struct ptp_clock_info ptp_vmw_clock_info = {
	.owner		= THIS_MODULE,
	.name		= "ptp_vmw",
	.max_adj	= 0,
	.adjtime	= ptp_vmw_adjtime,
	.adjfine	= ptp_vmw_adjfine,
	.gettime64	= ptp_vmw_gettime,
	.settime64	= ptp_vmw_settime,
	.enable		= ptp_vmw_enable,
};

/*
 * ACPI driver ops for VMware "precision clock" virtual device.
 */

static int ptp_vmw_acpi_add(struct acpi_device *device)
{
	ptp_vmw_clock = ptp_clock_register(&ptp_vmw_clock_info, NULL);
	if (IS_ERR(ptp_vmw_clock)) {
		pr_err("failed to register ptp clock\n");
		return PTR_ERR(ptp_vmw_clock);
	}

	ptp_vmw_acpi_device = device;
	return 0;
}

static void ptp_vmw_acpi_remove(struct acpi_device *device)
{
	ptp_clock_unregister(ptp_vmw_clock);
}

static const struct acpi_device_id ptp_vmw_acpi_device_ids[] = {
	{ "VMW0005", 0 },
	{ "", 0 },
};

MODULE_DEVICE_TABLE(acpi, ptp_vmw_acpi_device_ids);

static struct acpi_driver ptp_vmw_acpi_driver = {
	.name = "ptp_vmw",
	.ids = ptp_vmw_acpi_device_ids,
	.ops = {
		.add = ptp_vmw_acpi_add,
		.remove	= ptp_vmw_acpi_remove
	},
};

static int __init ptp_vmw_init(void)
{
	if (x86_hyper_type != X86_HYPER_VMWARE)
		return -1;
	return acpi_bus_register_driver(&ptp_vmw_acpi_driver);
}

static void __exit ptp_vmw_exit(void)
{
	acpi_bus_unregister_driver(&ptp_vmw_acpi_driver);
}

module_init(ptp_vmw_init);
module_exit(ptp_vmw_exit);

MODULE_DESCRIPTION("VMware virtual PTP clock driver");
MODULE_AUTHOR("VMware, Inc.");
MODULE_LICENSE("Dual BSD/GPL");