summaryrefslogtreecommitdiff
path: root/drivers/dpll/zl3073x/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/dpll/zl3073x/core.c')
-rw-r--r--drivers/dpll/zl3073x/core.c1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
new file mode 100644
index 000000000000..7ebcfc5ec1f0
--- /dev/null
+++ b/drivers/dpll/zl3073x/core.c
@@ -0,0 +1,1030 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/regmap.h>
+#include <linux/sprintf.h>
+#include <linux/string_choices.h>
+#include <linux/unaligned.h>
+#include <net/devlink.h>
+
+#include "core.h"
+#include "devlink.h"
+#include "dpll.h"
+#include "regs.h"
+
+/* Chip IDs for zl30731 */
+static const u16 zl30731_ids[] = {
+ 0x0E93,
+ 0x1E93,
+ 0x2E93,
+};
+
+const struct zl3073x_chip_info zl30731_chip_info = {
+ .ids = zl30731_ids,
+ .num_ids = ARRAY_SIZE(zl30731_ids),
+ .num_channels = 1,
+};
+EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, "ZL3073X");
+
+/* Chip IDs for zl30732 */
+static const u16 zl30732_ids[] = {
+ 0x0E30,
+ 0x0E94,
+ 0x1E94,
+ 0x1F60,
+ 0x2E94,
+ 0x3FC4,
+};
+
+const struct zl3073x_chip_info zl30732_chip_info = {
+ .ids = zl30732_ids,
+ .num_ids = ARRAY_SIZE(zl30732_ids),
+ .num_channels = 2,
+};
+EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, "ZL3073X");
+
+/* Chip IDs for zl30733 */
+static const u16 zl30733_ids[] = {
+ 0x0E95,
+ 0x1E95,
+ 0x2E95,
+};
+
+const struct zl3073x_chip_info zl30733_chip_info = {
+ .ids = zl30733_ids,
+ .num_ids = ARRAY_SIZE(zl30733_ids),
+ .num_channels = 3,
+};
+EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, "ZL3073X");
+
+/* Chip IDs for zl30734 */
+static const u16 zl30734_ids[] = {
+ 0x0E96,
+ 0x1E96,
+ 0x2E96,
+};
+
+const struct zl3073x_chip_info zl30734_chip_info = {
+ .ids = zl30734_ids,
+ .num_ids = ARRAY_SIZE(zl30734_ids),
+ .num_channels = 4,
+};
+EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, "ZL3073X");
+
+/* Chip IDs for zl30735 */
+static const u16 zl30735_ids[] = {
+ 0x0E97,
+ 0x1E97,
+ 0x2E97,
+};
+
+const struct zl3073x_chip_info zl30735_chip_info = {
+ .ids = zl30735_ids,
+ .num_ids = ARRAY_SIZE(zl30735_ids),
+ .num_channels = 5,
+};
+EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, "ZL3073X");
+
+#define ZL_RANGE_OFFSET 0x80
+#define ZL_PAGE_SIZE 0x80
+#define ZL_NUM_PAGES 15
+#define ZL_PAGE_SEL 0x7F
+#define ZL_PAGE_SEL_MASK GENMASK(3, 0)
+#define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE)
+
+/* Regmap range configuration */
+static const struct regmap_range_cfg zl3073x_regmap_range = {
+ .range_min = ZL_RANGE_OFFSET,
+ .range_max = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
+ .selector_reg = ZL_PAGE_SEL,
+ .selector_mask = ZL_PAGE_SEL_MASK,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = ZL_PAGE_SIZE,
+};
+
+static bool
+zl3073x_is_volatile_reg(struct device *dev __maybe_unused, unsigned int reg)
+{
+ /* Only page selector is non-volatile */
+ return reg != ZL_PAGE_SEL;
+}
+
+const struct regmap_config zl3073x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
+ .ranges = &zl3073x_regmap_range,
+ .num_ranges = 1,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = zl3073x_is_volatile_reg,
+};
+EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X");
+
+/**
+ * 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;
+}
+
+static bool
+zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size)
+{
+ /* Check that multiop lock is held when accessing registers
+ * from page 10 and above.
+ */
+ if (ZL_REG_PAGE(reg) >= 10)
+ lockdep_assert_held(&zldev->multiop_lock);
+
+ /* Check the index is in valid range for indexed register */
+ if (ZL_REG_OFFSET(reg) > ZL_REG_MAX_OFFSET(reg)) {
+ dev_err(zldev->dev, "Index out of range for reg 0x%04lx\n",
+ ZL_REG_ADDR(reg));
+ return false;
+ }
+ /* Check the requested size corresponds to register size */
+ if (ZL_REG_SIZE(reg) != size) {
+ dev_err(zldev->dev, "Invalid size %zu for reg 0x%04lx\n",
+ size, ZL_REG_ADDR(reg));
+ return false;
+ }
+
+ return true;
+}
+
+static int
+zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val,
+ size_t size)
+{
+ int rc;
+
+ if (!zl3073x_check_reg(zldev, reg, size))
+ return -EINVAL;
+
+ /* Map the register address to virtual range */
+ reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
+
+ rc = regmap_bulk_read(zldev->regmap, reg, val, size);
+ if (rc) {
+ dev_err(zldev->dev, "Failed to read reg 0x%04x: %pe\n", reg,
+ ERR_PTR(rc));
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val,
+ size_t size)
+{
+ int rc;
+
+ if (!zl3073x_check_reg(zldev, reg, size))
+ return -EINVAL;
+
+ /* Map the register address to virtual range */
+ reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
+
+ rc = regmap_bulk_write(zldev->regmap, reg, val, size);
+ if (rc) {
+ dev_err(zldev->dev, "Failed to write reg 0x%04x: %pe\n", reg,
+ ERR_PTR(rc));
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_read_u8 - read value from 8bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Reads value from given 8bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val)
+{
+ return zl3073x_read_reg(zldev, reg, val, sizeof(*val));
+}
+
+/**
+ * zl3073x_write_u8 - write value to 16bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Writes value into given 8bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val)
+{
+ return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
+}
+
+/**
+ * zl3073x_read_u16 - read value from 16bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Reads value from given 16bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val)
+{
+ int rc;
+
+ rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
+ if (!rc)
+ be16_to_cpus(val);
+
+ return rc;
+}
+
+/**
+ * zl3073x_write_u16 - write value to 16bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Writes value into given 16bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val)
+{
+ cpu_to_be16s(&val);
+
+ return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
+}
+
+/**
+ * zl3073x_read_u32 - read value from 32bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Reads value from given 32bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val)
+{
+ int rc;
+
+ rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
+ if (!rc)
+ be32_to_cpus(val);
+
+ return rc;
+}
+
+/**
+ * zl3073x_write_u32 - write value to 32bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Writes value into given 32bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val)
+{
+ cpu_to_be32s(&val);
+
+ return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
+}
+
+/**
+ * zl3073x_read_u48 - read value from 48bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Reads value from given 48bit register.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val)
+{
+ u8 buf[6];
+ int rc;
+
+ rc = zl3073x_read_reg(zldev, reg, buf, sizeof(buf));
+ if (!rc)
+ *val = get_unaligned_be48(buf);
+
+ return rc;
+}
+
+/**
+ * zl3073x_write_u48 - write value to 48bit register
+ * @zldev: zl3073x device pointer
+ * @reg: register to write to
+ * @val: value to write
+ *
+ * Writes value into given 48bit register.
+ * The value must be from the interval -S48_MIN to U48_MAX.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
+{
+ u8 buf[6];
+
+ /* Check the value belongs to <S48_MIN, U48_MAX>
+ * Any value >= S48_MIN has bits 47..63 set.
+ */
+ if (val > GENMASK_ULL(47, 0) && val < GENMASK_ULL(63, 47)) {
+ dev_err(zldev->dev, "Value 0x%0llx out of range\n", val);
+ return -EINVAL;
+ }
+
+ put_unaligned_be48(val, buf);
+
+ return zl3073x_write_reg(zldev, reg, buf, sizeof(buf));
+}
+
+/**
+ * zl3073x_poll_zero_u8 - wait for register to be cleared by device
+ * @zldev: zl3073x device pointer
+ * @reg: register to poll (has to be 8bit register)
+ * @mask: bit mask for polling
+ *
+ * Waits for bits specified by @mask in register @reg value to be cleared
+ * by the device.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
+{
+ /* Register polling sleep & timeout */
+#define ZL_POLL_SLEEP_US 10
+#define ZL_POLL_TIMEOUT_US 2000000
+ unsigned int val;
+
+ /* Check the register is 8bit */
+ if (ZL_REG_SIZE(reg) != 1) {
+ dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n",
+ ZL_REG_ADDR(reg));
+ return -EINVAL;
+ }
+
+ /* Map the register address to virtual range */
+ reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
+
+ return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
+ ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
+}
+
+int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
+ unsigned int mask_reg, u16 mask_val)
+{
+ int rc;
+
+ /* Set mask for the operation */
+ rc = zl3073x_write_u16(zldev, mask_reg, mask_val);
+ if (rc)
+ return rc;
+
+ /* Trigger the operation */
+ rc = zl3073x_write_u8(zldev, op_reg, op_val);
+ if (rc)
+ return rc;
+
+ /* Wait for the operation to actually finish */
+ return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
+}
+
+/**
+ * zl3073x_ref_state_fetch - get input reference state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input reference index to fetch state for
+ *
+ * Function fetches information for the given input reference that are
+ * invariant and stores them for later use.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_ref *input = &zldev->ref[index];
+ u8 ref_config;
+ int rc;
+
+ /* If the input is differential then the configuration for N-pin
+ * reference is ignored and P-pin config is used for both.
+ */
+ if (zl3073x_is_n_pin(index) &&
+ zl3073x_ref_is_diff(zldev, index - 1)) {
+ input->enabled = zl3073x_ref_is_enabled(zldev, index - 1);
+ input->diff = true;
+
+ return 0;
+ }
+
+ 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;
+
+ input->enabled = FIELD_GET(ZL_REF_CONFIG_ENABLE, ref_config);
+ input->diff = FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref_config);
+
+ dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index,
+ str_enabled_disabled(input->enabled),
+ input->diff ? "differential" : "single-ended");
+
+ return rc;
+}
+
+/**
+ * zl3073x_out_state_fetch - get output state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: output index to fetch state for
+ *
+ * Function fetches information for the given output (not output pin)
+ * that are invariant and stores them for later use.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_out *out = &zldev->out[index];
+ u8 output_ctrl, output_mode;
+ int rc;
+
+ /* Read output configuration */
+ rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &output_ctrl);
+ if (rc)
+ return rc;
+
+ /* Store info about output enablement and synthesizer the output
+ * is connected to.
+ */
+ out->enabled = FIELD_GET(ZL_OUTPUT_CTRL_EN, output_ctrl);
+ out->synth = FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, output_ctrl);
+
+ dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index,
+ str_enabled_disabled(out->enabled), out->synth);
+
+ guard(mutex)(&zldev->multiop_lock);
+
+ /* Read output configuration */
+ rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
+ ZL_REG_OUTPUT_MB_MASK, BIT(index));
+ if (rc)
+ return rc;
+
+ /* Read output_mode */
+ rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
+ if (rc)
+ return rc;
+
+ /* Extract and store output signal format */
+ out->signal_format = FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT,
+ output_mode);
+
+ dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index,
+ out->signal_format);
+
+ return rc;
+}
+
+/**
+ * zl3073x_synth_state_fetch - get synth state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: synth index to fetch state for
+ *
+ * Function fetches information for the given synthesizer that are
+ * invariant and stores them for later use.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_synth *synth = &zldev->synth[index];
+ u16 base, m, n;
+ u8 synth_ctrl;
+ u32 mult;
+ int rc;
+
+ /* Read synth control register */
+ rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth_ctrl);
+ if (rc)
+ return rc;
+
+ /* Store info about synth enablement and DPLL channel the synth is
+ * driven by.
+ */
+ synth->enabled = FIELD_GET(ZL_SYNTH_CTRL_EN, synth_ctrl);
+ synth->dpll = FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth_ctrl);
+
+ dev_dbg(zldev->dev, "SYNTH%u is %s and driven by DPLL%u\n", index,
+ str_enabled_disabled(synth->enabled), synth->dpll);
+
+ guard(mutex)(&zldev->multiop_lock);
+
+ /* Read synth configuration */
+ rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD,
+ ZL_REG_SYNTH_MB_MASK, BIT(index));
+ if (rc)
+ return rc;
+
+ /* The output frequency is determined by the following formula:
+ * base * multiplier * numerator / denominator
+ *
+ * Read registers with these values
+ */
+ rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &base);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &mult);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &m);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &n);
+ if (rc)
+ return rc;
+
+ /* Check denominator for zero to avoid div by 0 */
+ if (!n) {
+ dev_err(zldev->dev,
+ "Zero divisor for SYNTH%u retrieved from device\n",
+ index);
+ return -EINVAL;
+ }
+
+ /* Compute and store synth frequency */
+ zldev->synth[index].freq = div_u64(mul_u32_u32(base * m, mult), n);
+
+ dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index,
+ zldev->synth[index].freq);
+
+ return rc;
+}
+
+static int
+zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
+{
+ int rc;
+ u8 i;
+
+ for (i = 0; i < ZL3073X_NUM_REFS; i++) {
+ rc = zl3073x_ref_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch input state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
+ for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
+ rc = zl3073x_synth_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch synth state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
+ for (i = 0; i < ZL3073X_NUM_OUTS; i++) {
+ rc = zl3073x_out_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch output state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * zl3073x_ref_phase_offsets_update - update reference phase offsets
+ * @zldev: pointer to zl3073x_dev structure
+ * @channel: DPLL channel number or -1
+ *
+ * The function asks device to update phase offsets latch registers with
+ * the latest measured values. There are 2 sets of latch registers:
+ *
+ * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset
+ * values between particular DPLL channel and its *connected* input
+ * reference.
+ *
+ * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values
+ * between selected DPLL channel and all input references.
+ *
+ * If the caller is interested in 2) then it has to pass DPLL channel number
+ * in @channel parameter. If it is interested only in 1) then it should pass
+ * @channel parameter with value of -1.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
+{
+ int rc;
+
+ /* Per datasheet we have to wait for 'dpll_ref_phase_err_rqst_rd'
+ * to be zero to ensure that the measured data are coherent.
+ */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+ ZL_REF_PHASE_ERR_READ_RQST_RD);
+ if (rc)
+ return rc;
+
+ /* Select DPLL channel if it is specified */
+ if (channel != -1) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel);
+ if (rc)
+ return rc;
+ }
+
+ /* Request to update phase offsets measurement values */
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+ ZL_REF_PHASE_ERR_READ_RQST_RD);
+ if (rc)
+ return rc;
+
+ /* Wait for finish */
+ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+ ZL_REF_PHASE_ERR_READ_RQST_RD);
+}
+
+/**
+ * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to update fractional frequency offsets latch
+ * registers the latest measured values, reads and stores them into
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ /* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero
+ * to ensure that the measured data are coherent.
+ */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+ ZL_REF_FREQ_MEAS_CTRL);
+ if (rc)
+ return rc;
+
+ /* Select all references for measurement */
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_3_0,
+ GENMASK(7, 0)); /* REF0P..REF3N */
+ if (rc)
+ return rc;
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_4,
+ GENMASK(1, 0)); /* REF4P..REF4N */
+ if (rc)
+ return rc;
+
+ /* Request frequency offset measurement */
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+ ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
+ if (rc)
+ return rc;
+
+ /* Wait for finish */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+ ZL_REF_FREQ_MEAS_CTRL);
+ if (rc)
+ return rc;
+
+ /* Read DPLL-to-REFx frequency offset measurements */
+ for (i = 0; i < ZL3073X_NUM_REFS; i++) {
+ s32 value;
+
+ /* Read value stored in units of 2^-32 signed */
+ rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
+ if (rc)
+ return rc;
+
+ /* Convert to ppm -> ffo = (10^6 * value) / 2^32 */
+ zldev->ref[i].ffo = mul_s64_u64_shr(value, 1000000, 32);
+ }
+
+ return 0;
+}
+
+static void
+zl3073x_dev_periodic_work(struct kthread_work *work)
+{
+ struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev,
+ work.work);
+ struct zl3073x_dpll *zldpll;
+ int rc;
+
+ /* Update DPLL-to-connected-ref phase offsets registers */
+ rc = zl3073x_ref_phase_offsets_update(zldev, -1);
+ if (rc)
+ dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
+ ERR_PTR(rc));
+
+ /* Update references' fractional frequency offsets */
+ rc = zl3073x_ref_ffo_update(zldev);
+ if (rc)
+ dev_warn(zldev->dev,
+ "Failed to update fractional frequency offsets: %pe\n",
+ ERR_PTR(rc));
+
+ list_for_each_entry(zldpll, &zldev->dplls, list)
+ zl3073x_dpll_changes_check(zldpll);
+
+ /* Run twice a second */
+ kthread_queue_delayed_work(zldev->kworker, &zldev->work,
+ msecs_to_jiffies(500));
+}
+
+static void zl3073x_dev_dpll_fini(void *ptr)
+{
+ struct zl3073x_dpll *zldpll, *next;
+ struct zl3073x_dev *zldev = ptr;
+
+ /* Stop monitoring thread */
+ if (zldev->kworker) {
+ kthread_cancel_delayed_work_sync(&zldev->work);
+ kthread_destroy_worker(zldev->kworker);
+ zldev->kworker = NULL;
+ }
+
+ /* Release DPLLs */
+ list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) {
+ zl3073x_dpll_unregister(zldpll);
+ list_del(&zldpll->list);
+ zl3073x_dpll_free(zldpll);
+ }
+}
+
+static int
+zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
+{
+ struct kthread_worker *kworker;
+ struct zl3073x_dpll *zldpll;
+ unsigned int i;
+ int rc;
+
+ INIT_LIST_HEAD(&zldev->dplls);
+
+ /* Initialize all DPLLs */
+ for (i = 0; i < num_dplls; i++) {
+ zldpll = zl3073x_dpll_alloc(zldev, i);
+ if (IS_ERR(zldpll)) {
+ dev_err_probe(zldev->dev, PTR_ERR(zldpll),
+ "Failed to alloc DPLL%u\n", i);
+ rc = PTR_ERR(zldpll);
+ goto error;
+ }
+
+ rc = zl3073x_dpll_register(zldpll);
+ if (rc) {
+ dev_err_probe(zldev->dev, rc,
+ "Failed to register DPLL%u\n", i);
+ zl3073x_dpll_free(zldpll);
+ goto error;
+ }
+
+ list_add_tail(&zldpll->list, &zldev->dplls);
+ }
+
+ /* Perform initial firmware fine phase correction */
+ rc = zl3073x_dpll_init_fine_phase_adjust(zldev);
+ if (rc) {
+ dev_err_probe(zldev->dev, rc,
+ "Failed to init fine phase correction\n");
+ goto error;
+ }
+
+ /* Initialize monitoring thread */
+ kthread_init_delayed_work(&zldev->work, zl3073x_dev_periodic_work);
+ kworker = kthread_run_worker(0, "zl3073x-%s", dev_name(zldev->dev));
+ if (IS_ERR(kworker)) {
+ rc = PTR_ERR(kworker);
+ goto error;
+ }
+
+ zldev->kworker = kworker;
+ kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0);
+
+ /* Add devres action to release DPLL related resources */
+ rc = devm_add_action_or_reset(zldev->dev, zl3073x_dev_dpll_fini, zldev);
+ if (rc)
+ goto error;
+
+ return 0;
+
+error:
+ zl3073x_dev_dpll_fini(zldev);
+
+ return rc;
+}
+
+/**
+ * zl3073x_dev_phase_meas_setup - setup phase offset measurement
+ * @zldev: pointer to zl3073x_dev structure
+ * @num_channels: number of DPLL channels
+ *
+ * Enable phase offset measurement block, set measurement averaging factor
+ * and enable DPLL-to-its-ref phase measurement for all DPLLs.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+static int
+zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev, int num_channels)
+{
+ u8 dpll_meas_ctrl, mask;
+ int i, rc;
+
+ /* Read DPLL phase measurement control register */
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
+ if (rc)
+ return rc;
+
+ /* Setup phase measurement averaging factor */
+ dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
+ dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3);
+
+ /* Enable DPLL measurement block */
+ dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN;
+
+ /* Update phase measurement control register */
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
+ if (rc)
+ return rc;
+
+ /* Enable DPLL-to-connected-ref measurement for each channel */
+ for (i = 0, mask = 0; i < num_channels; i++)
+ mask |= BIT(i);
+
+ return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask);
+}
+
+/**
+ * zl3073x_dev_probe - initialize zl3073x device
+ * @zldev: pointer to zl3073x device
+ * @chip_info: chip info based on compatible
+ *
+ * Common initialization of zl3073x device structure.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int zl3073x_dev_probe(struct zl3073x_dev *zldev,
+ const struct zl3073x_chip_info *chip_info)
+{
+ u16 id, revision, fw_ver;
+ unsigned int i;
+ u32 cfg_ver;
+ int rc;
+
+ /* Read chip ID */
+ rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id);
+ if (rc)
+ return rc;
+
+ /* Check it matches */
+ for (i = 0; i < chip_info->num_ids; i++) {
+ if (id == chip_info->ids[i])
+ break;
+ }
+
+ if (i == chip_info->num_ids) {
+ return dev_err_probe(zldev->dev, -ENODEV,
+ "Unknown or non-match chip ID: 0x%0x\n",
+ id);
+ }
+
+ /* Read revision, firmware version and custom config version */
+ rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
+ if (rc)
+ return rc;
+ rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver);
+ if (rc)
+ return rc;
+ rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver);
+ if (rc)
+ return rc;
+
+ dev_dbg(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n", id,
+ revision, fw_ver);
+ dev_dbg(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
+ FIELD_GET(GENMASK(31, 24), cfg_ver),
+ FIELD_GET(GENMASK(23, 16), cfg_ver),
+ FIELD_GET(GENMASK(15, 8), cfg_ver),
+ FIELD_GET(GENMASK(7, 0), cfg_ver));
+
+ /* Generate random clock ID as the device has not such property that
+ * could be used for this purpose. A user can later change this value
+ * using devlink.
+ */
+ zldev->clock_id = get_random_u64();
+
+ /* Initialize mutex for operations where multiple reads, writes
+ * and/or polls are required to be done atomically.
+ */
+ rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
+ if (rc)
+ return dev_err_probe(zldev->dev, rc,
+ "Failed to initialize mutex\n");
+
+ /* Fetch device state */
+ rc = zl3073x_dev_state_fetch(zldev);
+ if (rc)
+ return rc;
+
+ /* Setup phase offset measurement block */
+ rc = zl3073x_dev_phase_meas_setup(zldev, chip_info->num_channels);
+ if (rc)
+ return dev_err_probe(zldev->dev, rc,
+ "Failed to setup phase measurement\n");
+
+ /* Register DPLL channels */
+ rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels);
+ if (rc)
+ return rc;
+
+ /* Register the devlink instance and parameters */
+ rc = zl3073x_devlink_register(zldev);
+ if (rc)
+ return dev_err_probe(zldev->dev, rc,
+ "Failed to register devlink instance\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, "ZL3073X");
+
+MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
+MODULE_DESCRIPTION("Microchip ZL3073x core driver");
+MODULE_LICENSE("GPL");