| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/bitfield.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/string.h> | |
| #include <linux/string_choices.h> | |
| #include <linux/types.h> | |
| #include "core.h" | |
| #include "ref.h" | |
| /** | |
| * zl3073x_ref_freq_factorize - factorize given frequency | |
| * @freq: input frequency | |
| * @base: base frequency | |
| * @mult: multiplier | |
| * | |
| * Checks if the given frequency can be factorized using one of the | |
| * supported base frequencies. If so the base frequency and multiplier | |
| * are stored into appropriate parameters if they are not NULL. | |
| * | |
| * Return: 0 on success, -EINVAL if the frequency cannot be factorized | |
| */ | |
| int | |
| zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) | |
| { | |
| static const u16 base_freqs[] = { | |
| 1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125, | |
| 128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000, | |
| 1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250, | |
| 6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250, | |
| 32000, 40000, 50000, 62500, | |
| }; | |
| u32 div; | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(base_freqs); i++) { | |
| div = freq / base_freqs[i]; | |
| if (div <= U16_MAX && (freq % base_freqs[i]) == 0) { | |
| if (base) | |
| *base = base_freqs[i]; | |
| if (mult) | |
| *mult = div; | |
| return 0; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| /** | |
| * zl3073x_ref_state_update - update input reference status from HW | |
| * @zldev: pointer to zl3073x_dev structure | |
| * @index: input reference index | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index) | |
| { | |
| struct zl3073x_ref *ref = &zldev->ref[index]; | |
| return zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(index), | |
| &ref->mon_status); | |
| } | |
| /** | |
| * zl3073x_ref_state_fetch - fetch input reference state from hardware | |
| * @zldev: pointer to zl3073x_dev structure | |
| * @index: input reference index to fetch state for | |
| * | |
| * Function fetches state for the given input reference from hardware and | |
| * stores it for later use. | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) | |
| { | |
| struct zl3073x_ref *ref = &zldev->ref[index]; | |
| int rc; | |
| /* For differential type inputs the N-pin reference shares | |
| * part of the configuration with the P-pin counterpart. | |
| */ | |
| if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(ref - 1)) { | |
| struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/ | |
| /* Copy the shared items from the P-pin */ | |
| ref->cfg = p_ref->cfg; | |
| ref->inv = p_ref->inv; | |
| return 0; /* Finish - no non-shared items for now */ | |
| } | |
| /* Read reference status */ | |
| rc = zl3073x_ref_state_update(zldev, index); | |
| if (rc) | |
| return rc; | |
| guard(mutex)(&zldev->multiop_lock); | |
| /* Read reference configuration */ | |
| rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, | |
| ZL_REG_REF_MB_MASK, BIT(index)); | |
| if (rc) | |
| return rc; | |
| /* Read ref_config register */ | |
| rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config); | |
| if (rc) | |
| return rc; | |
| /* Read frequency related registers */ | |
| rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &ref->freq_base); | |
| if (rc) | |
| return rc; | |
| rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &ref->freq_mult); | |
| if (rc) | |
| return rc; | |
| rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &ref->freq_ratio_m); | |
| if (rc) | |
| return rc; | |
| rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &ref->freq_ratio_n); | |
| if (rc) | |
| return rc; | |
| /* Read eSync and N-div rated registers */ | |
| rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &ref->esync_n_div); | |
| if (rc) | |
| return rc; | |
| rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref->sync_ctrl); | |
| if (rc) | |
| return rc; | |
| /* Read phase compensation register */ | |
| if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) { | |
| u32 val; | |
| rc = zl3073x_read_u32(zldev, ZL_REG_REF_PHASE_OFFSET_COMP_32, | |
| &val); | |
| ref->phase_comp = val; | |
| } else { | |
| rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, | |
| &ref->phase_comp); | |
| } | |
| if (rc) | |
| return rc; | |
| dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, | |
| str_enabled_disabled(zl3073x_ref_is_enabled(ref)), | |
| zl3073x_ref_is_diff(ref) ? "differential" : "single-ended"); | |
| return rc; | |
| } | |
| /** | |
| * zl3073x_ref_state_get - get current input reference state | |
| * @zldev: pointer to zl3073x_dev structure | |
| * @index: input reference index to get state for | |
| * | |
| * Return: pointer to given input reference state | |
| */ | |
| const struct zl3073x_ref * | |
| zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index) | |
| { | |
| return &zldev->ref[index]; | |
| } | |
| /** | |
| * zl3073x_ref_state_set - commit input reference state changes to hardware | |
| * @zldev: pointer to zl3073x_dev structure | |
| * @index: input reference index to set state for | |
| * @ref: desired reference state | |
| * | |
| * Validates that invariant fields have not been modified, skips the HW | |
| * write if the mutable configuration is unchanged, and otherwise writes | |
| * only the changed cfg fields to hardware via the mailbox interface. | |
| * | |
| * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error | |
| */ | |
| int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index, | |
| const struct zl3073x_ref *ref) | |
| { | |
| struct zl3073x_ref *dref = &zldev->ref[index]; | |
| int rc; | |
| /* Reject attempts to change invariant fields (set at init only) */ | |
| if (WARN_ON(memcmp(&dref->inv, &ref->inv, sizeof(ref->inv)))) | |
| return -EINVAL; | |
| /* Skip HW write if configuration hasn't changed */ | |
| if (!memcmp(&dref->cfg, &ref->cfg, sizeof(ref->cfg))) | |
| return 0; | |
| guard(mutex)(&zldev->multiop_lock); | |
| /* Read reference configuration into mailbox */ | |
| rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, | |
| ZL_REG_REF_MB_MASK, BIT(index)); | |
| if (rc) | |
| return rc; | |
| /* Update mailbox with changed values */ | |
| if (dref->freq_base != ref->freq_base) | |
| rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE, | |
| ref->freq_base); | |
| if (!rc && dref->freq_mult != ref->freq_mult) | |
| rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT, | |
| ref->freq_mult); | |
| if (!rc && dref->freq_ratio_m != ref->freq_ratio_m) | |
| rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M, | |
| ref->freq_ratio_m); | |
| if (!rc && dref->freq_ratio_n != ref->freq_ratio_n) | |
| rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N, | |
| ref->freq_ratio_n); | |
| if (!rc && dref->esync_n_div != ref->esync_n_div) | |
| rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV, | |
| ref->esync_n_div); | |
| if (!rc && dref->sync_ctrl != ref->sync_ctrl) | |
| rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, | |
| ref->sync_ctrl); | |
| if (!rc && dref->phase_comp != ref->phase_comp) { | |
| if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) | |
| rc = zl3073x_write_u32(zldev, | |
| ZL_REG_REF_PHASE_OFFSET_COMP_32, | |
| ref->phase_comp); | |
| else | |
| rc = zl3073x_write_u48(zldev, | |
| ZL_REG_REF_PHASE_OFFSET_COMP, | |
| ref->phase_comp); | |
| } | |
| if (rc) | |
| return rc; | |
| /* Commit reference configuration */ | |
| rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, | |
| ZL_REG_REF_MB_MASK, BIT(index)); | |
| if (rc) | |
| return rc; | |
| /* After successful commit store new state */ | |
| dref->cfg = ref->cfg; | |
| return 0; | |
| } |