linux-stable/drivers/ptp/ptp_ocp.c

4582 lines
104 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 Facebook */
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/serial_8250.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/platform_device.h>
#include <linux/platform_data/i2c-xiic.h>
#include <linux/platform_data/i2c-ocores.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/spi/spi.h>
#include <linux/spi/xilinx_spi.h>
#include <linux/spi/altera.h>
#include <net/devlink.h>
#include <linux/i2c.h>
#include <linux/mtd/mtd.h>
#include <linux/nvmem-consumer.h>
#include <linux/crc16.h>
#include <linux/dpll.h>
#define PCI_VENDOR_ID_FACEBOOK 0x1d9b
#define PCI_DEVICE_ID_FACEBOOK_TIMECARD 0x0400
#define PCI_VENDOR_ID_CELESTICA 0x18d4
#define PCI_DEVICE_ID_CELESTICA_TIMECARD 0x1008
#define PCI_VENDOR_ID_OROLIA 0x1ad7
#define PCI_DEVICE_ID_OROLIA_ARTCARD 0xa000
static struct class timecard_class = {
.name = "timecard",
};
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;
u32 __pad2[2];
u32 drift_ns;
u32 drift_window_ns;
u32 __pad3[6];
u32 servo_offset_p;
u32 servo_offset_i;
u32 servo_drift_p;
u32 servo_drift_i;
u32 status_offset;
u32 status_drift;
};
#define OCP_CTRL_ENABLE BIT(0)
#define OCP_CTRL_ADJUST_TIME BIT(1)
#define OCP_CTRL_ADJUST_OFFSET BIT(2)
#define OCP_CTRL_ADJUST_DRIFT BIT(3)
#define OCP_CTRL_ADJUST_SERVO BIT(8)
#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_STATUS_IN_HOLDOVER BIT(1)
#define OCP_SELECT_CLK_NONE 0
#define OCP_SELECT_CLK_REG 0xfe
struct tod_reg {
u32 ctrl;
u32 status;
u32 uart_polarity;
u32 version;
u32 adj_sec;
u32 __pad0[3];
u32 uart_baud;
u32 __pad1[3];
u32 utc_status;
u32 leap;
};
#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 GENMASK(3, 0)
#define TOD_CTRL_GNSS_SHIFT 24
#define TOD_STATUS_UTC_MASK GENMASK(7, 0)
#define TOD_STATUS_UTC_VALID BIT(8)
#define TOD_STATUS_LEAP_ANNOUNCE BIT(12)
#define TOD_STATUS_LEAP_VALID BIT(16)
struct ts_reg {
u32 enable;
u32 error;
u32 polarity;
u32 version;
u32 __pad0[4];
u32 cable_delay;
u32 __pad1[3];
u32 intr;
u32 intr_mask;
u32 event_count;
u32 __pad2[1];
u32 ts_count;
u32 time_ns;
u32 time_sec;
u32 data_width;
u32 data;
};
struct pps_reg {
u32 ctrl;
u32 status;
u32 __pad0[6];
u32 cable_delay;
};
#define PPS_STATUS_FILTER_ERR BIT(0)
#define PPS_STATUS_SUPERV_ERR BIT(1)
struct img_reg {
u32 version;
};
struct gpio_reg {
u32 gpio1;
u32 __pad0;
u32 gpio2;
u32 __pad1;
};
struct irig_master_reg {
u32 ctrl;
u32 status;
u32 __pad0;
u32 version;
u32 adj_sec;
u32 mode_ctrl;
};
#define IRIG_M_CTRL_ENABLE BIT(0)
struct irig_slave_reg {
u32 ctrl;
u32 status;
u32 __pad0;
u32 version;
u32 adj_sec;
u32 mode_ctrl;
};
#define IRIG_S_CTRL_ENABLE BIT(0)
struct dcf_master_reg {
u32 ctrl;
u32 status;
u32 __pad0;
u32 version;
u32 adj_sec;
};
#define DCF_M_CTRL_ENABLE BIT(0)
struct dcf_slave_reg {
u32 ctrl;
u32 status;
u32 __pad0;
u32 version;
u32 adj_sec;
};
#define DCF_S_CTRL_ENABLE BIT(0)
struct signal_reg {
u32 enable;
u32 status;
u32 polarity;
u32 version;
u32 __pad0[4];
u32 cable_delay;
u32 __pad1[3];
u32 intr;
u32 intr_mask;
u32 __pad2[2];
u32 start_ns;
u32 start_sec;
u32 pulse_ns;
u32 pulse_sec;
u32 period_ns;
u32 period_sec;
u32 repeat_count;
};
struct frequency_reg {
u32 ctrl;
u32 status;
};
struct board_config_reg {
u32 mro50_serial_activate;
};
#define FREQ_STATUS_VALID BIT(31)
#define FREQ_STATUS_ERROR BIT(30)
#define FREQ_STATUS_OVERRUN BIT(29)
#define FREQ_STATUS_MASK GENMASK(23, 0)
struct ptp_ocp_flash_info {
const char *name;
int pci_offset;
int data_size;
void *data;
};
struct ptp_ocp_firmware_header {
char magic[4];
__be16 pci_vendor_id;
__be16 pci_device_id;
__be32 image_size;
__be16 hw_revision;
__be16 crc;
};
#define OCP_FIRMWARE_MAGIC_HEADER "OCPC"
struct ptp_ocp_i2c_info {
const char *name;
unsigned long fixed_rate;
size_t data_size;
void *data;
};
struct ptp_ocp_ext_info {
int index;
irqreturn_t (*irq_fcn)(int irq, void *priv);
int (*enable)(void *priv, u32 req, bool enable);
};
struct ptp_ocp_ext_src {
void __iomem *mem;
struct ptp_ocp *bp;
struct ptp_ocp_ext_info *info;
int irq_vec;
};
enum ptp_ocp_sma_mode {
SMA_MODE_IN,
SMA_MODE_OUT,
};
static struct dpll_pin_frequency ptp_ocp_sma_freq[] = {
DPLL_PIN_FREQUENCY_1PPS,
DPLL_PIN_FREQUENCY_10MHZ,
DPLL_PIN_FREQUENCY_IRIG_B,
DPLL_PIN_FREQUENCY_DCF77,
};
struct ptp_ocp_sma_connector {
enum ptp_ocp_sma_mode mode;
bool fixed_fcn;
bool fixed_dir;
bool disabled;
u8 default_fcn;
struct dpll_pin *dpll_pin;
struct dpll_pin_properties dpll_prop;
};
struct ocp_attr_group {
u64 cap;
const struct attribute_group *group;
};
#define OCP_CAP_BASIC BIT(0)
#define OCP_CAP_SIGNAL BIT(1)
#define OCP_CAP_FREQ BIT(2)
struct ptp_ocp_signal {
ktime_t period;
ktime_t pulse;
ktime_t phase;
ktime_t start;
int duty;
bool polarity;
bool running;
};
struct ptp_ocp_serial_port {
int line;
int baud;
};
#define OCP_BOARD_ID_LEN 13
#define OCP_SERIAL_LEN 6
#define OCP_SMA_NUM 4
struct ptp_ocp {
struct pci_dev *pdev;
struct device dev;
spinlock_t lock;
struct ocp_reg __iomem *reg;
struct tod_reg __iomem *tod;
struct pps_reg __iomem *pps_to_ext;
struct pps_reg __iomem *pps_to_clk;
struct board_config_reg __iomem *board_config;
struct gpio_reg __iomem *pps_select;
struct gpio_reg __iomem *sma_map1;
struct gpio_reg __iomem *sma_map2;
struct irig_master_reg __iomem *irig_out;
struct irig_slave_reg __iomem *irig_in;
struct dcf_master_reg __iomem *dcf_out;
struct dcf_slave_reg __iomem *dcf_in;
struct tod_reg __iomem *nmea_out;
struct frequency_reg __iomem *freq_in[4];
struct ptp_ocp_ext_src *signal_out[4];
struct ptp_ocp_ext_src *pps;
struct ptp_ocp_ext_src *ts0;
struct ptp_ocp_ext_src *ts1;
struct ptp_ocp_ext_src *ts2;
struct ptp_ocp_ext_src *ts3;
struct ptp_ocp_ext_src *ts4;
struct ocp_art_gpio_reg __iomem *art_sma;
struct img_reg __iomem *image;
struct ptp_clock *ptp;
struct ptp_clock_info ptp_info;
struct platform_device *i2c_ctrl;
struct platform_device *spi_flash;
struct clk_hw *i2c_clk;
struct timer_list watchdog;
const struct attribute_group **attr_group;
const struct ptp_ocp_eeprom_map *eeprom_map;
struct dentry *debug_root;
bool sync;
time64_t gnss_lost;
struct delayed_work sync_work;
int id;
int n_irqs;
struct ptp_ocp_serial_port gnss_port;
struct ptp_ocp_serial_port gnss2_port;
struct ptp_ocp_serial_port mac_port; /* miniature atomic clock */
struct ptp_ocp_serial_port nmea_port;
bool fw_loader;
u8 fw_tag;
u16 fw_version;
u8 board_id[OCP_BOARD_ID_LEN];
u8 serial[OCP_SERIAL_LEN];
bool has_eeprom_data;
u32 pps_req_map;
int flash_start;
u32 utc_tai_offset;
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
u32 ts_window_adjust;
u64 fw_cap;
struct ptp_ocp_signal signal[4];
struct ptp_ocp_sma_connector sma[OCP_SMA_NUM];
const struct ocp_sma_op *sma_op;
struct dpll_device *dpll;
};
#define OCP_REQ_TIMESTAMP BIT(0)
#define OCP_REQ_PPS BIT(1)
struct ocp_resource {
unsigned long offset;
int size;
int irq_vec;
int (*setup)(struct ptp_ocp *bp, struct ocp_resource *r);
void *extra;
unsigned long bp_offset;
const char * const name;
};
static int ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r);
static int ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r);
static int ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r);
static int ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r);
static int ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r);
static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv);
static irqreturn_t ptp_ocp_signal_irq(int irq, void *priv);
static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable);
static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
struct ptp_perout_request *req);
static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);
static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);
static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
static const struct ocp_attr_group fb_timecard_groups[];
static const struct ocp_attr_group art_timecard_groups[];
struct ptp_ocp_eeprom_map {
u16 off;
u16 len;
u32 bp_offset;
const void * const tag;
};
#define EEPROM_ENTRY(addr, member) \
.off = addr, \
.len = sizeof_field(struct ptp_ocp, member), \
.bp_offset = offsetof(struct ptp_ocp, member)
#define BP_MAP_ENTRY_ADDR(bp, map) ({ \
(void *)((uintptr_t)(bp) + (map)->bp_offset); \
})
static struct ptp_ocp_eeprom_map fb_eeprom_map[] = {
{ EEPROM_ENTRY(0x43, board_id) },
{ EEPROM_ENTRY(0x00, serial), .tag = "mac" },
{ }
};
static struct ptp_ocp_eeprom_map art_eeprom_map[] = {
{ EEPROM_ENTRY(0x200 + 0x43, board_id) },
{ EEPROM_ENTRY(0x200 + 0x63, serial) },
{ }
};
#define bp_assign_entry(bp, res, val) ({ \
uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \
*(typeof(val) *)addr = val; \
})
#define OCP_RES_LOCATION(member) \
.name = #member, .bp_offset = offsetof(struct ptp_ocp, member)
#define OCP_MEM_RESOURCE(member) \
OCP_RES_LOCATION(member), .setup = ptp_ocp_register_mem
#define OCP_SERIAL_RESOURCE(member) \
OCP_RES_LOCATION(member), .setup = ptp_ocp_register_serial
#define OCP_I2C_RESOURCE(member) \
OCP_RES_LOCATION(member), .setup = ptp_ocp_register_i2c
#define OCP_SPI_RESOURCE(member) \
OCP_RES_LOCATION(member), .setup = ptp_ocp_register_spi
#define OCP_EXT_RESOURCE(member) \
OCP_RES_LOCATION(member), .setup = ptp_ocp_register_ext
/* This is the MSI vector mapping used.
* 0: PPS (TS5)
* 1: TS0
* 2: TS1
* 3: GNSS1
* 4: GNSS2
* 5: MAC
* 6: TS2
* 7: I2C controller
* 8: HWICAP (notused)
* 9: SPI Flash
* 10: NMEA
* 11: Signal Generator 1
* 12: Signal Generator 2
* 13: Signal Generator 3
* 14: Signal Generator 4
* 15: TS3
* 16: TS4
--
* 8: Orolia TS1
* 10: Orolia TS2
* 11: Orolia TS0 (GNSS)
* 12: Orolia PPS
* 14: Orolia TS3
* 15: Orolia TS4
*/
static struct ocp_resource ocp_fb_resource[] = {
{
OCP_MEM_RESOURCE(reg),
.offset = 0x01000000, .size = 0x10000,
},
{
OCP_EXT_RESOURCE(ts0),
.offset = 0x01010000, .size = 0x10000, .irq_vec = 1,
.extra = &(struct ptp_ocp_ext_info) {
.index = 0,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts1),
.offset = 0x01020000, .size = 0x10000, .irq_vec = 2,
.extra = &(struct ptp_ocp_ext_info) {
.index = 1,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts2),
.offset = 0x01060000, .size = 0x10000, .irq_vec = 6,
.extra = &(struct ptp_ocp_ext_info) {
.index = 2,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts3),
.offset = 0x01110000, .size = 0x10000, .irq_vec = 15,
.extra = &(struct ptp_ocp_ext_info) {
.index = 3,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts4),
.offset = 0x01120000, .size = 0x10000, .irq_vec = 16,
.extra = &(struct ptp_ocp_ext_info) {
.index = 4,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
/* Timestamp for PHC and/or PPS generator */
{
OCP_EXT_RESOURCE(pps),
.offset = 0x010C0000, .size = 0x10000, .irq_vec = 0,
.extra = &(struct ptp_ocp_ext_info) {
.index = 5,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(signal_out[0]),
.offset = 0x010D0000, .size = 0x10000, .irq_vec = 11,
.extra = &(struct ptp_ocp_ext_info) {
.index = 1,
.irq_fcn = ptp_ocp_signal_irq,
.enable = ptp_ocp_signal_enable,
},
},
{
OCP_EXT_RESOURCE(signal_out[1]),
.offset = 0x010E0000, .size = 0x10000, .irq_vec = 12,
.extra = &(struct ptp_ocp_ext_info) {
.index = 2,
.irq_fcn = ptp_ocp_signal_irq,
.enable = ptp_ocp_signal_enable,
},
},
{
OCP_EXT_RESOURCE(signal_out[2]),
.offset = 0x010F0000, .size = 0x10000, .irq_vec = 13,
.extra = &(struct ptp_ocp_ext_info) {
.index = 3,
.irq_fcn = ptp_ocp_signal_irq,
.enable = ptp_ocp_signal_enable,
},
},
{
OCP_EXT_RESOURCE(signal_out[3]),
.offset = 0x01100000, .size = 0x10000, .irq_vec = 14,
.extra = &(struct ptp_ocp_ext_info) {
.index = 4,
.irq_fcn = ptp_ocp_signal_irq,
.enable = ptp_ocp_signal_enable,
},
},
{
OCP_MEM_RESOURCE(pps_to_ext),
.offset = 0x01030000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(pps_to_clk),
.offset = 0x01040000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(tod),
.offset = 0x01050000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(irig_in),
.offset = 0x01070000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(irig_out),
.offset = 0x01080000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(dcf_in),
.offset = 0x01090000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(dcf_out),
.offset = 0x010A0000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(nmea_out),
.offset = 0x010B0000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(image),
.offset = 0x00020000, .size = 0x1000,
},
{
OCP_MEM_RESOURCE(pps_select),
.offset = 0x00130000, .size = 0x1000,
},
{
OCP_MEM_RESOURCE(sma_map1),
.offset = 0x00140000, .size = 0x1000,
},
{
OCP_MEM_RESOURCE(sma_map2),
.offset = 0x00220000, .size = 0x1000,
},
{
OCP_I2C_RESOURCE(i2c_ctrl),
.offset = 0x00150000, .size = 0x10000, .irq_vec = 7,
.extra = &(struct ptp_ocp_i2c_info) {
.name = "xiic-i2c",
.fixed_rate = 50000000,
.data_size = sizeof(struct xiic_i2c_platform_data),
.data = &(struct xiic_i2c_platform_data) {
.num_devices = 2,
.devices = (struct i2c_board_info[]) {
{ I2C_BOARD_INFO("24c02", 0x50) },
{ I2C_BOARD_INFO("24mac402", 0x58),
.platform_data = "mac" },
},
},
},
},
{
OCP_SERIAL_RESOURCE(gnss_port),
.offset = 0x00160000 + 0x1000, .irq_vec = 3,
.extra = &(struct ptp_ocp_serial_port) {
.baud = 115200,
},
},
{
OCP_SERIAL_RESOURCE(gnss2_port),
.offset = 0x00170000 + 0x1000, .irq_vec = 4,
.extra = &(struct ptp_ocp_serial_port) {
.baud = 115200,
},
},
{
OCP_SERIAL_RESOURCE(mac_port),
.offset = 0x00180000 + 0x1000, .irq_vec = 5,
.extra = &(struct ptp_ocp_serial_port) {
.baud = 57600,
},
},
{
OCP_SERIAL_RESOURCE(nmea_port),
.offset = 0x00190000 + 0x1000, .irq_vec = 10,
},
{
OCP_SPI_RESOURCE(spi_flash),
.offset = 0x00310000, .size = 0x10000, .irq_vec = 9,
.extra = &(struct ptp_ocp_flash_info) {
.name = "xilinx_spi", .pci_offset = 0,
.data_size = sizeof(struct xspi_platform_data),
.data = &(struct xspi_platform_data) {
.num_chipselect = 1,
.bits_per_word = 8,
.num_devices = 1,
.force_irq = true,
.devices = &(struct spi_board_info) {
.modalias = "spi-nor",
},
},
},
},
{
OCP_MEM_RESOURCE(freq_in[0]),
.offset = 0x01200000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(freq_in[1]),
.offset = 0x01210000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(freq_in[2]),
.offset = 0x01220000, .size = 0x10000,
},
{
OCP_MEM_RESOURCE(freq_in[3]),
.offset = 0x01230000, .size = 0x10000,
},
{
.setup = ptp_ocp_fb_board_init,
},
{ }
};
#define OCP_ART_CONFIG_SIZE 144
#define OCP_ART_TEMP_TABLE_SIZE 368
struct ocp_art_gpio_reg {
struct {
u32 gpio;
u32 __pad[3];
} map[4];
};
static struct ocp_resource ocp_art_resource[] = {
{
OCP_MEM_RESOURCE(reg),
.offset = 0x01000000, .size = 0x10000,
},
{
OCP_SERIAL_RESOURCE(gnss_port),
.offset = 0x00160000 + 0x1000, .irq_vec = 3,
.extra = &(struct ptp_ocp_serial_port) {
.baud = 115200,
},
},
{
OCP_MEM_RESOURCE(art_sma),
.offset = 0x003C0000, .size = 0x1000,
},
/* Timestamp associated with GNSS1 receiver PPS */
{
OCP_EXT_RESOURCE(ts0),
.offset = 0x360000, .size = 0x20, .irq_vec = 12,
.extra = &(struct ptp_ocp_ext_info) {
.index = 0,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts1),
.offset = 0x380000, .size = 0x20, .irq_vec = 8,
.extra = &(struct ptp_ocp_ext_info) {
.index = 1,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts2),
.offset = 0x390000, .size = 0x20, .irq_vec = 10,
.extra = &(struct ptp_ocp_ext_info) {
.index = 2,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts3),
.offset = 0x3A0000, .size = 0x20, .irq_vec = 14,
.extra = &(struct ptp_ocp_ext_info) {
.index = 3,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_EXT_RESOURCE(ts4),
.offset = 0x3B0000, .size = 0x20, .irq_vec = 15,
.extra = &(struct ptp_ocp_ext_info) {
.index = 4,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
/* Timestamp associated with Internal PPS of the card */
{
OCP_EXT_RESOURCE(pps),
.offset = 0x00330000, .size = 0x20, .irq_vec = 11,
.extra = &(struct ptp_ocp_ext_info) {
.index = 5,
.irq_fcn = ptp_ocp_ts_irq,
.enable = ptp_ocp_ts_enable,
},
},
{
OCP_SPI_RESOURCE(spi_flash),
.offset = 0x00310000, .size = 0x10000, .irq_vec = 9,
.extra = &(struct ptp_ocp_flash_info) {
.name = "spi_altera", .pci_offset = 0,
.data_size = sizeof(struct altera_spi_platform_data),
.data = &(struct altera_spi_platform_data) {
.num_chipselect = 1,
.num_devices = 1,
.devices = &(struct spi_board_info) {
.modalias = "spi-nor",
},
},
},
},
{
OCP_I2C_RESOURCE(i2c_ctrl),
.offset = 0x350000, .size = 0x100, .irq_vec = 4,
.extra = &(struct ptp_ocp_i2c_info) {
.name = "ocores-i2c",
.fixed_rate = 400000,
.data_size = sizeof(struct ocores_i2c_platform_data),
.data = &(struct ocores_i2c_platform_data) {
.clock_khz = 125000,
.bus_khz = 400,
.num_devices = 1,
.devices = &(struct i2c_board_info) {
I2C_BOARD_INFO("24c08", 0x50),
},
},
},
},
{
OCP_SERIAL_RESOURCE(mac_port),
.offset = 0x00190000, .irq_vec = 7,
.extra = &(struct ptp_ocp_serial_port) {
.baud = 9600,
},
},
{
OCP_MEM_RESOURCE(board_config),
.offset = 0x210000, .size = 0x1000,
},
{
.setup = ptp_ocp_art_board_init,
},
{ }
};
static const struct pci_device_id ptp_ocp_pcidev_id[] = {
{ PCI_DEVICE_DATA(FACEBOOK, TIMECARD, &ocp_fb_resource) },
{ PCI_DEVICE_DATA(CELESTICA, TIMECARD, &ocp_fb_resource) },
{ PCI_DEVICE_DATA(OROLIA, ARTCARD, &ocp_art_resource) },
{ }
};
MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id);
static DEFINE_MUTEX(ptp_ocp_lock);
static DEFINE_IDR(ptp_ocp_idr);
struct ocp_selector {
const char *name;
int value;
u64 frequency;
};
static const struct ocp_selector ptp_ocp_clock[] = {
{ .name = "NONE", .value = 0 },
{ .name = "TOD", .value = 1 },
{ .name = "IRIG", .value = 2 },
{ .name = "PPS", .value = 3 },
{ .name = "PTP", .value = 4 },
{ .name = "RTC", .value = 5 },
{ .name = "DCF", .value = 6 },
{ .name = "REGS", .value = 0xfe },
{ .name = "EXT", .value = 0xff },
{ }
};
#define SMA_DISABLE BIT(16)
#define SMA_ENABLE BIT(15)
#define SMA_SELECT_MASK GENMASK(14, 0)
static const struct ocp_selector ptp_ocp_sma_in[] = {
{ .name = "10Mhz", .value = 0x0000, .frequency = 10000000 },
{ .name = "PPS1", .value = 0x0001, .frequency = 1 },
{ .name = "PPS2", .value = 0x0002, .frequency = 1 },
{ .name = "TS1", .value = 0x0004, .frequency = 0 },
{ .name = "TS2", .value = 0x0008, .frequency = 0 },
{ .name = "IRIG", .value = 0x0010, .frequency = 10000 },
{ .name = "DCF", .value = 0x0020, .frequency = 77500 },
{ .name = "TS3", .value = 0x0040, .frequency = 0 },
{ .name = "TS4", .value = 0x0080, .frequency = 0 },
{ .name = "FREQ1", .value = 0x0100, .frequency = 0 },
{ .name = "FREQ2", .value = 0x0200, .frequency = 0 },
{ .name = "FREQ3", .value = 0x0400, .frequency = 0 },
{ .name = "FREQ4", .value = 0x0800, .frequency = 0 },
{ .name = "None", .value = SMA_DISABLE, .frequency = 0 },
{ }
};
static const struct ocp_selector ptp_ocp_sma_out[] = {
{ .name = "10Mhz", .value = 0x0000, .frequency = 10000000 },
{ .name = "PHC", .value = 0x0001, .frequency = 1 },
{ .name = "MAC", .value = 0x0002, .frequency = 1 },
{ .name = "GNSS1", .value = 0x0004, .frequency = 1 },
{ .name = "GNSS2", .value = 0x0008, .frequency = 1 },
{ .name = "IRIG", .value = 0x0010, .frequency = 10000 },
{ .name = "DCF", .value = 0x0020, .frequency = 77000 },
{ .name = "GEN1", .value = 0x0040 },
{ .name = "GEN2", .value = 0x0080 },
{ .name = "GEN3", .value = 0x0100 },
{ .name = "GEN4", .value = 0x0200 },
{ .name = "GND", .value = 0x2000 },
{ .name = "VCC", .value = 0x4000 },
{ }
};
static const struct ocp_selector ptp_ocp_art_sma_in[] = {
{ .name = "PPS1", .value = 0x0001, .frequency = 1 },
{ .name = "10Mhz", .value = 0x0008, .frequency = 1000000 },
{ }
};
static const struct ocp_selector ptp_ocp_art_sma_out[] = {
{ .name = "PHC", .value = 0x0002, .frequency = 1 },
{ .name = "GNSS", .value = 0x0004, .frequency = 1 },
{ .name = "10Mhz", .value = 0x0010, .frequency = 10000000 },
{ }
};
struct ocp_sma_op {
const struct ocp_selector *tbl[2];
void (*init)(struct ptp_ocp *bp);
u32 (*get)(struct ptp_ocp *bp, int sma_nr);
int (*set_inputs)(struct ptp_ocp *bp, int sma_nr, u32 val);
int (*set_output)(struct ptp_ocp *bp, int sma_nr, u32 val);
};
static void
ptp_ocp_sma_init(struct ptp_ocp *bp)
{
return bp->sma_op->init(bp);
}
static u32
ptp_ocp_sma_get(struct ptp_ocp *bp, int sma_nr)
{
return bp->sma_op->get(bp, sma_nr);
}
static int
ptp_ocp_sma_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val)
{
return bp->sma_op->set_inputs(bp, sma_nr, val);
}
static int
ptp_ocp_sma_set_output(struct ptp_ocp *bp, int sma_nr, u32 val)
{
return bp->sma_op->set_output(bp, sma_nr, val);
}
static const char *
ptp_ocp_select_name_from_val(const struct ocp_selector *tbl, int val)
{
int i;
for (i = 0; tbl[i].name; i++)
if (tbl[i].value == val)
return tbl[i].name;
return NULL;
}
static int
ptp_ocp_select_val_from_name(const struct ocp_selector *tbl, const char *name)
{
const char *select;
int i;
for (i = 0; tbl[i].name; i++) {
select = tbl[i].name;
if (!strncasecmp(name, select, strlen(select)))
return tbl[i].value;
}
return -EINVAL;
}
static ssize_t
ptp_ocp_select_table_show(const struct ocp_selector *tbl, char *buf)
{
ssize_t count;
int i;
count = 0;
for (i = 0; tbl[i].name; i++)
count += sysfs_emit_at(buf, count, "%s ", tbl[i].name);
if (count)
count--;
count += sysfs_emit_at(buf, count, "\n");
return count;
}
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;
ptp_read_system_prets(sts);
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE;
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);
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
if (sts && bp->ts_window_adjust) {
s64 ns = timespec64_to_ns(&sts->post_ts);
sts->post_ts = ns_to_timespec64(ns - bp->ts_window_adjust);
}
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);
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ctrl = OCP_CTRL_ADJUST_TIME | OCP_CTRL_ENABLE;
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;
spin_lock_irqsave(&bp->lock, flags);
__ptp_ocp_settime_locked(bp, ts);
spin_unlock_irqrestore(&bp->lock, flags);
return 0;
}
static void
__ptp_ocp_adjtime_locked(struct ptp_ocp *bp, u32 adj_val)
{
u32 select, ctrl;
select = ioread32(&bp->reg->select);
iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select);
iowrite32(adj_val, &bp->reg->offset_ns);
iowrite32(NSEC_PER_SEC, &bp->reg->offset_window_ns);
ctrl = OCP_CTRL_ADJUST_OFFSET | OCP_CTRL_ENABLE;
iowrite32(ctrl, &bp->reg->ctrl);
/* restore clock selection */
iowrite32(select >> 16, &bp->reg->select);
}
static void
ptp_ocp_adjtime_coarse(struct ptp_ocp *bp, s64 delta_ns)
{
struct timespec64 ts;
unsigned long flags;
int err;
spin_lock_irqsave(&bp->lock, flags);
err = __ptp_ocp_gettime_locked(bp, &ts, NULL);
if (likely(!err)) {
set_normalized_timespec64(&ts, ts.tv_sec,
ts.tv_nsec + delta_ns);
__ptp_ocp_settime_locked(bp, &ts);
}
spin_unlock_irqrestore(&bp->lock, flags);
}
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);
unsigned long flags;
u32 adj_ns, sign;
if (delta_ns > NSEC_PER_SEC || -delta_ns > NSEC_PER_SEC) {
ptp_ocp_adjtime_coarse(bp, delta_ns);
return 0;
}
sign = delta_ns < 0 ? BIT(31) : 0;
adj_ns = sign ? -delta_ns : delta_ns;
spin_lock_irqsave(&bp->lock, flags);
__ptp_ocp_adjtime_locked(bp, sign | adj_ns);
spin_unlock_irqrestore(&bp->lock, flags);
return 0;
}
static int
ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm)
{
if (scaled_ppm == 0)
return 0;
return -EOPNOTSUPP;
}
static s32
ptp_ocp_null_getmaxphase(struct ptp_clock_info *ptp_info)
{
return 0;
}
static int
ptp_ocp_null_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns)
{
return -EOPNOTSUPP;
}
static int
ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq,
int on)
{
struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
struct ptp_ocp_ext_src *ext = NULL;
u32 req;
int err;
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
req = OCP_REQ_TIMESTAMP;
switch (rq->extts.index) {
case 0:
ext = bp->ts0;
break;
case 1:
ext = bp->ts1;
break;
case 2:
ext = bp->ts2;
break;
case 3:
ext = bp->ts3;
break;
case 4:
ext = bp->ts4;
break;
case 5:
ext = bp->pps;
break;
}
break;
case PTP_CLK_REQ_PPS:
req = OCP_REQ_PPS;
ext = bp->pps;
break;
case PTP_CLK_REQ_PEROUT:
switch (rq->perout.index) {
case 0:
/* This is a request for 1PPS on an output SMA.
* Allow, but assume manual configuration.
*/
if (on && (rq->perout.period.sec != 1 ||
rq->perout.period.nsec != 0))
return -EINVAL;
return 0;
case 1:
case 2:
case 3:
case 4:
req = rq->perout.index - 1;
ext = bp->signal_out[req];
err = ptp_ocp_signal_from_perout(bp, req, &rq->perout);
if (err)
return err;
break;
}
break;
default:
return -EOPNOTSUPP;
}
err = -ENXIO;
if (ext)
err = ext->info->enable(ext, req, on);
return err;
}
static int
ptp_ocp_verify(struct ptp_clock_info *ptp_info, unsigned pin,
enum ptp_pin_function func, unsigned chan)
{
struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
char buf[16];
switch (func) {
case PTP_PF_NONE:
snprintf(buf, sizeof(buf), "IN: None");
break;
case PTP_PF_EXTTS:
/* Allow timestamps, but require sysfs configuration. */
return 0;
case PTP_PF_PEROUT:
/* channel 0 is 1PPS from PHC.
* channels 1..4 are the frequency generators.
*/
if (chan)
snprintf(buf, sizeof(buf), "OUT: GEN%d", chan);
else
snprintf(buf, sizeof(buf), "OUT: PHC");
break;
default:
return -EOPNOTSUPP;
}
return ptp_ocp_sma_store(bp, buf, pin + 1);
}
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,
.adjphase = ptp_ocp_null_adjphase,
.getmaxphase = ptp_ocp_null_getmaxphase,
.enable = ptp_ocp_enable,
.verify = ptp_ocp_verify,
.pps = true,
.n_ext_ts = 6,
.n_per_out = 5,
};
static void
__ptp_ocp_clear_drift_locked(struct ptp_ocp *bp)
{
u32 ctrl, select;
select = ioread32(&bp->reg->select);
iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select);
iowrite32(0, &bp->reg->drift_ns);
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ctrl = OCP_CTRL_ADJUST_DRIFT | OCP_CTRL_ENABLE;
iowrite32(ctrl, &bp->reg->ctrl);
/* restore clock selection */
iowrite32(select >> 16, &bp->reg->select);
}
static void
ptp_ocp_utc_distribute(struct ptp_ocp *bp, u32 val)
{
unsigned long flags;
spin_lock_irqsave(&bp->lock, flags);
bp->utc_tai_offset = val;
if (bp->irig_out)
iowrite32(val, &bp->irig_out->adj_sec);
if (bp->dcf_out)
iowrite32(val, &bp->dcf_out->adj_sec);
if (bp->nmea_out)
iowrite32(val, &bp->nmea_out->adj_sec);
spin_unlock_irqrestore(&bp->lock, flags);
}
static void
ptp_ocp_watchdog(struct timer_list *t)
{
struct ptp_ocp *bp = from_timer(bp, t, watchdog);
unsigned long flags;
u32 status, utc_offset;
status = ioread32(&bp->pps_to_clk->status);
if (status & PPS_STATUS_SUPERV_ERR) {
iowrite32(status, &bp->pps_to_clk->status);
if (!bp->gnss_lost) {
spin_lock_irqsave(&bp->lock, flags);
__ptp_ocp_clear_drift_locked(bp);
spin_unlock_irqrestore(&bp->lock, flags);
bp->gnss_lost = ktime_get_real_seconds();
}
} else if (bp->gnss_lost) {
bp->gnss_lost = 0;
}
/* if GNSS provides correct data we can rely on
* it to get leap second information
*/
if (bp->tod) {
status = ioread32(&bp->tod->utc_status);
utc_offset = status & TOD_STATUS_UTC_MASK;
if (status & TOD_STATUS_UTC_VALID &&
utc_offset != bp->utc_tai_offset)
ptp_ocp_utc_distribute(bp, utc_offset);
}
mod_timer(&bp->watchdog, jiffies + HZ);
}
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
static void
ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp)
{
ktime_t start, end;
ktime_t delay;
u32 ctrl;
ctrl = ioread32(&bp->reg->ctrl);
ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE;
iowrite32(ctrl, &bp->reg->ctrl);
start = ktime_get_ns();
ctrl = ioread32(&bp->reg->ctrl);
end = ktime_get_ns();
delay = end - start;
bp->ts_window_adjust = (delay >> 5) * 3;
}
static int
ptp_ocp_init_clock(struct ptp_ocp *bp)
{
struct timespec64 ts;
u32 ctrl;
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ctrl = OCP_CTRL_ENABLE;
iowrite32(ctrl, &bp->reg->ctrl);
/* NO DRIFT Correction */
/* offset_p:i 1/8, offset_i: 1/16, drift_p: 0, drift_i: 0 */
iowrite32(0x2000, &bp->reg->servo_offset_p);
iowrite32(0x1000, &bp->reg->servo_offset_i);
iowrite32(0, &bp->reg->servo_drift_p);
iowrite32(0, &bp->reg->servo_drift_i);
/* latch servo values */
ctrl |= OCP_CTRL_ADJUST_SERVO;
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;
}
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ptp_ocp_estimate_pci_timing(bp);
bp->sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
if (!bp->sync) {
ktime_get_clocktai_ts64(&ts);
ptp_ocp_settime(&bp->ptp_info, &ts);
}
/* If there is a clock supervisor, then enable the watchdog */
if (bp->pps_to_clk) {
timer_setup(&bp->watchdog, ptp_ocp_watchdog, 0);
mod_timer(&bp->watchdog, jiffies + HZ);
}
return 0;
}
static void
ptp_ocp_tod_init(struct ptp_ocp *bp)
{
u32 ctrl, reg;
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);
reg = ioread32(&bp->tod->utc_status);
if (reg & TOD_STATUS_UTC_VALID)
ptp_ocp_utc_distribute(bp, reg & TOD_STATUS_UTC_MASK);
}
static const char *
ptp_ocp_tod_proto_name(const int idx)
{
static const char * const proto_name[] = {
"NMEA", "NMEA_ZDA", "NMEA_RMC", "NMEA_none",
"UBX", "UBX_UTC", "UBX_LS", "UBX_none"
};
return proto_name[idx];
}
static const char *
ptp_ocp_tod_gnss_name(int idx)
{
static const char * const gnss_name[] = {
"ALL", "COMBINED", "GPS", "GLONASS", "GALILEO", "BEIDOU",
"Unknown"
};
if (idx >= ARRAY_SIZE(gnss_name))
idx = ARRAY_SIZE(gnss_name) - 1;
return gnss_name[idx];
}
struct ptp_ocp_nvmem_match_info {
struct ptp_ocp *bp;
const void * const tag;
};
static int
ptp_ocp_nvmem_match(struct device *dev, const void *data)
{
const struct ptp_ocp_nvmem_match_info *info = data;
dev = dev->parent;
if (!i2c_verify_client(dev) || info->tag != dev->platform_data)
return 0;
while ((dev = dev->parent))
if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
return info->bp == dev_get_drvdata(dev);
return 0;
}
static inline struct nvmem_device *
ptp_ocp_nvmem_device_get(struct ptp_ocp *bp, const void * const tag)
{
struct ptp_ocp_nvmem_match_info info = { .bp = bp, .tag = tag };
return nvmem_device_find(&info, ptp_ocp_nvmem_match);
}
static inline void
ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)
{
if (!IS_ERR_OR_NULL(*nvmemp))
nvmem_device_put(*nvmemp);
*nvmemp = NULL;
}
static void
ptp_ocp_read_eeprom(struct ptp_ocp *bp)
{
const struct ptp_ocp_eeprom_map *map;
struct nvmem_device *nvmem;
const void *tag;
int ret;
if (!bp->i2c_ctrl)
return;
tag = NULL;
nvmem = NULL;
for (map = bp->eeprom_map; map->len; map++) {
if (map->tag != tag) {
tag = map->tag;
ptp_ocp_nvmem_device_put(&nvmem);
}
if (!nvmem) {
nvmem = ptp_ocp_nvmem_device_get(bp, tag);
if (IS_ERR(nvmem)) {
ret = PTR_ERR(nvmem);
goto fail;
}
}
ret = nvmem_device_read(nvmem, map->off, map->len,
BP_MAP_ENTRY_ADDR(bp, map));
if (ret != map->len)
goto fail;
}
bp->has_eeprom_data = true;
out:
ptp_ocp_nvmem_device_put(&nvmem);
return;
fail:
dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret);
goto out;
}
static struct device *
ptp_ocp_find_flash(struct ptp_ocp *bp)
{
struct device *dev, *last;
last = NULL;
dev = &bp->spi_flash->dev;
while ((dev = device_find_any_child(dev))) {
if (!strcmp("mtd", dev_bus_name(dev)))
break;
put_device(last);
last = dev;
}
put_device(last);
return dev;
}
static int
ptp_ocp_devlink_fw_image(struct devlink *devlink, const struct firmware *fw,
const u8 **data, size_t *size)
{
struct ptp_ocp *bp = devlink_priv(devlink);
const struct ptp_ocp_firmware_header *hdr;
size_t offset, length;
u16 crc;
hdr = (const struct ptp_ocp_firmware_header *)fw->data;
if (memcmp(hdr->magic, OCP_FIRMWARE_MAGIC_HEADER, 4)) {
devlink_flash_update_status_notify(devlink,
"No firmware header found, cancel firmware upgrade",
NULL, 0, 0);
return -EINVAL;
}
if (be16_to_cpu(hdr->pci_vendor_id) != bp->pdev->vendor ||
be16_to_cpu(hdr->pci_device_id) != bp->pdev->device) {
devlink_flash_update_status_notify(devlink,
"Firmware image compatibility check failed",
NULL, 0, 0);
return -EINVAL;
}
offset = sizeof(*hdr);
length = be32_to_cpu(hdr->image_size);
if (length != (fw->size - offset)) {
devlink_flash_update_status_notify(devlink,
"Firmware image size check failed",
NULL, 0, 0);
return -EINVAL;
}
crc = crc16(0xffff, &fw->data[offset], length);
if (be16_to_cpu(hdr->crc) != crc) {
devlink_flash_update_status_notify(devlink,
"Firmware image CRC check failed",
NULL, 0, 0);
return -EINVAL;
}
*data = &fw->data[offset];
*size = length;
return 0;
}
static int
ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev,
const struct firmware *fw)
{
struct mtd_info *mtd = dev_get_drvdata(dev);
struct ptp_ocp *bp = devlink_priv(devlink);
size_t off, len, size, resid, wrote;
struct erase_info erase;
size_t base, blksz;
const u8 *data;
int err;
err = ptp_ocp_devlink_fw_image(devlink, fw, &data, &size);
if (err)
goto out;
off = 0;
base = bp->flash_start;
blksz = 4096;
resid = size;
while (resid) {
devlink_flash_update_status_notify(devlink, "Flashing",
NULL, off, size);
len = min_t(size_t, resid, blksz);
erase.addr = base + off;
erase.len = blksz;
err = mtd_erase(mtd, &erase);
if (err)
goto out;
err = mtd_write(mtd, base + off, len, &wrote, data + off);
if (err)
goto out;
off += blksz;
resid -= len;
}
out:
return err;
}
static int
ptp_ocp_devlink_flash_update(struct devlink *devlink,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
{
struct ptp_ocp *bp = devlink_priv(devlink);
struct device *dev;
const char *msg;
int err;
dev = ptp_ocp_find_flash(bp);
if (!dev) {
dev_err(&bp->pdev->dev, "Can't find Flash SPI adapter\n");
return -ENODEV;
}
devlink_flash_update_status_notify(devlink, "Preparing to flash",
NULL, 0, 0);
err = ptp_ocp_devlink_flash(devlink, dev, params->fw);
msg = err ? "Flash error" : "Flash complete";
devlink_flash_update_status_notify(devlink, msg, NULL, 0, 0);
put_device(dev);
return err;
}
static int
ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct ptp_ocp *bp = devlink_priv(devlink);
const char *fw_image;
char buf[32];
int err;
fw_image = bp->fw_loader ? "loader" : "fw";
sprintf(buf, "%d.%d", bp->fw_tag, bp->fw_version);
err = devlink_info_version_running_put(req, fw_image, buf);
if (err)
return err;
if (!bp->has_eeprom_data) {
ptp_ocp_read_eeprom(bp);
if (!bp->has_eeprom_data)
return 0;
}
sprintf(buf, "%pM", bp->serial);
err = devlink_info_serial_number_put(req, buf);
if (err)
return err;
err = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_BOARD_ID,
bp->board_id);
if (err)
return err;
return 0;
}
static const struct devlink_ops ptp_ocp_devlink_ops = {
.flash_update = ptp_ocp_devlink_flash_update,
.info_get = ptp_ocp_devlink_info_get,
};
static void __iomem *
__ptp_ocp_get_mem(struct ptp_ocp *bp, resource_size_t start, int size)
{
struct resource res = DEFINE_RES_MEM_NAMED(start, size, "ptp_ocp");
return devm_ioremap_resource(&bp->pdev->dev, &res);
}
static void __iomem *
ptp_ocp_get_mem(struct ptp_ocp *bp, struct ocp_resource *r)
{
resource_size_t start;
start = pci_resource_start(bp->pdev, 0) + r->offset;
return __ptp_ocp_get_mem(bp, start, r->size);
}
static int
ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r)
{
struct ptp_ocp_flash_info *info;
struct pci_dev *pdev = bp->pdev;
struct platform_device *p;
struct resource res[2];
resource_size_t start;
int id;
start = pci_resource_start(pdev, 0) + r->offset;
res[0] = DEFINE_RES_MEM(start, r->size);
res[1] = DEFINE_RES_IRQ(pci_irq_vector(pdev, r->irq_vec));
info = r->extra;
id = pci_dev_id(pdev) << 1;
id += info->pci_offset;
p = platform_device_register_resndata(&pdev->dev, info->name, id,
res, ARRAY_SIZE(res), info->data,
info->data_size);
if (IS_ERR(p))
return PTR_ERR(p);
bp_assign_entry(bp, r, p);
return 0;
}
static struct platform_device *
ptp_ocp_i2c_bus(struct pci_dev *pdev, struct ocp_resource *r, int id)
{
struct ptp_ocp_i2c_info *info;
struct resource res[2];
resource_size_t start;
info = r->extra;
start = pci_resource_start(pdev, 0) + r->offset;
res[0] = DEFINE_RES_MEM(start, r->size);
res[1] = DEFINE_RES_IRQ(pci_irq_vector(pdev, r->irq_vec));
return platform_device_register_resndata(&pdev->dev, info->name,
id, res, ARRAY_SIZE(res),
info->data, info->data_size);
}
static int
ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r)
{
struct pci_dev *pdev = bp->pdev;
struct ptp_ocp_i2c_info *info;
struct platform_device *p;
struct clk_hw *clk;
char buf[32];
int id;
info = r->extra;
id = pci_dev_id(bp->pdev);
sprintf(buf, "AXI.%d", id);
clk = clk_hw_register_fixed_rate(&pdev->dev, buf, NULL, 0,
info->fixed_rate);
if (IS_ERR(clk))
return PTR_ERR(clk);
bp->i2c_clk = clk;
sprintf(buf, "%s.%d", info->name, id);
devm_clk_hw_register_clkdev(&pdev->dev, clk, NULL, buf);
p = ptp_ocp_i2c_bus(bp->pdev, r, id);
if (IS_ERR(p))
return PTR_ERR(p);
bp_assign_entry(bp, r, p);
return 0;
}
/* The expectation is that this is triggered only on error. */
static irqreturn_t
ptp_ocp_signal_irq(int irq, void *priv)
{
struct ptp_ocp_ext_src *ext = priv;
struct signal_reg __iomem *reg = ext->mem;
struct ptp_ocp *bp = ext->bp;
u32 enable, status;
int gen;
gen = ext->info->index - 1;
enable = ioread32(&reg->enable);
status = ioread32(&reg->status);
/* disable generator on error */
if (status || !enable) {
iowrite32(0, &reg->intr_mask);
iowrite32(0, &reg->enable);
bp->signal[gen].running = false;
}
iowrite32(0, &reg->intr); /* ack interrupt */
return IRQ_HANDLED;
}
static int
ptp_ocp_signal_set(struct ptp_ocp *bp, int gen, struct ptp_ocp_signal *s)
{
struct ptp_system_timestamp sts;
struct timespec64 ts;
ktime_t start_ns;
int err;
if (!s->period)
return 0;
if (!s->pulse)
s->pulse = ktime_divns(s->period * s->duty, 100);
err = ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts);
if (err)
return err;
start_ns = ktime_set(ts.tv_sec, ts.tv_nsec) + NSEC_PER_MSEC;
if (!s->start) {
/* roundup() does not work on 32-bit systems */
s->start = DIV64_U64_ROUND_UP(start_ns, s->period);
s->start = ktime_add(s->start, s->phase);
}
if (s->duty < 1 || s->duty > 99)
return -EINVAL;
if (s->pulse < 1 || s->pulse > s->period)
return -EINVAL;
if (s->start < start_ns)
return -EINVAL;
bp->signal[gen] = *s;
return 0;
}
static int
ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
struct ptp_perout_request *req)
{
struct ptp_ocp_signal s = { };
s.polarity = bp->signal[gen].polarity;
s.period = ktime_set(req->period.sec, req->period.nsec);
if (!s.period)
return 0;
if (req->flags & PTP_PEROUT_DUTY_CYCLE) {
s.pulse = ktime_set(req->on.sec, req->on.nsec);
s.duty = ktime_divns(s.pulse * 100, s.period);
}
if (req->flags & PTP_PEROUT_PHASE)
s.phase = ktime_set(req->phase.sec, req->phase.nsec);
else
s.start = ktime_set(req->start.sec, req->start.nsec);
return ptp_ocp_signal_set(bp, gen, &s);
}
static int
ptp_ocp_signal_enable(void *priv, u32 req, bool enable)
{
struct ptp_ocp_ext_src *ext = priv;
struct signal_reg __iomem *reg = ext->mem;
struct ptp_ocp *bp = ext->bp;
struct timespec64 ts;
int gen;
gen = ext->info->index - 1;
iowrite32(0, &reg->intr_mask);
iowrite32(0, &reg->enable);
bp->signal[gen].running = false;
if (!enable)
return 0;
ts = ktime_to_timespec64(bp->signal[gen].start);
iowrite32(ts.tv_sec, &reg->start_sec);
iowrite32(ts.tv_nsec, &reg->start_ns);
ts = ktime_to_timespec64(bp->signal[gen].period);
iowrite32(ts.tv_sec, &reg->period_sec);
iowrite32(ts.tv_nsec, &reg->period_ns);
ts = ktime_to_timespec64(bp->signal[gen].pulse);
iowrite32(ts.tv_sec, &reg->pulse_sec);
iowrite32(ts.tv_nsec, &reg->pulse_ns);
iowrite32(bp->signal[gen].polarity, &reg->polarity);
iowrite32(0, &reg->repeat_count);
iowrite32(0, &reg->intr); /* clear interrupt state */
iowrite32(1, &reg->intr_mask); /* enable interrupt */
iowrite32(3, &reg->enable); /* valid & enable */
bp->signal[gen].running = true;
return 0;
}
static irqreturn_t
ptp_ocp_ts_irq(int irq, void *priv)
{
struct ptp_ocp_ext_src *ext = priv;
struct ts_reg __iomem *reg = ext->mem;
struct ptp_clock_event ev;
u32 sec, nsec;
if (ext == ext->bp->pps) {
if (ext->bp->pps_req_map & OCP_REQ_PPS) {
ev.type = PTP_CLOCK_PPS;
ptp_clock_event(ext->bp->ptp, &ev);
}
if ((ext->bp->pps_req_map & ~OCP_REQ_PPS) == 0)
goto out;
}
/* XXX should fix API - this converts s/ns -> ts -> s/ns */
sec = ioread32(&reg->time_sec);
nsec = ioread32(&reg->time_ns);
ev.type = PTP_CLOCK_EXTTS;
ev.index = ext->info->index;
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
ev.timestamp = sec * NSEC_PER_SEC + nsec;
ptp_clock_event(ext->bp->ptp, &ev);
out:
iowrite32(1, &reg->intr); /* write 1 to ack */
return IRQ_HANDLED;
}
static int
ptp_ocp_ts_enable(void *priv, u32 req, bool enable)
{
struct ptp_ocp_ext_src *ext = priv;
struct ts_reg __iomem *reg = ext->mem;
struct ptp_ocp *bp = ext->bp;
if (ext == bp->pps) {
u32 old_map = bp->pps_req_map;
if (enable)
bp->pps_req_map |= req;
else
bp->pps_req_map &= ~req;
/* if no state change, just return */
if ((!!old_map ^ !!bp->pps_req_map) == 0)
return 0;
}
if (enable) {
iowrite32(1, &reg->enable);
iowrite32(1, &reg->intr_mask);
iowrite32(1, &reg->intr);
} else {
iowrite32(0, &reg->intr_mask);
iowrite32(0, &reg->enable);
}
return 0;
}
static void
ptp_ocp_unregister_ext(struct ptp_ocp_ext_src *ext)
{
ext->info->enable(ext, ~0, false);
pci_free_irq(ext->bp->pdev, ext->irq_vec, ext);
kfree(ext);
}
static int
ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r)
{
struct pci_dev *pdev = bp->pdev;
struct ptp_ocp_ext_src *ext;
int err;
ext = kzalloc(sizeof(*ext), GFP_KERNEL);
if (!ext)
return -ENOMEM;
ext->mem = ptp_ocp_get_mem(bp, r);
if (IS_ERR(ext->mem)) {
err = PTR_ERR(ext->mem);
goto out;
}
ext->bp = bp;
ext->info = r->extra;
ext->irq_vec = r->irq_vec;
err = pci_request_irq(pdev, r->irq_vec, ext->info->irq_fcn, NULL,
ext, "ocp%d.%s", bp->id, r->name);
if (err) {
dev_err(&pdev->dev, "Could not get irq %d\n", r->irq_vec);
goto out;
}
bp_assign_entry(bp, r, ext);
return 0;
out:
kfree(ext);
return err;
}
static int
ptp_ocp_serial_line(struct ptp_ocp *bp, struct ocp_resource *r)
{
struct pci_dev *pdev = bp->pdev;
struct uart_8250_port uart;
/* Setting UPF_IOREMAP and leaving port.membase unspecified lets
* the serial port device claim and release the pci resource.
*/
memset(&uart, 0, sizeof(uart));
uart.port.dev = &pdev->dev;
uart.port.iotype = UPIO_MEM;
uart.port.regshift = 2;
uart.port.mapbase = pci_resource_start(pdev, 0) + r->offset;
uart.port.irq = pci_irq_vector(pdev, r->irq_vec);
uart.port.uartclk = 50000000;
uart.port.flags = UPF_FIXED_TYPE | UPF_IOREMAP | UPF_NO_THRE_TEST;
uart.port.type = PORT_16550A;
return serial8250_register_8250_port(&uart);
}
static int
ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r)
{
struct ptp_ocp_serial_port *p = (struct ptp_ocp_serial_port *)r->extra;
struct ptp_ocp_serial_port port = {};
port.line = ptp_ocp_serial_line(bp, r);
if (port.line < 0)
return port.line;
if (p)
port.baud = p->baud;
bp_assign_entry(bp, r, port);
return 0;
}
static int
ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r)
{
void __iomem *mem;
mem = ptp_ocp_get_mem(bp, r);
if (IS_ERR(mem))
return PTR_ERR(mem);
bp_assign_entry(bp, r, mem);
return 0;
}
static void
ptp_ocp_nmea_out_init(struct ptp_ocp *bp)
{
if (!bp->nmea_out)
return;
iowrite32(0, &bp->nmea_out->ctrl); /* disable */
iowrite32(7, &bp->nmea_out->uart_baud); /* 115200 */
iowrite32(1, &bp->nmea_out->ctrl); /* enable */
}
static void
_ptp_ocp_signal_init(struct ptp_ocp_signal *s, struct signal_reg __iomem *reg)
{
u32 val;
iowrite32(0, &reg->enable); /* disable */
val = ioread32(&reg->polarity);
s->polarity = val ? true : false;
s->duty = 50;
}
static void
ptp_ocp_signal_init(struct ptp_ocp *bp)
{
int i;
for (i = 0; i < 4; i++)
if (bp->signal_out[i])
_ptp_ocp_signal_init(&bp->signal[i],
bp->signal_out[i]->mem);
}
static void
ptp_ocp_attr_group_del(struct ptp_ocp *bp)
{
sysfs_remove_groups(&bp->dev.kobj, bp->attr_group);
kfree(bp->attr_group);
}
static int
ptp_ocp_attr_group_add(struct ptp_ocp *bp,
const struct ocp_attr_group *attr_tbl)
{
int count, i;
int err;
count = 0;
for (i = 0; attr_tbl[i].cap; i++)
if (attr_tbl[i].cap & bp->fw_cap)
count++;
bp->attr_group = kcalloc(count + 1, sizeof(struct attribute_group *),
GFP_KERNEL);
if (!bp->attr_group)
return -ENOMEM;
count = 0;
for (i = 0; attr_tbl[i].cap; i++)
if (attr_tbl[i].cap & bp->fw_cap)
bp->attr_group[count++] = attr_tbl[i].group;
err = sysfs_create_groups(&bp->dev.kobj, bp->attr_group);
if (err)
bp->attr_group[0] = NULL;
return err;
}
static void
ptp_ocp_enable_fpga(u32 __iomem *reg, u32 bit, bool enable)
{
u32 ctrl;
bool on;
ctrl = ioread32(reg);
on = ctrl & bit;
if (on ^ enable) {
ctrl &= ~bit;
ctrl |= enable ? bit : 0;
iowrite32(ctrl, reg);
}
}
static void
ptp_ocp_irig_out(struct ptp_ocp *bp, bool enable)
{
return ptp_ocp_enable_fpga(&bp->irig_out->ctrl,
IRIG_M_CTRL_ENABLE, enable);
}
static void
ptp_ocp_irig_in(struct ptp_ocp *bp, bool enable)
{
return ptp_ocp_enable_fpga(&bp->irig_in->ctrl,
IRIG_S_CTRL_ENABLE, enable);
}
static void
ptp_ocp_dcf_out(struct ptp_ocp *bp, bool enable)
{
return ptp_ocp_enable_fpga(&bp->dcf_out->ctrl,
DCF_M_CTRL_ENABLE, enable);
}
static void
ptp_ocp_dcf_in(struct ptp_ocp *bp, bool enable)
{
return ptp_ocp_enable_fpga(&bp->dcf_in->ctrl,
DCF_S_CTRL_ENABLE, enable);
}
static void
__handle_signal_outputs(struct ptp_ocp *bp, u32 val)
{
ptp_ocp_irig_out(bp, val & 0x00100010);
ptp_ocp_dcf_out(bp, val & 0x00200020);
}
static void
__handle_signal_inputs(struct ptp_ocp *bp, u32 val)
{
ptp_ocp_irig_in(bp, val & 0x00100010);
ptp_ocp_dcf_in(bp, val & 0x00200020);
}
static u32
ptp_ocp_sma_fb_get(struct ptp_ocp *bp, int sma_nr)
{
u32 __iomem *gpio;
u32 shift;
if (bp->sma[sma_nr - 1].fixed_fcn)
return (sma_nr - 1) & 1;
if (bp->sma[sma_nr - 1].mode == SMA_MODE_IN)
gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1;
else
gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2;
shift = sma_nr & 1 ? 0 : 16;
return (ioread32(gpio) >> shift) & 0xffff;
}
static int
ptp_ocp_sma_fb_set_output(struct ptp_ocp *bp, int sma_nr, u32 val)
{
u32 reg, mask, shift;
unsigned long flags;
u32 __iomem *gpio;
gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2;
shift = sma_nr & 1 ? 0 : 16;
mask = 0xffff << (16 - shift);
spin_lock_irqsave(&bp->lock, flags);
reg = ioread32(gpio);
reg = (reg & mask) | (val << shift);
__handle_signal_outputs(bp, reg);
iowrite32(reg, gpio);
spin_unlock_irqrestore(&bp->lock, flags);
return 0;
}
static int
ptp_ocp_sma_fb_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val)
{
u32 reg, mask, shift;
unsigned long flags;
u32 __iomem *gpio;
gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1;
shift = sma_nr & 1 ? 0 : 16;
mask = 0xffff << (16 - shift);
spin_lock_irqsave(&bp->lock, flags);
reg = ioread32(gpio);
reg = (reg & mask) | (val << shift);
__handle_signal_inputs(bp, reg);
iowrite32(reg, gpio);
spin_unlock_irqrestore(&bp->lock, flags);
return 0;
}
static void
ptp_ocp_sma_fb_init(struct ptp_ocp *bp)
{
struct dpll_pin_properties prop = {
.board_label = NULL,
.type = DPLL_PIN_TYPE_EXT,
.capabilities = DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE,
.freq_supported_num = ARRAY_SIZE(ptp_ocp_sma_freq),
.freq_supported = ptp_ocp_sma_freq,
};
u32 reg;
int i;
/* defaults */
for (i = 0; i < OCP_SMA_NUM; i++) {
bp->sma[i].default_fcn = i & 1;
bp->sma[i].dpll_prop = prop;
bp->sma[i].dpll_prop.board_label =
bp->ptp_info.pin_config[i].name;
}
bp->sma[0].mode = SMA_MODE_IN;
bp->sma[1].mode = SMA_MODE_IN;
bp->sma[2].mode = SMA_MODE_OUT;
bp->sma[3].mode = SMA_MODE_OUT;
/* If no SMA1 map, the pin functions and directions are fixed. */
if (!bp->sma_map1) {
for (i = 0; i < OCP_SMA_NUM; i++) {
bp->sma[i].fixed_fcn = true;
bp->sma[i].fixed_dir = true;
bp->sma[1].dpll_prop.capabilities &=
~DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE;
}
return;
}
/* If SMA2 GPIO output map is all 1, it is not present.
* This indicates the firmware has fixed direction SMA pins.
*/
reg = ioread32(&bp->sma_map2->gpio2);
if (reg == 0xffffffff) {
for (i = 0; i < OCP_SMA_NUM; i++)
bp->sma[i].fixed_dir = true;
} else {
reg = ioread32(&bp->sma_map1->gpio1);
bp->sma[0].mode = reg & BIT(15) ? SMA_MODE_IN : SMA_MODE_OUT;
bp->sma[1].mode = reg & BIT(31) ? SMA_MODE_IN : SMA_MODE_OUT;
reg = ioread32(&bp->sma_map1->gpio2);
bp->sma[2].mode = reg & BIT(15) ? SMA_MODE_OUT : SMA_MODE_IN;
bp->sma[3].mode = reg & BIT(31) ? SMA_MODE_OUT : SMA_MODE_IN;
}
}
static const struct ocp_sma_op ocp_fb_sma_op = {
.tbl = { ptp_ocp_sma_in, ptp_ocp_sma_out },
.init = ptp_ocp_sma_fb_init,
.get = ptp_ocp_sma_fb_get,
.set_inputs = ptp_ocp_sma_fb_set_inputs,
.set_output = ptp_ocp_sma_fb_set_output,
};
static int
ptp_ocp_set_pins(struct ptp_ocp *bp)
{
struct ptp_pin_desc *config;
int i;
config = kcalloc(4, sizeof(*config), GFP_KERNEL);
if (!config)
return -ENOMEM;
for (i = 0; i < 4; i++) {
sprintf(config[i].name, "sma%d", i + 1);
config[i].index = i;
}
bp->ptp_info.n_pins = 4;
bp->ptp_info.pin_config = config;
return 0;
}
static void
ptp_ocp_fb_set_version(struct ptp_ocp *bp)
{
u64 cap = OCP_CAP_BASIC;
u32 version;
version = ioread32(&bp->image->version);
/* if lower 16 bits are empty, this is the fw loader. */
if ((version & 0xffff) == 0) {
version = version >> 16;
bp->fw_loader = true;
}
bp->fw_tag = version >> 15;
bp->fw_version = version & 0x7fff;
if (bp->fw_tag) {
/* FPGA firmware */
if (version >= 5)
cap |= OCP_CAP_SIGNAL | OCP_CAP_FREQ;
} else {
/* SOM firmware */
if (version >= 19)
cap |= OCP_CAP_SIGNAL;
if (version >= 20)
cap |= OCP_CAP_FREQ;
}
bp->fw_cap = cap;
}
/* FB specific board initializers; last "resource" registered. */
static int
ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r)
{
int err;
bp->flash_start = 1024 * 4096;
bp->eeprom_map = fb_eeprom_map;
bp->fw_version = ioread32(&bp->image->version);
bp->sma_op = &ocp_fb_sma_op;
ptp_ocp_fb_set_version(bp);
ptp_ocp_tod_init(bp);
ptp_ocp_nmea_out_init(bp);
ptp_ocp_signal_init(bp);
err = ptp_ocp_attr_group_add(bp, fb_timecard_groups);
if (err)
return err;
err = ptp_ocp_set_pins(bp);
if (err)
return err;
ptp_ocp_sma_init(bp);
return ptp_ocp_init_clock(bp);
}
static bool
ptp_ocp_allow_irq(struct ptp_ocp *bp, struct ocp_resource *r)
{
bool allow = !r->irq_vec || r->irq_vec < bp->n_irqs;
if (!allow)
dev_err(&bp->pdev->dev, "irq %d out of range, skipping %s\n",
r->irq_vec, r->name);
return allow;
}
static int
ptp_ocp_register_resources(struct ptp_ocp *bp, kernel_ulong_t driver_data)
{
struct ocp_resource *r, *table;
int err = 0;
table = (struct ocp_resource *)driver_data;
for (r = table; r->setup; r++) {
if (!ptp_ocp_allow_irq(bp, r))
continue;
err = r->setup(bp, r);
if (err) {
dev_err(&bp->pdev->dev,
"Could not register %s: err %d\n",
r->name, err);
break;
}
}
return err;
}
static void
ptp_ocp_art_sma_init(struct ptp_ocp *bp)
{
struct dpll_pin_properties prop = {
.board_label = NULL,
.type = DPLL_PIN_TYPE_EXT,
.capabilities = 0,
.freq_supported_num = ARRAY_SIZE(ptp_ocp_sma_freq),
.freq_supported = ptp_ocp_sma_freq,
};
u32 reg;
int i;
/* defaults */
bp->sma[0].mode = SMA_MODE_IN;
bp->sma[1].mode = SMA_MODE_IN;
bp->sma[2].mode = SMA_MODE_OUT;
bp->sma[3].mode = SMA_MODE_OUT;
bp->sma[0].default_fcn = 0x08; /* IN: 10Mhz */
bp->sma[1].default_fcn = 0x01; /* IN: PPS1 */
bp->sma[2].default_fcn = 0x10; /* OUT: 10Mhz */
bp->sma[3].default_fcn = 0x02; /* OUT: PHC */
for (i = 0; i < OCP_SMA_NUM; i++) {
/* If no SMA map, the pin functions and directions are fixed. */
bp->sma[i].dpll_prop = prop;
bp->sma[i].dpll_prop.board_label =
bp->ptp_info.pin_config[i].name;
if (!bp->art_sma) {
bp->sma[i].fixed_fcn = true;
bp->sma[i].fixed_dir = true;
continue;
}
reg = ioread32(&bp->art_sma->map[i].gpio);
switch (reg & 0xff) {
case 0:
bp->sma[i].fixed_fcn = true;
bp->sma[i].fixed_dir = true;
break;
case 1:
case 8:
bp->sma[i].mode = SMA_MODE_IN;
bp->sma[i].dpll_prop.capabilities =
DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE;
break;
default:
bp->sma[i].mode = SMA_MODE_OUT;
bp->sma[i].dpll_prop.capabilities =
DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE;
break;
}
}
}
static u32
ptp_ocp_art_sma_get(struct ptp_ocp *bp, int sma_nr)
{
if (bp->sma[sma_nr - 1].fixed_fcn)
return bp->sma[sma_nr - 1].default_fcn;
return ioread32(&bp->art_sma->map[sma_nr - 1].gpio) & 0xff;
}
/* note: store 0 is considered invalid. */
static int
ptp_ocp_art_sma_set(struct ptp_ocp *bp, int sma_nr, u32 val)
{
unsigned long flags;
u32 __iomem *gpio;
int err = 0;
u32 reg;
val &= SMA_SELECT_MASK;
if (hweight32(val) > 1)
return -EINVAL;
gpio = &bp->art_sma->map[sma_nr - 1].gpio;
spin_lock_irqsave(&bp->lock, flags);
reg = ioread32(gpio);
if (((reg >> 16) & val) == 0) {
err = -EOPNOTSUPP;
} else {
reg = (reg & 0xff00) | (val & 0xff);
iowrite32(reg, gpio);
}
spin_unlock_irqrestore(&bp->lock, flags);
return err;
}
static const struct ocp_sma_op ocp_art_sma_op = {
.tbl = { ptp_ocp_art_sma_in, ptp_ocp_art_sma_out },
.init = ptp_ocp_art_sma_init,
.get = ptp_ocp_art_sma_get,
.set_inputs = ptp_ocp_art_sma_set,
.set_output = ptp_ocp_art_sma_set,
};
/* ART specific board initializers; last "resource" registered. */
static int
ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r)
{
int err;
bp->flash_start = 0x1000000;
bp->eeprom_map = art_eeprom_map;
bp->fw_cap = OCP_CAP_BASIC;
bp->fw_version = ioread32(&bp->reg->version);
bp->fw_tag = 2;
bp->sma_op = &ocp_art_sma_op;
/* Enable MAC serial port during initialisation */
iowrite32(1, &bp->board_config->mro50_serial_activate);
err = ptp_ocp_set_pins(bp);
if (err)
return err;
ptp_ocp_sma_init(bp);
err = ptp_ocp_attr_group_add(bp, art_timecard_groups);
if (err)
return err;
return ptp_ocp_init_clock(bp);
}
static ssize_t
ptp_ocp_show_output(const struct ocp_selector *tbl, u32 val, char *buf,
int def_val)
{
const char *name;
ssize_t count;
count = sysfs_emit(buf, "OUT: ");
name = ptp_ocp_select_name_from_val(tbl, val);
if (!name)
name = ptp_ocp_select_name_from_val(tbl, def_val);
count += sysfs_emit_at(buf, count, "%s\n", name);
return count;
}
static ssize_t
ptp_ocp_show_inputs(const struct ocp_selector *tbl, u32 val, char *buf,
int def_val)
{
const char *name;
ssize_t count;
int i;
count = sysfs_emit(buf, "IN: ");
for (i = 0; tbl[i].name; i++) {
if (val & tbl[i].value) {
name = tbl[i].name;
count += sysfs_emit_at(buf, count, "%s ", name);
}
}
if (!val && def_val >= 0) {
name = ptp_ocp_select_name_from_val(tbl, def_val);
count += sysfs_emit_at(buf, count, "%s ", name);
}
if (count)
count--;
count += sysfs_emit_at(buf, count, "\n");
return count;
}
static int
sma_parse_inputs(const struct ocp_selector * const tbl[], const char *buf,
enum ptp_ocp_sma_mode *mode)
{
int idx, count, dir;
char **argv;
int ret;
argv = argv_split(GFP_KERNEL, buf, &count);
if (!argv)
return -ENOMEM;
ret = -EINVAL;
if (!count)
goto out;
idx = 0;
dir = *mode == SMA_MODE_IN ? 0 : 1;
if (!strcasecmp("IN:", argv[0])) {
dir = 0;
idx++;
}
if (!strcasecmp("OUT:", argv[0])) {
dir = 1;
idx++;
}
*mode = dir == 0 ? SMA_MODE_IN : SMA_MODE_OUT;
ret = 0;
for (; idx < count; idx++)
ret |= ptp_ocp_select_val_from_name(tbl[dir], argv[idx]);
if (ret < 0)
ret = -EINVAL;
out:
argv_free(argv);
return ret;
}
static ssize_t
ptp_ocp_sma_show(struct ptp_ocp *bp, int sma_nr, char *buf,
int default_in_val, int default_out_val)
{
struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1];
const struct ocp_selector * const *tbl;
u32 val;
tbl = bp->sma_op->tbl;
val = ptp_ocp_sma_get(bp, sma_nr) & SMA_SELECT_MASK;
if (sma->mode == SMA_MODE_IN) {
if (sma->disabled)
val = SMA_DISABLE;
return ptp_ocp_show_inputs(tbl[0], val, buf, default_in_val);
}
return ptp_ocp_show_output(tbl[1], val, buf, default_out_val);
}
static ssize_t
sma1_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_sma_show(bp, 1, buf, 0, 1);
}
static ssize_t
sma2_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_sma_show(bp, 2, buf, -1, 1);
}
static ssize_t
sma3_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_sma_show(bp, 3, buf, -1, 0);
}
static ssize_t
sma4_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_sma_show(bp, 4, buf, -1, 1);
}
static int
ptp_ocp_sma_store_val(struct ptp_ocp *bp, int val, enum ptp_ocp_sma_mode mode, int sma_nr)
{
struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1];
if (sma->fixed_dir && (mode != sma->mode || val & SMA_DISABLE))
return -EOPNOTSUPP;
if (sma->fixed_fcn) {
if (val != sma->default_fcn)
return -EOPNOTSUPP;
return 0;
}
sma->disabled = !!(val & SMA_DISABLE);
if (mode != sma->mode) {
if (mode == SMA_MODE_IN)
ptp_ocp_sma_set_output(bp, sma_nr, 0);
else
ptp_ocp_sma_set_inputs(bp, sma_nr, 0);
sma->mode = mode;
}
if (!sma->fixed_dir)
val |= SMA_ENABLE; /* add enable bit */
if (sma->disabled)
val = 0;
if (mode == SMA_MODE_IN)
val = ptp_ocp_sma_set_inputs(bp, sma_nr, val);
else
val = ptp_ocp_sma_set_output(bp, sma_nr, val);
return val;
}
static int
ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr)
{
struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1];
enum ptp_ocp_sma_mode mode;
int val;
mode = sma->mode;
val = sma_parse_inputs(bp->sma_op->tbl, buf, &mode);
if (val < 0)
return val;
return ptp_ocp_sma_store_val(bp, val, mode, sma_nr);
}
static ssize_t
sma1_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
err = ptp_ocp_sma_store(bp, buf, 1);
return err ? err : count;
}
static ssize_t
sma2_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
err = ptp_ocp_sma_store(bp, buf, 2);
return err ? err : count;
}
static ssize_t
sma3_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
err = ptp_ocp_sma_store(bp, buf, 3);
return err ? err : count;
}
static ssize_t
sma4_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
err = ptp_ocp_sma_store(bp, buf, 4);
return err ? err : count;
}
static DEVICE_ATTR_RW(sma1);
static DEVICE_ATTR_RW(sma2);
static DEVICE_ATTR_RW(sma3);
static DEVICE_ATTR_RW(sma4);
static ssize_t
available_sma_inputs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_select_table_show(bp->sma_op->tbl[0], buf);
}
static DEVICE_ATTR_RO(available_sma_inputs);
static ssize_t
available_sma_outputs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return ptp_ocp_select_table_show(bp->sma_op->tbl[1], buf);
}
static DEVICE_ATTR_RO(available_sma_outputs);
#define EXT_ATTR_RO(_group, _name, _val) \
struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \
{ __ATTR_RO(_name), (void *)_val }
#define EXT_ATTR_RW(_group, _name, _val) \
struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \
{ __ATTR_RW(_name), (void *)_val }
#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr)
/* period [duty [phase [polarity]]] */
static ssize_t
signal_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
struct ptp_ocp_signal s = { };
int gen = (uintptr_t)ea->var;
int argc, err;
char **argv;
argv = argv_split(GFP_KERNEL, buf, &argc);
if (!argv)
return -ENOMEM;
err = -EINVAL;
s.duty = bp->signal[gen].duty;
s.phase = bp->signal[gen].phase;
s.period = bp->signal[gen].period;
s.polarity = bp->signal[gen].polarity;
switch (argc) {
case 4:
argc--;
err = kstrtobool(argv[argc], &s.polarity);
if (err)
goto out;
fallthrough;
case 3:
argc--;
err = kstrtou64(argv[argc], 0, &s.phase);
if (err)
goto out;
fallthrough;
case 2:
argc--;
err = kstrtoint(argv[argc], 0, &s.duty);
if (err)
goto out;
fallthrough;
case 1:
argc--;
err = kstrtou64(argv[argc], 0, &s.period);
if (err)
goto out;
break;
default:
goto out;
}
err = ptp_ocp_signal_set(bp, gen, &s);
if (err)
goto out;
err = ptp_ocp_signal_enable(bp->signal_out[gen], gen, s.period != 0);
out:
argv_free(argv);
return err ? err : count;
}
static ssize_t
signal_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
struct ptp_ocp_signal *signal;
struct timespec64 ts;
ssize_t count;
int i;
i = (uintptr_t)ea->var;
signal = &bp->signal[i];
count = sysfs_emit(buf, "%llu %d %llu %d", signal->period,
signal->duty, signal->phase, signal->polarity);
ts = ktime_to_timespec64(signal->start);
count += sysfs_emit_at(buf, count, " %ptT TAI\n", &ts);
return count;
}
static EXT_ATTR_RW(signal, signal, 0);
static EXT_ATTR_RW(signal, signal, 1);
static EXT_ATTR_RW(signal, signal, 2);
static EXT_ATTR_RW(signal, signal, 3);
static ssize_t
duty_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
return sysfs_emit(buf, "%d\n", bp->signal[i].duty);
}
static EXT_ATTR_RO(signal, duty, 0);
static EXT_ATTR_RO(signal, duty, 1);
static EXT_ATTR_RO(signal, duty, 2);
static EXT_ATTR_RO(signal, duty, 3);
static ssize_t
period_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
return sysfs_emit(buf, "%llu\n", bp->signal[i].period);
}
static EXT_ATTR_RO(signal, period, 0);
static EXT_ATTR_RO(signal, period, 1);
static EXT_ATTR_RO(signal, period, 2);
static EXT_ATTR_RO(signal, period, 3);
static ssize_t
phase_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
return sysfs_emit(buf, "%llu\n", bp->signal[i].phase);
}
static EXT_ATTR_RO(signal, phase, 0);
static EXT_ATTR_RO(signal, phase, 1);
static EXT_ATTR_RO(signal, phase, 2);
static EXT_ATTR_RO(signal, phase, 3);
static ssize_t
polarity_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
return sysfs_emit(buf, "%d\n", bp->signal[i].polarity);
}
static EXT_ATTR_RO(signal, polarity, 0);
static EXT_ATTR_RO(signal, polarity, 1);
static EXT_ATTR_RO(signal, polarity, 2);
static EXT_ATTR_RO(signal, polarity, 3);
static ssize_t
running_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
return sysfs_emit(buf, "%d\n", bp->signal[i].running);
}
static EXT_ATTR_RO(signal, running, 0);
static EXT_ATTR_RO(signal, running, 1);
static EXT_ATTR_RO(signal, running, 2);
static EXT_ATTR_RO(signal, running, 3);
static ssize_t
start_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int i = (uintptr_t)ea->var;
struct timespec64 ts;
ts = ktime_to_timespec64(bp->signal[i].start);
return sysfs_emit(buf, "%llu.%lu\n", ts.tv_sec, ts.tv_nsec);
}
static EXT_ATTR_RO(signal, start, 0);
static EXT_ATTR_RO(signal, start, 1);
static EXT_ATTR_RO(signal, start, 2);
static EXT_ATTR_RO(signal, start, 3);
static ssize_t
seconds_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int idx = (uintptr_t)ea->var;
u32 val;
int err;
err = kstrtou32(buf, 0, &val);
if (err)
return err;
if (val > 0xff)
return -EINVAL;
if (val)
val = (val << 8) | 0x1;
iowrite32(val, &bp->freq_in[idx]->ctrl);
return count;
}
static ssize_t
seconds_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int idx = (uintptr_t)ea->var;
u32 val;
val = ioread32(&bp->freq_in[idx]->ctrl);
if (val & 1)
val = (val >> 8) & 0xff;
else
val = 0;
return sysfs_emit(buf, "%u\n", val);
}
static EXT_ATTR_RW(freq, seconds, 0);
static EXT_ATTR_RW(freq, seconds, 1);
static EXT_ATTR_RW(freq, seconds, 2);
static EXT_ATTR_RW(freq, seconds, 3);
static ssize_t
frequency_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct dev_ext_attribute *ea = to_ext_attr(attr);
struct ptp_ocp *bp = dev_get_drvdata(dev);
int idx = (uintptr_t)ea->var;
u32 val;
val = ioread32(&bp->freq_in[idx]->status);
if (val & FREQ_STATUS_ERROR)
return sysfs_emit(buf, "error\n");
if (val & FREQ_STATUS_OVERRUN)
return sysfs_emit(buf, "overrun\n");
if (val & FREQ_STATUS_VALID)
return sysfs_emit(buf, "%lu\n", val & FREQ_STATUS_MASK);
return 0;
}
static EXT_ATTR_RO(freq, frequency, 0);
static EXT_ATTR_RO(freq, frequency, 1);
static EXT_ATTR_RO(freq, frequency, 2);
static EXT_ATTR_RO(freq, frequency, 3);
static ssize_t
serialnum_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
if (!bp->has_eeprom_data)
ptp_ocp_read_eeprom(bp);
return sysfs_emit(buf, "%pM\n", bp->serial);
}
static DEVICE_ATTR_RO(serialnum);
static ssize_t
gnss_sync_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
ssize_t ret;
if (bp->gnss_lost)
ret = sysfs_emit(buf, "LOST @ %ptT\n", &bp->gnss_lost);
else
ret = sysfs_emit(buf, "SYNC\n");
return ret;
}
static DEVICE_ATTR_RO(gnss_sync);
static ssize_t
utc_tai_offset_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", bp->utc_tai_offset);
}
static ssize_t
utc_tai_offset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
u32 val;
err = kstrtou32(buf, 0, &val);
if (err)
return err;
ptp_ocp_utc_distribute(bp, val);
return count;
}
static DEVICE_ATTR_RW(utc_tai_offset);
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
static ssize_t
ts_window_adjust_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", bp->ts_window_adjust);
}
static ssize_t
ts_window_adjust_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
int err;
u32 val;
err = kstrtou32(buf, 0, &val);
if (err)
return err;
bp->ts_window_adjust = val;
return count;
}
static DEVICE_ATTR_RW(ts_window_adjust);
static ssize_t
irig_b_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
u32 val;
val = ioread32(&bp->irig_out->ctrl);
val = (val >> 16) & 0x07;
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t
irig_b_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
unsigned long flags;
int err;
u32 reg;
u8 val;
err = kstrtou8(buf, 0, &val);
if (err)
return err;
if (val > 7)
return -EINVAL;
reg = ((val & 0x7) << 16);
spin_lock_irqsave(&bp->lock, flags);
iowrite32(0, &bp->irig_out->ctrl); /* disable */
iowrite32(reg, &bp->irig_out->ctrl); /* change mode */
iowrite32(reg | IRIG_M_CTRL_ENABLE, &bp->irig_out->ctrl);
spin_unlock_irqrestore(&bp->lock, flags);
return count;
}
static DEVICE_ATTR_RW(irig_b_mode);
static ssize_t
clock_source_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
const char *p;
u32 select;
select = ioread32(&bp->reg->select);
p = ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16);
return sysfs_emit(buf, "%s\n", p);
}
static ssize_t
clock_source_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
unsigned long flags;
int val;
val = ptp_ocp_select_val_from_name(ptp_ocp_clock, buf);
if (val < 0)
return val;
spin_lock_irqsave(&bp->lock, flags);
iowrite32(val, &bp->reg->select);
spin_unlock_irqrestore(&bp->lock, flags);
return count;
}
static DEVICE_ATTR_RW(clock_source);
static ssize_t
available_clock_sources_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return ptp_ocp_select_table_show(ptp_ocp_clock, buf);
}
static DEVICE_ATTR_RO(available_clock_sources);
static ssize_t
clock_status_drift_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
u32 val;
int res;
val = ioread32(&bp->reg->status_drift);
res = (val & ~INT_MAX) ? -1 : 1;
res *= (val & INT_MAX);
return sysfs_emit(buf, "%d\n", res);
}
static DEVICE_ATTR_RO(clock_status_drift);
static ssize_t
clock_status_offset_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
u32 val;
int res;
val = ioread32(&bp->reg->status_offset);
res = (val & ~INT_MAX) ? -1 : 1;
res *= (val & INT_MAX);
return sysfs_emit(buf, "%d\n", res);
}
static DEVICE_ATTR_RO(clock_status_offset);
static ssize_t
tod_correction_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
u32 val;
int res;
val = ioread32(&bp->tod->adj_sec);
res = (val & ~INT_MAX) ? -1 : 1;
res *= (val & INT_MAX);
return sysfs_emit(buf, "%d\n", res);
}
static ssize_t
tod_correction_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
unsigned long flags;
int err, res;
u32 val = 0;
err = kstrtos32(buf, 0, &res);
if (err)
return err;
if (res < 0) {
res *= -1;
val |= BIT(31);
}
val |= res;
spin_lock_irqsave(&bp->lock, flags);
iowrite32(val, &bp->tod->adj_sec);
spin_unlock_irqrestore(&bp->lock, flags);
return count;
}
static DEVICE_ATTR_RW(tod_correction);
#define _DEVICE_SIGNAL_GROUP_ATTRS(_nr) \
static struct attribute *fb_timecard_signal##_nr##_attrs[] = { \
&dev_attr_signal##_nr##_signal.attr.attr, \
&dev_attr_signal##_nr##_duty.attr.attr, \
&dev_attr_signal##_nr##_phase.attr.attr, \
&dev_attr_signal##_nr##_period.attr.attr, \
&dev_attr_signal##_nr##_polarity.attr.attr, \
&dev_attr_signal##_nr##_running.attr.attr, \
&dev_attr_signal##_nr##_start.attr.attr, \
NULL, \
}
#define DEVICE_SIGNAL_GROUP(_name, _nr) \
_DEVICE_SIGNAL_GROUP_ATTRS(_nr); \
static const struct attribute_group \
fb_timecard_signal##_nr##_group = { \
.name = #_name, \
.attrs = fb_timecard_signal##_nr##_attrs, \
}
DEVICE_SIGNAL_GROUP(gen1, 0);
DEVICE_SIGNAL_GROUP(gen2, 1);
DEVICE_SIGNAL_GROUP(gen3, 2);
DEVICE_SIGNAL_GROUP(gen4, 3);
#define _DEVICE_FREQ_GROUP_ATTRS(_nr) \
static struct attribute *fb_timecard_freq##_nr##_attrs[] = { \
&dev_attr_freq##_nr##_seconds.attr.attr, \
&dev_attr_freq##_nr##_frequency.attr.attr, \
NULL, \
}
#define DEVICE_FREQ_GROUP(_name, _nr) \
_DEVICE_FREQ_GROUP_ATTRS(_nr); \
static const struct attribute_group \
fb_timecard_freq##_nr##_group = { \
.name = #_name, \
.attrs = fb_timecard_freq##_nr##_attrs, \
}
DEVICE_FREQ_GROUP(freq1, 0);
DEVICE_FREQ_GROUP(freq2, 1);
DEVICE_FREQ_GROUP(freq3, 2);
DEVICE_FREQ_GROUP(freq4, 3);
static ssize_t
disciplining_config_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
size_t size = OCP_ART_CONFIG_SIZE;
struct nvmem_device *nvmem;
ssize_t err;
nvmem = ptp_ocp_nvmem_device_get(bp, NULL);
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
if (off > size) {
err = 0;
goto out;
}
if (off + count > size)
count = size - off;
// the configuration is in the very beginning of the EEPROM
err = nvmem_device_read(nvmem, off, count, buf);
if (err != count) {
err = -EFAULT;
goto out;
}
out:
ptp_ocp_nvmem_device_put(&nvmem);
return err;
}
static ssize_t
disciplining_config_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
struct nvmem_device *nvmem;
ssize_t err;
/* Allow write of the whole area only */
if (off || count != OCP_ART_CONFIG_SIZE)
return -EFAULT;
nvmem = ptp_ocp_nvmem_device_get(bp, NULL);
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
err = nvmem_device_write(nvmem, 0x00, count, buf);
if (err != count)
err = -EFAULT;
ptp_ocp_nvmem_device_put(&nvmem);
return err;
}
static BIN_ATTR_RW(disciplining_config, OCP_ART_CONFIG_SIZE);
static ssize_t
temperature_table_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
size_t size = OCP_ART_TEMP_TABLE_SIZE;
struct nvmem_device *nvmem;
ssize_t err;
nvmem = ptp_ocp_nvmem_device_get(bp, NULL);
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
if (off > size) {
err = 0;
goto out;
}
if (off + count > size)
count = size - off;
// the configuration is in the very beginning of the EEPROM
err = nvmem_device_read(nvmem, 0x90 + off, count, buf);
if (err != count) {
err = -EFAULT;
goto out;
}
out:
ptp_ocp_nvmem_device_put(&nvmem);
return err;
}
static ssize_t
temperature_table_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
struct nvmem_device *nvmem;
ssize_t err;
/* Allow write of the whole area only */
if (off || count != OCP_ART_TEMP_TABLE_SIZE)
return -EFAULT;
nvmem = ptp_ocp_nvmem_device_get(bp, NULL);
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
err = nvmem_device_write(nvmem, 0x90, count, buf);
if (err != count)
err = -EFAULT;
ptp_ocp_nvmem_device_put(&nvmem);
return err;
}
static BIN_ATTR_RW(temperature_table, OCP_ART_TEMP_TABLE_SIZE);
static struct attribute *fb_timecard_attrs[] = {
&dev_attr_serialnum.attr,
&dev_attr_gnss_sync.attr,
&dev_attr_clock_source.attr,
&dev_attr_available_clock_sources.attr,
&dev_attr_sma1.attr,
&dev_attr_sma2.attr,
&dev_attr_sma3.attr,
&dev_attr_sma4.attr,
&dev_attr_available_sma_inputs.attr,
&dev_attr_available_sma_outputs.attr,
&dev_attr_clock_status_drift.attr,
&dev_attr_clock_status_offset.attr,
&dev_attr_irig_b_mode.attr,
&dev_attr_utc_tai_offset.attr,
ptp: ocp: Add timestamp window adjustment The following process is used to read the PHC clock and correlate the reading with the "correct" system time. - get starting timestamp - issue PCI write command - issue PCI read command - get ending timestamp - read latched sec/nsec registers The write command is posted to PCI bus and returns. When the write arrives at the FPGA, the PHC time is latched into the sec/nsec registers, and a flag is set indicating the registers are valid. The read command returns this flag, and the time retrieval proceeds. Below is a non-scaled picture of the timing diagram involved. The PHC time corresponds to some SYS time between [start, end]. Userspace usually uses the midpoint between [start, end] to estimate the PCI delay and match this with the PHC time. [start] | | write |-------+ | | \ | read |----+ +----->| | \ * PHC time latched into register | \ | midpoint | +------->| | | | | | +----| | / | |<--------+ | [end] | | As the diagram indicates, the PHC time is latched before the midpoint, so the system clock time is slightly off the real PHC time. This shows up as a phase error with an oscilliscope. The workaround here is to provide a tunable which reduces (shrinks) the end time in the above diagram. This in turn moves the calculated midpoint so the system time and PHC time are in agreemment. Currently, the adjustment reduces the end time by 3/16th of the entire window. E.g.: [start, end] ==> [start, (end - (3/16 * end)], which produces reasonably good results. Also reduce delays by just writing to the clock control register instead of performing a read/modify/write sequence, as the contents of the control register are known. Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-09-15 02:16:35 +00:00
&dev_attr_ts_window_adjust.attr,
&dev_attr_tod_correction.attr,
NULL,
};
static const struct attribute_group fb_timecard_group = {
.attrs = fb_timecard_attrs,
};
static const struct ocp_attr_group fb_timecard_groups[] = {
{ .cap = OCP_CAP_BASIC, .group = &fb_timecard_group },
{ .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group },
{ .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group },
{ .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal2_group },
{ .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal3_group },
{ .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group },
{ .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group },
{ .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq2_group },
{ .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq3_group },
{ },
};
static struct attribute *art_timecard_attrs[] = {
&dev_attr_serialnum.attr,
&dev_attr_clock_source.attr,
&dev_attr_available_clock_sources.attr,
&dev_attr_utc_tai_offset.attr,
&dev_attr_ts_window_adjust.attr,
&dev_attr_sma1.attr,
&dev_attr_sma2.attr,
&dev_attr_sma3.attr,
&dev_attr_sma4.attr,
&dev_attr_available_sma_inputs.attr,
&dev_attr_available_sma_outputs.attr,
NULL,
};
static struct bin_attribute *bin_art_timecard_attrs[] = {
&bin_attr_disciplining_config,
&bin_attr_temperature_table,
NULL,
};
static const struct attribute_group art_timecard_group = {
.attrs = art_timecard_attrs,
.bin_attrs = bin_art_timecard_attrs,
};
static const struct ocp_attr_group art_timecard_groups[] = {
{ .cap = OCP_CAP_BASIC, .group = &art_timecard_group },
{ },
};
static void
gpio_input_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit,
const char *def)
{
int i;
for (i = 0; i < 4; i++) {
if (bp->sma[i].mode != SMA_MODE_IN)
continue;
if (map[i][0] & (1 << bit)) {
sprintf(buf, "sma%d", i + 1);
return;
}
}
if (!def)
def = "----";
strcpy(buf, def);
}
static void
gpio_output_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit)
{
char *ans = buf;
int i;
strcpy(ans, "----");
for (i = 0; i < 4; i++) {
if (bp->sma[i].mode != SMA_MODE_OUT)
continue;
if (map[i][1] & (1 << bit))
ans += sprintf(ans, "sma%d ", i + 1);
}
}
static void
_signal_summary_show(struct seq_file *s, struct ptp_ocp *bp, int nr)
{
struct signal_reg __iomem *reg = bp->signal_out[nr]->mem;
struct ptp_ocp_signal *signal = &bp->signal[nr];
char label[8];
bool on;
u32 val;
if (!signal)
return;
on = signal->running;
sprintf(label, "GEN%d", nr + 1);
seq_printf(s, "%7s: %s, period:%llu duty:%d%% phase:%llu pol:%d",
label, on ? " ON" : "OFF",
signal->period, signal->duty, signal->phase,
signal->polarity);
val = ioread32(&reg->enable);
seq_printf(s, " [%x", val);
val = ioread32(&reg->status);
seq_printf(s, " %x]", val);
seq_printf(s, " start:%llu\n", signal->start);
}
static void
_frequency_summary_show(struct seq_file *s, int nr,
struct frequency_reg __iomem *reg)
{
char label[8];
bool on;
u32 val;
if (!reg)
return;
sprintf(label, "FREQ%d", nr + 1);
val = ioread32(&reg->ctrl);
on = val & 1;
val = (val >> 8) & 0xff;
seq_printf(s, "%7s: %s, sec:%u",
label,
on ? " ON" : "OFF",
val);
val = ioread32(&reg->status);
if (val & FREQ_STATUS_ERROR)
seq_printf(s, ", error");
if (val & FREQ_STATUS_OVERRUN)
seq_printf(s, ", overrun");
if (val & FREQ_STATUS_VALID)
seq_printf(s, ", freq %lu Hz", val & FREQ_STATUS_MASK);
seq_printf(s, " reg:%x\n", val);
}
static int
ptp_ocp_summary_show(struct seq_file *s, void *data)
{
struct device *dev = s->private;
struct ptp_system_timestamp sts;
struct ts_reg __iomem *ts_reg;
char *buf, *src, *mac_src;
struct timespec64 ts;
struct ptp_ocp *bp;
u16 sma_val[4][2];
u32 ctrl, val;
bool on, map;
int i;
buf = (char *)__get_free_page(GFP_KERNEL);
if (!buf)
return -ENOMEM;
bp = dev_get_drvdata(dev);
seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp));
if (bp->gnss_port.line != -1)
seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS1",
bp->gnss_port.line);
if (bp->gnss2_port.line != -1)
seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS2",
bp->gnss2_port.line);
if (bp->mac_port.line != -1)
seq_printf(s, "%7s: /dev/ttyS%d\n", "MAC", bp->mac_port.line);
if (bp->nmea_port.line != -1)
seq_printf(s, "%7s: /dev/ttyS%d\n", "NMEA", bp->nmea_port.line);
memset(sma_val, 0xff, sizeof(sma_val));
if (bp->sma_map1) {
u32 reg;
reg = ioread32(&bp->sma_map1->gpio1);
sma_val[0][0] = reg & 0xffff;
sma_val[1][0] = reg >> 16;
reg = ioread32(&bp->sma_map1->gpio2);
sma_val[2][1] = reg & 0xffff;
sma_val[3][1] = reg >> 16;
reg = ioread32(&bp->sma_map2->gpio1);
sma_val[2][0] = reg & 0xffff;
sma_val[3][0] = reg >> 16;
reg = ioread32(&bp->sma_map2->gpio2);
sma_val[0][1] = reg & 0xffff;
sma_val[1][1] = reg >> 16;
}
sma1_show(dev, NULL, buf);
seq_printf(s, " sma1: %04x,%04x %s",
sma_val[0][0], sma_val[0][1], buf);
sma2_show(dev, NULL, buf);
seq_printf(s, " sma2: %04x,%04x %s",
sma_val[1][0], sma_val[1][1], buf);
sma3_show(dev, NULL, buf);
seq_printf(s, " sma3: %04x,%04x %s",
sma_val[2][0], sma_val[2][1], buf);
sma4_show(dev, NULL, buf);
seq_printf(s, " sma4: %04x,%04x %s",
sma_val[3][0], sma_val[3][1], buf);
if (bp->ts0) {
ts_reg = bp->ts0->mem;
on = ioread32(&ts_reg->enable);
src = "GNSS1";
seq_printf(s, "%7s: %s, src: %s\n", "TS0",
on ? " ON" : "OFF", src);
}
if (bp->ts1) {
ts_reg = bp->ts1->mem;
on = ioread32(&ts_reg->enable);
gpio_input_map(buf, bp, sma_val, 2, NULL);
seq_printf(s, "%7s: %s, src: %s\n", "TS1",
on ? " ON" : "OFF", buf);
}
if (bp->ts2) {
ts_reg = bp->ts2->mem;
on = ioread32(&ts_reg->enable);
gpio_input_map(buf, bp, sma_val, 3, NULL);
seq_printf(s, "%7s: %s, src: %s\n", "TS2",
on ? " ON" : "OFF", buf);
}
if (bp->ts3) {
ts_reg = bp->ts3->mem;
on = ioread32(&ts_reg->enable);
gpio_input_map(buf, bp, sma_val, 6, NULL);
seq_printf(s, "%7s: %s, src: %s\n", "TS3",
on ? " ON" : "OFF", buf);
}
if (bp->ts4) {
ts_reg = bp->ts4->mem;
on = ioread32(&ts_reg->enable);
gpio_input_map(buf, bp, sma_val, 7, NULL);
seq_printf(s, "%7s: %s, src: %s\n", "TS4",
on ? " ON" : "OFF", buf);
}
if (bp->pps) {
ts_reg = bp->pps->mem;
src = "PHC";
on = ioread32(&ts_reg->enable);
map = !!(bp->pps_req_map & OCP_REQ_TIMESTAMP);
seq_printf(s, "%7s: %s, src: %s\n", "TS5",
on && map ? " ON" : "OFF", src);
map = !!(bp->pps_req_map & OCP_REQ_PPS);
seq_printf(s, "%7s: %s, src: %s\n", "PPS",
on && map ? " ON" : "OFF", src);
}
if (bp->fw_cap & OCP_CAP_SIGNAL)
for (i = 0; i < 4; i++)
_signal_summary_show(s, bp, i);
if (bp->fw_cap & OCP_CAP_FREQ)
for (i = 0; i < 4; i++)
_frequency_summary_show(s, i, bp->freq_in[i]);
if (bp->irig_out) {
ctrl = ioread32(&bp->irig_out->ctrl);
on = ctrl & IRIG_M_CTRL_ENABLE;
val = ioread32(&bp->irig_out->status);
gpio_output_map(buf, bp, sma_val, 4);
seq_printf(s, "%7s: %s, error: %d, mode %d, out: %s\n", "IRIG",
on ? " ON" : "OFF", val, (ctrl >> 16), buf);
}
if (bp->irig_in) {
on = ioread32(&bp->irig_in->ctrl) & IRIG_S_CTRL_ENABLE;
val = ioread32(&bp->irig_in->status);
gpio_input_map(buf, bp, sma_val, 4, NULL);
seq_printf(s, "%7s: %s, error: %d, src: %s\n", "IRIG in",
on ? " ON" : "OFF", val, buf);
}
if (bp->dcf_out) {
on = ioread32(&bp->dcf_out->ctrl) & DCF_M_CTRL_ENABLE;
val = ioread32(&bp->dcf_out->status);
gpio_output_map(buf, bp, sma_val, 5);
seq_printf(s, "%7s: %s, error: %d, out: %s\n", "DCF",
on ? " ON" : "OFF", val, buf);
}
if (bp->dcf_in) {
on = ioread32(&bp->dcf_in->ctrl) & DCF_S_CTRL_ENABLE;
val = ioread32(&bp->dcf_in->status);
gpio_input_map(buf, bp, sma_val, 5, NULL);
seq_printf(s, "%7s: %s, error: %d, src: %s\n", "DCF in",
on ? " ON" : "OFF", val, buf);
}
if (bp->nmea_out) {
on = ioread32(&bp->nmea_out->ctrl) & 1;
val = ioread32(&bp->nmea_out->status);
seq_printf(s, "%7s: %s, error: %d\n", "NMEA",
on ? " ON" : "OFF", val);
}
/* compute src for PPS1, used below. */
if (bp->pps_select) {
val = ioread32(&bp->pps_select->gpio1);
src = &buf[80];
mac_src = "GNSS1";
if (val & 0x01) {
gpio_input_map(src, bp, sma_val, 0, NULL);
mac_src = src;
} else if (val & 0x02) {
src = "MAC";
} else if (val & 0x04) {
src = "GNSS1";
} else {
src = "----";
mac_src = src;
}
} else {
src = "?";
mac_src = src;
}
seq_printf(s, "MAC PPS1 src: %s\n", mac_src);
gpio_input_map(buf, bp, sma_val, 1, "GNSS2");
seq_printf(s, "MAC PPS2 src: %s\n", buf);
/* assumes automatic switchover/selection */
val = ioread32(&bp->reg->select);
switch (val >> 16) {
case 0:
sprintf(buf, "----");
break;
case 2:
sprintf(buf, "IRIG");
break;
case 3:
sprintf(buf, "%s via PPS1", src);
break;
case 6:
sprintf(buf, "DCF");
break;
default:
strcpy(buf, "unknown");
break;
}
seq_printf(s, "%7s: %s, state: %s\n", "PHC src", buf,
bp->sync ? "sync" : "unsynced");
if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts)) {
struct timespec64 sys_ts;
s64 pre_ns, post_ns, ns;
pre_ns = timespec64_to_ns(&sts.pre_ts);
post_ns = timespec64_to_ns(&sts.post_ts);
ns = (pre_ns + post_ns) / 2;
ns += (s64)bp->utc_tai_offset * NSEC_PER_SEC;
sys_ts = ns_to_timespec64(ns);
seq_printf(s, "%7s: %lld.%ld == %ptT TAI\n", "PHC",
ts.tv_sec, ts.tv_nsec, &ts);
seq_printf(s, "%7s: %lld.%ld == %ptT UTC offset %d\n", "SYS",
sys_ts.tv_sec, sys_ts.tv_nsec, &sys_ts,
bp->utc_tai_offset);
seq_printf(s, "%7s: PHC:SYS offset: %lld window: %lld\n", "",
timespec64_to_ns(&ts) - ns,
post_ns - pre_ns);
}
free_page((unsigned long)buf);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ptp_ocp_summary);
static int
ptp_ocp_tod_status_show(struct seq_file *s, void *data)
{
struct device *dev = s->private;
struct ptp_ocp *bp;
u32 val;
int idx;
bp = dev_get_drvdata(dev);
val = ioread32(&bp->tod->ctrl);
if (!(val & TOD_CTRL_ENABLE)) {
seq_printf(s, "TOD Slave disabled\n");
return 0;
}
seq_printf(s, "TOD Slave enabled, Control Register 0x%08X\n", val);
idx = val & TOD_CTRL_PROTOCOL ? 4 : 0;
idx += (val >> 16) & 3;
seq_printf(s, "Protocol %s\n", ptp_ocp_tod_proto_name(idx));
idx = (val >> TOD_CTRL_GNSS_SHIFT) & TOD_CTRL_GNSS_MASK;
seq_printf(s, "GNSS %s\n", ptp_ocp_tod_gnss_name(idx));
val = ioread32(&bp->tod->version);
seq_printf(s, "TOD Version %d.%d.%d\n",
val >> 24, (val >> 16) & 0xff, val & 0xffff);
val = ioread32(&bp->tod->status);
seq_printf(s, "Status register: 0x%08X\n", val);
val = ioread32(&bp->tod->adj_sec);
idx = (val & ~INT_MAX) ? -1 : 1;
idx *= (val & INT_MAX);
seq_printf(s, "Correction seconds: %d\n", idx);
val = ioread32(&bp->tod->utc_status);
seq_printf(s, "UTC status register: 0x%08X\n", val);
seq_printf(s, "UTC offset: %ld valid:%d\n",
val & TOD_STATUS_UTC_MASK, val & TOD_STATUS_UTC_VALID ? 1 : 0);
seq_printf(s, "Leap second info valid:%d, Leap second announce %d\n",
val & TOD_STATUS_LEAP_VALID ? 1 : 0,
val & TOD_STATUS_LEAP_ANNOUNCE ? 1 : 0);
val = ioread32(&bp->tod->leap);
seq_printf(s, "Time to next leap second (in sec): %d\n", (s32) val);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ptp_ocp_tod_status);
static struct dentry *ptp_ocp_debugfs_root;
static void
ptp_ocp_debugfs_add_device(struct ptp_ocp *bp)
{
struct dentry *d;
d = debugfs_create_dir(dev_name(&bp->dev), ptp_ocp_debugfs_root);
bp->debug_root = d;
debugfs_create_file("summary", 0444, bp->debug_root,
&bp->dev, &ptp_ocp_summary_fops);
if (bp->tod)
debugfs_create_file("tod_status", 0444, bp->debug_root,
&bp->dev, &ptp_ocp_tod_status_fops);
}
static void
ptp_ocp_debugfs_remove_device(struct ptp_ocp *bp)
{
debugfs_remove_recursive(bp->debug_root);
}
static void
ptp_ocp_debugfs_init(void)
{
ptp_ocp_debugfs_root = debugfs_create_dir("timecard", NULL);
}
static void
ptp_ocp_debugfs_fini(void)
{
debugfs_remove_recursive(ptp_ocp_debugfs_root);
}
static void
ptp_ocp_dev_release(struct device *dev)
{
struct ptp_ocp *bp = dev_get_drvdata(dev);
mutex_lock(&ptp_ocp_lock);
idr_remove(&ptp_ocp_idr, bp->id);
mutex_unlock(&ptp_ocp_lock);
}
static int
ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)
{
int err;
mutex_lock(&ptp_ocp_lock);
err = idr_alloc(&ptp_ocp_idr, bp, 0, 0, GFP_KERNEL);
mutex_unlock(&ptp_ocp_lock);
if (err < 0) {
dev_err(&pdev->dev, "idr_alloc failed: %d\n", err);
return err;
}
bp->id = err;
bp->ptp_info = ptp_ocp_clock_info;
spin_lock_init(&bp->lock);
bp->gnss_port.line = -1;
bp->gnss2_port.line = -1;
bp->mac_port.line = -1;
bp->nmea_port.line = -1;
bp->pdev = pdev;
device_initialize(&bp->dev);
dev_set_name(&bp->dev, "ocp%d", bp->id);
bp->dev.class = &timecard_class;
bp->dev.parent = &pdev->dev;
bp->dev.release = ptp_ocp_dev_release;
dev_set_drvdata(&bp->dev, bp);
err = device_add(&bp->dev);
if (err) {
dev_err(&bp->dev, "device add failed: %d\n", err);
goto out;
}
pci_set_drvdata(pdev, bp);
return 0;
out:
put_device(&bp->dev);
return err;
}
static void
ptp_ocp_symlink(struct ptp_ocp *bp, struct device *child, const char *link)
{
struct device *dev = &bp->dev;
if (sysfs_create_link(&dev->kobj, &child->kobj, link))
dev_err(dev, "%s symlink failed\n", link);
}
static void
ptp_ocp_link_child(struct ptp_ocp *bp, const char *name, const char *link)
{
struct device *dev, *child;
dev = &bp->pdev->dev;
child = device_find_child_by_name(dev, name);
if (!child) {
dev_err(dev, "Could not find device %s\n", name);
return;
}
ptp_ocp_symlink(bp, child, link);
put_device(child);
}
static int
ptp_ocp_complete(struct ptp_ocp *bp)
{
struct pps_device *pps;
char buf[32];
if (bp->gnss_port.line != -1) {
sprintf(buf, "ttyS%d", bp->gnss_port.line);
ptp_ocp_link_child(bp, buf, "ttyGNSS");
}
if (bp->gnss2_port.line != -1) {
sprintf(buf, "ttyS%d", bp->gnss2_port.line);
ptp_ocp_link_child(bp, buf, "ttyGNSS2");
}
if (bp->mac_port.line != -1) {
sprintf(buf, "ttyS%d", bp->mac_port.line);
ptp_ocp_link_child(bp, buf, "ttyMAC");
}
if (bp->nmea_port.line != -1) {
sprintf(buf, "ttyS%d", bp->nmea_port.line);
ptp_ocp_link_child(bp, buf, "ttyNMEA");
}
sprintf(buf, "ptp%d", ptp_clock_index(bp->ptp));
ptp_ocp_link_child(bp, buf, "ptp");
pps = pps_lookup_dev(bp->ptp);
if (pps)
ptp_ocp_symlink(bp, pps->dev, "pps");
ptp_ocp_debugfs_add_device(bp);
return 0;
}
static void
ptp_ocp_phc_info(struct ptp_ocp *bp)
{
struct timespec64 ts;
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,
ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16),
ptp_clock_index(bp->ptp));
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,
bp->sync ? "in-sync" : "UNSYNCED");
}
static void
ptp_ocp_serial_info(struct device *dev, const char *name, int port, int baud)
{
if (port != -1)
dev_info(dev, "%5s: /dev/ttyS%-2d @ %6d\n", name, port, baud);
}
static void
ptp_ocp_info(struct ptp_ocp *bp)
{
static int nmea_baud[] = {
1200, 2400, 4800, 9600, 19200, 38400,
57600, 115200, 230400, 460800, 921600,
1000000, 2000000
};
struct device *dev = &bp->pdev->dev;
u32 reg;
ptp_ocp_phc_info(bp);
ptp_ocp_serial_info(dev, "GNSS", bp->gnss_port.line,
bp->gnss_port.baud);
ptp_ocp_serial_info(dev, "GNSS2", bp->gnss2_port.line,
bp->gnss2_port.baud);
ptp_ocp_serial_info(dev, "MAC", bp->mac_port.line, bp->mac_port.baud);
if (bp->nmea_out && bp->nmea_port.line != -1) {
bp->nmea_port.baud = -1;
reg = ioread32(&bp->nmea_out->uart_baud);
if (reg < ARRAY_SIZE(nmea_baud))
bp->nmea_port.baud = nmea_baud[reg];
ptp_ocp_serial_info(dev, "NMEA", bp->nmea_port.line,
bp->nmea_port.baud);
}
}
static void
ptp_ocp_detach_sysfs(struct ptp_ocp *bp)
{
struct device *dev = &bp->dev;
sysfs_remove_link(&dev->kobj, "ttyGNSS");
sysfs_remove_link(&dev->kobj, "ttyGNSS2");
sysfs_remove_link(&dev->kobj, "ttyMAC");
sysfs_remove_link(&dev->kobj, "ptp");
sysfs_remove_link(&dev->kobj, "pps");
}
static void
ptp_ocp_detach(struct ptp_ocp *bp)
{
int i;
ptp_ocp_debugfs_remove_device(bp);
ptp_ocp_detach_sysfs(bp);
ptp_ocp_attr_group_del(bp);
if (timer_pending(&bp->watchdog))
del_timer_sync(&bp->watchdog);
if (bp->ts0)
ptp_ocp_unregister_ext(bp->ts0);
if (bp->ts1)
ptp_ocp_unregister_ext(bp->ts1);
if (bp->ts2)
ptp_ocp_unregister_ext(bp->ts2);
if (bp->ts3)
ptp_ocp_unregister_ext(bp->ts3);
if (bp->ts4)
ptp_ocp_unregister_ext(bp->ts4);
if (bp->pps)
ptp_ocp_unregister_ext(bp->pps);
for (i = 0; i < 4; i++)
if (bp->signal_out[i])
ptp_ocp_unregister_ext(bp->signal_out[i]);
if (bp->gnss_port.line != -1)
serial8250_unregister_port(bp->gnss_port.line);
if (bp->gnss2_port.line != -1)
serial8250_unregister_port(bp->gnss2_port.line);
if (bp->mac_port.line != -1)
serial8250_unregister_port(bp->mac_port.line);
if (bp->nmea_port.line != -1)
serial8250_unregister_port(bp->nmea_port.line);
platform_device_unregister(bp->spi_flash);
platform_device_unregister(bp->i2c_ctrl);
if (bp->i2c_clk)
clk_hw_unregister_fixed_rate(bp->i2c_clk);
if (bp->n_irqs)
pci_free_irq_vectors(bp->pdev);
if (bp->ptp)
ptp_clock_unregister(bp->ptp);
kfree(bp->ptp_info.pin_config);
device_unregister(&bp->dev);
}
static int ptp_ocp_dpll_lock_status_get(const struct dpll_device *dpll,
void *priv,
enum dpll_lock_status *status,
struct netlink_ext_ack *extack)
{
struct ptp_ocp *bp = priv;
*status = bp->sync ? DPLL_LOCK_STATUS_LOCKED : DPLL_LOCK_STATUS_UNLOCKED;
return 0;
}
static int ptp_ocp_dpll_state_get(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *priv,
enum dpll_pin_state *state,
struct netlink_ext_ack *extack)
{
struct ptp_ocp *bp = priv;
int idx;
if (bp->pps_select) {
idx = ioread32(&bp->pps_select->gpio1);
*state = (&bp->sma[idx] == pin_priv) ? DPLL_PIN_STATE_CONNECTED :
DPLL_PIN_STATE_SELECTABLE;
return 0;
}
NL_SET_ERR_MSG(extack, "pin selection is not supported on current HW");
return -EINVAL;
}
static int ptp_ocp_dpll_mode_get(const struct dpll_device *dpll, void *priv,
enum dpll_mode *mode, struct netlink_ext_ack *extack)
{
*mode = DPLL_MODE_AUTOMATIC;
return 0;
}
static int ptp_ocp_dpll_direction_get(const struct dpll_pin *pin,
void *pin_priv,
const struct dpll_device *dpll,
void *priv,
enum dpll_pin_direction *direction,
struct netlink_ext_ack *extack)
{
struct ptp_ocp_sma_connector *sma = pin_priv;
*direction = sma->mode == SMA_MODE_IN ?
DPLL_PIN_DIRECTION_INPUT :
DPLL_PIN_DIRECTION_OUTPUT;
return 0;
}
static int ptp_ocp_dpll_direction_set(const struct dpll_pin *pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv,
enum dpll_pin_direction direction,
struct netlink_ext_ack *extack)
{
struct ptp_ocp_sma_connector *sma = pin_priv;
struct ptp_ocp *bp = dpll_priv;
enum ptp_ocp_sma_mode mode;
int sma_nr = (sma - bp->sma);
if (sma->fixed_dir)
return -EOPNOTSUPP;
mode = direction == DPLL_PIN_DIRECTION_INPUT ?
SMA_MODE_IN : SMA_MODE_OUT;
return ptp_ocp_sma_store_val(bp, 0, mode, sma_nr);
}
static int ptp_ocp_dpll_frequency_set(const struct dpll_pin *pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv, u64 frequency,
struct netlink_ext_ack *extack)
{
struct ptp_ocp_sma_connector *sma = pin_priv;
struct ptp_ocp *bp = dpll_priv;
const struct ocp_selector *tbl;
int sma_nr = (sma - bp->sma);
int i;
if (sma->fixed_fcn)
return -EOPNOTSUPP;
tbl = bp->sma_op->tbl[sma->mode];
for (i = 0; tbl[i].name; i++)
if (tbl[i].frequency == frequency)
return ptp_ocp_sma_store_val(bp, i, sma->mode, sma_nr);
return -EINVAL;
}
static int ptp_ocp_dpll_frequency_get(const struct dpll_pin *pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv, u64 *frequency,
struct netlink_ext_ack *extack)
{
struct ptp_ocp_sma_connector *sma = pin_priv;
struct ptp_ocp *bp = dpll_priv;
const struct ocp_selector *tbl;
int sma_nr = (sma - bp->sma);
u32 val;
int i;
val = bp->sma_op->get(bp, sma_nr);
tbl = bp->sma_op->tbl[sma->mode];
for (i = 0; tbl[i].name; i++)
if (val == tbl[i].value) {
*frequency = tbl[i].frequency;
return 0;
}
return -EINVAL;
}
static const struct dpll_device_ops dpll_ops = {
.lock_status_get = ptp_ocp_dpll_lock_status_get,
.mode_get = ptp_ocp_dpll_mode_get,
};
static const struct dpll_pin_ops dpll_pins_ops = {
.frequency_get = ptp_ocp_dpll_frequency_get,
.frequency_set = ptp_ocp_dpll_frequency_set,
.direction_get = ptp_ocp_dpll_direction_get,
.direction_set = ptp_ocp_dpll_direction_set,
.state_on_dpll_get = ptp_ocp_dpll_state_get,
};
static void
ptp_ocp_sync_work(struct work_struct *work)
{
struct ptp_ocp *bp;
bool sync;
bp = container_of(work, struct ptp_ocp, sync_work.work);
sync = !!(ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC);
if (bp->sync != sync)
dpll_device_change_ntf(bp->dpll);
bp->sync = sync;
queue_delayed_work(system_power_efficient_wq, &bp->sync_work, HZ);
}
static int
ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct devlink *devlink;
struct ptp_ocp *bp;
int err, i;
u64 clkid;
devlink = devlink_alloc(&ptp_ocp_devlink_ops, sizeof(*bp), &pdev->dev);
if (!devlink) {
dev_err(&pdev->dev, "devlink_alloc failed\n");
return -ENOMEM;
}
err = pci_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "pci_enable_device\n");
goto out_free;
}
bp = devlink_priv(devlink);
err = ptp_ocp_device_init(bp, pdev);
if (err)
goto out_disable;
INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work);
/* compat mode.
* Older FPGA firmware only returns 2 irq's.
* allow this - if not all of the IRQ's are returned, skip the
* extra devices and just register the clock.
*/
err = pci_alloc_irq_vectors(pdev, 1, 17, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (err < 0) {
dev_err(&pdev->dev, "alloc_irq_vectors err: %d\n", err);
goto out;
}
bp->n_irqs = err;
pci_set_master(pdev);
err = ptp_ocp_register_resources(bp, id->driver_data);
if (err)
goto out;
bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev);
if (IS_ERR(bp->ptp)) {
err = PTR_ERR(bp->ptp);
dev_err(&pdev->dev, "ptp_clock_register: %d\n", err);
bp->ptp = NULL;
goto out;
}
err = ptp_ocp_complete(bp);
if (err)
goto out;
ptp_ocp_info(bp);
devlink_register(devlink);
clkid = pci_get_dsn(pdev);
bp->dpll = dpll_device_get(clkid, 0, THIS_MODULE);
if (IS_ERR(bp->dpll)) {
err = PTR_ERR(bp->dpll);
dev_err(&pdev->dev, "dpll_device_alloc failed\n");
goto out;
}
err = dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp);
if (err)
goto out;
for (i = 0; i < OCP_SMA_NUM; i++) {
bp->sma[i].dpll_pin = dpll_pin_get(clkid, i, THIS_MODULE, &bp->sma[i].dpll_prop);
if (IS_ERR(bp->sma[i].dpll_pin)) {
err = PTR_ERR(bp->sma[i].dpll_pin);
goto out_dpll;
}
err = dpll_pin_register(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops,
&bp->sma[i]);
if (err) {
dpll_pin_put(bp->sma[i].dpll_pin);
goto out_dpll;
}
}
queue_delayed_work(system_power_efficient_wq, &bp->sync_work, HZ);
return 0;
out_dpll:
while (i) {
--i;
dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->sma[i]);
dpll_pin_put(bp->sma[i].dpll_pin);
}
dpll_device_put(bp->dpll);
out:
ptp_ocp_detach(bp);
out_disable:
pci_disable_device(pdev);
out_free:
devlink_free(devlink);
return err;
}
static void
ptp_ocp_remove(struct pci_dev *pdev)
{
struct ptp_ocp *bp = pci_get_drvdata(pdev);
struct devlink *devlink = priv_to_devlink(bp);
int i;
cancel_delayed_work_sync(&bp->sync_work);
for (i = 0; i < OCP_SMA_NUM; i++) {
if (bp->sma[i].dpll_pin) {
dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->sma[i]);
dpll_pin_put(bp->sma[i].dpll_pin);
}
}
dpll_device_unregister(bp->dpll, &dpll_ops, bp);
dpll_device_put(bp->dpll);
devlink_unregister(devlink);
ptp_ocp_detach(bp);
pci_disable_device(pdev);
devlink_free(devlink);
}
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
ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
unsigned long action, void *data)
{
struct device *dev, *child = data;
struct ptp_ocp *bp;
bool add;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
case BUS_NOTIFY_DEL_DEVICE:
add = action == BUS_NOTIFY_ADD_DEVICE;
break;
default:
return 0;
}
if (!i2c_verify_adapter(child))
return 0;
dev = child;
while ((dev = dev->parent))
if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
goto found;
return 0;
found:
bp = dev_get_drvdata(dev);
if (add)
ptp_ocp_symlink(bp, child, "i2c");
else
sysfs_remove_link(&bp->dev.kobj, "i2c");
return 0;
}
static struct notifier_block ptp_ocp_i2c_notifier = {
.notifier_call = ptp_ocp_i2c_notifier_call,
};
static int __init
ptp_ocp_init(void)
{
const char *what;
int err;
ptp_ocp_debugfs_init();
what = "timecard class";
err = class_register(&timecard_class);
if (err)
goto out;
what = "i2c notifier";
err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
if (err)
goto out_notifier;
what = "ptp_ocp driver";
err = pci_register_driver(&ptp_ocp_driver);
if (err)
goto out_register;
return 0;
out_register:
bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
out_notifier:
class_unregister(&timecard_class);
out:
ptp_ocp_debugfs_fini();
pr_err(KBUILD_MODNAME ": failed to register %s: %d\n", what, err);
return err;
}
static void __exit
ptp_ocp_fini(void)
{
bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
pci_unregister_driver(&ptp_ocp_driver);
class_unregister(&timecard_class);
ptp_ocp_debugfs_fini();
}
module_init(ptp_ocp_init);
module_exit(ptp_ocp_fini);
MODULE_DESCRIPTION("OpenCompute TimeCard driver");
MODULE_LICENSE("GPL v2");