From: Oleg Zadorozhnyi The part has an internal line by which one PLL can drive the others as a reference. Model it as two pins: an output pin on the source PLL, whose state says whether it drives the line, and an input pin on each destination, selectable like any input and reporting no signal while no PLL drives the line. Taking the line writes a pattern to the source's EXT page and sets a global enable; giving it up writes the disable pattern. The owner is read back from the pages, and a line enabled from a page the driver does not recognize, or from two pages, refuses every connect. Resume re-reads the line owner, the output mutes and the output delays, which a profile reload may have changed. Signed-off-by: Oleg Zadorozhnyi Signed-off-by: Ali Rouhi --- drivers/dpll/sit9531x/core.c | 708 ++++++++++++++++++++++++++++++++++- drivers/dpll/sit9531x/core.h | 32 ++ drivers/dpll/sit9531x/dpll.c | 398 ++++++++++++++++++-- drivers/dpll/sit9531x/regs.h | 3 + 4 files changed, 1112 insertions(+), 29 deletions(-) diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c index 8f305cc27737..e3ce7b6cad6b 100644 --- a/drivers/dpll/sit9531x/core.c +++ b/drivers/dpll/sit9531x/core.c @@ -857,6 +857,8 @@ static bool sit9531x_prio_src_live(const struct sit9531x_dev *sitdev, u8 src) { u8 index = sit9531x_hw_src_input(src); + if (index == SIT9531X_INTSYNC_PIN_ID) + return sit9531x_intsync_driven(sitdev); if (index >= sitdev->info->num_inputs) return true; @@ -1091,6 +1093,18 @@ static u8 sit9531x_prio_activesel_pick(struct sit9531x_dev *sitdev, return srcs[0]; } +static bool sit9531x_prio_table_lists_intsync(const u8 *srcs) +{ + u8 slot; + + for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) + if ((srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK) == + SIT9531X_PRIO_SRC_INTSYNC) + return true; + + return false; +} + static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx, const u8 *srcs) { @@ -1107,6 +1121,11 @@ static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx, if (sit9531x_prio_src_usable(srcs[slot])) empty = false; + if (sitdev->intsync_stale && + (sit9531x_prio_table_lists_intsync(srcs) || + sit9531x_prio_table_lists_intsync(chan->prio_srcs))) + sit9531x_intsync_refresh(sitdev); + memcpy(back, chan->table_pending ? chan->table_back : chan->prio_srcs, sizeof(back)); @@ -2585,6 +2604,556 @@ static int sit9531x_clear_notifications(struct sit9531x_dev *sitdev) return 0; } +/* + * Close the debug window on a PLL's EXT page; the key opens every debug + * register on the page while it holds the unlock value. + */ +static int sit9531x_intsync_debug_lock(struct sit9531x_dev *sitdev, u8 ext_page) +{ + return sit9531x_write_close(sitdev, + SIT9531X_REG(ext_page, + SIT9531X_PLL_REG_DEBUG), + SIT9531X_PLL_DEBUG_LOCK); +} + +/* + * Values written to the source PLL's EXT page to enable or disable INTSYNC, + * which locks the frequency PLL to the phase PLL. + */ +struct sit9531x_intsync_reg { + u8 offset; + u8 en_val; + u8 dis_val; +}; + +static const struct sit9531x_intsync_reg intsync_config[] = { + { 0x2D, 0x02, 0x00 }, + { 0x50, 0x08, 0x00 }, + { 0x51, 0x04, 0x00 }, + { 0x54, 0x02, 0x00 }, + { 0x55, 0x28, 0x20 }, + { 0x5C, 0x0F, 0x00 }, + { 0x5D, 0xFF, 0x00 }, + { 0x6C, 0xDD, 0x00 }, +}; + +/* + * Find the PLL whose EXT page holds the INTSYNC enable pattern, whole or in + * part. Caller must hold sitdev->multiop_lock. + */ +int sit9531x_intsync_src_detect(struct sit9531x_dev *sitdev) +{ + s8 src = -1, partial = -1; + int rc, ret, n_en, n_dis; + u8 pll, ext_page; + u8 n_pages = 0; + unsigned int i; + bool enabled; + u8 global; + + lockdep_assert_held(&sitdev->multiop_lock); + + rc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &global); + if (rc) + return rc; + enabled = global & BIT(SIT9531X_INTSYNC_EN_BIT); + + /* + * Read the EXT pages even with the global enable clear; a page can hold + * the enable pattern under a clear bit. + */ + for (pll = 0; pll < SIT9531X_NUM_PLLS; pll++) { + ext_page = SIT9531X_PLL_EXT_PAGE(pll); + + /* + * Open the EXT page's debug window to read the INTSYNC + * registers. + */ + rc = sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + SIT9531X_PLL_REG_DEBUG), + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) { + sit9531x_intsync_debug_lock(sitdev, ext_page); + return rc; + } + + n_en = 0; + n_dis = 0; + for (i = 0; i < ARRAY_SIZE(intsync_config); i++) { + u16 reg; + u8 val; + + reg = SIT9531X_REG(ext_page, intsync_config[i].offset); + + rc = sit9531x_read_u8(sitdev, reg, &val); + if (rc) + break; + if (val == intsync_config[i].en_val) + n_en++; + else if (val == intsync_config[i].dis_val) + n_dis++; + } + + ret = sit9531x_intsync_debug_lock(sitdev, ext_page); + if (!rc) + rc = ret; + if (rc) + return rc; + + /* + * A page with the disable pattern or with other values is not a + * source. + */ + if (!n_en || n_en + n_dis != ARRAY_SIZE(intsync_config)) + continue; + n_pages++; + + if (n_dis || !enabled) { + /* + * A page holding both patterns, or the whole enable + * pattern under a clear global enable, does not drive + * the line. + */ + if (partial < 0) + partial = pll; + continue; + } + + if (src < 0) + src = pll; + } + + /* + * Record a PLL with a partial pattern, though it does not drive the + * line, so that no other PLL is enabled alongside it. + */ + sitdev->intsync_partial = src < 0 && partial >= 0; + if (sitdev->intsync_partial) { + dev_warn(sitdev->dev, + "PLL%c holds an INTSYNC source configuration that does not drive the line\n", + 'A' + partial); + src = partial; + } + + /* + * With more than one page holding the pattern, refuse every connect; an + * enable on one would also set the global enable over the other. + */ + sitdev->intsync_conflict = n_pages > 1; + if (sitdev->intsync_conflict) + dev_warn(sitdev->dev, + "%u PLLs hold the INTSYNC source pattern; recording PLL%c\n", + n_pages, 'A' + src); + + sitdev->intsync_src = src; + sitdev->intsync_foreign = enabled && src < 0; + + return 0; +} + +/** + * sit9531x_intsync_readback - re-read the sync source record from the part + * @sitdev: device pointer + * + * A page reads the same latched or not, so a recorded partial source is kept + * unless the scan resolves it. Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, <0 on a read error + */ +int sit9531x_intsync_readback(struct sit9531x_dev *sitdev) +{ + bool partial = sitdev->intsync_partial; + s8 src = sitdev->intsync_src; + int rc; + + rc = sit9531x_intsync_src_detect(sitdev); + if (rc) + return rc; + + if (partial && (sitdev->intsync_src < 0 || + (sitdev->intsync_src == src && + !sitdev->intsync_partial))) { + sitdev->intsync_src = src; + sitdev->intsync_partial = true; + sitdev->intsync_foreign = false; + } + + return 0; +} + +/** + * sit9531x_intsync_refresh - re-read a stale sync source record + * @sitdev: device pointer + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 when the record is current, <0 when the scan fails + */ +int sit9531x_intsync_refresh(struct sit9531x_dev *sitdev) +{ + s8 owner; + int rc; + + if (!sitdev->intsync_stale) + return 0; + + owner = sit9531x_intsync_owner(sitdev); + rc = sit9531x_intsync_readback(sitdev); + if (rc) + return rc; + sitdev->intsync_stale = false; + if (owner != sit9531x_intsync_owner(sitdev)) + sitdev->intsync_ntf_pending = true; + + return 0; +} + +/* + * Check if the inter-PLL sync line carries a signal, from the recorded source + * or a PLL locked to the line. Caller must hold sitdev->multiop_lock. + */ +bool sit9531x_intsync_driven(const struct sit9531x_dev *sitdev) +{ + const struct sit9531x_chan *chan; + u8 pll; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (sitdev->intsync_src >= 0 && !sitdev->intsync_partial && + !sitdev->intsync_stale) + return true; + + for (pll = 0; pll < SIT9531X_NUM_PLLS; pll++) { + chan = sit9531x_chan_state_get(sitdev, pll); + if (chan->routed_ref == SIT9531X_INTSYNC_PIN_ID && + chan->locked && !chan->ho_freeze && + !chan->stale && !chan->intsync_dropped) + return true; + } + + return false; +} + +static int __sit9531x_intsync_disable(struct sit9531x_dev *sitdev, + u8 src_pll_idx, bool restore, + bool *owner_kept, bool *touched); + +/* + * Set the global enable and latch the INTSYNC source pattern into the PLL's EXT + * page. Caller must hold sitdev->multiop_lock. + */ +int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx, + bool *rollback_failed, bool *touched) +{ + u8 ext_page, val; + int rc, lock_rc; + unsigned int i; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (rollback_failed) + *rollback_failed = false; + if (touched) + *touched = false; + if (src_pll_idx >= SIT9531X_NUM_PLLS) + return -EINVAL; + + ext_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx); + + rc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val); + if (rc) + return rc; + if (touched) + *touched = true; + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, + val | BIT(SIT9531X_INTSYNC_EN_BIT)); + if (rc) + goto err_disable; + + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + usleep_range(1000, 2000); + if (rc) + goto relock_err; + + rc = sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + SIT9531X_PLL_REG_DEBUG), + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + goto relock_err; + + for (i = 0; i < ARRAY_SIZE(intsync_config); i++) { + rc = sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + intsync_config[i].offset), + intsync_config[i].en_val); + if (rc) + goto relock_err; + } + + rc = sit9531x_pll_small_update(sitdev, src_pll_idx); + if (rc) + goto relock_err; + + rc = 0; + goto relock; + +relock_err: + sit9531x_intsync_debug_lock(sitdev, ext_page); + goto err_disable; + +relock: + lock_rc = sit9531x_intsync_debug_lock(sitdev, ext_page); + if (lock_rc) + dev_warn(sitdev->dev, + "PLL%c: INTSYNC enabled but the debug window was left open: %d\n", + 'A' + src_pll_idx, lock_rc); + + return rc; + +err_disable: + { + int rollback_rc; + + rollback_rc = __sit9531x_intsync_disable(sitdev, src_pll_idx, + false, NULL, NULL); + if (rollback_rc) { + dev_warn(sitdev->dev, + "INTSYNC rollback failed after enable error: %d (original %d)\n", + rollback_rc, rc); + if (rollback_failed) + *rollback_failed = true; + } + } + + return rc; +} + +/* + * Check if every INTSYNC register of an EXT page holds its disable value. The + * page's debug window must be open. + */ +static int sit9531x_intsync_page_off(struct sit9531x_dev *sitdev, + u8 ext_page, bool *off) +{ + unsigned int i; + u8 val; + int rc; + + *off = false; + for (i = 0; i < ARRAY_SIZE(intsync_config); i++) { + rc = sit9531x_read_u8(sitdev, + SIT9531X_REG(ext_page, + intsync_config[i].offset), + &val); + if (rc) + return rc; + if (val != intsync_config[i].dis_val) + return 0; + } + *off = true; + + return 0; +} + +static int __sit9531x_intsync_disable(struct sit9531x_dev *sitdev, + u8 src_pll_idx, bool restore, + bool *owner_kept, bool *touched) +{ + bool in_window = false, was_enabled; + u8 ext_page, val; + int rc, lock_rc; + unsigned int i; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (owner_kept) + *owner_kept = false; + if (touched) + *touched = false; + if (src_pll_idx >= SIT9531X_NUM_PLLS) + return -EINVAL; + + ext_page = SIT9531X_PLL_EXT_PAGE(src_pll_idx); + + rc = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val); + if (rc) + return rc; + was_enabled = val & BIT(SIT9531X_INTSYNC_EN_BIT); + if (touched) + *touched = true; + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, + val & ~BIT(SIT9531X_INTSYNC_EN_BIT)); + if (rc) + goto restore_global; + /* A sample taken before this write can still show a lock. */ + for (i = 0; i < SIT9531X_NUM_PLLS; i++) + if (sitdev->chan[i].routed_ref == SIT9531X_INTSYNC_PIN_ID) + sitdev->chan[i].intsync_dropped = true; + + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + usleep_range(1000, 2000); + if (rc) + goto restore_global; + + rc = sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + SIT9531X_PLL_REG_DEBUG), + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + goto restore_global; + in_window = true; + + for (i = 0; i < ARRAY_SIZE(intsync_config); i++) { + rc = sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + intsync_config[i].offset), + intsync_config[i].dis_val); + if (rc) + goto restore_global; + } + + rc = sit9531x_pll_small_update(sitdev, src_pll_idx); + if (rc && owner_kept) + *owner_kept = true; + + goto relock; + +restore_global: + /* + * Restore the global enable, or finish the disable if the page holds + * the disable pattern. + */ + if (restore && in_window) { + bool off; + + if (sit9531x_intsync_page_off(sitdev, ext_page, &off)) { + if (owner_kept) + *owner_kept = true; + } else if (off) { + rc = sit9531x_pll_small_update(sitdev, src_pll_idx); + if (rc && owner_kept) + *owner_kept = true; + goto relock; + } + } + + if (restore && was_enabled) { + int ret; + + ret = sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, + &val); + if (!ret) + ret = sit9531x_write_u8(sitdev, + SIT9531X_REG_INTSYNC_GLOBAL, + val | + BIT(SIT9531X_INTSYNC_EN_BIT)); + if (!ret) { + /* The page-0 small update applies the global enable */ + ret = sit9531x_write_close(sitdev, + SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + usleep_range(1000, 2000); + } + if (ret) { + dev_warn(sitdev->dev, + "PLL%c: INTSYNC enable not put back after a failed disable: %d\n", + 'A' + src_pll_idx, ret); + if (owner_kept) + *owner_kept = true; + } + } + +relock: + lock_rc = sit9531x_intsync_debug_lock(sitdev, ext_page); + if (lock_rc) { + if (rc) + dev_warn(sitdev->dev, + "PLL%c: debug window left open: %d\n", + 'A' + src_pll_idx, lock_rc); + else + dev_warn(sitdev->dev, + "PLL%c: INTSYNC disabled but the debug window was left open: %d\n", + 'A' + src_pll_idx, lock_rc); + } + + return rc; +} + +/* + * Clear the global enable and latch the INTSYNC disable pattern into the PLL's + * EXT page. Caller must hold sitdev->multiop_lock. + */ +int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx, + bool *owner_kept, bool *touched) +{ + return __sit9531x_intsync_disable(sitdev, src_pll_idx, true, + owner_kept, touched); +} + +/* + * Notify the output pins in @outs and, when @src, the INTSYNC source pin. Call + * without multiop_lock: the notification takes the subsystem locks, which are + * taken before multiop_lock. + */ +static void sit9531x_drive_pins_ntf(struct sit9531x_dev *sitdev, u16 outs, + bool src) +{ + struct sit9531x_dpll *sitdpll; + struct sit9531x_dpll_pin *pin; + struct dpll_pin *dpll_pin; + int rc; + + if (!outs && !src) + return; + + list_for_each_entry(sitdpll, &sitdev->dplls, list) { + list_for_each_entry(pin, &sitdpll->pins, list) { + if (pin->dir != DPLL_PIN_DIRECTION_OUTPUT) + continue; + if (pin->id == SIT9531X_INTSYNC_OUT_PIN_ID ? + !src : !(outs & BIT(pin->id))) + continue; + dpll_pin = READ_ONCE(pin->dpll_pin); + if (!dpll_pin) + continue; + rc = dpll_pin_change_ntf(dpll_pin); + /* + * The source pin is one pin registered on every DPLL. + */ + if (pin->id == SIT9531X_INTSYNC_OUT_PIN_ID) + src = false; + if (!rc) + continue; + mutex_lock(&sitdev->multiop_lock); + if (pin->id == SIT9531X_INTSYNC_OUT_PIN_ID) + sitdev->intsync_ntf_pending = true; + else + sitdev->out[pin->id].ntf_pending = true; + mutex_unlock(&sitdev->multiop_lock); + } + } +} + +/* + * Re-read a stale sync source record; return true when a source pin + * notification is pending. Caller must hold sitdev->multiop_lock. + */ +static bool sit9531x_intsync_stale_retry(struct sit9531x_dev *sitdev) +{ + if (sit9531x_intsync_refresh(sitdev)) + return false; + + if (!sitdev->intsync_ntf_pending) + return false; + sitdev->intsync_ntf_pending = false; + + return true; +} + /** * sit9531x_phase_offset_read - read phase difference via TDC * @sitdev: device pointer @@ -3022,6 +3591,7 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx) chan->ho_freeze = !!(ho_freeze & BIT(pll_idx)); chan->ho_valid = !!(pll_status_1 & SIT9531X_PLL_STATUS_1_HO_VALID); chan->stale = false; + chan->intsync_dropped = false; return 0; } @@ -3197,6 +3767,15 @@ static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev) return rc; } + mutex_lock(&sitdev->multiop_lock); + rc = sit9531x_intsync_src_detect(sitdev); + mutex_unlock(&sitdev->multiop_lock); + if (rc) { + dev_err(sitdev->dev, + "Failed to detect INTSYNC source: %d\n", rc); + return rc; + } + for (i = 0; i < sitdev->info->num_outputs; i++) { bool beyond; s64 phase_ps; @@ -3522,6 +4101,7 @@ static void sit9531x_dev_periodic_work(struct kthread_work *work) struct sit9531x_dev *sitdev = container_of(work, struct sit9531x_dev, work.work); struct sit9531x_dpll *sitdpll; + bool src_moved; bool answered; int rc; @@ -3531,16 +4111,16 @@ static void sit9531x_dev_periodic_work(struct kthread_work *work) */ mutex_lock(&sitdev->multiop_lock); answered = sit9531x_dev_ref_states_update(sitdev); + src_moved = sit9531x_intsync_stale_retry(sitdev); answered |= sit9531x_dev_chan_states_update(sitdev); mutex_unlock(&sitdev->multiop_lock); list_for_each_entry(sitdpll, &sitdev->dplls, list) sit9531x_dpll_changes_check(sitdpll); + /* The source pin is not a selection pin; the walk above skips it. */ + sit9531x_drive_pins_ntf(sitdev, 0, src_moved); - /* - * Clear the W1C notification latches on pages 0 and 6 after acting on - * them; while set they keep INTRB asserted. - */ + /* The latches are W1C; left set, they keep INTRB asserted. */ mutex_lock(&sitdev->multiop_lock); rc = sit9531x_clear_notifications(sitdev); mutex_unlock(&sitdev->multiop_lock); @@ -4015,13 +4595,13 @@ static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll, if (index == SIT9531X_MAX_INPUTS) return true; if (index == SIT9531X_INTSYNC_PIN_ID) - return false; + return true; return sit9531x_input_pin_is_registrable(sitdev, index); } if (index == SIT9531X_INTSYNC_OUT_PIN_ID) - return false; + return true; if (index >= sitdev->info->num_outputs) return false; @@ -4420,12 +5000,128 @@ static int sit9531x_suspend(struct device *dev) static int sit9531x_resume(struct device *dev) { struct sit9531x_dev *sitdev = dev_get_drvdata(dev); + u16 outs_was = 0, outs_now = 0, outs_unread = 0, outs_moved = 0; + bool src_moved; + s8 owner; + int rc; + u8 i; /* After power loss the part is on page 0, not the cached page */ sit9531x_page_cache_drop(sitdev); + + /* + * A part that lost power returns with its profile's INTSYNC routing and + * output mutes, which the poll does not read; read them back. Arm one + * re-select for a source lost and restored between samples. + */ mutex_lock(&sitdev->multiop_lock); sit9531x_reselect_arm(sitdev); + /* + * Drop and report writes pending from before suspend: a reloaded + * profile may have replaced their registers. + */ + for (i = 0; i < SIT9531X_NUM_PLLS; i++) { + struct sit9531x_chan *chan = &sitdev->chan[i]; + bool retime = false; + u8 j, ho; + + /* + * A table write-back is pending only under forced holdover; + * once that is cleared, the next poll reads the table from the + * registers. + */ + if ((chan->table_pending || chan->ho_release_pending) && + !sit9531x_read_pll_u8(sitdev, i, SIT9531X_PLL_REG_HO_CTRL, + &ho) && + !(ho & BIT(SIT9531X_PLL_HO_FORCE_BIT))) { + dev_warn(sitdev->dev, + "PLL%c: forced holdover cleared over suspend, priority table restore dropped\n", + 'A' + i); + chan->table_pending = false; + chan->ho_release_pending = false; + } + + for (j = 0; j < sitdev->info->num_outputs; j++) { + if (!sitdev->out[j].retime_pending || + sitdev->out[j].pll_idx != i) + continue; + sitdev->out[j].retime_pending = false; + retime = true; + } + if (!retime && !chan->flush_pending && + !(sitdev->flush_arm_pending & BIT(i))) + continue; + dev_warn(sitdev->dev, + "PLL%c: pending before suspend, dropped:%s%s%s\n", + 'A' + i, retime ? " delay re-time" : "", + chan->flush_pending ? " output realignment" : "", + (sitdev->flush_arm_pending & BIT(i)) ? + " on-demand flush enable" : ""); + chan->flush_pending = false; + sitdev->flush_arm_pending &= ~BIT(i); + } + if (sitdev->flush_ctrl_pending) { + dev_warn(sitdev->dev, + "flush trigger source select pending before suspend, dropped\n"); + sitdev->flush_ctrl_pending = false; + } + owner = sit9531x_intsync_owner(sitdev); + /* + * The delay registers are reloaded with the profile too; read them back + * as probe does. + */ + for (i = 0; i < sitdev->info->num_outputs; i++) { + struct sit9531x_out *out = &sitdev->out[i]; + bool beyond; + s64 phase_ps; + + if (!out->routed) + continue; + rc = sit9531x_output_phase_read(sitdev, i, &phase_ps, + &beyond); + if (rc) { + dev_warn(sitdev->dev, + "Output %u delay not read on resume: %d\n", + i, rc); + out->phase_stale = true; + outs_unread |= BIT(i); + continue; + } + if (out->phase_adj != phase_ps || out->phase_stale) + outs_moved |= BIT(i); + out->phase_adj = phase_ps; + out->phase_armed = !beyond && phase_ps; + out->phase_stale = false; + } + rc = sit9531x_intsync_readback(sitdev); + if (rc) + dev_warn(sitdev->dev, + "INTSYNC source not re-detected on resume: %d\n", rc); + sitdev->intsync_stale = rc != 0; + src_moved = rc || owner != sit9531x_intsync_owner(sitdev); + for (i = 0; i < sitdev->info->num_outputs; i++) { + if (sitdev->out[i].enabled) + outs_was |= BIT(i); + /* The mute state of a request left unconfirmed is unknown */ + if (sitdev->out[i].state_stale) + outs_unread |= BIT(i); + rc = sit9531x_output_state_refresh(sitdev, i); + if (rc) { + dev_warn(sitdev->dev, + "Output %u mute state not read on resume: %d\n", + i, rc); + sitdev->out[i].state_stale = true; + outs_unread |= BIT(i); + } + if (sitdev->out[i].enabled) + outs_now |= BIT(i); + } mutex_unlock(&sitdev->multiop_lock); + + sit9531x_drive_pins_ntf(sitdev, + (outs_was ^ outs_now) | outs_unread | + outs_moved, src_moved); + if (sitdev->irq > 0) enable_irq(sitdev->irq); kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0); diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h index 8718c463c560..7dc78ddb18e5 100644 --- a/drivers/dpll/sit9531x/core.h +++ b/drivers/dpll/sit9531x/core.h @@ -142,6 +142,8 @@ struct sit9531x_out { * @locked: PLL is active and its outer loop reports lock * @stale: last state read failed; the fields hold the previous sample and * are not reported + * @intsync_dropped: driver turned INTSYNC off after this sample; a lock to + * INTSYNC in it is not trusted * @mode: 0 = sync (outer loop enabled), 1 = free-run * @routed_ref: input the device routes to the PLL, as a logical index * (INTSYNC: SIT9531X_INTSYNC_PIN_ID); SIT9531X_REF_INVALID if none @@ -184,6 +186,7 @@ struct sit9531x_chan { bool active; bool locked; bool stale; + bool intsync_dropped; u8 mode; u8 routed_ref; bool inner_lol; @@ -233,6 +236,12 @@ struct sit9531x_chan { * @reset_gpio: optional reset line, NULL if absent * @irq: optional INTRB IRQ, 0 if not wired * @intsync_src: PLL sourcing INTSYNC, or -1 + * @intsync_partial: @intsync_src has a partial source configuration or the + * global enable clear; not driving + * @intsync_conflict: more than one PLL page holds the source pattern + * @intsync_foreign: global enable set with no page holding the pattern + * @intsync_stale: @intsync_src must be read back before use + * @intsync_ntf_pending: the poll must send a source pin notification * @irq_ack_fails: consecutive failed INTRB acknowledgements * @fvco_band_warned: bit per PLL warned for Fref * DIVN outside its VCO * band; written under @multiop_lock @@ -270,6 +279,11 @@ struct sit9531x_dev { int irq; s8 intsync_src; + bool intsync_partial; + bool intsync_conflict; + bool intsync_foreign; + bool intsync_stale; + bool intsync_ntf_pending; bool loops_open; bool flush_ctrl_pending; @@ -318,6 +332,24 @@ int sit9531x_output_phase_read(struct sit9531x_dev *sitdev, u8 out_idx, int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev, u8 out_idx, s32 phase_ps); +/* + * Return the PLL driving INTSYNC with a complete source configuration, or -1. + * Caller must hold sitdev->multiop_lock. + */ +static inline s8 sit9531x_intsync_owner(const struct sit9531x_dev *sitdev) +{ + return sitdev->intsync_partial ? -1 : sitdev->intsync_src; +} + +int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx, + bool *rollback_failed, bool *touched); +int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx, + bool *owner_kept, bool *touched); +int sit9531x_intsync_src_detect(struct sit9531x_dev *sitdev); +int sit9531x_intsync_readback(struct sit9531x_dev *sitdev); +int sit9531x_intsync_refresh(struct sit9531x_dev *sitdev); +bool sit9531x_intsync_driven(const struct sit9531x_dev *sitdev); + int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, u8 pll_idx, u8 *ref); int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev); diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c index ba4ac5e7d365..1bd2b962cdab 100644 --- a/drivers/dpll/sit9531x/dpll.c +++ b/drivers/dpll/sit9531x/dpll.c @@ -27,6 +27,20 @@ static bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_pin *pin) return pin->dir == DPLL_PIN_DIRECTION_INPUT; } +static bool +sit9531x_dpll_is_intsync_pin(const struct sit9531x_dpll_pin *pin) +{ + return sit9531x_dpll_is_input_pin(pin) && + pin->id == SIT9531X_INTSYNC_PIN_ID; +} + +static bool +sit9531x_dpll_is_intsync_src_pin(const struct sit9531x_dpll_pin *pin) +{ + return !sit9531x_dpll_is_input_pin(pin) && + pin->id == SIT9531X_INTSYNC_OUT_PIN_ID; +} + static bool sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin) { @@ -177,16 +191,18 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = { }; /* - * Pin-state contract. An input is SELECTABLE while listed in this PLL's - * priority table and DISCONNECTED otherwise; CONNECTED is refused because the - * device selects by priority. Operstate is ACTIVE when the device feeds the PLL - * this input (chan->routed_ref), the PLL is tracking and the lane has signal; - * NO_SIGNAL on loss of signal; QUAL_FAILED on frequency drift; STANDBY - * otherwise. The routed reference is used because on loss of signal the device - * falls back to another listed source and leaves the selection naming the lost - * one. On manual input select, the routed input is CONNECTED. An output is - * CONNECTED while driven and DISCONNECTED while muted; SELECTABLE is refused. - * The XO pin is always CONNECTED. + * Pin-state contract. An input or the INTSYNC destination is SELECTABLE while + * listed in this PLL's priority table and DISCONNECTED otherwise; CONNECTED is + * refused because the device selects by priority. Operstate is ACTIVE when the + * device feeds the PLL this source (chan->routed_ref), the PLL is tracking and + * the lane has signal; NO_SIGNAL on loss of signal; QUAL_FAILED on frequency + * drift; STANDBY otherwise. The routed reference is used because on loss of + * signal the device falls back to another listed source and leaves the + * selection naming the lost one. On manual input select, the routed input is + * CONNECTED. The INTSYNC destination has signal while the driver's record shows + * a PLL driving the line. An output or the INTSYNC source is CONNECTED while + * driven and DISCONNECTED otherwise; SELECTABLE is refused. The XO pin is + * always CONNECTED. */ /* Caller must hold sitdev->multiop_lock. */ @@ -215,6 +231,22 @@ sit9531x_dpll_selection_state_get(struct sit9531x_dev *sitdev, *state = DPLL_PIN_STATE_DISCONNECTED; } +/* + * For INTSYNC, use the driver's record of a PLL driving or locked to the line + * in place of the LOS monitor. Caller must hold sitdev->multiop_lock. + */ +static bool +sit9531x_dpll_selection_no_signal(struct sit9531x_dev *sitdev, u8 pin_id) +{ + lockdep_assert_held(&sitdev->multiop_lock); + + if (pin_id == SIT9531X_INTSYNC_PIN_ID) + return !sit9531x_intsync_driven(sitdev); + + return pin_id < sitdev->info->num_inputs && + sit9531x_ref_state_get(sitdev, pin_id)->los; +} + /* Caller must hold sitdev->multiop_lock. */ static bool sit9531x_dpll_selection_active(struct sit9531x_dev *sitdev, @@ -229,12 +261,7 @@ sit9531x_dpll_selection_active(struct sit9531x_dev *sitdev, if (chan->routed_ref != pin_id || !sit9531x_chan_tracking(chan)) return false; - /* - * Do not report a lane without signal as locked, whatever the lock bit - * says; the PLL is about to switch away from it. - */ - if (pin_id < sitdev->info->num_inputs && - sit9531x_ref_state_get(sitdev, pin_id)->los) + if (sit9531x_dpll_selection_no_signal(sitdev, pin_id)) return false; return true; @@ -256,12 +283,13 @@ sit9531x_dpll_selection_operstate_get(struct sit9531x_dev *sitdev, return; } + if (sit9531x_dpll_selection_no_signal(sitdev, pin_id)) { + *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL; + return; + } + if (pin_id < sitdev->info->num_inputs) { ref = sit9531x_ref_state_get(sitdev, pin_id); - if (ref->los) { - *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL; - return; - } if (ref->qual_fail) { *operstate = DPLL_PIN_OPERSTATE_QUAL_FAILED; return; @@ -587,8 +615,7 @@ sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin, "Failed to read the input clock monitors"); return rc; } - if (dpin->id < sitdev->info->num_inputs && - sit9531x_ref_state_get(sitdev, dpin->id)->los) + if (sit9531x_dpll_selection_no_signal(sitdev, dpin->id)) rc = -ENODATA; } mutex_unlock(&sitdev->multiop_lock); @@ -662,6 +689,325 @@ sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin, enum dpll_pin_direction *direction, struct netlink_ext_ack *extack); +/* Caller must hold sitdev->multiop_lock. */ +static bool +sit9531x_dpll_intsync_src_connected(const struct sit9531x_dev *sitdev, + const struct sit9531x_dpll *sitdpll) +{ + return sitdev->intsync_src == sitdpll->id && !sitdev->intsync_partial; +} + +/* Caller must hold sitdev->multiop_lock. */ +static int sit9531x_dpll_intsync_refresh(struct sit9531x_dev *sitdev, + struct netlink_ext_ack *extack) +{ + int rc; + + rc = sit9531x_intsync_refresh(sitdev); + if (rc) + NL_SET_ERR_MSG(extack, "INTSYNC source could not be read back"); + + return rc; +} + +static int +sit9531x_dpll_intsync_src_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc = sit9531x_dpll_intsync_refresh(sitdev, extack); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + if (sit9531x_dpll_intsync_src_connected(sitdev, sitdpll)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * Driving the line is an explicit routing, not a selection candidate, so + * SELECTABLE is refused. + */ +static int +sit9531x_dpll_intsync_src_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin = pin_priv; + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + bool was_connected, moved, owner_kept = false; + int rc = 0, detect_rc = 0; + bool changed = false; + u8 hw_src; + + mutex_lock(&sitdev->multiop_lock); + + rc = sit9531x_dpll_intsync_refresh(sitdev, extack); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + was_connected = sit9531x_dpll_intsync_src_connected(sitdev, sitdpll); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (was_connected) + break; + if (sitdev->intsync_src >= 0 && + sitdev->intsync_src != sitdpll->id) { + NL_SET_ERR_MSG(extack, + "INTSYNC is already sourced by another PLL"); + rc = -EBUSY; + break; + } + /* + * The enable drives the line from every page holding the source + * pattern. + */ + if (sitdev->intsync_conflict) { + NL_SET_ERR_MSG(extack, + "More than one PLL holds the INTSYNC source pattern"); + rc = -EBUSY; + break; + } + /* + * The global enable covers every page, so this PLL would drive + * the line alongside the configuration that set it. + */ + if (sitdev->intsync_foreign) { + NL_SET_ERR_MSG(extack, + "The sync line is enabled by a configuration the driver does not recognise"); + rc = -EBUSY; + break; + } + hw_src = sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID); + if (sit9531x_input_prio_present(sitdev, sitdpll->id, hw_src)) { + NL_SET_ERR_MSG(extack, + "PLL selects INTSYNC as a reference; it cannot drive it"); + rc = -EBUSY; + break; + } + rc = sit9531x_intsync_enable(sitdev, sitdpll->id, &owner_kept, + &changed); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (sitdev->intsync_src != sitdpll->id) + break; + rc = sit9531x_intsync_disable(sitdev, sitdpll->id, + &owner_kept, &changed); + break; + default: + rc = -EINVAL; + break; + } + + if (changed && !rc) { + sitdev->intsync_src = state == DPLL_PIN_STATE_CONNECTED ? + sitdpll->id : -1; + sitdev->intsync_partial = false; + } + + if (changed) + detect_rc = sit9531x_intsync_src_detect(sitdev); + if (changed && rc) { + if (owner_kept || + (state == DPLL_PIN_STATE_CONNECTED && + (detect_rc || sitdev->intsync_src == sitdpll->id))) { + sitdev->intsync_src = sitdpll->id; + sitdev->intsync_partial = true; + sitdev->intsync_foreign = false; + } else if (detect_rc) { + sitdev->intsync_stale = true; + } + } else if (changed && detect_rc && sitdev->intsync_conflict) { + sitdev->intsync_stale = true; + } + if (detect_rc) + dev_warn(sitdev->dev, + "INTSYNC source cache not refreshed: %d\n", + detect_rc); + + /* The core notifies only a request that succeeded */ + moved = sit9531x_dpll_intsync_src_connected(sitdev, sitdpll) != + was_connected; + + mutex_unlock(&sitdev->multiop_lock); + + if (rc) { + struct dpll_pin *dpll_pin; + + if (rc != -EBUSY && rc != -EINVAL && rc != -EOPNOTSUPP) + NL_SET_ERR_MSG(extack, + "Failed to set INTSYNC source state"); + /* + * Runs under the core's lock, as __dpll_pin_change_ntf() + * requires. + */ + dpll_pin = READ_ONCE(dpin->dpll_pin); + if (moved && dpll_pin && __dpll_pin_change_ntf(dpll_pin)) { + mutex_lock(&sitdev->multiop_lock); + sitdev->intsync_ntf_pending = true; + mutex_unlock(&sitdev->multiop_lock); + } + } + + return rc; +} + +static const struct dpll_pin_ops sit9531x_dpll_intsync_src_pin_ops = { + .direction_get = sit9531x_dpll_output_pin_direction_get, + .state_on_dpll_get = sit9531x_dpll_intsync_src_state_on_dpll_get, + .state_on_dpll_set = sit9531x_dpll_intsync_src_state_on_dpll_set, +}; + +static int +sit9531x_dpll_intsync_dst_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc = sit9531x_dpll_intsync_refresh(sitdev, extack); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + if (sitdev->intsync_src == sitdpll->id) + *state = DPLL_PIN_STATE_DISCONNECTED; + else + sit9531x_dpll_selection_state_get(sitdev, sitdpll, + SIT9531X_INTSYNC_PIN_ID, + state); + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* INTSYNC is an internal line; only the per-PLL priority table is touched. */ +static int +sit9531x_dpll_intsync_dst_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + u8 hw_src = sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID); + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + + rc = sit9531x_dpll_intsync_refresh(sitdev, extack); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + switch (state) { + case DPLL_PIN_STATE_DISCONNECTED: + rc = sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src); + break; + case DPLL_PIN_STATE_CONNECTED: + NL_SET_ERR_MSG(extack, + "Device selects its reference by priority; use selectable"); + rc = -EOPNOTSUPP; + break; + case DPLL_PIN_STATE_SELECTABLE: + if (sitdev->intsync_src == sitdpll->id) { + NL_SET_ERR_MSG(extack, + "PLL cannot lock to the INTSYNC it drives"); + rc = -EINVAL; + break; + } + rc = sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src); + break; + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&sitdev->multiop_lock); + + if (rc == -ENOSPC) + NL_SET_ERR_MSG(extack, + "Priority table is full of unique sources on this PLL"); + else if (rc && rc != -EINVAL && rc != -EOPNOTSUPP) + NL_SET_ERR_MSG(extack, "Failed to set INTSYNC input state"); + + return rc; +} + +static int +sit9531x_dpll_intsync_dst_operstate_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc = sit9531x_dpll_intsync_refresh(sitdev, extack); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + sit9531x_dpll_selection_operstate_get(sitdev, sitdpll, + SIT9531X_INTSYNC_PIN_ID, + state); + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * The destination pin id SIT9531X_INTSYNC_PIN_ID is one past the end of ref[]; + * do not add ops that index ref[id]. + */ +static const struct dpll_pin_ops sit9531x_dpll_intsync_dst_pin_ops = { + .direction_get = sit9531x_dpll_input_pin_direction_get, + .state_on_dpll_get = sit9531x_dpll_intsync_dst_state_on_dpll_get, + .state_on_dpll_set = sit9531x_dpll_intsync_dst_state_on_dpll_set, + .operstate_on_dpll_get = + sit9531x_dpll_intsync_dst_operstate_on_dpll_get, + .prio_get = sit9531x_dpll_input_pin_prio_get, + .prio_set = sit9531x_dpll_input_pin_prio_set, +}; + +static const struct dpll_pin_ops sit9531x_dpll_intsync_dst_pin_manual_ops = { + .direction_get = sit9531x_dpll_input_pin_direction_get, + .state_on_dpll_get = sit9531x_dpll_intsync_dst_state_on_dpll_get, + .operstate_on_dpll_get = + sit9531x_dpll_intsync_dst_operstate_on_dpll_get, +}; + /* The XO feeds every PLL and is always connected. */ static int @@ -974,12 +1320,18 @@ sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin) const struct sit9531x_dev *sitdev = pin->dpll->dev; bool manual; - if (!sit9531x_dpll_is_input_pin(pin)) + if (!sit9531x_dpll_is_input_pin(pin)) { + if (sit9531x_dpll_is_intsync_src_pin(pin)) + return &sit9531x_dpll_intsync_src_pin_ops; return &sit9531x_dpll_output_pin_ops; + } if (sit9531x_dpll_is_xo_pin(pin)) return &sit9531x_dpll_xo_pin_ops; /* Read once at probe, before any pin is registered. */ manual = sit9531x_chan_state_get(sitdev, pin->dpll->id)->manual_input; + if (sit9531x_dpll_is_intsync_pin(pin)) + return manual ? &sit9531x_dpll_intsync_dst_pin_manual_ops : + &sit9531x_dpll_intsync_dst_pin_ops; /* * Set before any pin of the input is published and not written again. */ diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h index d6c6b4a6a6bb..6913f9f7f0ad 100644 --- a/drivers/dpll/sit9531x/regs.h +++ b/drivers/dpll/sit9531x/regs.h @@ -254,6 +254,9 @@ #define SIT9531X_PLL_REG_ACTIVE 0x02 #define SIT9531X_PLL_ACTIVE_BIT BIT(0) /* PLL reached active state */ +/* PLL EXT page INTSYNC configuration registers */ +#define SIT9531X_PLL_EXT_PAGE(_idx) (SIT9531X_PAGE_PLLA_EXT + (_idx)) + #define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5) /* -- 2.39.2 (Apple Git-143)