From: Oleg Zadorozhnyi An output's state says whether it carries a signal: CONNECTED while it drives, DISCONNECTED while muted. Setting it forces the output's pads to Hi-Z, which leaves the divider alone. Output pins are registered from this patch on. A mute forces the slot's three Hi-Z pairs, differential, CMOS OutP and CMOS OutN, inside the programming state; an un-mute releases the force and the pads return to the loaded configuration. An output is reported muted only when both pads are forced off. Signed-off-by: Oleg Zadorozhnyi Signed-off-by: Ali Rouhi --- drivers/dpll/sit9531x/core.c | 247 +++++++++++++++++++++++++++++------ drivers/dpll/sit9531x/core.h | 10 ++ drivers/dpll/sit9531x/dpll.c | 139 +++++++++++++++++++- 3 files changed, 355 insertions(+), 41 deletions(-) diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c index c7ab33993c28..458de502bc8f 100644 --- a/drivers/dpll/sit9531x/core.c +++ b/drivers/dpll/sit9531x/core.c @@ -459,53 +459,42 @@ static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev, return 0; } -#define SIT9531X_LOOP_LOCK_TRIES 3 - -/* Close the loops and lock the debug block without committing */ -static void sit9531x_prg_abort(struct sit9531x_dev *sitdev) +/* Re-read an output's mute state. Caller must hold sitdev->multiop_lock. */ +int sit9531x_output_state_refresh(struct sit9531x_dev *sitdev, u8 out_idx) { - int rc = -EIO; - u8 attempt; + bool muted, part; + int rc; - for (attempt = 0; attempt < SIT9531X_LOOP_LOCK_TRIES; attempt++) { - rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, - SIT9531X_LOOP_LOCK); - if (!rc) - break; - usleep_range(1000, 2000); - } + lockdep_assert_held(&sitdev->multiop_lock); + + rc = sit9531x_output_forced_hiz(sitdev, out_idx, &muted, &part); if (rc) - dev_err(sitdev->dev, - "output loops left unlocked after a failed entry: %d\n", - rc); + return rc; + + sitdev->out[out_idx].enabled = !muted; + sitdev->out[out_idx].part_muted = part; + sitdev->out[out_idx].state_stale = false; - sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG, - SIT9531X_DEBUG_LOCK_VAL); + return 0; } -/* - * Unlock the Page 3 debug registers and enter PRG_CMD; output-system writes - * take effect only in this state. - */ -static int sit9531x_prg_enter(struct sit9531x_dev *sitdev) +static int sit9531x_hiz_set_bit(struct sit9531x_dev *sitdev, + unsigned int reg, u8 bit, bool set) { + u8 cur, new_val; int rc; - rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG, - SIT9531X_DEBUG_UNLOCK_VAL); + rc = sit9531x_read_u8(sitdev, reg, &cur); if (rc) return rc; - rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, - SIT9531X_PRG_CMD_STATE); - if (rc) { - sit9531x_prg_abort(sitdev); - return rc; - } + new_val = set ? (cur | BIT(bit)) : (cur & ~BIT(bit)); - return 0; + return sit9531x_write_u8(sitdev, reg, new_val); } +#define SIT9531X_LOOP_LOCK_TRIES 3 + /* * Update the NVM shadow and re-lock the loops after sit9531x_prg_enter(). The * sleep is the settling time after loop lock. @@ -554,6 +543,174 @@ static int sit9531x_prg_commit(struct sit9531x_dev *sitdev) return 0; } +/* + * Unlock the Page 3 debug registers and enter PRG_CMD; output-system writes + * take effect only in this state. + */ +static int sit9531x_prg_enter(struct sit9531x_dev *sitdev) +{ + int rc; + + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG, + SIT9531X_DEBUG_UNLOCK_VAL); + if (rc) { + sit9531x_write_close(sitdev, SIT9531X_REG_OUTSYS_DEBUG, + SIT9531X_DEBUG_LOCK_VAL); + return rc; + } + + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, + SIT9531X_PRG_CMD_STATE); + if (rc) { + sit9531x_prg_commit(sitdev); + return rc; + } + + return 0; +} + +/* + * Mute or unmute every Hi-Z pair of a slot. Caller must already be in the + * programming state. + */ +static int sit9531x_output_hiz_write(struct sit9531x_dev *sitdev, u8 slot, + bool mute) +{ + u8 mask[SIT9531X_HIZ_PAIRS], state[SIT9531X_HIZ_PAIRS]; + struct sit9531x_hiz_pair p[SIT9531X_HIZ_PAIRS]; + int i, rc, undo_rc; + + sit9531x_output_get_hiz_regs(slot, p); + + if (!mute) { + for (i = 0; i < SIT9531X_HIZ_PAIRS; i++) { + rc = sit9531x_hiz_set_bit(sitdev, p[i].mask, p[i].bit, + false); + if (rc) + return rc; + } + return 0; + } + + for (i = 0; i < SIT9531X_HIZ_PAIRS; i++) { + rc = sit9531x_read_u8(sitdev, p[i].state, &state[i]); + if (rc) + return rc; + rc = sit9531x_read_u8(sitdev, p[i].mask, &mask[i]); + if (rc) + return rc; + } + + /* + * STATE before MASK: enabling the override first can drive the pad for + * one I2C transfer. + */ + for (i = 0; i < SIT9531X_HIZ_PAIRS; i++) { + rc = sit9531x_hiz_set_bit(sitdev, p[i].state, p[i].bit, false); + if (!rc) + rc = sit9531x_hiz_set_bit(sitdev, p[i].mask, p[i].bit, + true); + if (rc) + break; + } + if (!rc) + return 0; + + for (; i >= 0; i--) { + u8 bit = BIT(p[i].bit); + + undo_rc = sit9531x_hiz_set_bit(sitdev, p[i].mask, p[i].bit, + mask[i] & bit); + if (!undo_rc) + undo_rc = sit9531x_hiz_set_bit(sitdev, p[i].state, + p[i].bit, + state[i] & bit); + if (undo_rc) + dev_err(sitdev->dev, + "slot%u: Hi-Z override left part applied (%d)\n", + slot, undo_rc); + } + + return rc; +} + +/* Caller must hold sitdev->multiop_lock. */ +int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index) +{ + const struct sit9531x_chip_info *info = sitdev->info; + int rc, ret, state_rc; + bool muted, part; + u8 slot; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (index >= info->num_outputs) + return -EINVAL; + + slot = info->clkout_map[index]; + rc = sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + rc = sit9531x_output_hiz_write(sitdev, slot, true); + + ret = sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc = ret; + + /* Read back the Hi-Z state */ + state_rc = sit9531x_output_forced_hiz(sitdev, index, &muted, &part); + if (!state_rc) { + sitdev->out[index].enabled = !muted; + sitdev->out[index].part_muted = part; + sitdev->out[index].state_stale = false; + } else { + sitdev->out[index].state_stale = true; + if (!rc) + rc = state_rc; + } + + return rc; +} + +/* Caller must hold sitdev->multiop_lock. */ +int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index) +{ + const struct sit9531x_chip_info *info = sitdev->info; + int rc, ret, state_rc; + bool muted, part; + u8 slot; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (index >= info->num_outputs) + return -EINVAL; + + slot = info->clkout_map[index]; + rc = sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + rc = sit9531x_output_hiz_write(sitdev, slot, false); + + ret = sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc = ret; + + state_rc = sit9531x_output_forced_hiz(sitdev, index, &muted, &part); + if (!state_rc) { + sitdev->out[index].enabled = !muted; + sitdev->out[index].part_muted = part; + sitdev->out[index].state_stale = false; + } else { + sitdev->out[index].state_stale = true; + if (!rc) + rc = state_rc; + } + + return rc; +} + /* * Procedure: * 1. Force PLL into holdover (PLL page reg 0x6F bit 4) @@ -2084,6 +2241,7 @@ static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index) return rc; sitdev->out[index].part_muted = part; + sitdev->out[index].state_stale = false; /* * Each PLL page holds a 12-bit output-enable mask: OUT_MAP_LO bits 0-7 @@ -2854,7 +3012,7 @@ static void sit9531x_dpll_pin_unregister(struct sit9531x_dpll_pin *pin) * Clear before unregistering: callbacks test it under the DPLL device * lock, which dpll_pin_unregister() also takes. */ - pin->dpll_pin = NULL; + WRITE_ONCE(pin->dpll_pin, NULL); dpll_pin_unregister(sitdpll->dpll_dev, dpll_pin, ops, pin); dpll_pin_put(dpll_pin, &pin->tracker); @@ -2890,20 +3048,33 @@ static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll) } } -/* Check if the pin class has a state callback, which the DPLL core requires */ +/* + * An output is registered when routed to this DPLL, whether or not it is + * driving. + */ static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll, enum dpll_pin_direction dir, u8 index) { struct sit9531x_dev *sitdev = sitdpll->dev; - if (dir != DPLL_PIN_DIRECTION_INPUT) + if (dir == DPLL_PIN_DIRECTION_INPUT) { + if (index == SIT9531X_MAX_INPUTS) + return true; + if (index == SIT9531X_INTSYNC_PIN_ID) + return false; + + return sit9531x_input_pin_is_registrable(sitdev, index); + } + + if (index == SIT9531X_INTSYNC_OUT_PIN_ID) return false; - if (index == SIT9531X_MAX_INPUTS) - return true; + if (index >= sitdev->info->num_outputs) + return false; - return sit9531x_input_pin_is_registrable(sitdev, index); + return sitdev->out[index].pll_idx == sitdpll->id && + sitdev->out[index].routed; } static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll) diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h index 6b50b5b64b75..4f3f74ac8d7c 100644 --- a/drivers/dpll/sit9531x/core.h +++ b/drivers/dpll/sit9531x/core.h @@ -101,6 +101,9 @@ struct sit9531x_ref { * @freq: output frequency in Hz * @enabled: output is driving, not forced into Hi-Z * @cmos: single-ended; the CMOS pad Hi-Z pairs apply, not the differential one + * @state_stale: cached mute state must be read back before it is reported + * @ntf_pending: pin change not yet notified; the poll notifies once the state + * is read * @part_muted: single-ended with one pad forced and the other not; reported * as driven. The driver's mute forces both pads. * @routed: mapped to @pll_idx by the initial configuration; an unrouted @@ -113,6 +116,8 @@ struct sit9531x_out { u64 freq; bool enabled; bool cmos; + bool state_stale; + bool ntf_pending; bool part_muted; bool routed; u8 pll_idx; @@ -281,6 +286,11 @@ int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx, int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx, u8 input_idx); +int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index); +int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index); +int sit9531x_output_state_refresh(struct sit9531x_dev *sitdev, + u8 out_idx); + int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx, u8 pll_idx, u64 frequency); int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx, diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c index a450a81d02f4..a32d37a396b1 100644 --- a/drivers/dpll/sit9531x/dpll.c +++ b/drivers/dpll/sit9531x/dpll.c @@ -184,8 +184,9 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = { * 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 XO pin is - * always CONNECTED. + * 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. */ /* Caller must hold sitdev->multiop_lock. */ @@ -638,10 +639,114 @@ sit9531x_dpll_output_pin_frequency_set(const struct dpll_pin *pin, return rc; } +/* + * DISCONNECTED while the output is forced to Hi-Z, by a mute request or by the + * loaded profile. + */ +static int +sit9531x_dpll_output_pin_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_pin *dpin = pin_priv; + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + const struct sit9531x_out *out; + int rc = 0; + + mutex_lock(&sitdev->multiop_lock); + out = sit9531x_out_state_get(sitdev, dpin->id); + if (out->state_stale) + rc = sit9531x_output_state_refresh(sitdev, dpin->id); + if (!rc) + *state = out->enabled ? DPLL_PIN_STATE_CONNECTED + : DPLL_PIN_STATE_DISCONNECTED; + mutex_unlock(&sitdev->multiop_lock); + if (rc) + NL_SET_ERR_MSG(extack, + "Output mute state could not be read back"); + + return rc; +} + +/* + * Force Hi-Z through the slot's Hi-Z force/state register pairs: differential, + * CMOS OutP and CMOS OutN. + */ +static int +sit9531x_dpll_output_pin_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) +{ + bool enable = state == DPLL_PIN_STATE_CONNECTED; + struct sit9531x_dpll_pin *dpin = pin_priv; + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + bool was_enabled, changed; + int rc; + + /* An output is routed explicitly, not a selection candidate */ + if (!enable && state != DPLL_PIN_STATE_DISCONNECTED) + return -EINVAL; + + mutex_lock(&sitdev->multiop_lock); + + /* + * Skip a request for the current state: the programming sequence + * unlocks the output loops for its 100 ms commit. One CMOS pad forced + * and the other not is neither state. + */ + if (!sit9531x_output_state_refresh(sitdev, dpin->id) && + sitdev->out[dpin->id].enabled == enable && + !sitdev->out[dpin->id].part_muted) { + mutex_unlock(&sitdev->multiop_lock); + return 0; + } + + was_enabled = sitdev->out[dpin->id].enabled; + + if (enable) + rc = sit9531x_output_enable(sitdev, dpin->id); + else + rc = sit9531x_output_disable(sitdev, dpin->id); + + changed = sitdev->out[dpin->id].enabled != was_enabled || + sitdev->out[dpin->id].state_stale; + + mutex_unlock(&sitdev->multiop_lock); + + if (rc) { + struct dpll_pin *dpll_pin; + + NL_SET_ERR_MSG(extack, "Failed to set output pin state"); + /* + * The core notifies only a request that succeeded. Teardown + * clears dpll_pin without the core's lock, which is held here; + * read it once, as a set handle stays valid until this returns. + */ + dpll_pin = READ_ONCE(dpin->dpll_pin); + if (changed && dpll_pin && __dpll_pin_change_ntf(dpll_pin)) { + mutex_lock(&sitdev->multiop_lock); + sitdev->out[dpin->id].ntf_pending = true; + mutex_unlock(&sitdev->multiop_lock); + } + } + + return rc; +} + static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = { .direction_get = sit9531x_dpll_output_pin_direction_get, .frequency_get = sit9531x_dpll_output_pin_frequency_get, .frequency_set = sit9531x_dpll_output_pin_frequency_set, + .state_on_dpll_get = sit9531x_dpll_output_pin_state_on_dpll_get, + .state_on_dpll_set = sit9531x_dpll_output_pin_state_on_dpll_set, }; const struct dpll_pin_ops * @@ -703,6 +808,31 @@ void sit9531x_dpll_pin_baseline(struct sit9531x_dpll_pin *pin) &pin->operstate, &pin->prio); } +/* + * Re-read an output with a pending change notification; return true once the + * read succeeds. Caller must hold sitdev->multiop_lock. + */ +static bool +sit9531x_dpll_output_ntf_pending(struct sit9531x_dev *sitdev, + const struct sit9531x_dpll_pin *pin) +{ + struct sit9531x_out *out; + + if (sit9531x_dpll_is_input_pin(pin) || + pin->id >= sitdev->info->num_outputs) + return false; + + out = &sitdev->out[pin->id]; + if (!out->ntf_pending) + return false; + if (out->state_stale && sit9531x_output_state_refresh(sitdev, pin->id)) + return false; + + out->ntf_pending = false; + + return true; +} + /* * Send notifications for changed lock status and pin state; called from * sit9531x_dev_periodic_work(). @@ -744,8 +874,11 @@ void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll) bool changed; u8 prio; - if (!sit9531x_dpll_is_selection_pin(pin)) + if (!sit9531x_dpll_is_selection_pin(pin)) { + pin->ntf_pending |= + sit9531x_dpll_output_ntf_pending(sitdev, pin); continue; + } sit9531x_dpll_pin_snapshot(sitdpll, pin, &state, &operstate, &prio); -- 2.39.2 (Apple Git-143)