diff options
Diffstat (limited to 'drivers/ptp')
-rw-r--r-- | drivers/ptp/idt8a340_reg.h | 65 | ||||
-rw-r--r-- | drivers/ptp/ptp_clockmatrix.c | 766 | ||||
-rw-r--r-- | drivers/ptp/ptp_clockmatrix.h | 64 | ||||
-rw-r--r-- | drivers/ptp/ptp_ocp.c | 1312 |
4 files changed, 1706 insertions, 501 deletions
diff --git a/drivers/ptp/idt8a340_reg.h b/drivers/ptp/idt8a340_reg.h index ac524cf0f31f..1c5210187110 100644 --- a/drivers/ptp/idt8a340_reg.h +++ b/drivers/ptp/idt8a340_reg.h @@ -5,7 +5,7 @@ * https://github.com/richardcochran/regen * * Hand modified to include some HW registers. - * Based on 4.8.0, SCSR rev C commit a03c7ae5 + * Based on 5.2.0, Family Programming Guide (Sept 30, 2020) */ #ifndef HAVE_IDT8A340_REG #define HAVE_IDT8A340_REG @@ -100,6 +100,7 @@ #define RESET_CTRL 0xc000 #define SM_RESET 0x0012 +#define SM_RESET_V520 0x0013 #define SM_RESET_CMD 0x5A #define GENERAL_STATUS 0xc014 @@ -130,6 +131,8 @@ #define GPIO_USER_CONTROL 0xc160 #define GPIO0_TO_7_OUT 0x0000 #define GPIO8_TO_15_OUT 0x0001 +#define GPIO0_TO_7_OUT_V520 0x0002 +#define GPIO8_TO_15_OUT_V520 0x0003 #define STICKY_STATUS_CLEAR 0xc164 @@ -216,22 +219,27 @@ #define DPLL_REF_MODE 0x0035 #define DPLL_PHASE_MEASUREMENT_CFG 0x0036 #define DPLL_MODE 0x0037 +#define DPLL_MODE_V520 0x003B #define DPLL_1 0xc400 #define DPLL_2 0xc438 +#define DPLL_2_V520 0xc43c #define DPLL_3 0xc480 #define DPLL_4 0xc4b8 +#define DPLL_4_V520 0xc4bc #define DPLL_5 0xc500 #define DPLL_6 0xc538 +#define DPLL_6_V520 0xc53c #define DPLL_7 0xc580 #define SYS_DPLL 0xc5b8 +#define SYS_DPLL_V520 0xc5bc #define DPLL_CTRL_0 0xc600 #define DPLL_CTRL_DPLL_MANU_REF_CFG 0x0001 @@ -331,6 +339,7 @@ #define GPIO_ALERT_OUT_CFG 0x000e #define GPIO_TOD_NOTIFICATION_CFG 0x000f #define GPIO_CTRL 0x0010 +#define GPIO_CTRL_V520 0x0011 #define GPIO_1 0xc8d4 @@ -365,6 +374,7 @@ #define OUT_DIV_MUX 0xca12 #define OUTPUT_0 0xca14 +#define OUTPUT_0_V520 0xca20 /* FOD frequency output divider value */ #define OUT_DIV 0x0000 #define OUT_DUTY_CYCLE_HIGH 0x0004 @@ -374,28 +384,40 @@ #define OUT_PHASE_ADJ 0x000c #define OUTPUT_1 0xca24 +#define OUTPUT_1_V520 0xca30 #define OUTPUT_2 0xca34 +#define OUTPUT_2_V520 0xca40 #define OUTPUT_3 0xca44 +#define OUTPUT_3_V520 0xca50 #define OUTPUT_4 0xca54 +#define OUTPUT_4_V520 0xca60 #define OUTPUT_5 0xca64 +#define OUTPUT_5_V520 0xca80 #define OUTPUT_6 0xca80 +#define OUTPUT_6_V520 0xca90 #define OUTPUT_7 0xca90 +#define OUTPUT_7_V520 0xcaa0 #define OUTPUT_8 0xcaa0 +#define OUTPUT_8_V520 0xcab0 #define OUTPUT_9 0xcab0 +#define OUTPUT_9_V520 0xcac0 #define OUTPUT_10 0xcac0 +#define OUTPUT_10_V520 0xcad0 #define OUTPUT_11 0xcad0 +#define OUTPUT_11_V520 0xcae0 #define SERIAL 0xcae0 +#define SERIAL_V520 0xcaf0 #define PWM_ENCODER_0 0xcb00 @@ -416,50 +438,72 @@ #define PWM_DECODER_0 0xcb40 #define PWM_DECODER_1 0xcb48 +#define PWM_DECODER_1_V520 0xcb4a #define PWM_DECODER_2 0xcb50 +#define PWM_DECODER_2_V520 0xcb54 #define PWM_DECODER_3 0xcb58 +#define PWM_DECODER_3_V520 0xcb5e #define PWM_DECODER_4 0xcb60 +#define PWM_DECODER_4_V520 0xcb68 #define PWM_DECODER_5 0xcb68 +#define PWM_DECODER_5_V520 0xcb80 #define PWM_DECODER_6 0xcb70 +#define PWM_DECODER_6_V520 0xcb8a #define PWM_DECODER_7 0xcb80 +#define PWM_DECODER_7_V520 0xcb94 #define PWM_DECODER_8 0xcb88 +#define PWM_DECODER_8_V520 0xcb9e #define PWM_DECODER_9 0xcb90 +#define PWM_DECODER_9_V520 0xcba8 #define PWM_DECODER_10 0xcb98 +#define PWM_DECODER_10_V520 0xcbb2 #define PWM_DECODER_11 0xcba0 +#define PWM_DECODER_11_V520 0xcbbc #define PWM_DECODER_12 0xcba8 +#define PWM_DECODER_12_V520 0xcbc6 #define PWM_DECODER_13 0xcbb0 +#define PWM_DECODER_13_V520 0xcbd0 #define PWM_DECODER_14 0xcbb8 +#define PWM_DECODER_14_V520 0xcbda #define PWM_DECODER_15 0xcbc0 +#define PWM_DECODER_15_V520 0xcbe4 #define PWM_USER_DATA 0xcbc8 +#define PWM_USER_DATA_V520 0xcbf0 #define TOD_0 0xcbcc +#define TOD_0_V520 0xcc00 /* Enable TOD counter, output channel sync and even-PPS mode */ #define TOD_CFG 0x0000 +#define TOD_CFG_V520 0x0001 #define TOD_1 0xcbce +#define TOD_1_V520 0xcc02 #define TOD_2 0xcbd0 +#define TOD_2_V520 0xcc04 #define TOD_3 0xcbd2 +#define TOD_3_V520 0xcc06 #define TOD_WRITE_0 0xcc00 +#define TOD_WRITE_0_V520 0xcc10 /* 8-bit subns, 32-bit ns, 48-bit seconds */ #define TOD_WRITE 0x0000 /* Counter increments after TOD write is completed */ @@ -470,12 +514,16 @@ #define TOD_WRITE_CMD 0x000f #define TOD_WRITE_1 0xcc10 +#define TOD_WRITE_1_V520 0xcc20 #define TOD_WRITE_2 0xcc20 +#define TOD_WRITE_2_V520 0xcc30 #define TOD_WRITE_3 0xcc30 +#define TOD_WRITE_3_V520 0xcc40 #define TOD_READ_PRIMARY_0 0xcc40 +#define TOD_READ_PRIMARY_0_V520 0xcc50 /* 8-bit subns, 32-bit ns, 48-bit seconds */ #define TOD_READ_PRIMARY 0x0000 /* Counter increments after TOD write is completed */ @@ -484,22 +532,31 @@ #define TOD_READ_PRIMARY_SEL_CFG_0 0x000c /* Read trigger selection */ #define TOD_READ_PRIMARY_CMD 0x000e +#define TOD_READ_PRIMARY_CMD_V520 0x000f #define TOD_READ_PRIMARY_1 0xcc50 +#define TOD_READ_PRIMARY_1_V520 0xcc60 #define TOD_READ_PRIMARY_2 0xcc60 +#define TOD_READ_PRIMARY_2_V520 0xcc80 #define TOD_READ_PRIMARY_3 0xcc80 +#define TOD_READ_PRIMARY_3_V520 0xcc90 #define TOD_READ_SECONDARY_0 0xcc90 +#define TOD_READ_SECONDARY_0_V520 0xcca0 #define TOD_READ_SECONDARY_1 0xcca0 +#define TOD_READ_SECONDARY_1_V520 0xccb0 #define TOD_READ_SECONDARY_2 0xccb0 +#define TOD_READ_SECONDARY_2_V520 0xccc0 #define TOD_READ_SECONDARY_3 0xccc0 +#define TOD_READ_SECONDARY_3_V520 0xccd0 #define OUTPUT_TDC_CFG 0xccd0 +#define OUTPUT_TDC_CFG_V520 0xcce0 #define OUTPUT_TDC_0 0xcd00 @@ -512,8 +569,10 @@ #define INPUT_TDC 0xcd20 #define SCRATCH 0xcf50 +#define SCRATCH_V520 0xcf4c #define EEPROM 0xcf68 +#define EEPROM_V520 0xcf64 #define OTP 0xcf70 @@ -576,6 +635,10 @@ #define STATE_MODE_SHIFT (0) #define STATE_MODE_MASK (0x7) +/* Bit definitions for the DPLL_MANU_REF_CFG register */ +#define MANUAL_REFERENCE_SHIFT (0) +#define MANUAL_REFERENCE_MASK (0x1f) + /* Bit definitions for the GPIO_CFG_GBL register */ #define SUPPLY_MODE_SHIFT (0) #define SUPPLY_MODE_MASK (0x3) diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c index fa636951169e..1a2e3c2e4328 100644 --- a/drivers/ptp/ptp_clockmatrix.c +++ b/drivers/ptp/ptp_clockmatrix.c @@ -33,15 +33,23 @@ module_param(firmware, charp, 0); #define SETTIME_CORRECTION (0) -static int contains_full_configuration(const struct firmware *fw) +static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm); + +static int contains_full_configuration(struct idtcm *idtcm, + const struct firmware *fw) { - s32 full_count = FULL_FW_CFG_BYTES - FULL_FW_CFG_SKIPPED_BYTES; struct idtcm_fwrc *rec = (struct idtcm_fwrc *)fw->data; + u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH); + s32 full_count; s32 count = 0; u16 regaddr; u8 loaddr; s32 len; + /* 4 bytes skipped every 0x80 */ + full_count = (scratch - GPIO_USER_CONTROL) - + ((scratch >> 7) - (GPIO_USER_CONTROL >> 7)) * 4; + /* If the firmware contains 'full configuration' SM_RESET can be used * to ensure proper configuration. * @@ -57,7 +65,7 @@ static int contains_full_configuration(const struct firmware *fw) rec++; /* Top (status registers) and bottom are read-only */ - if (regaddr < GPIO_USER_CONTROL || regaddr >= SCRATCH) + if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch) continue; /* Page size 128, last 4 bytes of page skipped */ @@ -152,6 +160,19 @@ static int idtcm_strverscmp(const char *version1, const char *version2) return 0; } +static enum fw_version idtcm_fw_version(const char *version) +{ + enum fw_version ver = V_DEFAULT; + + if (idtcm_strverscmp(version, "4.8.7") >= 0) + ver = V487; + + if (idtcm_strverscmp(version, "5.2.0") >= 0) + ver = V520; + + return ver; +} + static int idtcm_xfer_read(struct idtcm *idtcm, u8 regaddr, u8 *buf, @@ -388,13 +409,14 @@ static int _idtcm_gettime(struct idtcm_channel *channel, struct timespec64 *ts) { struct idtcm *idtcm = channel->idtcm; + u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_PRIMARY_CMD); u8 buf[TOD_BYTE_COUNT]; u8 timeout = 10; u8 trigger; int err; err = idtcm_read(idtcm, channel->tod_read_primary, - TOD_READ_PRIMARY_CMD, &trigger, sizeof(trigger)); + tod_read_cmd, &trigger, sizeof(trigger)); if (err) return err; @@ -403,7 +425,7 @@ static int _idtcm_gettime(struct idtcm_channel *channel, trigger &= ~TOD_READ_TRIGGER_MODE; /* single shot */ err = idtcm_write(idtcm, channel->tod_read_primary, - TOD_READ_PRIMARY_CMD, &trigger, sizeof(trigger)); + tod_read_cmd, &trigger, sizeof(trigger)); if (err) return err; @@ -413,7 +435,7 @@ static int _idtcm_gettime(struct idtcm_channel *channel, idtcm->start_time = ktime_get_raw(); err = idtcm_read(idtcm, channel->tod_read_primary, - TOD_READ_PRIMARY_CMD, &trigger, + tod_read_cmd, &trigger, sizeof(trigger)); if (err) return err; @@ -559,35 +581,10 @@ static int _sync_pll_output(struct idtcm *idtcm, return err; } -static int sync_source_dpll_tod_pps(u16 tod_addr, u8 *sync_src) -{ - int err = 0; - - switch (tod_addr) { - case TOD_0: - *sync_src = SYNC_SOURCE_DPLL0_TOD_PPS; - break; - case TOD_1: - *sync_src = SYNC_SOURCE_DPLL1_TOD_PPS; - break; - case TOD_2: - *sync_src = SYNC_SOURCE_DPLL2_TOD_PPS; - break; - case TOD_3: - *sync_src = SYNC_SOURCE_DPLL3_TOD_PPS; - break; - default: - err = -EINVAL; - } - - return err; -} - static int idtcm_sync_pps_output(struct idtcm_channel *channel) { struct idtcm *idtcm = channel->idtcm; u8 pll; - u8 sync_src; u8 qn; u8 qn_plus_1; int err = 0; @@ -596,10 +593,6 @@ static int idtcm_sync_pps_output(struct idtcm_channel *channel) u8 temp; u16 output_mask = channel->output_mask; - err = sync_source_dpll_tod_pps(channel->tod_n, &sync_src); - if (err) - return err; - err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE, &temp, sizeof(temp)); if (err) @@ -655,8 +648,8 @@ static int idtcm_sync_pps_output(struct idtcm_channel *channel) } if (qn != 0 || qn_plus_1 != 0) - err = _sync_pll_output(idtcm, pll, sync_src, qn, - qn_plus_1); + err = _sync_pll_output(idtcm, pll, channel->sync_src, + qn, qn_plus_1); if (err) return err; @@ -666,8 +659,8 @@ static int idtcm_sync_pps_output(struct idtcm_channel *channel) } static int _idtcm_set_dpll_hw_tod(struct idtcm_channel *channel, - struct timespec64 const *ts, - enum hw_tod_write_trig_sel wr_trig) + struct timespec64 const *ts, + enum hw_tod_write_trig_sel wr_trig) { struct idtcm *idtcm = channel->idtcm; u8 buf[TOD_BYTE_COUNT]; @@ -793,46 +786,46 @@ static int _idtcm_set_dpll_scsr_tod(struct idtcm_channel *channel, return 0; } -static int get_output_base_addr(u8 outn) +static int get_output_base_addr(enum fw_version ver, u8 outn) { int base; switch (outn) { case 0: - base = OUTPUT_0; + base = IDTCM_FW_REG(ver, V520, OUTPUT_0); break; case 1: - base = OUTPUT_1; + base = IDTCM_FW_REG(ver, V520, OUTPUT_1); break; case 2: - base = OUTPUT_2; + base = IDTCM_FW_REG(ver, V520, OUTPUT_2); break; case 3: - base = OUTPUT_3; + base = IDTCM_FW_REG(ver, V520, OUTPUT_3); break; case 4: - base = OUTPUT_4; + base = IDTCM_FW_REG(ver, V520, OUTPUT_4); break; case 5: - base = OUTPUT_5; + base = IDTCM_FW_REG(ver, V520, OUTPUT_5); break; case 6: - base = OUTPUT_6; + base = IDTCM_FW_REG(ver, V520, OUTPUT_6); break; case 7: - base = OUTPUT_7; + base = IDTCM_FW_REG(ver, V520, OUTPUT_7); break; case 8: - base = OUTPUT_8; + base = IDTCM_FW_REG(ver, V520, OUTPUT_8); break; case 9: - base = OUTPUT_9; + base = IDTCM_FW_REG(ver, V520, OUTPUT_9); break; case 10: - base = OUTPUT_10; + base = IDTCM_FW_REG(ver, V520, OUTPUT_10); break; case 11: - base = OUTPUT_11; + base = IDTCM_FW_REG(ver, V520, OUTPUT_11); break; default: base = -EINVAL; @@ -929,9 +922,9 @@ static int idtcm_start_phase_pull_in(struct idtcm_channel *channel) return err; } -static int idtcm_do_phase_pull_in(struct idtcm_channel *channel, - s32 offset_ns, - u32 max_ffo_ppb) +static int do_phase_pull_in_fw(struct idtcm_channel *channel, + s32 offset_ns, + u32 max_ffo_ppb) { int err; @@ -1000,7 +993,7 @@ static int _idtcm_adjtime_deprecated(struct idtcm_channel *channel, s64 delta) s64 now; if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS_DEPRECATED) { - err = idtcm_do_phase_pull_in(channel, delta, 0); + err = channel->do_phase_pull_in(channel, delta, 0); } else { idtcm->calculate_overhead_flag = 1; @@ -1032,7 +1025,9 @@ static int idtcm_state_machine_reset(struct idtcm *idtcm) clear_boot_status(idtcm); - err = idtcm_write(idtcm, RESET_CTRL, SM_RESET, &byte, sizeof(byte)); + err = idtcm_write(idtcm, RESET_CTRL, + IDTCM_FW_REG(idtcm->fw_ver, V520, SM_RESET), + &byte, sizeof(byte)); if (!err) { for (i = 0; i < 30; i++) { @@ -1214,6 +1209,7 @@ static void display_pll_and_masks(struct idtcm *idtcm) static int idtcm_load_firmware(struct idtcm *idtcm, struct device *dev) { + u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH); char fname[128] = FW_FILENAME; const struct firmware *fw; struct idtcm_fwrc *rec; @@ -1226,7 +1222,7 @@ static int idtcm_load_firmware(struct idtcm *idtcm, if (firmware) /* module parameter */ snprintf(fname, sizeof(fname), "%s", firmware); - dev_dbg(&idtcm->client->dev, "requesting firmware '%s'", fname); + dev_info(&idtcm->client->dev, "firmware '%s'", fname); err = request_firmware(&fw, fname, dev); if (err) { @@ -1239,7 +1235,7 @@ static int idtcm_load_firmware(struct idtcm *idtcm, rec = (struct idtcm_fwrc *) fw->data; - if (contains_full_configuration(fw)) + if (contains_full_configuration(idtcm, fw)) idtcm_state_machine_reset(idtcm); for (len = fw->size; len > 0; len -= sizeof(*rec)) { @@ -1263,7 +1259,7 @@ static int idtcm_load_firmware(struct idtcm *idtcm, err = 0; /* Top (status registers) and bottom are read-only */ - if (regaddr < GPIO_USER_CONTROL || regaddr >= SCRATCH) + if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch) continue; /* Page size 128, last 4 bytes of page skipped */ @@ -1292,7 +1288,7 @@ static int idtcm_output_enable(struct idtcm_channel *channel, int err; u8 val; - base = get_output_base_addr(outn); + base = get_output_base_addr(idtcm->fw_ver, outn); if (!(base > 0)) { dev_err(&idtcm->client->dev, @@ -1360,53 +1356,331 @@ static int idtcm_perout_enable(struct idtcm_channel *channel, } static int idtcm_get_pll_mode(struct idtcm_channel *channel, - enum pll_mode *pll_mode) + enum pll_mode *mode) { struct idtcm *idtcm = channel->idtcm; int err; u8 dpll_mode; - err = idtcm_read(idtcm, channel->dpll_n, DPLL_MODE, + err = idtcm_read(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), &dpll_mode, sizeof(dpll_mode)); if (err) return err; - *pll_mode = (dpll_mode >> PLL_MODE_SHIFT) & PLL_MODE_MASK; + *mode = (dpll_mode >> PLL_MODE_SHIFT) & PLL_MODE_MASK; return 0; } static int idtcm_set_pll_mode(struct idtcm_channel *channel, - enum pll_mode pll_mode) + enum pll_mode mode) { struct idtcm *idtcm = channel->idtcm; int err; u8 dpll_mode; - err = idtcm_read(idtcm, channel->dpll_n, DPLL_MODE, + err = idtcm_read(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), &dpll_mode, sizeof(dpll_mode)); if (err) return err; dpll_mode &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT); - dpll_mode |= (pll_mode << PLL_MODE_SHIFT); - - channel->pll_mode = pll_mode; + dpll_mode |= (mode << PLL_MODE_SHIFT); - err = idtcm_write(idtcm, channel->dpll_n, DPLL_MODE, + err = idtcm_write(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), &dpll_mode, sizeof(dpll_mode)); + return err; +} + +static int idtcm_get_manual_reference(struct idtcm_channel *channel, + enum manual_reference *ref) +{ + struct idtcm *idtcm = channel->idtcm; + u8 dpll_manu_ref_cfg; + int err; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); if (err) return err; + dpll_manu_ref_cfg &= (MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT); + + *ref = dpll_manu_ref_cfg >> MANUAL_REFERENCE_SHIFT; + return 0; } +static int idtcm_set_manual_reference(struct idtcm_channel *channel, + enum manual_reference ref) +{ + struct idtcm *idtcm = channel->idtcm; + u8 dpll_manu_ref_cfg; + int err; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); + if (err) + return err; + + dpll_manu_ref_cfg &= ~(MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT); + + dpll_manu_ref_cfg |= (ref << MANUAL_REFERENCE_SHIFT); + + err = idtcm_write(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); + + return err; +} + +static int configure_dpll_mode_write_frequency(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY); + + if (err) + dev_err(&idtcm->client->dev, "Failed to set pll mode to write frequency"); + else + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + + return err; +} + +static int configure_dpll_mode_write_phase(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_PHASE); + + if (err) + dev_err(&idtcm->client->dev, "Failed to set pll mode to write phase"); + else + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + + return err; +} + +static int configure_manual_reference_write_frequency(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_FREQUENCY); + + if (err) + dev_err(&idtcm->client->dev, "Failed to set manual reference to write frequency"); + else + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + + return err; +} + +static int configure_manual_reference_write_phase(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_PHASE); + + if (err) + dev_err(&idtcm->client->dev, "Failed to set manual reference to write phase"); + else + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + + return err; +} + +static int idtcm_stop_phase_pull_in(struct idtcm_channel *channel) +{ + int err; + + err = _idtcm_adjfine(channel, channel->current_freq_scaled_ppm); + if (err) + return err; + + channel->phase_pull_in = false; + + return 0; +} + +static long idtcm_work_handler(struct ptp_clock_info *ptp) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + + mutex_lock(&idtcm->reg_lock); + + (void)idtcm_stop_phase_pull_in(channel); + + mutex_unlock(&idtcm->reg_lock); + + /* Return a negative value here to not reschedule */ + return -1; +} + +static s32 phase_pull_in_scaled_ppm(s32 current_ppm, s32 phase_pull_in_ppb) +{ + /* ppb = scaled_ppm * 125 / 2^13 */ + /* scaled_ppm = ppb * 2^13 / 125 */ + + s64 max_scaled_ppm = (PHASE_PULL_IN_MAX_PPB << 13) / 125; + s64 scaled_ppm = (phase_pull_in_ppb << 13) / 125; + + current_ppm += scaled_ppm; + + if (current_ppm > max_scaled_ppm) + current_ppm = max_scaled_ppm; + else if (current_ppm < -max_scaled_ppm) + current_ppm = -max_scaled_ppm; + + return current_ppm; +} + +static int do_phase_pull_in_sw(struct idtcm_channel *channel, + s32 delta_ns, + u32 max_ffo_ppb) +{ + s32 current_ppm = channel->current_freq_scaled_ppm; + u32 duration_ms = MSEC_PER_SEC; + s32 delta_ppm; + s32 ppb; + int err; + + /* If the ToD correction is less than PHASE_PULL_IN_MIN_THRESHOLD_NS, + * skip. The error introduced by the ToD adjustment procedure would + * be bigger than the required ToD correction + */ + if (abs(delta_ns) < PHASE_PULL_IN_MIN_THRESHOLD_NS) + return 0; + + if (max_ffo_ppb == 0) + max_ffo_ppb = PHASE_PULL_IN_MAX_PPB; + + /* For most cases, keep phase pull-in duration 1 second */ + ppb = delta_ns; + while (abs(ppb) > max_ffo_ppb) { + duration_ms *= 2; + ppb /= 2; + } + + delta_ppm = phase_pull_in_scaled_ppm(current_ppm, ppb); + + err = _idtcm_adjfine(channel, delta_ppm); + + if (err) + return err; + + /* schedule the worker to cancel phase pull-in */ + ptp_schedule_worker(channel->ptp_clock, + msecs_to_jiffies(duration_ms) - 1); + + channel->phase_pull_in = true; + + return 0; +} + +static int initialize_operating_mode_with_manual_reference(struct idtcm_channel *channel, + enum manual_reference ref) +{ + struct idtcm *idtcm = channel->idtcm; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + channel->configure_write_frequency = configure_manual_reference_write_frequency; + channel->configure_write_phase = configure_manual_reference_write_phase; + channel->do_phase_pull_in = do_phase_pull_in_sw; + + switch (ref) { + case MANU_REF_WRITE_PHASE: + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + break; + case MANU_REF_WRITE_FREQUENCY: + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + break; + default: + dev_warn(&idtcm->client->dev, + "Unsupported MANUAL_REFERENCE: 0x%02x", ref); + } + + return 0; +} + +static int initialize_operating_mode_with_pll_mode(struct idtcm_channel *channel, + enum pll_mode mode) +{ + struct idtcm *idtcm = channel->idtcm; + int err = 0; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + channel->configure_write_frequency = configure_dpll_mode_write_frequency; + channel->configure_write_phase = configure_dpll_mode_write_phase; + channel->do_phase_pull_in = do_phase_pull_in_fw; + + switch (mode) { + case PLL_MODE_WRITE_PHASE: + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + break; + case PLL_MODE_WRITE_FREQUENCY: + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + break; + default: + dev_err(&idtcm->client->dev, + "Unsupported PLL_MODE: 0x%02x", mode); + err = -EINVAL; + } + + return err; +} + +static int initialize_dco_operating_mode(struct idtcm_channel *channel) +{ + enum manual_reference ref = MANU_REF_XO_DPLL; + enum pll_mode mode = PLL_MODE_DISABLED; + struct idtcm *idtcm = channel->idtcm; + int err; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + + err = idtcm_get_pll_mode(channel, &mode); + if (err) { + dev_err(&idtcm->client->dev, "Unable to read pll mode!"); + return err; + } + + if (mode == PLL_MODE_PLL) { + err = idtcm_get_manual_reference(channel, &ref); + if (err) { + dev_err(&idtcm->client->dev, "Unable to read manual reference!"); + return err; + } + err = initialize_operating_mode_with_manual_reference(channel, ref); + } else { + err = initialize_operating_mode_with_pll_mode(channel, mode); + } + + if (channel->mode == PTP_PLL_MODE_WRITE_PHASE) + channel->configure_write_frequency(channel); + + return err; +} + /* PTP Hardware Clock interface */ -/* +/** * Maximum absolute value for write phase offset in picoseconds * + * @channel: channel + * @delta_ns: delta in nanoseconds + * * Destination signed register is 32-bit register in resolution of 50ps * * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350 @@ -1420,8 +1694,8 @@ static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns) s32 phase_50ps; s64 offset_ps; - if (channel->pll_mode != PLL_MODE_WRITE_PHASE) { - err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_PHASE); + if (channel->mode != PTP_PLL_MODE_WRITE_PHASE) { + err = channel->configure_write_phase(channel); if (err) return err; } @@ -1459,8 +1733,8 @@ static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm) u8 buf[6] = {0}; s64 fcw; - if (channel->pll_mode != PLL_MODE_WRITE_FREQUENCY) { - err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY); + if (channel->mode != PTP_PLL_MODE_WRITE_FREQUENCY) { + err = channel->configure_write_frequency(channel); if (err) return err; } @@ -1577,29 +1851,29 @@ static int idtcm_adjtime(struct ptp_clock_info *ptp, s64 delta) enum scsr_tod_write_type_sel type; int err; + if (channel->phase_pull_in == true) + return 0; + + mutex_lock(&idtcm->reg_lock); + if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS) { - err = idtcm_do_phase_pull_in(channel, delta, 0); + err = channel->do_phase_pull_in(channel, delta, 0); if (err) dev_err(&idtcm->client->dev, "Failed at line %d in %s!", __LINE__, __func__); - return err; - } - - if (delta >= 0) { - ts = ns_to_timespec64(delta); - type = SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS; } else { - ts = ns_to_timespec64(-delta); - type = SCSR_TOD_WR_TYPE_SEL_DELTA_MINUS; + if (delta >= 0) { + ts = ns_to_timespec64(delta); + type = SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS; + } else { + ts = ns_to_timespec64(-delta); + type = SCSR_TOD_WR_TYPE_SEL_DELTA_MINUS; + } + err = _idtcm_settime(channel, &ts, type); + if (err) + dev_err(&idtcm->client->dev, + "Failed at line %d in %s!", __LINE__, __func__); } - - mutex_lock(&idtcm->reg_lock); - - err = _idtcm_settime(channel, &ts, type); - if (err) - dev_err(&idtcm->client->dev, - "Failed at line %d in %s!", __LINE__, __func__); - mutex_unlock(&idtcm->reg_lock); return err; @@ -1629,15 +1903,21 @@ static int idtcm_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) struct idtcm *idtcm = channel->idtcm; int err; + if (channel->phase_pull_in == true) + return 0; + + if (scaled_ppm == channel->current_freq_scaled_ppm) + return 0; + mutex_lock(&idtcm->reg_lock); err = _idtcm_adjfine(channel, scaled_ppm); - if (err) - dev_err(&idtcm->client->dev, - "Failed at line %d in %s!", __LINE__, __func__); mutex_unlock(&idtcm->reg_lock); + if (!err) + channel->current_freq_scaled_ppm = scaled_ppm; + return err; } @@ -1675,243 +1955,35 @@ static int idtcm_enable(struct ptp_clock_info *ptp, return -EOPNOTSUPP; } -static int _enable_pll_tod_sync(struct idtcm *idtcm, - u8 pll, - u8 sync_src, - u8 qn, - u8 qn_plus_1) -{ - int err; - u8 val; - u16 dpll; - u16 out0 = 0, out1 = 0; - - if (qn == 0 && qn_plus_1 == 0) - return 0; - - switch (pll) { - case 0: - dpll = DPLL_0; - if (qn) - out0 = OUTPUT_0; - if (qn_plus_1) - out1 = OUTPUT_1; - break; - case 1: - dpll = DPLL_1; - if (qn) - out0 = OUTPUT_2; - if (qn_plus_1) - out1 = OUTPUT_3; - break; - case 2: - dpll = DPLL_2; - if (qn) - out0 = OUTPUT_4; - if (qn_plus_1) - out1 = OUTPUT_5; - break; - case 3: - dpll = DPLL_3; - if (qn) - out0 = OUTPUT_6; - if (qn_plus_1) - out1 = OUTPUT_7; - break; - case 4: - dpll = DPLL_4; - if (qn) - out0 = OUTPUT_8; - break; - case 5: - dpll = DPLL_5; - if (qn) - out0 = OUTPUT_9; - if (qn_plus_1) - out1 = OUTPUT_8; - break; - case 6: - dpll = DPLL_6; - if (qn) - out0 = OUTPUT_10; - if (qn_plus_1) - out1 = OUTPUT_11; - break; - case 7: - dpll = DPLL_7; - if (qn) - out0 = OUTPUT_11; - break; - default: - return -EINVAL; - } - - /* - * Enable OUTPUT OUT_SYNC. - */ - if (out0) { - err = idtcm_read(idtcm, out0, OUT_CTRL_1, &val, sizeof(val)); - if (err) - return err; - - val &= ~OUT_SYNC_DISABLE; - - err = idtcm_write(idtcm, out0, OUT_CTRL_1, &val, sizeof(val)); - if (err) - return err; - } - - if (out1) { - err = idtcm_read(idtcm, out1, OUT_CTRL_1, &val, sizeof(val)); - if (err) - return err; - - val &= ~OUT_SYNC_DISABLE; - - err = idtcm_write(idtcm, out1, OUT_CTRL_1, &val, sizeof(val)); - if (err) - return err; - } - - /* enable dpll sync tod pps, must be set before dpll_mode */ - err = idtcm_read(idtcm, dpll, DPLL_TOD_SYNC_CFG, &val, sizeof(val)); - if (err) - return err; - - val &= ~(TOD_SYNC_SOURCE_MASK << TOD_SYNC_SOURCE_SHIFT); - val |= (sync_src << TOD_SYNC_SOURCE_SHIFT); - val |= TOD_SYNC_EN; - - return idtcm_write(idtcm, dpll, DPLL_TOD_SYNC_CFG, &val, sizeof(val)); -} - -static int idtcm_enable_tod_sync(struct idtcm_channel *channel) +static int idtcm_enable_tod(struct idtcm_channel *channel) { struct idtcm *idtcm = channel->idtcm; - u8 pll; - u8 sync_src; - u8 qn; - u8 qn_plus_1; + struct timespec64 ts = {0, 0}; + u16 tod_cfg = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_CFG); u8 cfg; - int err = 0; - u16 output_mask = channel->output_mask; - u8 out8_mux = 0; - u8 out11_mux = 0; - u8 temp; - - /* - * set tod_out_sync_enable to 0. - */ - err = idtcm_read(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg)); - if (err) - return err; - - cfg &= ~TOD_OUT_SYNC_ENABLE; - - err = idtcm_write(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg)); - if (err) - return err; - - switch (channel->tod_n) { - case TOD_0: - sync_src = 0; - break; - case TOD_1: - sync_src = 1; - break; - case TOD_2: - sync_src = 2; - break; - case TOD_3: - sync_src = 3; - break; - default: - return -EINVAL; - } - - err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE, &temp, sizeof(temp)); - if (err) - return err; - - if ((temp & Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) == - Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) - out8_mux = 1; - - err = idtcm_read(idtcm, 0, HW_Q11_CTRL_SPARE, &temp, sizeof(temp)); - if (err) - return err; - - if ((temp & Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) == - Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) - out11_mux = 1; - - for (pll = 0; pll < 8; pll++) { - qn = 0; - qn_plus_1 = 0; - - if (pll < 4) { - /* First 4 pll has 2 outputs */ - qn = output_mask & 0x1; - output_mask = output_mask >> 1; - qn_plus_1 = output_mask & 0x1; - output_mask = output_mask >> 1; - } else if (pll == 4) { - if (out8_mux == 0) { - qn = output_mask & 0x1; - output_mask = output_mask >> 1; - } - } else if (pll == 5) { - if (out8_mux) { - qn_plus_1 = output_mask & 0x1; - output_mask = output_mask >> 1; - } - qn = output_mask & 0x1; - output_mask = output_mask >> 1; - } else if (pll == 6) { - qn = output_mask & 0x1; - output_mask = output_mask >> 1; - if (out11_mux) { - qn_plus_1 = output_mask & 0x1; - output_mask = output_mask >> 1; - } - } else if (pll == 7) { - if (out11_mux == 0) { - qn = output_mask & 0x1; - output_mask = output_mask >> 1; - } - } + int err; - if (qn != 0 || qn_plus_1 != 0) - err = _enable_pll_tod_sync(idtcm, pll, sync_src, qn, - qn_plus_1); + /* STEELAI-366 - Temporary workaround for ts2phc compatibility */ + if (0) { + err = idtcm_output_mask_enable(channel, false); if (err) return err; } - return err; -} - -static int idtcm_enable_tod(struct idtcm_channel *channel) -{ - struct idtcm *idtcm = channel->idtcm; - struct timespec64 ts = {0, 0}; - u8 cfg; - int err; - /* * Start the TOD clock ticking. */ - err = idtcm_read(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg)); + err = idtcm_read(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg)); if (err) return err; cfg |= TOD_ENABLE; - err = idtcm_write(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg)); + err = idtcm_write(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg)); if (err) return err; - if (idtcm->deprecated) + if (idtcm->fw_ver < V487) return _idtcm_settime_deprecated(channel, &ts); else return _idtcm_settime(channel, &ts, @@ -1939,10 +2011,7 @@ static void idtcm_set_version_info(struct idtcm *idtcm) snprintf(idtcm->version, sizeof(idtcm->version), "%u.%u.%u", major, minor, hotfix); - if (idtcm_strverscmp(idtcm->version, "4.8.7") >= 0) - idtcm->deprecated = 0; - else - idtcm->deprecated = 1; + idtcm->fw_ver = idtcm_fw_version(idtcm->version); dev_info(&idtcm->client->dev, "%d.%d.%d, Id: 0x%04x HW Rev: %d OTP Config Select: %d", @@ -1960,6 +2029,7 @@ static const struct ptp_clock_info idtcm_caps = { .gettime64 = &idtcm_gettime, .settime64 = &idtcm_settime, .enable = &idtcm_enable, + .do_aux_work = &idtcm_work_handler, }; static const struct ptp_clock_info idtcm_caps_deprecated = { @@ -1972,10 +2042,12 @@ static const struct ptp_clock_info idtcm_caps_deprecated = { .gettime64 = &idtcm_gettime, .settime64 = &idtcm_settime_deprecated, .enable = &idtcm_enable, + .do_aux_work = &idtcm_work_handler, }; static int configure_channel_pll(struct idtcm_channel *channel) { + struct idtcm *idtcm = channel->idtcm; int err = 0; switch (channel->pll) { @@ -1997,7 +2069,7 @@ static int configure_channel_pll(struct idtcm_channel *channel) break; case 2: channel->dpll_freq = DPLL_FREQ_2; - channel->dpll_n = DPLL_2; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_2); channel->hw_dpll_n = HW_DPLL_2; channel->dpll_phase = DPLL_PHASE_2; channel->dpll_ctrl_n = DPLL_CTRL_2; @@ -2013,7 +2085,7 @@ static int configure_channel_pll(struct idtcm_channel *channel) break; case 4: channel->dpll_freq = DPLL_FREQ_4; - channel->dpll_n = DPLL_4; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_4); channel->hw_dpll_n = HW_DPLL_4; channel->dpll_phase = DPLL_PHASE_4; channel->dpll_ctrl_n = DPLL_CTRL_4; @@ -2029,7 +2101,7 @@ static int configure_channel_pll(struct idtcm_channel *channel) break; case 6: channel->dpll_freq = DPLL_FREQ_6; - channel->dpll_n = DPLL_6; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_6); channel->hw_dpll_n = HW_DPLL_6; channel->dpll_phase = DPLL_PHASE_6; channel->dpll_ctrl_n = DPLL_CTRL_6; @@ -2052,6 +2124,7 @@ static int configure_channel_pll(struct idtcm_channel *channel) static int idtcm_enable_channel(struct idtcm *idtcm, u32 index) { + enum fw_version fw_ver = idtcm->fw_ver; struct idtcm_channel *channel; int err; @@ -2060,6 +2133,9 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index) channel = &idtcm->channel[index]; + channel->idtcm = idtcm; + channel->current_freq_scaled_ppm = 0; + /* Set pll addresses */ err = configure_channel_pll(channel); if (err) @@ -2068,32 +2144,34 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index) /* Set tod addresses */ switch (index) { case 0: - channel->tod_read_primary = TOD_READ_PRIMARY_0; - channel->tod_write = TOD_WRITE_0; - channel->tod_n = TOD_0; + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_0); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_0); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_0); + channel->sync_src = SYNC_SOURCE_DPLL0_TOD_PPS; break; case 1: - channel->tod_read_primary = TOD_READ_PRIMARY_1; - channel->tod_write = TOD_WRITE_1; - channel->tod_n = TOD_1; + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_1); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_1); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_1); + channel->sync_src = SYNC_SOURCE_DPLL1_TOD_PPS; break; case 2: - channel->tod_read_primary = TOD_READ_PRIMARY_2; - channel->tod_write = TOD_WRITE_2; - channel->tod_n = TOD_2; + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_2); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_2); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_2); + channel->sync_src = SYNC_SOURCE_DPLL2_TOD_PPS; break; case 3: - channel->tod_read_primary = TOD_READ_PRIMARY_3; - channel->tod_write = TOD_WRITE_3; - channel->tod_n = TOD_3; + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_3); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_3); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_3); + channel->sync_src = SYNC_SOURCE_DPLL3_TOD_PPS; break; default: return -EINVAL; } - channel->idtcm = idtcm; - - if (idtcm->deprecated) + if (idtcm->fw_ver < V487) channel->caps = idtcm_caps_deprecated; else channel->caps = idtcm_caps; @@ -2101,22 +2179,9 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index) snprintf(channel->caps.name, sizeof(channel->caps.name), "IDT CM TOD%u", index); - if (!idtcm->deprecated) { - err = idtcm_enable_tod_sync(channel); - if (err) { - dev_err(&idtcm->client->dev, - "Failed at line %d in %s!", __LINE__, __func__); - return err; - } - } - - /* Sync pll mode with hardware */ - err = idtcm_get_pll_mode(channel, &channel->pll_mode); - if (err) { - dev_err(&idtcm->client->dev, - "Error: %s - Unable to read pll mode", __func__); + err = initialize_dco_operating_mode(channel); + if (err) return err; - } err = idtcm_enable_tod(channel); if (err) { @@ -2149,7 +2214,6 @@ static void ptp_clock_unregister_all(struct idtcm *idtcm) for (i = 0; i < MAX_TOD; i++) { channel = &idtcm->channel[i]; - if (channel->ptp_clock) ptp_clock_unregister(channel->ptp_clock); } diff --git a/drivers/ptp/ptp_clockmatrix.h b/drivers/ptp/ptp_clockmatrix.h index fb323271063e..833e5907c351 100644 --- a/drivers/ptp/ptp_clockmatrix.h +++ b/drivers/ptp/ptp_clockmatrix.h @@ -57,15 +57,27 @@ #define IDTCM_MAX_WRITE_COUNT (512) -#define FULL_FW_CFG_BYTES (SCRATCH - GPIO_USER_CONTROL) -#define FULL_FW_CFG_SKIPPED_BYTES (((SCRATCH >> 7) \ - - (GPIO_USER_CONTROL >> 7)) \ - * 4) /* 4 bytes skipped every 0x80 */ +#define PHASE_PULL_IN_MAX_PPB (144000) +#define PHASE_PULL_IN_MIN_THRESHOLD_NS (2) + +/* + * Return register address based on passed in firmware version + */ +#define IDTCM_FW_REG(FW, VER, REG) (((FW) < (VER)) ? (REG) : (REG##_##VER)) + +/* PTP PLL Mode */ +enum ptp_pll_mode { + PTP_PLL_MODE_MIN = 0, + PTP_PLL_MODE_WRITE_FREQUENCY = PTP_PLL_MODE_MIN, + PTP_PLL_MODE_WRITE_PHASE, + PTP_PLL_MODE_UNSUPPORTED, + PTP_PLL_MODE_MAX = PTP_PLL_MODE_UNSUPPORTED, +}; /* Values of DPLL_N.DPLL_MODE.PLL_MODE */ enum pll_mode { PLL_MODE_MIN = 0, - PLL_MODE_NORMAL = PLL_MODE_MIN, + PLL_MODE_PLL = PLL_MODE_MIN, PLL_MODE_WRITE_PHASE = 1, PLL_MODE_WRITE_FREQUENCY = 2, PLL_MODE_GPIO_INC_DEC = 3, @@ -75,6 +87,31 @@ enum pll_mode { PLL_MODE_MAX = PLL_MODE_DISABLED, }; +/* Values of DPLL_CTRL_n.DPLL_MANU_REF_CFG.MANUAL_REFERENCE */ +enum manual_reference { + MANU_REF_MIN = 0, + MANU_REF_CLK0 = MANU_REF_MIN, + MANU_REF_CLK1, + MANU_REF_CLK2, + MANU_REF_CLK3, + MANU_REF_CLK4, + MANU_REF_CLK5, + MANU_REF_CLK6, + MANU_REF_CLK7, + MANU_REF_CLK8, + MANU_REF_CLK9, + MANU_REF_CLK10, + MANU_REF_CLK11, + MANU_REF_CLK12, + MANU_REF_CLK13, + MANU_REF_CLK14, + MANU_REF_CLK15, + MANU_REF_WRITE_PHASE, + MANU_REF_WRITE_FREQUENCY, + MANU_REF_XO_DPLL, + MANU_REF_MAX = MANU_REF_XO_DPLL, +}; + enum hw_tod_write_trig_sel { HW_TOD_WR_TRIG_SEL_MIN = 0, HW_TOD_WR_TRIG_SEL_MSB = HW_TOD_WR_TRIG_SEL_MIN, @@ -119,6 +156,12 @@ enum dpll_state { DPLL_STATE_MAX = DPLL_STATE_OPEN_LOOP, }; +enum fw_version { + V_DEFAULT = 0, + V487 = 1, + V520 = 2, +}; + struct idtcm; struct idtcm_channel { @@ -134,7 +177,14 @@ struct idtcm_channel { u16 tod_write; u16 tod_n; u16 hw_dpll_n; - enum pll_mode pll_mode; + u8 sync_src; + enum ptp_pll_mode mode; + int (*configure_write_frequency)(struct idtcm_channel *channel); + int (*configure_write_phase)(struct idtcm_channel *channel); + int (*do_phase_pull_in)(struct idtcm_channel *channel, + s32 offset_ns, u32 max_ffo_ppb); + s32 current_freq_scaled_ppm; + bool phase_pull_in; u8 pll; u16 output_mask; }; @@ -145,7 +195,7 @@ struct idtcm { u8 page_offset; u8 tod_mask; char version[16]; - u8 deprecated; + enum fw_version fw_ver; /* Overhead calculation for adjtime */ u8 calculate_overhead_flag; diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index caf9b37c5eb1..4c25467198e3 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -4,6 +4,7 @@ #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> @@ -72,7 +73,7 @@ struct tod_reg { u32 status; u32 uart_polarity; u32 version; - u32 correction_sec; + u32 adj_sec; u32 __pad0[3]; u32 uart_baud; u32 __pad1[3]; @@ -124,6 +125,55 @@ 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 ptp_ocp_flash_info { const char *name; int pci_offset; @@ -131,11 +181,17 @@ struct ptp_ocp_flash_info { void *data; }; -struct ptp_ocp_ext_info { +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, bool enable); + int (*enable)(void *priv, u32 req, bool enable); }; struct ptp_ocp_ext_src { @@ -153,9 +209,17 @@ struct ptp_ocp { struct tod_reg __iomem *tod; struct pps_reg __iomem *pps_to_ext; struct pps_reg __iomem *pps_to_clk; + struct gpio_reg __iomem *pps_select; + struct gpio_reg __iomem *sma; + 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 ptp_ocp_ext_src *pps; struct ptp_ocp_ext_src *ts0; struct ptp_ocp_ext_src *ts1; + struct ptp_ocp_ext_src *ts2; struct img_reg __iomem *image; struct ptp_clock *ptp; struct ptp_clock_info ptp_info; @@ -163,16 +227,25 @@ struct ptp_ocp { struct platform_device *spi_flash; struct clk_hw *i2c_clk; struct timer_list watchdog; + struct dentry *debug_root; time64_t gnss_lost; int id; int n_irqs; int gnss_port; + int gnss2_port; int mac_port; /* miniature atomic clock */ + int nmea_port; u8 serial[6]; - int flash_start; bool has_serial; + u32 pps_req_map; + int flash_start; + u32 utc_tai_offset; + u32 ts_window_adjust; }; +#define OCP_REQ_TIMESTAMP BIT(0) +#define OCP_REQ_PPS BIT(1) + struct ocp_resource { unsigned long offset; int size; @@ -180,6 +253,7 @@ struct ocp_resource { 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); @@ -189,7 +263,7 @@ 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 int ptp_ocp_ts_enable(void *priv, bool enable); +static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable); #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \ @@ -197,7 +271,7 @@ static int ptp_ocp_ts_enable(void *priv, bool enable); }) #define OCP_RES_LOCATION(member) \ - .bp_offset = offsetof(struct ptp_ocp, member) + .name = #member, .bp_offset = offsetof(struct ptp_ocp, member) #define OCP_MEM_RESOURCE(member) \ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_mem @@ -215,16 +289,17 @@ static int ptp_ocp_ts_enable(void *priv, bool enable); OCP_RES_LOCATION(member), .setup = ptp_ocp_register_ext /* This is the MSI vector mapping used. - * 0: N/C + * 0: TS3 (and PPS) * 1: TS0 * 2: TS1 - * 3: GPS - * 4: GPS2 (n/c) + * 3: GNSS + * 4: GNSS2 * 5: MAC - * 6: SPI IMU (inertial measurement unit) - * 7: I2C oscillator - * 8: HWICAP + * 6: TS2 + * 7: I2C controller + * 8: HWICAP (notused) * 9: SPI Flash + * 10: NMEA */ static struct ocp_resource ocp_fb_resource[] = { @@ -236,7 +311,7 @@ static struct ocp_resource ocp_fb_resource[] = { OCP_EXT_RESOURCE(ts0), .offset = 0x01010000, .size = 0x10000, .irq_vec = 1, .extra = &(struct ptp_ocp_ext_info) { - .name = "ts0", .index = 0, + .index = 0, .irq_fcn = ptp_ocp_ts_irq, .enable = ptp_ocp_ts_enable, }, @@ -245,7 +320,25 @@ static struct ocp_resource ocp_fb_resource[] = { OCP_EXT_RESOURCE(ts1), .offset = 0x01020000, .size = 0x10000, .irq_vec = 2, .extra = &(struct ptp_ocp_ext_info) { - .name = "ts1", .index = 1, + .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(pps), + .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, .irq_fcn = ptp_ocp_ts_irq, .enable = ptp_ocp_ts_enable, }, @@ -263,22 +356,62 @@ static struct ocp_resource ocp_fb_resource[] = { .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), + .offset = 0x00140000, .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, + }, }, { OCP_SERIAL_RESOURCE(gnss_port), .offset = 0x00160000 + 0x1000, .irq_vec = 3, }, { + OCP_SERIAL_RESOURCE(gnss2_port), + .offset = 0x00170000 + 0x1000, .irq_vec = 4, + }, + { OCP_SERIAL_RESOURCE(mac_port), .offset = 0x00180000 + 0x1000, .irq_vec = 5, }, { + 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) { @@ -309,10 +442,12 @@ MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id); static DEFINE_MUTEX(ptp_ocp_lock); static DEFINE_IDR(ptp_ocp_idr); -static struct { +struct ocp_selector { const char *name; int value; -} ptp_ocp_clock[] = { +}; + +static struct ocp_selector ptp_ocp_clock[] = { { .name = "NONE", .value = 0 }, { .name = "TOD", .value = 1 }, { .name = "IRIG", .value = 2 }, @@ -322,33 +457,71 @@ static struct { { .name = "DCF", .value = 6 }, { .name = "REGS", .value = 0xfe }, { .name = "EXT", .value = 0xff }, + { } +}; + +static struct ocp_selector ptp_ocp_sma_in[] = { + { .name = "10Mhz", .value = 0x00 }, + { .name = "PPS1", .value = 0x01 }, + { .name = "PPS2", .value = 0x02 }, + { .name = "TS1", .value = 0x04 }, + { .name = "TS2", .value = 0x08 }, + { .name = "IRIG", .value = 0x10 }, + { .name = "DCF", .value = 0x20 }, + { } +}; + +static struct ocp_selector ptp_ocp_sma_out[] = { + { .name = "10Mhz", .value = 0x00 }, + { .name = "PHC", .value = 0x01 }, + { .name = "MAC", .value = 0x02 }, + { .name = "GNSS", .value = 0x04 }, + { .name = "GNSS2", .value = 0x08 }, + { .name = "IRIG", .value = 0x10 }, + { .name = "DCF", .value = 0x20 }, + { } }; static const char * -ptp_ocp_clock_name_from_val(int val) +ptp_ocp_select_name_from_val(struct ocp_selector *tbl, int val) { int i; - for (i = 0; i < ARRAY_SIZE(ptp_ocp_clock); i++) - if (ptp_ocp_clock[i].value == val) - return ptp_ocp_clock[i].name; + for (i = 0; tbl[i].name; i++) + if (tbl[i].value == val) + return tbl[i].name; return NULL; } static int -ptp_ocp_clock_val_from_name(const char *name) +ptp_ocp_select_val_from_name(struct ocp_selector *tbl, const char *name) { - const char *clk; + const char *select; int i; - for (i = 0; i < ARRAY_SIZE(ptp_ocp_clock); i++) { - clk = ptp_ocp_clock[i].name; - if (!strncasecmp(name, clk, strlen(clk))) - return ptp_ocp_clock[i].value; + 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(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) @@ -356,10 +529,9 @@ __ptp_ocp_gettime_locked(struct ptp_ocp *bp, struct timespec64 *ts, u32 ctrl, time_sec, time_ns; int i; - ctrl = ioread32(&bp->reg->ctrl); - ctrl |= OCP_CTRL_READ_TIME_REQ; - ptp_read_system_prets(sts); + + ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE; iowrite32(ctrl, &bp->reg->ctrl); for (i = 0; i < 100; i++) { @@ -369,6 +541,12 @@ __ptp_ocp_gettime_locked(struct ptp_ocp *bp, struct timespec64 *ts, } ptp_read_system_postts(sts); + 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); @@ -408,8 +586,7 @@ __ptp_ocp_settime_locked(struct ptp_ocp *bp, const struct timespec64 *ts) iowrite32(time_ns, &bp->reg->adjust_ns); iowrite32(time_sec, &bp->reg->adjust_sec); - ctrl = ioread32(&bp->reg->ctrl); - ctrl |= OCP_CTRL_ADJUST_TIME; + ctrl = OCP_CTRL_ADJUST_TIME | OCP_CTRL_ENABLE; iowrite32(ctrl, &bp->reg->ctrl); /* restore clock selection */ @@ -422,9 +599,6 @@ ptp_ocp_settime(struct ptp_clock_info *ptp_info, const struct timespec64 *ts) struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); unsigned long flags; - if (ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC) - return 0; - spin_lock_irqsave(&bp->lock, flags); __ptp_ocp_settime_locked(bp, ts); spin_unlock_irqrestore(&bp->lock, flags); @@ -432,26 +606,39 @@ ptp_ocp_settime(struct ptp_clock_info *ptp_info, const struct timespec64 *ts) return 0; } +static void +__ptp_ocp_adjtime_locked(struct ptp_ocp *bp, u64 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(adj_val & 0x7f, &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 int ptp_ocp_adjtime(struct ptp_clock_info *ptp_info, s64 delta_ns) { struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); - struct timespec64 ts; unsigned long flags; - int err; + u32 adj_ns, sign; - if (ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC) - return 0; + sign = delta_ns < 0 ? BIT(31) : 0; + adj_ns = sign ? -delta_ns : delta_ns; spin_lock_irqsave(&bp->lock, flags); - err = __ptp_ocp_gettime_locked(bp, &ts, NULL); - if (likely(!err)) { - timespec64_add_ns(&ts, delta_ns); - __ptp_ocp_settime_locked(bp, &ts); - } + __ptp_ocp_adjtime_locked(bp, sign | adj_ns); spin_unlock_irqrestore(&bp->lock, flags); - return err; + return 0; } static int @@ -464,7 +651,7 @@ ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) } static int -ptp_ocp_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns) +ptp_ocp_null_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns) { return -EOPNOTSUPP; } @@ -475,10 +662,12 @@ ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, { 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; @@ -486,18 +675,33 @@ ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, case 1: ext = bp->ts1; break; + case 2: + ext = bp->ts2; + break; + case 3: + ext = bp->pps; + break; } break; case PTP_CLK_REQ_PPS: + req = OCP_REQ_PPS; ext = bp->pps; break; + case PTP_CLK_REQ_PEROUT: + if (on && + (rq->perout.period.sec != 1 || rq->perout.period.nsec != 0)) + return -EINVAL; + /* This is a request for 1PPS on an output SMA. + * Allow, but assume manual configuration. + */ + return 0; default: return -EOPNOTSUPP; } err = -ENXIO; if (ext) - err = ext->info->enable(ext, on); + err = ext->info->enable(ext, req, on); return err; } @@ -510,10 +714,11 @@ static const struct ptp_clock_info ptp_ocp_clock_info = { .settime64 = ptp_ocp_settime, .adjtime = ptp_ocp_adjtime, .adjfine = ptp_ocp_null_adjfine, - .adjphase = ptp_ocp_adjphase, + .adjphase = ptp_ocp_null_adjphase, .enable = ptp_ocp_enable, .pps = true, - .n_ext_ts = 2, + .n_ext_ts = 4, + .n_per_out = 1, }; static void @@ -526,8 +731,7 @@ __ptp_ocp_clear_drift_locked(struct ptp_ocp *bp) iowrite32(0, &bp->reg->drift_ns); - ctrl = ioread32(&bp->reg->ctrl); - ctrl |= OCP_CTRL_ADJUST_DRIFT; + ctrl = OCP_CTRL_ADJUST_DRIFT | OCP_CTRL_ENABLE; iowrite32(ctrl, &bp->reg->ctrl); /* restore clock selection */ @@ -559,6 +763,28 @@ ptp_ocp_watchdog(struct timer_list *t) mod_timer(&bp->watchdog, jiffies + HZ); } +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) { @@ -566,9 +792,7 @@ ptp_ocp_init_clock(struct ptp_ocp *bp) bool sync; u32 ctrl; - /* make sure clock is enabled */ - ctrl = ioread32(&bp->reg->ctrl); - ctrl |= OCP_CTRL_ENABLE; + ctrl = OCP_CTRL_ENABLE; iowrite32(ctrl, &bp->reg->ctrl); /* NO DRIFT Correction */ @@ -587,23 +811,58 @@ ptp_ocp_init_clock(struct ptp_ocp *bp) return -ENODEV; } + ptp_ocp_estimate_pci_timing(bp); + sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; if (!sync) { - ktime_get_real_ts64(&ts); + ktime_get_clocktai_ts64(&ts); ptp_ocp_settime(&bp->ptp_info, &ts); } - if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, NULL)) - dev_info(&bp->pdev->dev, "Time: %lld.%ld, %s\n", - ts.tv_sec, ts.tv_nsec, - sync ? "in-sync" : "UNSYNCED"); - timer_setup(&bp->watchdog, ptp_ocp_watchdog, 0); - mod_timer(&bp->watchdog, jiffies + HZ); + /* 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_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_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 void ptp_ocp_tod_info(struct ptp_ocp *bp) { static const char * const proto_name[] = { @@ -621,11 +880,6 @@ ptp_ocp_tod_info(struct ptp_ocp *bp) version >> 24, (version >> 16) & 0xff, version & 0xffff); ctrl = ioread32(&bp->tod->ctrl); - ctrl |= TOD_CTRL_PROTOCOL | TOD_CTRL_ENABLE; - ctrl &= ~(TOD_CTRL_DISABLE_FMT_A | TOD_CTRL_DISABLE_FMT_B); - iowrite32(ctrl, &bp->tod->ctrl); - - ctrl = ioread32(&bp->tod->ctrl); idx = ctrl & TOD_CTRL_PROTOCOL ? 4 : 0; idx += (ctrl >> 16) & 3; dev_info(&bp->pdev->dev, "control: %x\n", ctrl); @@ -639,7 +893,7 @@ ptp_ocp_tod_info(struct ptp_ocp *bp) reg = ioread32(&bp->tod->status); dev_info(&bp->pdev->dev, "status: %x\n", reg); - reg = ioread32(&bp->tod->correction_sec); + reg = ioread32(&bp->tod->adj_sec); dev_info(&bp->pdev->dev, "correction: %d\n", reg); reg = ioread32(&bp->tod->utc_status); @@ -695,6 +949,9 @@ ptp_ocp_get_serial_number(struct ptp_ocp *bp) struct device *dev; int err; + if (!bp->i2c_ctrl) + return; + dev = device_find_child(&bp->i2c_ctrl->dev, NULL, ptp_ocp_firstchild); if (!dev) { dev_err(&bp->pdev->dev, "Can't find I2C adapter\n"); @@ -720,21 +977,6 @@ out: put_device(dev); } -static void -ptp_ocp_info(struct ptp_ocp *bp) -{ - 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_clock_name_from_val(select >> 16), - ptp_clock_index(bp->ptp)); - - ptp_ocp_tod_info(bp); -} - static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -910,18 +1152,6 @@ ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r) unsigned long start; int id; - /* XXX hack to work around old FPGA */ - if (bp->n_irqs < 10) { - dev_err(&bp->pdev->dev, "FPGA does not have SPI devices\n"); - return 0; - } - - if (r->irq_vec > bp->n_irqs) { - dev_err(&bp->pdev->dev, "spi device irq %d out of range\n", - r->irq_vec); - return 0; - } - start = pci_resource_start(pdev, 0) + r->offset; ptp_ocp_set_mem_resource(&res[0], start, r->size); ptp_ocp_set_irq_resource(&res[1], pci_irq_vector(pdev, r->irq_vec)); @@ -944,41 +1174,41 @@ ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r) 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]; unsigned long start; + info = r->extra; start = pci_resource_start(pdev, 0) + r->offset; ptp_ocp_set_mem_resource(&res[0], start, r->size); ptp_ocp_set_irq_resource(&res[1], pci_irq_vector(pdev, r->irq_vec)); - return platform_device_register_resndata(&pdev->dev, "xiic-i2c", - id, res, 2, NULL, 0); + return platform_device_register_resndata(&pdev->dev, info->name, + id, res, 2, + 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; - if (r->irq_vec > bp->n_irqs) { - dev_err(&bp->pdev->dev, "i2c device irq %d out of range\n", - r->irq_vec); - return 0; - } - + 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, 50000000); + 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, "xiic-i2c.%d", id); + 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)) @@ -997,26 +1227,51 @@ ptp_ocp_ts_irq(int irq, void *priv) 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(®->time_sec); nsec = ioread32(®->time_ns); ev.type = PTP_CLOCK_EXTTS; ev.index = ext->info->index; - ev.timestamp = sec * 1000000000ULL + nsec; + ev.timestamp = sec * NSEC_PER_SEC + nsec; ptp_clock_event(ext->bp->ptp, &ev); +out: iowrite32(1, ®->intr); /* write 1 to ack */ return IRQ_HANDLED; } static int -ptp_ocp_ts_enable(void *priv, bool enable) +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, ®->enable); @@ -1033,7 +1288,7 @@ ptp_ocp_ts_enable(void *priv, bool enable) static void ptp_ocp_unregister_ext(struct ptp_ocp_ext_src *ext) { - ext->info->enable(ext, false); + ext->info->enable(ext, ~0, false); pci_free_irq(ext->bp->pdev, ext->irq_vec, ext); kfree(ext); } @@ -1059,7 +1314,7 @@ ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r) 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, ext->info->name); + 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; @@ -1101,12 +1356,6 @@ ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r) { int port; - if (r->irq_vec > bp->n_irqs) { - dev_err(&bp->pdev->dev, "serial device irq %d out of range\n", - r->irq_vec); - return 0; - } - port = ptp_ocp_serial_line(bp, r); if (port < 0) return port; @@ -1130,15 +1379,40 @@ ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r) 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 */ +} + /* FB specific board initializers; last "resource" registered. */ static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r) { bp->flash_start = 1024 * 4096; + ptp_ocp_tod_init(bp); + ptp_ocp_nmea_out_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) { @@ -1147,13 +1421,373 @@ ptp_ocp_register_resources(struct ptp_ocp *bp, kernel_ulong_t driver_data) 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) + if (err) { + dev_err(&bp->pdev->dev, + "Could not register %s: err %d\n", + r->name, err); break; + } } 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); +} + +/* + * ANT0 == gps (in) + * ANT1 == sma1 (in) + * ANT2 == sma2 (in) + * ANT3 == sma3 (out) + * ANT4 == sma4 (out) + */ + +enum ptp_ocp_sma_mode { + SMA_MODE_IN, + SMA_MODE_OUT, +}; + +static struct ptp_ocp_sma_connector { + enum ptp_ocp_sma_mode mode; + bool fixed_mode; + u16 default_out_idx; +} ptp_ocp_sma_map[4] = { + { + .mode = SMA_MODE_IN, + .fixed_mode = true, + }, + { + .mode = SMA_MODE_IN, + .fixed_mode = true, + }, + { + .mode = SMA_MODE_OUT, + .fixed_mode = true, + .default_out_idx = 0, /* 10Mhz */ + }, + { + .mode = SMA_MODE_OUT, + .fixed_mode = true, + .default_out_idx = 1, /* PHC */ + }, +}; + +static ssize_t +ptp_ocp_show_output(u32 val, char *buf, int default_idx) +{ + const char *name; + ssize_t count; + + count = sysfs_emit(buf, "OUT: "); + name = ptp_ocp_select_name_from_val(ptp_ocp_sma_out, val); + if (!name) + name = ptp_ocp_sma_out[default_idx].name; + count += sysfs_emit_at(buf, count, "%s\n", name); + return count; +} + +static ssize_t +ptp_ocp_show_inputs(u32 val, char *buf, const char *zero_in) +{ + const char *name; + ssize_t count; + int i; + + count = sysfs_emit(buf, "IN: "); + for (i = 0; i < ARRAY_SIZE(ptp_ocp_sma_in); i++) { + if (val & ptp_ocp_sma_in[i].value) { + name = ptp_ocp_sma_in[i].name; + count += sysfs_emit_at(buf, count, "%s ", name); + } + } + if (!val && zero_in) + count += sysfs_emit_at(buf, count, "%s ", zero_in); + if (count) + count--; + count += sysfs_emit_at(buf, count, "\n"); + return count; +} + +static int +sma_parse_inputs(const char *buf, enum ptp_ocp_sma_mode *mode) +{ + struct ocp_selector *tbl[] = { ptp_ocp_sma_in, ptp_ocp_sma_out }; + 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[idx])) { + 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, u32 val, char *buf, + const char *zero_in) +{ + struct ptp_ocp_sma_connector *sma = &ptp_ocp_sma_map[sma_nr - 1]; + + if (sma->mode == SMA_MODE_IN) + return ptp_ocp_show_inputs(val, buf, zero_in); + + return ptp_ocp_show_output(val, buf, sma->default_out_idx); +} + +static ssize_t +sma1_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + + val = ioread32(&bp->sma->gpio1) & 0x3f; + return ptp_ocp_sma_show(bp, 1, val, buf, ptp_ocp_sma_in[0].name); +} + +static ssize_t +sma2_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + + val = (ioread32(&bp->sma->gpio1) >> 16) & 0x3f; + return ptp_ocp_sma_show(bp, 2, val, buf, NULL); +} + +static ssize_t +sma3_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + + val = ioread32(&bp->sma->gpio2) & 0x3f; + return ptp_ocp_sma_show(bp, 3, val, buf, NULL); +} + +static ssize_t +sma4_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + + val = (ioread32(&bp->sma->gpio2) >> 16) & 0x3f; + return ptp_ocp_sma_show(bp, 4, val, buf, NULL); +} + +static void +ptp_ocp_sma_store_output(struct ptp_ocp *bp, u32 val, u32 shift) +{ + unsigned long flags; + u32 gpio, mask; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + gpio = ioread32(&bp->sma->gpio2); + gpio = (gpio & mask) | (val << shift); + + __handle_signal_outputs(bp, gpio); + + iowrite32(gpio, &bp->sma->gpio2); + + spin_unlock_irqrestore(&bp->lock, flags); +} + +static void +ptp_ocp_sma_store_inputs(struct ptp_ocp *bp, u32 val, u32 shift) +{ + unsigned long flags; + u32 gpio, mask; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + gpio = ioread32(&bp->sma->gpio1); + gpio = (gpio & mask) | (val << shift); + + __handle_signal_inputs(bp, gpio); + + iowrite32(gpio, &bp->sma->gpio1); + + spin_unlock_irqrestore(&bp->lock, flags); +} + +static ssize_t +ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr, u32 shift) +{ + struct ptp_ocp_sma_connector *sma = &ptp_ocp_sma_map[sma_nr - 1]; + enum ptp_ocp_sma_mode mode; + int val; + + mode = sma->mode; + val = sma_parse_inputs(buf, &mode); + if (val < 0) + return val; + + if (mode != sma->mode && sma->fixed_mode) + return -EOPNOTSUPP; + + if (mode != sma->mode) { + pr_err("Mode changes not supported yet.\n"); + return -EOPNOTSUPP; + } + + if (sma->mode == SMA_MODE_IN) + ptp_ocp_sma_store_inputs(bp, val, shift); + else + ptp_ocp_sma_store_output(bp, val, shift); + + return 0; +} + +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, 0); + 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, 16); + 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, 0); + 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, 16); + 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) +{ + return ptp_ocp_select_table_show(ptp_ocp_sma_in, buf); +} +static DEVICE_ATTR_RO(available_sma_inputs); + +static ssize_t +available_sma_outputs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return ptp_ocp_select_table_show(ptp_ocp_sma_out, buf); +} +static DEVICE_ATTR_RO(available_sma_outputs); + static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1182,6 +1816,102 @@ gnss_sync_show(struct device *dev, struct device_attribute *attr, char *buf) 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); + +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); @@ -1189,7 +1919,7 @@ clock_source_show(struct device *dev, struct device_attribute *attr, char *buf) u32 select; select = ioread32(&bp->reg->select); - p = ptp_ocp_clock_name_from_val(select >> 16); + p = ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16); return sysfs_emit(buf, "%s\n", p); } @@ -1202,7 +1932,7 @@ clock_source_store(struct device *dev, struct device_attribute *attr, unsigned long flags; int val; - val = ptp_ocp_clock_val_from_name(buf); + val = ptp_ocp_select_val_from_name(ptp_ocp_clock, buf); if (val < 0) return val; @@ -1218,19 +1948,7 @@ static ssize_t available_clock_sources_show(struct device *dev, struct device_attribute *attr, char *buf) { - const char *clk; - ssize_t count; - int i; - - count = 0; - for (i = 0; i < ARRAY_SIZE(ptp_ocp_clock); i++) { - clk = ptp_ocp_clock[i].name; - count += sysfs_emit_at(buf, count, "%s ", clk); - } - if (count) - count--; - count += sysfs_emit_at(buf, count, "\n"); - return count; + return ptp_ocp_select_table_show(ptp_ocp_clock, buf); } static DEVICE_ATTR_RO(available_clock_sources); @@ -1239,10 +1957,258 @@ static struct attribute *timecard_attrs[] = { &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_irig_b_mode.attr, + &dev_attr_utc_tai_offset.attr, + &dev_attr_ts_window_adjust.attr, NULL, }; ATTRIBUTE_GROUPS(timecard); +static const char * +gpio_map(u32 gpio, u32 bit, const char *pri, const char *sec, const char *def) +{ + const char *ans; + + if (gpio & (1 << bit)) + ans = pri; + else if (gpio & (1 << (bit + 16))) + ans = sec; + else + ans = def; + return ans; +} + +static void +gpio_multi_map(char *buf, u32 gpio, u32 bit, + const char *pri, const char *sec, const char *def) +{ + char *ans = buf; + + strcpy(ans, def); + if (gpio & (1 << bit)) + ans += sprintf(ans, "%s ", pri); + if (gpio & (1 << (bit + 16))) + ans += sprintf(ans, "%s ", sec); +} + +static int +ptp_ocp_summary_show(struct seq_file *s, void *data) +{ + struct device *dev = s->private; + struct ptp_system_timestamp sts; + u32 sma_in, sma_out, ctrl, val; + struct ts_reg __iomem *ts_reg; + struct timespec64 ts; + struct ptp_ocp *bp; + const char *src; + bool on, map; + char *buf; + + buf = (char *)__get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + bp = dev_get_drvdata(dev); + sma_in = ioread32(&bp->sma->gpio1); + sma_out = ioread32(&bp->sma->gpio2); + + seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp)); + + sma1_show(dev, NULL, buf); + seq_printf(s, " sma1: %s", buf); + + sma2_show(dev, NULL, buf); + seq_printf(s, " sma2: %s", buf); + + sma3_show(dev, NULL, buf); + seq_printf(s, " sma3: %s", buf); + + sma4_show(dev, NULL, buf); + seq_printf(s, " sma4: %s", buf); + + if (bp->ts0) { + ts_reg = bp->ts0->mem; + on = ioread32(&ts_reg->enable); + src = "GNSS"; + 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); + src = gpio_map(sma_in, 2, "sma1", "sma2", "----"); + seq_printf(s, "%7s: %s, src: %s\n", "TS1", + on ? " ON" : "OFF", src); + } + + if (bp->ts2) { + ts_reg = bp->ts2->mem; + on = ioread32(&ts_reg->enable); + src = gpio_map(sma_in, 3, "sma1", "sma2", "----"); + seq_printf(s, "%7s: %s, src: %s\n", "TS2", + on ? " ON" : "OFF", src); + } + + 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", "TS3", + 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->irig_out) { + ctrl = ioread32(&bp->irig_out->ctrl); + on = ctrl & IRIG_M_CTRL_ENABLE; + val = ioread32(&bp->irig_out->status); + gpio_multi_map(buf, sma_out, 4, "sma3", "sma4", "----"); + 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); + src = gpio_map(sma_in, 4, "sma1", "sma2", "----"); + seq_printf(s, "%7s: %s, error: %d, src: %s\n", "IRIG in", + on ? " ON" : "OFF", val, src); + } + + if (bp->dcf_out) { + on = ioread32(&bp->dcf_out->ctrl) & DCF_M_CTRL_ENABLE; + val = ioread32(&bp->dcf_out->status); + gpio_multi_map(buf, sma_out, 5, "sma3", "sma4", "----"); + 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); + src = gpio_map(sma_in, 5, "sma1", "sma2", "----"); + seq_printf(s, "%7s: %s, error: %d, src: %s\n", "DCF in", + on ? " ON" : "OFF", val, src); + } + + 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); + if (val & 0x01) + src = gpio_map(sma_in, 0, "sma1", "sma2", "----"); + else if (val & 0x02) + src = "MAC"; + else if (val & 0x04) + src = "GNSS"; + else + src = "----"; + } else { + src = "?"; + } + + /* 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; + } + val = ioread32(&bp->reg->status); + seq_printf(s, "%7s: %s, state: %s\n", "PHC src", buf, + val & OCP_STATUS_IN_SYNC ? "sync" : "unsynced"); + + /* reuses PPS1 src from earlier */ + seq_printf(s, "MAC PPS1 src: %s\n", src); + + src = gpio_map(sma_in, 1, "sma1", "sma2", "GNSS2"); + seq_printf(s, "MAC PPS2 src: %s\n", src); + + 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 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); +} + +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) { @@ -1270,7 +2236,9 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev) bp->ptp_info = ptp_ocp_clock_info; spin_lock_init(&bp->lock); bp->gnss_port = -1; + bp->gnss2_port = -1; bp->mac_port = -1; + bp->nmea_port = -1; bp->pdev = pdev; device_initialize(&bp->dev); @@ -1332,10 +2300,18 @@ ptp_ocp_complete(struct ptp_ocp *bp) sprintf(buf, "ttyS%d", bp->gnss_port); ptp_ocp_link_child(bp, buf, "ttyGNSS"); } + if (bp->gnss2_port != -1) { + sprintf(buf, "ttyS%d", bp->gnss2_port); + ptp_ocp_link_child(bp, buf, "ttyGNSS2"); + } if (bp->mac_port != -1) { sprintf(buf, "ttyS%d", bp->mac_port); ptp_ocp_link_child(bp, buf, "ttyMAC"); } + if (bp->nmea_port != -1) { + sprintf(buf, "ttyS%d", bp->nmea_port); + ptp_ocp_link_child(bp, buf, "ttyNMEA"); + } sprintf(buf, "ptp%d", ptp_clock_index(bp->ptp)); ptp_ocp_link_child(bp, buf, "ptp"); @@ -1346,13 +2322,53 @@ ptp_ocp_complete(struct ptp_ocp *bp) if (device_add_groups(&bp->dev, timecard_groups)) pr_err("device add groups failed\n"); + ptp_ocp_debugfs_add_device(bp); + return 0; } static void -ptp_ocp_resource_summary(struct ptp_ocp *bp) +ptp_ocp_phc_info(struct ptp_ocp *bp) +{ + struct timespec64 ts; + u32 version, select; + bool sync; + + 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)); + + sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; + if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, NULL)) + dev_info(&bp->pdev->dev, "Time: %lld.%ld, %s\n", + ts.tv_sec, ts.tv_nsec, + sync ? "in-sync" : "UNSYNCED"); +} + +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); + if (bp->tod) + ptp_ocp_tod_info(bp); if (bp->image) { u32 ver = ioread32(&bp->image->version); @@ -1365,10 +2381,17 @@ ptp_ocp_resource_summary(struct ptp_ocp *bp) dev_info(dev, "golden image, version %d\n", ver >> 16); } - if (bp->gnss_port != -1) - dev_info(dev, "GNSS @ /dev/ttyS%d 115200\n", bp->gnss_port); - if (bp->mac_port != -1) - dev_info(dev, "MAC @ /dev/ttyS%d 57600\n", bp->mac_port); + ptp_ocp_serial_info(dev, "GNSS", bp->gnss_port, 115200); + ptp_ocp_serial_info(dev, "GNSS2", bp->gnss2_port, 115200); + ptp_ocp_serial_info(dev, "MAC", bp->mac_port, 57600); + if (bp->nmea_out && bp->nmea_port != -1) { + int baud = -1; + + reg = ioread32(&bp->nmea_out->uart_baud); + if (reg < ARRAY_SIZE(nmea_baud)) + baud = nmea_baud[reg]; + ptp_ocp_serial_info(dev, "NMEA", bp->nmea_port, baud); + } } static void @@ -1386,6 +2409,7 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp) static void ptp_ocp_detach(struct ptp_ocp *bp) { + ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); if (timer_pending(&bp->watchdog)) del_timer_sync(&bp->watchdog); @@ -1393,12 +2417,18 @@ ptp_ocp_detach(struct ptp_ocp *bp) 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->pps) ptp_ocp_unregister_ext(bp->pps); if (bp->gnss_port != -1) serial8250_unregister_port(bp->gnss_port); + if (bp->gnss2_port != -1) + serial8250_unregister_port(bp->gnss2_port); if (bp->mac_port != -1) serial8250_unregister_port(bp->mac_port); + if (bp->nmea_port != -1) + serial8250_unregister_port(bp->nmea_port); if (bp->spi_flash) platform_device_unregister(bp->spi_flash); if (bp->i2c_ctrl) @@ -1425,10 +2455,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) return -ENOMEM; } - err = devlink_register(devlink); - if (err) - goto out_free; - + devlink_register(devlink); err = pci_enable_device(pdev); if (err) { dev_err(&pdev->dev, "pci_enable_device\n"); @@ -1445,7 +2472,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) * 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, 10, PCI_IRQ_MSI | PCI_IRQ_MSIX); + err = pci_alloc_irq_vectors(pdev, 1, 11, PCI_IRQ_MSI | PCI_IRQ_MSIX); if (err < 0) { dev_err(&pdev->dev, "alloc_irq_vectors err: %d\n", err); goto out; @@ -1470,7 +2497,6 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out; ptp_ocp_info(bp); - ptp_ocp_resource_summary(bp); return 0; @@ -1481,9 +2507,7 @@ out_disable: pci_disable_device(pdev); out_unregister: devlink_unregister(devlink); -out_free: devlink_free(devlink); - return err; } @@ -1554,6 +2578,8 @@ ptp_ocp_init(void) const char *what; int err; + ptp_ocp_debugfs_init(); + what = "timecard class"; err = class_register(&timecard_class); if (err) @@ -1576,6 +2602,7 @@ out_register: 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; } @@ -1586,6 +2613,7 @@ 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); |