mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-21 13:45:53 -04:00
If the internal flash contains missing or corrupted configuration,
basic communication over the bus still functions, but the device
is not capable of normal operation (for example, using mailboxes).
This condition is indicated in the info register by the ready bit.
If this bit is cleared, the probe procedure times out while fetching
the device state.
Handle this case by checking the ready bit value in zl3073x_dev_start()
and skipping DPLL device and pin registration if it is cleared.
Do not report this condition as an error, allowing the devlink device
to be registered and enabling the user to flash the correct configuration.
Prior this patch:
[ 31.112299] zl3073x-i2c 1-0070: Failed to fetch input state: -ETIMEDOUT
[ 31.116332] zl3073x-i2c 1-0070: error -ETIMEDOUT: Failed to start device
[ 31.136881] zl3073x-i2c 1-0070: probe with driver zl3073x-i2c failed with error -110
After this patch:
[ 41.011438] zl3073x-i2c 1-0070: FW not fully ready - missing or corrupted config
Fixes: 75a71ecc24 ("dpll: zl3073x: Register DPLL devices and pins")
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20251008141445.841113-1-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1288 lines
32 KiB
C
1288 lines
32 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 256
|
|
#define ZL_PAGE_SEL 0x7F
|
|
#define ZL_PAGE_SEL_MASK GENMASK(7, 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 except the page 255 that does not
|
|
* need this protection.
|
|
*/
|
|
if (ZL_REG_PAGE(reg) >= 10 && ZL_REG_PAGE(reg) < 255)
|
|
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_do_hwreg_op - Perform HW register read/write operation
|
|
* @zldev: zl3073x device pointer
|
|
* @op: operation to perform
|
|
*
|
|
* Performs requested operation and waits for its completion.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
static int
|
|
zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op)
|
|
{
|
|
int rc;
|
|
|
|
/* Set requested operation and set pending bit */
|
|
rc = zl3073x_write_u8(zldev, ZL_REG_HWREG_OP, op | ZL_HWREG_OP_PENDING);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Poll for completion - pending bit cleared */
|
|
return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP,
|
|
ZL_HWREG_OP_PENDING);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_read_hwreg - Read HW register
|
|
* @zldev: zl3073x device pointer
|
|
* @addr: HW register address
|
|
* @value: Value of the HW register
|
|
*
|
|
* Reads HW register value and stores it into @value.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value)
|
|
{
|
|
int rc;
|
|
|
|
/* Set address to read data from */
|
|
rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform the read operation */
|
|
rc = zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_READ);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Read the received data */
|
|
return zl3073x_read_u32(zldev, ZL_REG_HWREG_READ_DATA, value);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_hwreg - Write value to HW register
|
|
* @zldev: zl3073x device pointer
|
|
* @addr: HW registers address
|
|
* @value: Value to be written to HW register
|
|
*
|
|
* Stores the requested value into HW register.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value)
|
|
{
|
|
int rc;
|
|
|
|
/* Set address to write data to */
|
|
rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Set data to be written */
|
|
rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_WRITE_DATA, value);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform the write operation */
|
|
return zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_WRITE);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_update_hwreg - Update certain bits in HW register
|
|
* @zldev: zl3073x device pointer
|
|
* @addr: HW register address
|
|
* @value: Value to be written into HW register
|
|
* @mask: Bitmask indicating bits to be updated
|
|
*
|
|
* Reads given HW register, updates requested bits specified by value and
|
|
* mask and writes result back to HW register.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value,
|
|
u32 mask)
|
|
{
|
|
u32 tmp;
|
|
int rc;
|
|
|
|
rc = zl3073x_read_hwreg(zldev, addr, &tmp);
|
|
if (rc)
|
|
return rc;
|
|
|
|
tmp &= ~mask;
|
|
tmp |= value & mask;
|
|
|
|
return zl3073x_write_hwreg(zldev, addr, tmp);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_hwreg_seq - Write HW registers sequence
|
|
* @zldev: pointer to device structure
|
|
* @seq: pointer to first sequence item
|
|
* @num_items: number of items in sequence
|
|
*
|
|
* Writes given HW registers sequence.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,
|
|
const struct zl3073x_hwreg_seq_item *seq,
|
|
size_t num_items)
|
|
{
|
|
int i, rc = 0;
|
|
|
|
for (i = 0; i < num_items; i++) {
|
|
dev_dbg(zldev->dev, "Write 0x%0x [0x%0x] to 0x%0x",
|
|
seq[i].value, seq[i].mask, seq[i].addr);
|
|
|
|
if (seq[i].mask == U32_MAX)
|
|
/* Write value directly */
|
|
rc = zl3073x_write_hwreg(zldev, seq[i].addr,
|
|
seq[i].value);
|
|
else
|
|
/* Update only bits specified by the mask */
|
|
rc = zl3073x_update_hwreg(zldev, seq[i].addr,
|
|
seq[i].value, seq[i].mask);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (seq->wait)
|
|
msleep(seq->wait);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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));
|
|
}
|
|
|
|
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
|
|
{
|
|
u8 dpll_meas_ctrl, value;
|
|
int rc;
|
|
|
|
/* Read DPLL phase measurement control register */
|
|
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Convert requested factor to register value */
|
|
value = (factor + 1) & 0x0f;
|
|
|
|
/* Update phase measurement control register */
|
|
dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
|
|
dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, value);
|
|
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Save the new factor */
|
|
zldev->phase_avg_factor = factor;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_phase_meas_setup - setup phase offset measurement
|
|
* @zldev: pointer to zl3073x_dev structure
|
|
*
|
|
* 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)
|
|
{
|
|
struct zl3073x_dpll *zldpll;
|
|
u8 dpll_meas_ctrl, mask = 0;
|
|
int rc;
|
|
|
|
/* Setup phase measurement averaging factor */
|
|
rc = zl3073x_dev_phase_avg_factor_set(zldev, zldev->phase_avg_factor);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Read DPLL phase measurement control register */
|
|
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* 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 */
|
|
list_for_each_entry(zldpll, &zldev->dplls, list)
|
|
mask |= BIT(zldpll->id);
|
|
|
|
return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_start - Start normal operation
|
|
* @zldev: zl3073x device pointer
|
|
* @full: perform full initialization
|
|
*
|
|
* The function starts normal operation, which means registering all DPLLs and
|
|
* their pins, and starting monitoring. If full initialization is requested,
|
|
* the function additionally initializes the phase offset measurement block and
|
|
* fetches hardware-invariant parameters.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full)
|
|
{
|
|
struct zl3073x_dpll *zldpll;
|
|
u8 info;
|
|
int rc;
|
|
|
|
rc = zl3073x_read_u8(zldev, ZL_REG_INFO, &info);
|
|
if (rc) {
|
|
dev_err(zldev->dev, "Failed to read device status info\n");
|
|
return rc;
|
|
}
|
|
|
|
if (!FIELD_GET(ZL_INFO_READY, info)) {
|
|
/* The ready bit indicates that the firmware was successfully
|
|
* configured and is ready for normal operation. If it is
|
|
* cleared then the configuration stored in flash is wrong
|
|
* or missing. In this situation the driver will expose
|
|
* only devlink interface to give an opportunity to flash
|
|
* the correct config.
|
|
*/
|
|
dev_info(zldev->dev,
|
|
"FW not fully ready - missing or corrupted config\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (full) {
|
|
/* 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);
|
|
if (rc) {
|
|
dev_err(zldev->dev,
|
|
"Failed to setup phase measurement\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Register all DPLLs */
|
|
list_for_each_entry(zldpll, &zldev->dplls, list) {
|
|
rc = zl3073x_dpll_register(zldpll);
|
|
if (rc) {
|
|
dev_err_probe(zldev->dev, rc,
|
|
"Failed to register DPLL%u\n",
|
|
zldpll->id);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* 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");
|
|
return rc;
|
|
}
|
|
|
|
/* Start monitoring */
|
|
kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_stop - Stop normal operation
|
|
* @zldev: zl3073x device pointer
|
|
*
|
|
* The function stops the normal operation that mean deregistration of all
|
|
* DPLLs and their pins and stop monitoring.
|
|
*
|
|
* Return: 0 on success, <0 on error
|
|
*/
|
|
void zl3073x_dev_stop(struct zl3073x_dev *zldev)
|
|
{
|
|
struct zl3073x_dpll *zldpll;
|
|
|
|
/* Stop monitoring */
|
|
kthread_cancel_delayed_work_sync(&zldev->work);
|
|
|
|
/* Unregister all DPLLs */
|
|
list_for_each_entry(zldpll, &zldev->dplls, list) {
|
|
if (zldpll->dpll_dev)
|
|
zl3073x_dpll_unregister(zldpll);
|
|
}
|
|
}
|
|
|
|
static void zl3073x_dev_dpll_fini(void *ptr)
|
|
{
|
|
struct zl3073x_dpll *zldpll, *next;
|
|
struct zl3073x_dev *zldev = ptr;
|
|
|
|
/* Stop monitoring and unregister DPLLs */
|
|
zl3073x_dev_stop(zldev);
|
|
|
|
/* Destroy monitoring thread */
|
|
if (zldev->kworker) {
|
|
kthread_destroy_worker(zldev->kworker);
|
|
zldev->kworker = NULL;
|
|
}
|
|
|
|
/* Free all DPLLs */
|
|
list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) {
|
|
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);
|
|
|
|
/* Allocate 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;
|
|
}
|
|
|
|
list_add_tail(&zldpll->list, &zldev->dplls);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* Start normal operation */
|
|
rc = zl3073x_dev_start(zldev, true);
|
|
if (rc) {
|
|
dev_err_probe(zldev->dev, rc, "Failed to start device\n");
|
|
goto error;
|
|
}
|
|
|
|
/* 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_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();
|
|
|
|
/* Default phase offset averaging factor */
|
|
zldev->phase_avg_factor = 2;
|
|
|
|
/* 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");
|
|
|
|
/* 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");
|