2020-12-03 19:51:28 -08:00
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 Facebook */
# include <linux/err.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/ptp_clock_kernel.h>
static const struct pci_device_id ptp_ocp_pcidev_id [ ] = {
{ PCI_DEVICE ( 0x1d9b , 0x0400 ) } ,
{ 0 }
} ;
MODULE_DEVICE_TABLE ( pci , ptp_ocp_pcidev_id ) ;
# define OCP_REGISTER_OFFSET 0x01000000
struct ocp_reg {
u32 ctrl ;
u32 status ;
u32 select ;
u32 version ;
u32 time_ns ;
u32 time_sec ;
u32 __pad0 [ 2 ] ;
u32 adjust_ns ;
u32 adjust_sec ;
u32 __pad1 [ 2 ] ;
u32 offset_ns ;
u32 offset_window_ns ;
} ;
# define OCP_CTRL_ENABLE BIT(0)
# define OCP_CTRL_ADJUST_TIME BIT(1)
# define OCP_CTRL_ADJUST_OFFSET BIT(2)
# define OCP_CTRL_READ_TIME_REQ BIT(30)
# define OCP_CTRL_READ_TIME_DONE BIT(31)
# define OCP_STATUS_IN_SYNC BIT(0)
# define OCP_SELECT_CLK_NONE 0
# define OCP_SELECT_CLK_REG 6
struct tod_reg {
u32 ctrl ;
u32 status ;
u32 uart_polarity ;
u32 version ;
u32 correction_sec ;
u32 __pad0 [ 3 ] ;
u32 uart_baud ;
u32 __pad1 [ 3 ] ;
u32 utc_status ;
u32 leap ;
} ;
# define TOD_REGISTER_OFFSET 0x01050000
# define TOD_CTRL_PROTOCOL BIT(28)
# define TOD_CTRL_DISABLE_FMT_A BIT(17)
# define TOD_CTRL_DISABLE_FMT_B BIT(16)
# define TOD_CTRL_ENABLE BIT(0)
# define TOD_CTRL_GNSS_MASK ((1U << 4) - 1)
# define TOD_CTRL_GNSS_SHIFT 24
# define TOD_STATUS_UTC_MASK 0xff
# define TOD_STATUS_UTC_VALID BIT(8)
# define TOD_STATUS_LEAP_VALID BIT(16)
struct ptp_ocp {
struct pci_dev * pdev ;
spinlock_t lock ;
void __iomem * base ;
struct ocp_reg __iomem * reg ;
struct tod_reg __iomem * tod ;
struct ptp_clock * ptp ;
struct ptp_clock_info ptp_info ;
} ;
static int
__ptp_ocp_gettime_locked ( struct ptp_ocp * bp , struct timespec64 * ts ,
struct ptp_system_timestamp * sts )
{
u32 ctrl , time_sec , time_ns ;
int i ;
ctrl = ioread32 ( & bp - > reg - > ctrl ) ;
ctrl | = OCP_CTRL_READ_TIME_REQ ;
ptp_read_system_prets ( sts ) ;
iowrite32 ( ctrl , & bp - > reg - > ctrl ) ;
for ( i = 0 ; i < 100 ; i + + ) {
ctrl = ioread32 ( & bp - > reg - > ctrl ) ;
if ( ctrl & OCP_CTRL_READ_TIME_DONE )
break ;
}
ptp_read_system_postts ( sts ) ;
time_ns = ioread32 ( & bp - > reg - > time_ns ) ;
time_sec = ioread32 ( & bp - > reg - > time_sec ) ;
ts - > tv_sec = time_sec ;
ts - > tv_nsec = time_ns ;
return ctrl & OCP_CTRL_READ_TIME_DONE ? 0 : - ETIMEDOUT ;
}
static int
ptp_ocp_gettimex ( struct ptp_clock_info * ptp_info , struct timespec64 * ts ,
struct ptp_system_timestamp * sts )
{
struct ptp_ocp * bp = container_of ( ptp_info , struct ptp_ocp , ptp_info ) ;
unsigned long flags ;
int err ;
spin_lock_irqsave ( & bp - > lock , flags ) ;
err = __ptp_ocp_gettime_locked ( bp , ts , sts ) ;
spin_unlock_irqrestore ( & bp - > lock , flags ) ;
return err ;
}
static void
__ptp_ocp_settime_locked ( struct ptp_ocp * bp , const struct timespec64 * ts )
{
u32 ctrl , time_sec , time_ns ;
u32 select ;
time_ns = ts - > tv_nsec ;
time_sec = ts - > tv_sec ;
select = ioread32 ( & bp - > reg - > select ) ;
iowrite32 ( OCP_SELECT_CLK_REG , & bp - > reg - > select ) ;
iowrite32 ( time_ns , & bp - > reg - > adjust_ns ) ;
iowrite32 ( time_sec , & bp - > reg - > adjust_sec ) ;
ctrl = ioread32 ( & bp - > reg - > ctrl ) ;
ctrl | = OCP_CTRL_ADJUST_TIME ;
iowrite32 ( ctrl , & bp - > reg - > ctrl ) ;
/* restore clock selection */
iowrite32 ( select > > 16 , & bp - > reg - > select ) ;
}
static int
ptp_ocp_settime ( struct ptp_clock_info * ptp_info , const struct timespec64 * ts )
{
struct ptp_ocp * bp = container_of ( ptp_info , struct ptp_ocp , ptp_info ) ;
unsigned long flags ;
if ( ioread32 ( & bp - > reg - > status ) & OCP_STATUS_IN_SYNC )
return 0 ;
spin_lock_irqsave ( & bp - > lock , flags ) ;
__ptp_ocp_settime_locked ( bp , ts ) ;
spin_unlock_irqrestore ( & bp - > lock , flags ) ;
return 0 ;
}
static int
ptp_ocp_adjtime ( struct ptp_clock_info * ptp_info , s64 delta_ns )
{
struct ptp_ocp * bp = container_of ( ptp_info , struct ptp_ocp , ptp_info ) ;
struct timespec64 ts ;
unsigned long flags ;
int err ;
if ( ioread32 ( & bp - > reg - > status ) & OCP_STATUS_IN_SYNC )
return 0 ;
spin_lock_irqsave ( & bp - > lock , flags ) ;
err = __ptp_ocp_gettime_locked ( bp , & ts , NULL ) ;
if ( likely ( ! err ) ) {
timespec64_add_ns ( & ts , delta_ns ) ;
__ptp_ocp_settime_locked ( bp , & ts ) ;
}
spin_unlock_irqrestore ( & bp - > lock , flags ) ;
return err ;
}
static int
ptp_ocp_null_adjfine ( struct ptp_clock_info * ptp_info , long scaled_ppm )
{
if ( scaled_ppm = = 0 )
return 0 ;
return - EOPNOTSUPP ;
}
static const struct ptp_clock_info ptp_ocp_clock_info = {
. owner = THIS_MODULE ,
. name = KBUILD_MODNAME ,
. max_adj = 100000000 ,
. gettimex64 = ptp_ocp_gettimex ,
. settime64 = ptp_ocp_settime ,
. adjtime = ptp_ocp_adjtime ,
. adjfine = ptp_ocp_null_adjfine ,
} ;
static int
ptp_ocp_check_clock ( struct ptp_ocp * bp )
{
struct timespec64 ts ;
bool sync ;
u32 ctrl ;
/* make sure clock is enabled */
ctrl = ioread32 ( & bp - > reg - > ctrl ) ;
ctrl | = OCP_CTRL_ENABLE ;
iowrite32 ( ctrl , & bp - > reg - > ctrl ) ;
if ( ( ioread32 ( & bp - > reg - > ctrl ) & OCP_CTRL_ENABLE ) = = 0 ) {
dev_err ( & bp - > pdev - > dev , " clock not enabled \n " ) ;
return - ENODEV ;
}
sync = ioread32 ( & bp - > reg - > status ) & OCP_STATUS_IN_SYNC ;
if ( ! sync ) {
ktime_get_real_ts64 ( & ts ) ;
ptp_ocp_settime ( & bp - > ptp_info , & ts ) ;
}
if ( ! ptp_ocp_gettimex ( & bp - > ptp_info , & ts , NULL ) )
dev_info ( & bp - > pdev - > dev , " Time: %lld.%ld, %s \n " ,
ts . tv_sec , ts . tv_nsec ,
sync ? " in-sync " : " UNSYNCED " ) ;
return 0 ;
}
static void
ptp_ocp_tod_info ( struct ptp_ocp * bp )
{
static const char * const proto_name [ ] = {
" NMEA " , " NMEA_ZDA " , " NMEA_RMC " , " NMEA_none " ,
" UBX " , " UBX_UTC " , " UBX_LS " , " UBX_none "
} ;
static const char * const gnss_name [ ] = {
" ALL " , " COMBINED " , " GPS " , " GLONASS " , " GALILEO " , " BEIDOU " ,
} ;
u32 version , ctrl , reg ;
int idx ;
version = ioread32 ( & bp - > tod - > version ) ;
dev_info ( & bp - > pdev - > dev , " TOD Version %d.%d.%d \n " ,
version > > 24 , ( version > > 16 ) & 0xff , version & 0xffff ) ;
ctrl = ioread32 ( & bp - > tod - > ctrl ) ;
ctrl | = TOD_CTRL_PROTOCOL | TOD_CTRL_ENABLE ;
ctrl & = ~ ( TOD_CTRL_DISABLE_FMT_A | TOD_CTRL_DISABLE_FMT_B ) ;
iowrite32 ( ctrl , & bp - > tod - > ctrl ) ;
ctrl = ioread32 ( & bp - > tod - > ctrl ) ;
idx = ctrl & TOD_CTRL_PROTOCOL ? 4 : 0 ;
idx + = ( ctrl > > 16 ) & 3 ;
dev_info ( & bp - > pdev - > dev , " control: %x \n " , ctrl ) ;
dev_info ( & bp - > pdev - > dev , " TOD Protocol %s %s \n " , proto_name [ idx ] ,
ctrl & TOD_CTRL_ENABLE ? " enabled " : " " ) ;
idx = ( ctrl > > TOD_CTRL_GNSS_SHIFT ) & TOD_CTRL_GNSS_MASK ;
if ( idx < ARRAY_SIZE ( gnss_name ) )
dev_info ( & bp - > pdev - > dev , " GNSS %s \n " , gnss_name [ idx ] ) ;
reg = ioread32 ( & bp - > tod - > status ) ;
dev_info ( & bp - > pdev - > dev , " status: %x \n " , reg ) ;
reg = ioread32 ( & bp - > tod - > correction_sec ) ;
dev_info ( & bp - > pdev - > dev , " correction: %d \n " , reg ) ;
reg = ioread32 ( & bp - > tod - > utc_status ) ;
dev_info ( & bp - > pdev - > dev , " utc_status: %x \n " , reg ) ;
dev_info ( & bp - > pdev - > dev , " utc_offset: %d valid:%d leap_valid:%d \n " ,
reg & TOD_STATUS_UTC_MASK , reg & TOD_STATUS_UTC_VALID ? 1 : 0 ,
reg & TOD_STATUS_LEAP_VALID ? 1 : 0 ) ;
}
static void
ptp_ocp_info ( struct ptp_ocp * bp )
{
static const char * const clock_name [ ] = {
" NO " , " TOD " , " IRIG " , " PPS " , " PTP " , " RTC " , " REGS " , " EXT "
} ;
u32 version , select ;
version = ioread32 ( & bp - > reg - > version ) ;
select = ioread32 ( & bp - > reg - > select ) ;
dev_info ( & bp - > pdev - > dev , " Version %d.%d.%d, clock %s, device ptp%d \n " ,
version > > 24 , ( version > > 16 ) & 0xff , version & 0xffff ,
clock_name [ select & 7 ] ,
ptp_clock_index ( bp - > ptp ) ) ;
ptp_ocp_tod_info ( bp ) ;
}
static int
ptp_ocp_probe ( struct pci_dev * pdev , const struct pci_device_id * id )
{
struct ptp_ocp * bp ;
int err ;
bp = kzalloc ( sizeof ( * bp ) , GFP_KERNEL ) ;
if ( ! bp )
return - ENOMEM ;
bp - > pdev = pdev ;
pci_set_drvdata ( pdev , bp ) ;
err = pci_enable_device ( pdev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " pci_enable_device \n " ) ;
goto out_free ;
}
err = pci_request_regions ( pdev , KBUILD_MODNAME ) ;
if ( err ) {
dev_err ( & pdev - > dev , " pci_request_region \n " ) ;
goto out_disable ;
}
bp - > base = pci_ioremap_bar ( pdev , 0 ) ;
if ( ! bp - > base ) {
dev_err ( & pdev - > dev , " io_remap bar0 \n " ) ;
err = - ENOMEM ;
2021-05-12 13:15:29 +02:00
goto out_release_regions ;
2020-12-03 19:51:28 -08:00
}
bp - > reg = bp - > base + OCP_REGISTER_OFFSET ;
bp - > tod = bp - > base + TOD_REGISTER_OFFSET ;
bp - > ptp_info = ptp_ocp_clock_info ;
spin_lock_init ( & bp - > lock ) ;
err = ptp_ocp_check_clock ( bp ) ;
if ( err )
goto out ;
bp - > ptp = ptp_clock_register ( & bp - > ptp_info , & pdev - > dev ) ;
if ( IS_ERR ( bp - > ptp ) ) {
dev_err ( & pdev - > dev , " ptp_clock_register \n " ) ;
err = PTR_ERR ( bp - > ptp ) ;
goto out ;
}
ptp_ocp_info ( bp ) ;
return 0 ;
out :
2021-05-12 13:15:29 +02:00
pci_iounmap ( pdev , bp - > base ) ;
out_release_regions :
2020-12-03 19:51:28 -08:00
pci_release_regions ( pdev ) ;
out_disable :
pci_disable_device ( pdev ) ;
out_free :
kfree ( bp ) ;
return err ;
}
static void
ptp_ocp_remove ( struct pci_dev * pdev )
{
struct ptp_ocp * bp = pci_get_drvdata ( pdev ) ;
ptp_clock_unregister ( bp - > ptp ) ;
pci_iounmap ( pdev , bp - > base ) ;
pci_release_regions ( pdev ) ;
pci_disable_device ( pdev ) ;
pci_set_drvdata ( pdev , NULL ) ;
kfree ( bp ) ;
}
static struct pci_driver ptp_ocp_driver = {
. name = KBUILD_MODNAME ,
. id_table = ptp_ocp_pcidev_id ,
. probe = ptp_ocp_probe ,
. remove = ptp_ocp_remove ,
} ;
static int __init
ptp_ocp_init ( void )
{
int err ;
err = pci_register_driver ( & ptp_ocp_driver ) ;
return err ;
}
static void __exit
ptp_ocp_fini ( void )
{
pci_unregister_driver ( & ptp_ocp_driver ) ;
}
module_init ( ptp_ocp_init ) ;
module_exit ( ptp_ocp_fini ) ;
MODULE_DESCRIPTION ( " OpenCompute TimeCard driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;