Commit 4cfe066a authored by Ivan Vecera's avatar Ivan Vecera Committed by Jakub Kicinski
Browse files

dpll: zl3073x: fix REF_PHASE_OFFSET_COMP register width for some chip IDs



The REF_PHASE_OFFSET_COMP register is 48-bit wide on most zl3073x chip
variants, but only 32-bit wide on chip IDs 0x0E30, 0x0E93..0x0E97 and
0x1F60. The driver unconditionally uses 48-bit read/write operations,
which on 32-bit variants causes reading 2 bytes past the register
boundary (corrupting the value) and writing 2 bytes into the adjacent
register.

Fix this by storing the chip ID in the device structure during probe
and adding a helper to detect the affected variants. Use the correct
register width for read/write operations and the matching sign extension
bit (31 vs 47) when interpreting the phase compensation value.

Fixes: 6287262f ("dpll: zl3073x: Add support to adjust phase")
Signed-off-by: default avatarIvan Vecera <ivecera@redhat.com>
Reviewed-by: default avatarSimon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20260220155755.448185-1-ivecera@redhat.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent ca220141
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1026,6 +1026,7 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
				     "Unknown or non-match chip ID: 0x%0x\n",
				     id);
	}
	zldev->chip_id = id;

	/* Read revision, firmware version and custom config version */
	rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
+28 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ struct zl3073x_dpll;
 * @dev: pointer to device
 * @regmap: regmap to access device registers
 * @multiop_lock: to serialize multiple register operations
 * @chip_id: chip ID read from hardware
 * @ref: array of input references' invariants
 * @out: array of outs' invariants
 * @synth: array of synths' invariants
@@ -48,6 +49,7 @@ struct zl3073x_dev {
	struct device		*dev;
	struct regmap		*regmap;
	struct mutex		multiop_lock;
	u16			chip_id;

	/* Invariants */
	struct zl3073x_ref	ref[ZL3073X_NUM_REFS];
@@ -144,6 +146,32 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,

int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);

/**
 * zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size
 * @zldev: pointer to zl3073x device
 *
 * Some chip IDs have a 32-bit wide ref_phase_offset_comp register instead
 * of the default 48-bit.
 *
 * Return: true if the register is 32-bit, false if 48-bit
 */
static inline bool
zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev)
{
	switch (zldev->chip_id) {
	case 0x0E30:
	case 0x0E93:
	case 0x0E94:
	case 0x0E95:
	case 0x0E96:
	case 0x0E97:
	case 0x1F60:
		return true;
	default:
		return false;
	}
}

static inline bool
zl3073x_is_n_pin(u8 id)
{
+5 −2
Original line number Diff line number Diff line
@@ -475,7 +475,10 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
	ref_id = zl3073x_input_pin_ref_get(pin->id);
	ref = zl3073x_ref_state_get(zldev, ref_id);

	/* Perform sign extension for 48bit signed value */
	/* Perform sign extension based on register width */
	if (zl3073x_dev_is_ref_phase_comp_32bit(zldev))
		phase_comp = sign_extend64(ref->phase_comp, 31);
	else
		phase_comp = sign_extend64(ref->phase_comp, 47);

	/* Reverse two's complement negation applied during set and convert
+20 −5
Original line number Diff line number Diff line
@@ -121,8 +121,16 @@ int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
		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;

@@ -179,9 +187,16 @@ int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
	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)
		rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP,
	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;

+1 −0
Original line number Diff line number Diff line
@@ -194,6 +194,7 @@
#define ZL_REF_CONFIG_DIFF_EN			BIT(2)

#define ZL_REG_REF_PHASE_OFFSET_COMP		ZL_REG(10, 0x28, 6)
#define ZL_REG_REF_PHASE_OFFSET_COMP_32	ZL_REG(10, 0x28, 4)

#define ZL_REG_REF_SYNC_CTRL			ZL_REG(10, 0x2e, 1)
#define ZL_REF_SYNC_CTRL_MODE			GENMASK(2, 0)