mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-30 13:03:01 +00:00

Adds support to get fractional frequency offset for input pins. Implement the appropriate callback and function that periodicaly performs reference frequency measurement and notifies DPLL core about changes. Reviewed-by: Jiri Pirko <jiri@nvidia.com> Tested-by: Prathosh Satish <prathosh.satish@microchip.com> Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com> Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com> Signed-off-by: Ivan Vecera <ivecera@redhat.com> Link: https://patch.msgid.link/20250715144633.149156-6-ivecera@redhat.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
1031 lines
25 KiB
C
1031 lines
25 KiB
C
// 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");
|