diff mbox series

[RFC,v1,3/3] ptp_ocp: implement DPLL ops

Message ID 20220623005717.31040-4-vfedorenko@novek.ru (mailing list archive)
State New, archived
Headers show
Series Create common DPLL/clock configuration API | expand

Commit Message

Vadim Fedorenko June 23, 2022, 12:57 a.m. UTC
From: Vadim Fedorenko <vadfed@fb.com>

Implement DPLL operations in ptp_ocp driver.

Signed-off-by: Vadim Fedorenko <vadfed@fb.com>
---
 drivers/ptp/Kconfig   |  1 +
 drivers/ptp/ptp_ocp.c | 86 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+)

Comments

Jonathan Lemon June 23, 2022, 6:28 p.m. UTC | #1
On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
> From: Vadim Fedorenko <vadfed@fb.com>
> 
> Implement DPLL operations in ptp_ocp driver.

Please CC: me as well.


> +static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
> +{
> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> +	int sync;
> +
> +	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
> +	return sync;
> +}

Please match existing code style.


> +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
> +{
> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> +	int ret;
> +
> +	if (bp->sma[sma].mode != SMA_MODE_IN)
> +		return -1;
> +
> +	switch (ptp_ocp_sma_get(bp, sma)) {
> +	case 0:
> +		ret = DPLL_TYPE_EXT_10MHZ;
> +		break;
> +	case 1:
> +	case 2:
> +		ret = DPLL_TYPE_EXT_1PPS;
> +		break;
> +	default:
> +		ret = DPLL_TYPE_INT_OSCILLATOR;
> +	}
> +
> +	return ret;
> +}

These case statements switch on private bits.  This needs to match
on the selector name instead.


> +static int ptp_ocp_dpll_get_output_type(struct dpll_device *dpll, int sma)
> +{
> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> +	int ret;
> +
> +	if (bp->sma[sma].mode != SMA_MODE_OUT)
> +		return -1;
> +
> +	switch (ptp_ocp_sma_get(bp, sma)) {
> +	case 0:
> +		ret = DPLL_TYPE_EXT_10MHZ;
> +		break;
> +	case 1:
> +	case 2:
> +		ret = DPLL_TYPE_INT_OSCILLATOR;
> +		break;
> +	case 4:
> +	case 8:
> +		ret = DPLL_TYPE_GNSS;
> +		break;
> +	default:
> +		ret = DPLL_TYPE_INT_OSCILLATOR;

Missing break;


> +	}
> +
> +	return ret;
> +}
> +
> +static struct dpll_device_ops dpll_ops = {
> +	.get_status		= ptp_ocp_dpll_get_status,
> +	.get_lock_status	= ptp_ocp_dpll_get_lock_status,
> +	.get_source_type	= ptp_ocp_dpll_get_source_type,
> +	.get_output_type	= ptp_ocp_dpll_get_output_type,
> +};

No 'set' statements here?  Also, what happens if there is more than
one GNSS receiver, how is this differentiated?
>  static int
>  ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>  {
> @@ -3768,6 +3846,14 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>  
>  	ptp_ocp_info(bp);
>  	devlink_register(devlink);
> +
> +	bp->dpll = dpll_device_alloc(&dpll_ops, ARRAY_SIZE(bp->sma), ARRAY_SIZE(bp->sma), bp);
> +	if (!bp->dpll) {
> +		dev_err(&pdev->dev, "dpll_device_alloc failed\n");
> +		return 0;
> +	}
> +	dpll_device_register(bp->dpll);
> +

How is the release/unregister path called when the module is unloaded?
Vadim Fedorenko June 23, 2022, 11:11 p.m. UTC | #2
On 23.06.2022 19:28, Jonathan Lemon wrote:
> On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
>> From: Vadim Fedorenko <vadfed@fb.com>
>>
>> Implement DPLL operations in ptp_ocp driver.
> 
> Please CC: me as well.
> 

Sure, sorry for not doing it last.

> 
>> +static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int sync;
>> +
>> +	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
>> +	return sync;
>> +}
> 
> Please match existing code style.
> 
> 
>> +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int ret;
>> +
>> +	if (bp->sma[sma].mode != SMA_MODE_IN)
>> +		return -1;
>> +
>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>> +	case 0:
>> +		ret = DPLL_TYPE_EXT_10MHZ;
>> +		break;
>> +	case 1:
>> +	case 2:
>> +		ret = DPLL_TYPE_EXT_1PPS;
>> +		break;
>> +	default:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>> +	}
>> +
>> +	return ret;
>> +}
> 
> These case statements switch on private bits.  This needs to match
> on the selector name instead.
> 

Not sure that string comparison is a good idea. Maybe it's better to extend 
struct ocp_selector with netlink type id and fill it according to hardware?

> 
>> +static int ptp_ocp_dpll_get_output_type(struct dpll_device *dpll, int sma)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int ret;
>> +
>> +	if (bp->sma[sma].mode != SMA_MODE_OUT)
>> +		return -1;
>> +
>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>> +	case 0:
>> +		ret = DPLL_TYPE_EXT_10MHZ;
>> +		break;
>> +	case 1:
>> +	case 2:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>> +		break;
>> +	case 4:
>> +	case 8:
>> +		ret = DPLL_TYPE_GNSS;
>> +		break;
>> +	default:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
> 
> Missing break;
> 
> 
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static struct dpll_device_ops dpll_ops = {
>> +	.get_status		= ptp_ocp_dpll_get_status,
>> +	.get_lock_status	= ptp_ocp_dpll_get_lock_status,
>> +	.get_source_type	= ptp_ocp_dpll_get_source_type,
>> +	.get_output_type	= ptp_ocp_dpll_get_output_type,
>> +};
> 
> No 'set' statements here?
No, didn't implement it yet

> Also, what happens if there is more than
> one GNSS receiver, how is this differentiated?

Good question. Haven't thought about such case, open for suggestions!

>>   static int
>>   ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>>   {
>> @@ -3768,6 +3846,14 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>>   
>>   	ptp_ocp_info(bp);
>>   	devlink_register(devlink);
>> +
>> +	bp->dpll = dpll_device_alloc(&dpll_ops, ARRAY_SIZE(bp->sma), ARRAY_SIZE(bp->sma), bp);
>> +	if (!bp->dpll) {
>> +		dev_err(&pdev->dev, "dpll_device_alloc failed\n");
>> +		return 0;
>> +	}
>> +	dpll_device_register(bp->dpll);
>> +
> 
> How is the release/unregister path called when the module is unloaded?

Thank you Jonathan for pointing it out. I've just realised that unregister path 
is messy in the subsystem, will polish it in the next version and add it to 
driver unload.
Jonathan Lemon June 23, 2022, 11:36 p.m. UTC | #3
On Fri, Jun 24, 2022 at 12:11:43AM +0100, Vadim Fedorenko wrote:
> On 23.06.2022 19:28, Jonathan Lemon wrote:
> > On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
> > > From: Vadim Fedorenko <vadfed@fb.com>
> > > +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
> > > +{
> > > +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> > > +	int ret;
> > > +
> > > +	if (bp->sma[sma].mode != SMA_MODE_IN)
> > > +		return -1;
> > > +
> > > +	switch (ptp_ocp_sma_get(bp, sma)) {
> > > +	case 0:
> > > +		ret = DPLL_TYPE_EXT_10MHZ;
> > > +		break;
> > > +	case 1:
> > > +	case 2:
> > > +		ret = DPLL_TYPE_EXT_1PPS;
> > > +		break;
> > > +	default:
> > > +		ret = DPLL_TYPE_INT_OSCILLATOR;
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > 
> > These case statements switch on private bits.  This needs to match
> > on the selector name instead.
> > 
> 
> Not sure that string comparison is a good idea. Maybe it's better to extend
> struct ocp_selector with netlink type id and fill it according to hardware?

Sure, that could be an option.  But, as this is DPLL only, how does it
handle things when a pin is used for non-clock IO?  In the timecard,
for example, we have the frequency counters for input, and the frequency
generators/VCC/GND for output.

Actually our HW has a multi-level input, where the DPLL selects its
source from an internal mux - this isn't reflected here.  The external
pins feed into some complex HW logic, which performs its own priority
calculations before presenting the end result as an available selection
for the DPLL.
Vadim Fedorenko June 26, 2022, 7:27 p.m. UTC | #4
On 23.06.2022 19:28, Jonathan Lemon wrote:
> On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
>> From: Vadim Fedorenko <vadfed@fb.com>
>>
>> +static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int sync;
>> +
>> +	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
>> +	return sync;
>> +}
> 
> Please match existing code style.
> 

Didn't get this point. The same code is used through out the driver.
Could you please explain?

> 
>> +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int ret;
>> +
>> +	if (bp->sma[sma].mode != SMA_MODE_IN)
>> +		return -1;
>> +
>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>> +	case 0:
>> +		ret = DPLL_TYPE_EXT_10MHZ;
>> +		break;
>> +	case 1:
>> +	case 2:
>> +		ret = DPLL_TYPE_EXT_1PPS;
>> +		break;
>> +	default:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>> +	}
>> +
>> +	return ret;
>> +}
> 
> These case statements switch on private bits.  This needs to match
> on the selector name instead.
> 

Addressed this in v2

> 
>> +static int ptp_ocp_dpll_get_output_type(struct dpll_device *dpll, int sma)
>> +{
>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>> +	int ret;
>> +
>> +	if (bp->sma[sma].mode != SMA_MODE_OUT)
>> +		return -1;
>> +
>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>> +	case 0:
>> +		ret = DPLL_TYPE_EXT_10MHZ;
>> +		break;
>> +	case 1:
>> +	case 2:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>> +		break;
>> +	case 4:
>> +	case 8:
>> +		ret = DPLL_TYPE_GNSS;
>> +		break;
>> +	default:
>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
> 
> Missing break;
> 
> 
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static struct dpll_device_ops dpll_ops = {
>> +	.get_status		= ptp_ocp_dpll_get_status,
>> +	.get_lock_status	= ptp_ocp_dpll_get_lock_status,
>> +	.get_source_type	= ptp_ocp_dpll_get_source_type,
>> +	.get_output_type	= ptp_ocp_dpll_get_output_type,
>> +};
> 
> No 'set' statements here?  Also, what happens if there is more than
> one GNSS receiver, how is this differentiated?
>>   static int
>>   ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>>   {
>> @@ -3768,6 +3846,14 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>>   
>>   	ptp_ocp_info(bp);
>>   	devlink_register(devlink);
>> +
>> +	bp->dpll = dpll_device_alloc(&dpll_ops, ARRAY_SIZE(bp->sma), ARRAY_SIZE(bp->sma), bp);
>> +	if (!bp->dpll) {
>> +		dev_err(&pdev->dev, "dpll_device_alloc failed\n");
>> +		return 0;
>> +	}
>> +	dpll_device_register(bp->dpll);
>> +
> 
> How is the release/unregister path called when the module is unloaded?

I re-implemented unregister/free in v2. Hope it fixes questions.
Vadim Fedorenko June 26, 2022, 7:28 p.m. UTC | #5
On 24.06.2022 00:36, Jonathan Lemon wrote:
> On Fri, Jun 24, 2022 at 12:11:43AM +0100, Vadim Fedorenko wrote:
>> On 23.06.2022 19:28, Jonathan Lemon wrote:
>>> On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
>>>> From: Vadim Fedorenko <vadfed@fb.com>
>>>> +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
>>>> +{
>>>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>>>> +	int ret;
>>>> +
>>>> +	if (bp->sma[sma].mode != SMA_MODE_IN)
>>>> +		return -1;
>>>> +
>>>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>>>> +	case 0:
>>>> +		ret = DPLL_TYPE_EXT_10MHZ;
>>>> +		break;
>>>> +	case 1:
>>>> +	case 2:
>>>> +		ret = DPLL_TYPE_EXT_1PPS;
>>>> +		break;
>>>> +	default:
>>>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>
>>> These case statements switch on private bits.  This needs to match
>>> on the selector name instead.
>>>
>>
>> Not sure that string comparison is a good idea. Maybe it's better to extend
>> struct ocp_selector with netlink type id and fill it according to hardware?
> 
> Sure, that could be an option.  But, as this is DPLL only, how does it
> handle things when a pin is used for non-clock IO?  In the timecard,
> for example, we have the frequency counters for input, and the frequency
> generators/VCC/GND for output.
> 
> Actually our HW has a multi-level input, where the DPLL selects its
> source from an internal mux - this isn't reflected here.  The external
> pins feed into some complex HW logic, which performs its own priority
> calculations before presenting the end result as an available selection
> for the DPLL.

I don't really know how to deal with such configuration. For now I simply added
CUSTOM type but I'm not sure how to deal it 'set' functions. Do you have any 
suggestions?
Jonathan Lemon June 27, 2022, 7:23 p.m. UTC | #6
On Sun, Jun 26, 2022 at 08:27:17PM +0100, Vadim Fedorenko wrote:
> On 23.06.2022 19:28, Jonathan Lemon wrote:
> > On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
> > > From: Vadim Fedorenko <vadfed@fb.com>
> > > 
> > > +static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
> > > +{
> > > +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> > > +	int sync;
> > > +
> > > +	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
> > > +	return sync;
> > > +}
> > 
> > Please match existing code style.
> > 
> 
> Didn't get this point. The same code is used through out the driver.
> Could you please explain?

Match existing function definition style.
Jonathan Lemon June 27, 2022, 7:27 p.m. UTC | #7
On Sun, Jun 26, 2022 at 08:28:34PM +0100, Vadim Fedorenko wrote:
> On 24.06.2022 00:36, Jonathan Lemon wrote:
> > On Fri, Jun 24, 2022 at 12:11:43AM +0100, Vadim Fedorenko wrote:
> > > On 23.06.2022 19:28, Jonathan Lemon wrote:
> > > > On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
> > > > > From: Vadim Fedorenko <vadfed@fb.com>
> > > > > +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
> > > > > +{
> > > > > +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
> > > > > +	int ret;
> > > > > +
> > > > > +	if (bp->sma[sma].mode != SMA_MODE_IN)
> > > > > +		return -1;
> > > > > +
> > > > > +	switch (ptp_ocp_sma_get(bp, sma)) {
> > > > > +	case 0:
> > > > > +		ret = DPLL_TYPE_EXT_10MHZ;
> > > > > +		break;
> > > > > +	case 1:
> > > > > +	case 2:
> > > > > +		ret = DPLL_TYPE_EXT_1PPS;
> > > > > +		break;
> > > > > +	default:
> > > > > +		ret = DPLL_TYPE_INT_OSCILLATOR;
> > > > > +	}
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > 
> > > > These case statements switch on private bits.  This needs to match
> > > > on the selector name instead.
> > > > 
> > > 
> > > Not sure that string comparison is a good idea. Maybe it's better to extend
> > > struct ocp_selector with netlink type id and fill it according to hardware?
> > 
> > Sure, that could be an option.  But, as this is DPLL only, how does it
> > handle things when a pin is used for non-clock IO?  In the timecard,
> > for example, we have the frequency counters for input, and the frequency
> > generators/VCC/GND for output.
> > 
> > Actually our HW has a multi-level input, where the DPLL selects its
> > source from an internal mux - this isn't reflected here.  The external
> > pins feed into some complex HW logic, which performs its own priority
> > calculations before presenting the end result as an available selection
> > for the DPLL.
> 
> I don't really know how to deal with such configuration. For now I simply added
> CUSTOM type but I'm not sure how to deal it 'set' functions. Do you have any
> suggestions?

No suggestions other than the API should be able to handle things?

Also, should there be a notifier if the source changes?
Vadim Fedorenko June 27, 2022, 10:04 p.m. UTC | #8
On 27.06.2022 20:23, Jonathan Lemon wrote:
> On Sun, Jun 26, 2022 at 08:27:17PM +0100, Vadim Fedorenko wrote:
>> On 23.06.2022 19:28, Jonathan Lemon wrote:
>>> On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
>>>> From: Vadim Fedorenko <vadfed@fb.com>
>>>>
>>>> +static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
>>>> +{
>>>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>>>> +	int sync;
>>>> +
>>>> +	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
>>>> +	return sync;
>>>> +}
>>>
>>> Please match existing code style.
>>>
>>
>> Didn't get this point. The same code is used through out the driver.
>> Could you please explain?
> 
> Match existing function definition style.

Got it. Will address in the next version, thanks!
Vadim Fedorenko June 27, 2022, 10:06 p.m. UTC | #9
On 27.06.2022 20:27, Jonathan Lemon wrote:
> On Sun, Jun 26, 2022 at 08:28:34PM +0100, Vadim Fedorenko wrote:
>> On 24.06.2022 00:36, Jonathan Lemon wrote:
>>> On Fri, Jun 24, 2022 at 12:11:43AM +0100, Vadim Fedorenko wrote:
>>>> On 23.06.2022 19:28, Jonathan Lemon wrote:
>>>>> On Thu, Jun 23, 2022 at 03:57:17AM +0300, Vadim Fedorenko wrote:
>>>>>> From: Vadim Fedorenko <vadfed@fb.com>
>>>>>> +static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
>>>>>> +{
>>>>>> +	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
>>>>>> +	int ret;
>>>>>> +
>>>>>> +	if (bp->sma[sma].mode != SMA_MODE_IN)
>>>>>> +		return -1;
>>>>>> +
>>>>>> +	switch (ptp_ocp_sma_get(bp, sma)) {
>>>>>> +	case 0:
>>>>>> +		ret = DPLL_TYPE_EXT_10MHZ;
>>>>>> +		break;
>>>>>> +	case 1:
>>>>>> +	case 2:
>>>>>> +		ret = DPLL_TYPE_EXT_1PPS;
>>>>>> +		break;
>>>>>> +	default:
>>>>>> +		ret = DPLL_TYPE_INT_OSCILLATOR;
>>>>>> +	}
>>>>>> +
>>>>>> +	return ret;
>>>>>> +}
>>>>>
>>>>> These case statements switch on private bits.  This needs to match
>>>>> on the selector name instead.
>>>>>
>>>>
>>>> Not sure that string comparison is a good idea. Maybe it's better to extend
>>>> struct ocp_selector with netlink type id and fill it according to hardware?
>>>
>>> Sure, that could be an option.  But, as this is DPLL only, how does it
>>> handle things when a pin is used for non-clock IO?  In the timecard,
>>> for example, we have the frequency counters for input, and the frequency
>>> generators/VCC/GND for output.
>>>
>>> Actually our HW has a multi-level input, where the DPLL selects its
>>> source from an internal mux - this isn't reflected here.  The external
>>> pins feed into some complex HW logic, which performs its own priority
>>> calculations before presenting the end result as an available selection
>>> for the DPLL.
>>
>> I don't really know how to deal with such configuration. For now I simply added
>> CUSTOM type but I'm not sure how to deal it 'set' functions. Do you have any
>> suggestions?
> 
> No suggestions other than the API should be able to handle things?

Ok, I will try to combine this with Arkadiusz's suggestion with priorities.
> 
> Also, should there be a notifier if the source changes?

Definitely. I'm working on *set operations and will add notifiers at the same time.
diff mbox series

Patch

diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index 458218f88c5e..f74846ebc177 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -176,6 +176,7 @@  config PTP_1588_CLOCK_OCP
 	depends on !S390
 	depends on COMMON_CLK
 	select NET_DEVLINK
+	select DPLL
 	help
 	  This driver adds support for an OpenCompute time card.
 
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index e59ea2173aac..693168fdda6c 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -21,6 +21,7 @@ 
 #include <linux/mtd/mtd.h>
 #include <linux/nvmem-consumer.h>
 #include <linux/crc16.h>
+#include <uapi/linux/dpll.h>
 
 #define PCI_VENDOR_ID_FACEBOOK			0x1d9b
 #define PCI_DEVICE_ID_FACEBOOK_TIMECARD		0x0400
@@ -336,6 +337,7 @@  struct ptp_ocp {
 	struct ptp_ocp_signal	signal[4];
 	struct ptp_ocp_sma_connector sma[4];
 	const struct ocp_sma_op *sma_op;
+	struct dpll_device *dpll;
 };
 
 #define OCP_REQ_TIMESTAMP	BIT(0)
@@ -3713,6 +3715,82 @@  ptp_ocp_detach(struct ptp_ocp *bp)
 	device_unregister(&bp->dev);
 }
 
+static int ptp_ocp_dpll_get_status(struct dpll_device *dpll)
+{
+	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
+	int sync;
+
+	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
+	return sync;
+}
+
+static int ptp_ocp_dpll_get_lock_status(struct dpll_device *dpll)
+{
+	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
+	int sync;
+
+	sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
+	return sync;
+}
+
+static int ptp_ocp_dpll_get_source_type(struct dpll_device *dpll, int sma)
+{
+	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
+	int ret;
+
+	if (bp->sma[sma].mode != SMA_MODE_IN)
+		return -1;
+
+	switch (ptp_ocp_sma_get(bp, sma)) {
+	case 0:
+		ret = DPLL_TYPE_EXT_10MHZ;
+		break;
+	case 1:
+	case 2:
+		ret = DPLL_TYPE_EXT_1PPS;
+		break;
+	default:
+		ret = DPLL_TYPE_INT_OSCILLATOR;
+	}
+
+	return ret;
+}
+
+
+static int ptp_ocp_dpll_get_output_type(struct dpll_device *dpll, int sma)
+{
+	struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll);
+	int ret;
+
+	if (bp->sma[sma].mode != SMA_MODE_OUT)
+		return -1;
+
+	switch (ptp_ocp_sma_get(bp, sma)) {
+	case 0:
+		ret = DPLL_TYPE_EXT_10MHZ;
+		break;
+	case 1:
+	case 2:
+		ret = DPLL_TYPE_INT_OSCILLATOR;
+		break;
+	case 4:
+	case 8:
+		ret = DPLL_TYPE_GNSS;
+		break;
+	default:
+		ret = DPLL_TYPE_INT_OSCILLATOR;
+	}
+
+	return ret;
+}
+
+static struct dpll_device_ops dpll_ops = {
+	.get_status		= ptp_ocp_dpll_get_status,
+	.get_lock_status	= ptp_ocp_dpll_get_lock_status,
+	.get_source_type	= ptp_ocp_dpll_get_source_type,
+	.get_output_type	= ptp_ocp_dpll_get_output_type,
+};
+
 static int
 ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 {
@@ -3768,6 +3846,14 @@  ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
 	ptp_ocp_info(bp);
 	devlink_register(devlink);
+
+	bp->dpll = dpll_device_alloc(&dpll_ops, ARRAY_SIZE(bp->sma), ARRAY_SIZE(bp->sma), bp);
+	if (!bp->dpll) {
+		dev_err(&pdev->dev, "dpll_device_alloc failed\n");
+		return 0;
+	}
+	dpll_device_register(bp->dpll);
+
 	return 0;
 
 out: