Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Generic DPLL drivers configuration
#
menu "DPLL device support"
config DPLL
bool
config DPLL_REFCNT_TRACKER
bool "DPLL reference count tracking"
depends on DEBUG_KERNEL && STACKTRACE_SUPPORT && DPLL
select REF_TRACKER
help
Enable reference count tracking for DPLL devices and pins.
This helps debugging reference leaks and use-after-free bugs
by recording stack traces for each get/put operation.
The tracking information is exposed via debugfs at:
/sys/kernel/debug/ref_tracker/dpll_device_*
/sys/kernel/debug/ref_tracker/dpll_pin_*
If unsure, say N.
source "drivers/dpll/zl3073x/Kconfig"
endmenu
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for DPLL drivers.
#
obj-$(CONFIG_DPLL) += dpll.o
dpll-y += dpll_core.o
dpll-y += dpll_netlink.o
dpll-y += dpll_nl.o
obj-$(CONFIG_ZL3073X) += zl3073x/
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Meta Platforms, Inc. and affiliates
* Copyright (c) 2023 Intel and affiliates
*/
#ifndef __DPLL_CORE_H__
#define __DPLL_CORE_H__
#include <linux/dpll.h>
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/ref_tracker.h>
#include "dpll_nl.h"
#define DPLL_REGISTERED XA_MARK_1
/**
* struct dpll_device - stores DPLL device internal data
* @id: unique id number for device given by dpll subsystem
* @device_idx: id given by dev driver
* @clock_id: unique identifier (clock_id) of a dpll
* @module: module of creator
* @type: type of a dpll
* @pin_refs: stores pins registered within a dpll
* @refcount: refcount
* @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @registration_list: list of registered ops and priv data of dpll owners
**/
struct dpll_device {
u32 id;
u32 device_idx;
u64 clock_id;
struct module *module;
enum dpll_type type;
struct xarray pin_refs;
refcount_t refcount;
struct ref_tracker_dir refcnt_tracker;
struct list_head registration_list;
};
/**
* struct dpll_pin - structure for a dpll pin
* @id: unique id number for pin given by dpll subsystem
* @pin_idx: index of a pin given by dev driver
* @clock_id: clock_id of creator
* @module: module of creator
* @module_name: module name of creator
* @fwnode: optional reference to firmware node
* @dpll_refs: hold referencees to dplls pin was registered with
* @parent_refs: hold references to parent pins pin was registered with
* @ref_sync_pins: hold references to pins for Reference SYNC feature
* @prop: pin properties copied from the registerer
* @refcount: refcount
* @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @rcu: rcu_head for kfree_rcu()
**/
struct dpll_pin {
u32 id;
u32 pin_idx;
u64 clock_id;
struct module *module;
char module_name[MODULE_NAME_LEN];
struct fwnode_handle *fwnode;
struct xarray dpll_refs;
struct xarray parent_refs;
struct xarray ref_sync_pins;
struct dpll_pin_properties prop;
refcount_t refcount;
struct ref_tracker_dir refcnt_tracker;
struct rcu_head rcu;
};
/**
* struct dpll_pin_ref - structure for referencing either dpll or pins
* @dpll: pointer to a dpll
* @pin: pointer to a pin
* @registration_list: list of ops and priv data registered with the ref
* @refcount: refcount
**/
struct dpll_pin_ref {
union {
struct dpll_device *dpll;
struct dpll_pin *pin;
};
struct list_head registration_list;
refcount_t refcount;
};
void *dpll_priv(struct dpll_device *dpll);
void *dpll_pin_on_dpll_priv(struct dpll_device *dpll, struct dpll_pin *pin);
void *dpll_pin_on_pin_priv(struct dpll_pin *parent, struct dpll_pin *pin);
const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll);
struct dpll_device *dpll_device_get_by_id(int id);
struct dpll_pin_ref *dpll_pin_own_dpll_ref_first(struct dpll_pin *pin);
const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref);
struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs);
extern struct xarray dpll_device_xa;
extern struct xarray dpll_pin_xa;
extern struct mutex dpll_lock;
void dpll_device_notify(struct dpll_device *dpll, unsigned long action);
void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id,
unsigned long action);
#endif
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Meta Platforms, Inc. and affiliates
* Copyright (c) 2023 Intel and affiliates
*/
int dpll_device_create_ntf(struct dpll_device *dpll);
int dpll_device_delete_ntf(struct dpll_device *dpll);
int dpll_pin_create_ntf(struct dpll_pin *pin, u64 src_clock_id);
int dpll_pin_delete_ntf(struct dpll_pin *pin, u64 src_clock_id);
+175
View File
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/dpll.yaml */
/* YNL-GEN kernel source */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "dpll_nl.h"
#include <uapi/linux/dpll.h>
/* Common nested types */
const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1] = {
[DPLL_A_PIN_PARENT_ID] = { .type = NLA_U32, },
[DPLL_A_PIN_DIRECTION] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PIN_PRIO] = { .type = NLA_U32, },
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
[DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, },
[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, },
};
const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = {
[DPLL_A_PIN_PARENT_ID] = { .type = NLA_U32, },
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
};
const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1] = {
[DPLL_A_PIN_ID] = { .type = NLA_U32, },
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
};
/* DPLL_CMD_DEVICE_ID_GET - do */
static const struct nla_policy dpll_device_id_get_nl_policy[DPLL_A_TYPE + 1] = {
[DPLL_A_MODULE_NAME] = { .type = NLA_NUL_STRING, },
[DPLL_A_CLOCK_ID] = { .type = NLA_U64, },
[DPLL_A_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
};
/* DPLL_CMD_DEVICE_GET - do */
static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
[DPLL_A_ID] = { .type = NLA_U32, },
};
/* DPLL_CMD_DEVICE_SET - do */
static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_FREQUENCY_MONITOR + 1] = {
[DPLL_A_ID] = { .type = NLA_U32, },
[DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
[DPLL_A_FREQUENCY_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
};
/* DPLL_CMD_PIN_ID_GET - do */
static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = {
[DPLL_A_PIN_MODULE_NAME] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_CLOCK_ID] = { .type = NLA_U64, },
[DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5),
};
/* DPLL_CMD_PIN_GET - do */
static const struct nla_policy dpll_pin_get_do_nl_policy[DPLL_A_PIN_ID + 1] = {
[DPLL_A_PIN_ID] = { .type = NLA_U32, },
};
/* DPLL_CMD_PIN_GET - dump */
static const struct nla_policy dpll_pin_get_dump_nl_policy[DPLL_A_PIN_ID + 1] = {
[DPLL_A_PIN_ID] = { .type = NLA_U32, },
};
/* DPLL_CMD_PIN_SET - do */
static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_REFERENCE_SYNC + 1] = {
[DPLL_A_PIN_ID] = { .type = NLA_U32, },
[DPLL_A_PIN_FREQUENCY] = { .type = NLA_U64, },
[DPLL_A_PIN_DIRECTION] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PIN_PRIO] = { .type = NLA_U32, },
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
[DPLL_A_PIN_PARENT_DEVICE] = NLA_POLICY_NESTED(dpll_pin_parent_device_nl_policy),
[DPLL_A_PIN_PARENT_PIN] = NLA_POLICY_NESTED(dpll_pin_parent_pin_nl_policy),
[DPLL_A_PIN_PHASE_ADJUST] = { .type = NLA_S32, },
[DPLL_A_PIN_ESYNC_FREQUENCY] = { .type = NLA_U64, },
[DPLL_A_PIN_REFERENCE_SYNC] = NLA_POLICY_NESTED(dpll_reference_sync_nl_policy),
};
/* Ops table for dpll */
static const struct genl_split_ops dpll_nl_ops[] = {
{
.cmd = DPLL_CMD_DEVICE_ID_GET,
.pre_doit = dpll_lock_doit,
.doit = dpll_nl_device_id_get_doit,
.post_doit = dpll_unlock_doit,
.policy = dpll_device_id_get_nl_policy,
.maxattr = DPLL_A_TYPE,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_DEVICE_GET,
.pre_doit = dpll_pre_doit,
.doit = dpll_nl_device_get_doit,
.post_doit = dpll_post_doit,
.policy = dpll_device_get_nl_policy,
.maxattr = DPLL_A_ID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_DEVICE_GET,
.dumpit = dpll_nl_device_get_dumpit,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
{
.cmd = DPLL_CMD_DEVICE_SET,
.pre_doit = dpll_pre_doit,
.doit = dpll_nl_device_set_doit,
.post_doit = dpll_post_doit,
.policy = dpll_device_set_nl_policy,
.maxattr = DPLL_A_FREQUENCY_MONITOR,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_PIN_ID_GET,
.pre_doit = dpll_lock_doit,
.doit = dpll_nl_pin_id_get_doit,
.post_doit = dpll_unlock_doit,
.policy = dpll_pin_id_get_nl_policy,
.maxattr = DPLL_A_PIN_TYPE,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_PIN_GET,
.pre_doit = dpll_pin_pre_doit,
.doit = dpll_nl_pin_get_doit,
.post_doit = dpll_pin_post_doit,
.policy = dpll_pin_get_do_nl_policy,
.maxattr = DPLL_A_PIN_ID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_PIN_GET,
.dumpit = dpll_nl_pin_get_dumpit,
.policy = dpll_pin_get_dump_nl_policy,
.maxattr = DPLL_A_PIN_ID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
{
.cmd = DPLL_CMD_PIN_SET,
.pre_doit = dpll_pin_pre_doit,
.doit = dpll_nl_pin_set_doit,
.post_doit = dpll_pin_post_doit,
.policy = dpll_pin_set_nl_policy,
.maxattr = DPLL_A_PIN_REFERENCE_SYNC,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};
static const struct genl_multicast_group dpll_nl_mcgrps[] = {
[DPLL_NLGRP_MONITOR] = { "monitor", },
};
struct genl_family dpll_nl_family __ro_after_init = {
.name = DPLL_FAMILY_NAME,
.version = DPLL_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = dpll_nl_ops,
.n_split_ops = ARRAY_SIZE(dpll_nl_ops),
.mcgrps = dpll_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(dpll_nl_mcgrps),
};
+51
View File
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/dpll.yaml */
/* YNL-GEN kernel header */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#ifndef _LINUX_DPLL_GEN_H
#define _LINUX_DPLL_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/dpll.h>
/* Common nested types */
extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1];
extern const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1];
extern const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1];
int dpll_lock_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
void
dpll_unlock_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
void
dpll_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
void
dpll_pin_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
int dpll_nl_device_id_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_pin_id_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info);
enum {
DPLL_NLGRP_MONITOR,
};
extern struct genl_family dpll_nl_family;
#endif /* _LINUX_DPLL_GEN_H */
+39
View File
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: GPL-2.0-only
config ZL3073X
tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST
depends on NET
select DPLL
select NET_DEVLINK
select REGMAP
help
This driver supports Microchip Azurite family DPLL/PTP/SyncE
devices that support up to 5 independent DPLL channels,
10 input pins and up to 20 output pins.
To compile this driver as a module, choose M here. The module
will be called zl3073x.
config ZL3073X_I2C
tristate "I2C bus implementation for Microchip Azurite devices"
depends on I2C && NET
select REGMAP_I2C
select ZL3073X
help
This is I2C bus implementation for Microchip Azurite DPLL/PTP/SyncE
devices.
To compile this driver as a module, choose M here: the module will
be called zl3073x_i2c.
config ZL3073X_SPI
tristate "SPI bus implementation for Microchip Azurite devices"
depends on NET && SPI
select REGMAP_SPI
select ZL3073X
help
This is SPI bus implementation for Microchip Azurite DPLL/PTP/SyncE
devices.
To compile this driver as a module, choose M here: the module will
be called zl3073x_spi.
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ZL3073X) += zl3073x.o
zl3073x-objs := chan.o core.o devlink.o dpll.o \
flash.o fw.o out.o prop.o ref.o synth.o
obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o
zl3073x_i2c-objs := i2c.o
obj-$(CONFIG_ZL3073X_SPI) += zl3073x_spi.o
zl3073x_spi-objs := spi.o
+192
View File
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/types.h>
#include "chan.h"
#include "core.h"
/**
* zl3073x_chan_state_update - update DPLL channel status from HW
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
u64 val;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
&chan->mon_status);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
&chan->refsel_status);
if (rc)
return rc;
/* Read df_offset vs tracked reference */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
if (rc)
return rc;
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
if (rc)
return rc;
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
if (rc)
return rc;
rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
if (rc)
return rc;
chan->df_offset = sign_extend64(val, 47);
return 0;
}
/**
* zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to fetch state for
*
* Reads the mode_refsel register and reference priority registers for
* the given DPLL channel and stores the raw values for later use.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
int rc, i;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
if (rc)
return rc;
dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
rc = zl3073x_chan_state_update(zldev, index);
if (rc)
return rc;
dev_dbg(zldev->dev,
"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
index, zl3073x_chan_lock_state_get(chan),
zl3073x_chan_is_ho_ready(chan) ? 1 : 0,
zl3073x_chan_refsel_state_get(chan),
zl3073x_chan_refsel_ref_get(chan));
guard(mutex)(&zldev->multiop_lock);
/* Read DPLL configuration from mailbox */
rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
ZL_REG_DPLL_MB_MASK, BIT(index));
if (rc)
return rc;
/* Read reference priority registers */
for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),
&chan->ref_prio[i]);
if (rc)
return rc;
}
return 0;
}
/**
* zl3073x_chan_state_get - get current DPLL channel state
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to get state for
*
* Return: pointer to given DPLL channel state
*/
const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
u8 index)
{
return &zldev->chan[index];
}
/**
* zl3073x_chan_state_set - commit DPLL channel state changes to hardware
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to set state for
* @chan: desired channel state
*
* Skips the HW write if the configuration is unchanged, and otherwise
* writes only the changed registers to hardware. The mode_refsel register
* is written directly, while the reference priority registers are written
* via the DPLL mailbox interface.
*
* Return: 0 on success, <0 on HW error
*/
int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan)
{
struct zl3073x_chan *dchan = &zldev->chan[index];
int rc, i;
/* Skip HW write if configuration hasn't changed */
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
/* Direct register write for mode_refsel */
if (dchan->mode_refsel != chan->mode_refsel) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
if (rc)
return rc;
dchan->mode_refsel = chan->mode_refsel;
}
/* Mailbox write for ref_prio if changed */
if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) {
dchan->cfg = chan->cfg;
return 0;
}
guard(mutex)(&zldev->multiop_lock);
/* Read DPLL configuration into mailbox */
rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
ZL_REG_DPLL_MB_MASK, BIT(index));
if (rc)
return rc;
/* Update changed ref_prio registers */
for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
if (dchan->ref_prio[i] != chan->ref_prio[i]) {
rc = zl3073x_write_u8(zldev,
ZL_REG_DPLL_REF_PRIO(i),
chan->ref_prio[i]);
if (rc)
return rc;
}
}
/* Commit DPLL configuration */
rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
ZL_REG_DPLL_MB_MASK, BIT(index));
if (rc)
return rc;
/* After successful write store new state */
dchan->cfg = chan->cfg;
return 0;
}
+188
View File
@@ -0,0 +1,188 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_CHAN_H
#define _ZL3073X_CHAN_H
#include <linux/bitfield.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
* @df_offset: frequency offset vs tracked reference in 2^-48 steps
*/
struct zl3073x_chan {
struct_group(cfg,
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
struct_group(stat,
u8 mon_status;
u8 refsel_status;
s64 df_offset;
);
};
int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index);
const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
u8 index);
int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan);
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
* @chan: pointer to channel state
*
* Return: frequency offset in 2^-48 steps
*/
static inline s64
zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
{
return chan->df_offset;
}
/**
* zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state
*
* Return: reference selection mode of the given DPLL channel
*/
static inline u8 zl3073x_chan_mode_get(const struct zl3073x_chan *chan)
{
return FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, chan->mode_refsel);
}
/**
* zl3073x_chan_ref_get - get manually selected reference
* @chan: pointer to channel state
*
* Return: reference selected in forced reference lock mode
*/
static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan)
{
return FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, chan->mode_refsel);
}
/**
* zl3073x_chan_mode_set - set DPLL channel operating mode
* @chan: pointer to channel state
* @mode: mode to set
*/
static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)
{
FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_MODE, &chan->mode_refsel, mode);
}
/**
* zl3073x_chan_ref_set - set manually selected reference
* @chan: pointer to channel state
* @ref: reference to set
*/
static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
{
FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_REF, &chan->mode_refsel, ref);
}
/**
* zl3073x_chan_ref_prio_get - get reference priority
* @chan: pointer to channel state
* @ref: input reference index
*
* Return: priority of the given reference <0, 15>
*/
static inline u8
zl3073x_chan_ref_prio_get(const struct zl3073x_chan *chan, u8 ref)
{
u8 val = chan->ref_prio[ref / 2];
if (!(ref & 1))
return FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, val);
else
return FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, val);
}
/**
* zl3073x_chan_ref_prio_set - set reference priority
* @chan: pointer to channel state
* @ref: input reference index
* @prio: priority to set
*/
static inline void
zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio)
{
u8 *val = &chan->ref_prio[ref / 2];
if (!(ref & 1))
FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_P, val, prio);
else
FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_N, val, prio);
}
/**
* zl3073x_chan_ref_is_selectable - check if reference is selectable
* @chan: pointer to channel state
* @ref: input reference index
*
* Return: true if the reference priority is not NONE, false otherwise
*/
static inline bool
zl3073x_chan_ref_is_selectable(const struct zl3073x_chan *chan, u8 ref)
{
return zl3073x_chan_ref_prio_get(chan, ref) != ZL_DPLL_REF_PRIO_NONE;
}
/**
* zl3073x_chan_lock_state_get - get DPLL channel lock state
* @chan: pointer to channel state
*
* Return: lock state of the given DPLL channel
*/
static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
{
return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
}
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state
*
* Return: true if holdover is ready, false otherwise
*/
static inline bool zl3073x_chan_is_ho_ready(const struct zl3073x_chan *chan)
{
return !!FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, chan->mon_status);
}
/**
* zl3073x_chan_refsel_state_get - get reference selection state
* @chan: pointer to channel state
*
* Return: reference selection state of the given DPLL channel
*/
static inline u8 zl3073x_chan_refsel_state_get(const struct zl3073x_chan *chan)
{
return FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, chan->refsel_status);
}
/**
* zl3073x_chan_refsel_ref_get - get currently selected reference in auto mode
* @chan: pointer to channel state
*
* Return: reference selected by the DPLL in automatic mode
*/
static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
{
return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
}
#endif /* _ZL3073X_CHAN_H */
File diff suppressed because it is too large Load Diff
+418
View File
@@ -0,0 +1,418 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_CORE_H
#define _ZL3073X_CORE_H
#include <linux/bitfield.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include "chan.h"
#include "out.h"
#include "ref.h"
#include "regs.h"
#include "synth.h"
struct device;
struct regmap;
struct zl3073x_dpll;
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
ZL3073X_FLAG_DIE_TEMP_BIT,
ZL3073X_FLAGS_NBITS /* must be last */
};
#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT)
#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32)
#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP)
/**
* struct zl3073x_chip_info - chip variant identification
* @id: chip ID
* @num_channels: number of DPLL channels supported by this variant
* @flags: chip variant flags
*/
struct zl3073x_chip_info {
u16 id;
u8 num_channels;
unsigned long flags;
};
/**
* struct zl3073x_dev - zl3073x device
* @dev: pointer to device
* @regmap: regmap to access device registers
* @info: detected chip info
* @multiop_lock: to serialize multiple register operations
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
* @chan: array of DPLL channels' state
* @dplls: list of DPLLs
* @kworker: thread for periodic work
* @work: periodic work
* @clock_id: clock id of the device
* @phase_avg_factor: phase offset measurement averaging factor
* @freq_monitor: is frequency monitor enabled
*/
struct zl3073x_dev {
struct device *dev;
struct regmap *regmap;
const struct zl3073x_chip_info *info;
struct mutex multiop_lock;
/* Invariants */
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
struct zl3073x_out out[ZL3073X_NUM_OUTS];
struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
struct zl3073x_chan chan[ZL3073X_MAX_CHANNELS];
/* DPLL channels */
struct list_head dplls;
/* Monitor */
struct kthread_worker *kworker;
struct kthread_delayed_work work;
/* Per-chip parameters */
u64 clock_id;
u8 phase_avg_factor;
bool freq_monitor;
};
extern const struct regmap_config zl3073x_regmap_config;
struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);
int zl3073x_dev_probe(struct zl3073x_dev *zldev);
int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full);
void zl3073x_dev_stop(struct zl3073x_dev *zldev);
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
{
return zldev->phase_avg_factor;
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
/**********************
* Registers operations
**********************/
/**
* struct zl3073x_hwreg_seq_item - HW register write sequence item
* @addr: HW register to be written
* @value: value to be written to HW register
* @mask: bitmask indicating bits to be updated
* @wait: number of ms to wait after register write
*/
struct zl3073x_hwreg_seq_item {
u32 addr;
u32 value;
u32 mask;
u32 wait;
};
#define HWREG_SEQ_ITEM(_addr, _value, _mask, _wait) \
{ \
.addr = _addr, \
.value = FIELD_PREP_CONST(_mask, _value), \
.mask = _mask, \
.wait = _wait, \
}
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
unsigned int mask_reg, u16 mask_val);
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask);
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);
int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val);
int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val);
int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val);
int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val);
int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val);
int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value);
int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value);
int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value,
u32 mask);
int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,
const struct zl3073x_hwreg_seq_item *seq,
size_t num_items);
/*****************
* Misc operations
*****************/
int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
/**
* zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size
* @zldev: pointer to zl3073x device
*
* Some chip IDs have a 32-bit wide ref_phase_offset_comp register instead
* of the default 48-bit.
*
* Return: true if the register is 32-bit, false if 48-bit
*/
static inline bool
zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev)
{
return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32;
}
static inline bool
zl3073x_is_n_pin(u8 id)
{
/* P-pins ids are even while N-pins are odd */
return id & 1;
}
static inline bool
zl3073x_is_p_pin(u8 id)
{
return !zl3073x_is_n_pin(id);
}
/**
* zl3073x_input_pin_ref_get - get reference for given input pin
* @id: input pin id
*
* Return: reference id for the given input pin
*/
static inline u8
zl3073x_input_pin_ref_get(u8 id)
{
return id;
}
/**
* zl3073x_output_pin_out_get - get output for the given output pin
* @id: output pin id
*
* Return: output id for the given output pin
*/
static inline u8
zl3073x_output_pin_out_get(u8 id)
{
/* Output pin pair shares the single output */
return id / 2;
}
/**
* zl3073x_dev_ref_freq_get - get input reference frequency
* @zldev: pointer to zl3073x device
* @index: input reference index
*
* Return: frequency of given input reference
*/
static inline u32
zl3073x_dev_ref_freq_get(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
return zl3073x_ref_freq_get(ref);
}
/**
* zl3073x_dev_ref_is_diff - check if the given input reference is differential
* @zldev: pointer to zl3073x device
* @index: input reference index
*
* Return: true if reference is differential, false if reference is single-ended
*/
static inline bool
zl3073x_dev_ref_is_diff(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
return zl3073x_ref_is_diff(ref);
}
/*
* zl3073x_dev_ref_is_status_ok - check the given input reference status
* @zldev: pointer to zl3073x device
* @index: input reference index
*
* Return: true if the status is ok, false otherwise
*/
static inline bool
zl3073x_dev_ref_is_status_ok(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
return zl3073x_ref_is_status_ok(ref);
}
/**
* zl3073x_dev_synth_freq_get - get synth current freq
* @zldev: pointer to zl3073x device
* @index: synth index
*
* Return: frequency of given synthetizer
*/
static inline u32
zl3073x_dev_synth_freq_get(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_synth *synth;
synth = zl3073x_synth_state_get(zldev, index);
return zl3073x_synth_freq_get(synth);
}
/**
* zl3073x_dev_out_synth_get - get synth connected to given output
* @zldev: pointer to zl3073x device
* @index: output index
*
* Return: index of synth connected to given output.
*/
static inline u8
zl3073x_dev_out_synth_get(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
return zl3073x_out_synth_get(out);
}
/**
* zl3073x_dev_out_is_enabled - check if the given output is enabled
* @zldev: pointer to zl3073x device
* @index: output index
*
* Return: true if the output is enabled, false otherwise
*/
static inline bool
zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
const struct zl3073x_synth *synth;
u8 synth_id;
/* Output is enabled only if associated synth is enabled */
synth_id = zl3073x_out_synth_get(out);
synth = zl3073x_synth_state_get(zldev, synth_id);
return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out);
}
/**
* zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by
* @zldev: pointer to zl3073x device
* @index: output index
*
* Return: ID of DPLL the given output is driven by
*/
static inline
u8 zl3073x_dev_out_dpll_get(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
const struct zl3073x_synth *synth;
u8 synth_id;
/* Get synthesizer connected to given output */
synth_id = zl3073x_out_synth_get(out);
synth = zl3073x_synth_state_get(zldev, synth_id);
/* Return DPLL that drives the synth */
return zl3073x_synth_dpll_get(synth);
}
/**
* zl3073x_dev_output_pin_freq_get - get output pin frequency
* @zldev: pointer to zl3073x device
* @id: output pin id
*
* Computes the output pin frequency based on the synth frequency, output
* divisor, and signal format. For N-div formats, N-pin frequency is
* additionally divided by esync_n_period.
*
* Return: frequency of the given output pin in Hz
*/
static inline u32
zl3073x_dev_output_pin_freq_get(struct zl3073x_dev *zldev, u8 id)
{
const struct zl3073x_synth *synth;
const struct zl3073x_out *out;
u8 out_id;
u32 freq;
out_id = zl3073x_output_pin_out_get(id);
out = zl3073x_out_state_get(zldev, out_id);
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out));
freq = zl3073x_synth_freq_get(synth) / out->div;
if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id))
freq /= out->esync_n_period;
return freq;
}
/**
* zl3073x_dev_out_is_diff - check if the given output is differential
* @zldev: pointer to zl3073x device
* @index: output index
*
* Return: true if output is differential, false if output is single-ended
*/
static inline bool
zl3073x_dev_out_is_diff(struct zl3073x_dev *zldev, u8 index)
{
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
return zl3073x_out_is_diff(out);
}
/**
* zl3073x_dev_output_pin_is_enabled - check if the given output pin is enabled
* @zldev: pointer to zl3073x device
* @id: output pin id
*
* Checks if the output of the given output pin is enabled and also that
* its signal format also enables the given pin.
*
* Return: true if output pin is enabled, false if output pin is disabled
*/
static inline bool
zl3073x_dev_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id)
{
u8 out_id = zl3073x_output_pin_out_get(id);
const struct zl3073x_out *out;
out = zl3073x_out_state_get(zldev, out_id);
/* Check if the output is enabled - call _dev_ helper that
* additionally checks for attached synth enablement.
*/
if (!zl3073x_dev_out_is_enabled(zldev, out_id))
return false;
/* Check signal format */
switch (zl3073x_out_signal_format_get(out)) {
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED:
/* Both output pins are disabled by signal format */
return false;
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P:
/* Output is one single ended P-pin output */
if (zl3073x_is_n_pin(id))
return false;
break;
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N:
/* Output is one single ended N-pin output */
if (zl3073x_is_p_pin(id))
return false;
break;
default:
/* For other format both pins are enabled */
break;
}
return true;
}
#endif /* _ZL3073X_CORE_H */
+390
View File
@@ -0,0 +1,390 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/device/devres.h>
#include <linux/netlink.h>
#include <linux/sprintf.h>
#include <linux/types.h>
#include <net/devlink.h>
#include "core.h"
#include "devlink.h"
#include "dpll.h"
#include "flash.h"
#include "fw.h"
#include "regs.h"
/**
* zl3073x_devlink_info_get - Devlink device info callback
* @devlink: devlink structure pointer
* @req: devlink request pointer to store information
* @extack: netlink extack pointer to report errors
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct zl3073x_dev *zldev = devlink_priv(devlink);
u16 id, revision, fw_ver;
char buf[16];
u32 cfg_ver;
int rc;
rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id);
if (rc)
return rc;
snprintf(buf, sizeof(buf), "%X", id);
rc = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
buf);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
if (rc)
return rc;
snprintf(buf, sizeof(buf), "%X", revision);
rc = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,
buf);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver);
if (rc)
return rc;
snprintf(buf, sizeof(buf), "%u", fw_ver);
rc = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW,
buf);
if (rc)
return rc;
rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver);
if (rc)
return rc;
/* No custom config version */
if (cfg_ver == U32_MAX)
return 0;
snprintf(buf, sizeof(buf), "%lu.%lu.%lu.%lu",
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));
return devlink_info_version_running_put(req, "custom_cfg", buf);
}
static int
zl3073x_devlink_reload_down(struct devlink *devlink, bool netns_change,
enum devlink_reload_action action,
enum devlink_reload_limit limit,
struct netlink_ext_ack *extack)
{
struct zl3073x_dev *zldev = devlink_priv(devlink);
if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT)
return -EOPNOTSUPP;
/* Stop normal operation */
zl3073x_dev_stop(zldev);
return 0;
}
static int
zl3073x_devlink_reload_up(struct devlink *devlink,
enum devlink_reload_action action,
enum devlink_reload_limit limit,
u32 *actions_performed,
struct netlink_ext_ack *extack)
{
struct zl3073x_dev *zldev = devlink_priv(devlink);
union devlink_param_value val;
int rc;
if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT)
return -EOPNOTSUPP;
rc = devl_param_driverinit_value_get(devlink,
DEVLINK_PARAM_GENERIC_ID_CLOCK_ID,
&val);
if (rc)
return rc;
if (zldev->clock_id != val.vu64) {
dev_dbg(zldev->dev,
"'clock_id' changed to %016llx\n", val.vu64);
zldev->clock_id = val.vu64;
}
/* Restart normal operation */
rc = zl3073x_dev_start(zldev, false);
if (rc)
dev_warn(zldev->dev, "Failed to re-start normal operation\n");
*actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT);
return 0;
}
void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg,
const char *component, u32 done, u32 total)
{
struct devlink *devlink = priv_to_devlink(zldev);
devlink_flash_update_status_notify(devlink, msg, component, done,
total);
}
/**
* zl3073x_devlink_flash_prepare - Prepare and enter flash mode
* @zldev: zl3073x device pointer
* @zlfw: pointer to loaded firmware
* @extack: netlink extack pointer to report errors
*
* The function stops normal operation and switches the device to flash mode.
* If an error occurs the normal operation is resumed.
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_devlink_flash_prepare(struct zl3073x_dev *zldev,
struct zl3073x_fw *zlfw,
struct netlink_ext_ack *extack)
{
struct zl3073x_fw_component *util;
int rc;
util = zlfw->component[ZL_FW_COMPONENT_UTIL];
if (!util) {
zl3073x_devlink_flash_notify(zldev,
"Utility is missing in firmware",
NULL, 0, 0);
return -ENOEXEC;
}
/* Stop normal operation prior entering flash mode */
zl3073x_dev_stop(zldev);
rc = zl3073x_flash_mode_enter(zldev, util->data, util->size, extack);
if (rc) {
zl3073x_devlink_flash_notify(zldev,
"Failed to enter flash mode",
NULL, 0, 0);
/* Resume normal operation */
zl3073x_dev_start(zldev, true);
return rc;
}
return 0;
}
/**
* zl3073x_devlink_flash_finish - Leave flash mode and resume normal operation
* @zldev: zl3073x device pointer
* @extack: netlink extack pointer to report errors
*
* The function switches the device back to standard mode and resumes normal
* operation.
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack)
{
int rc;
/* Reset device CPU to normal mode */
zl3073x_flash_mode_leave(zldev, extack);
/* Resume normal operation */
rc = zl3073x_dev_start(zldev, true);
if (rc)
zl3073x_devlink_flash_notify(zldev,
"Failed to start normal operation",
NULL, 0, 0);
return rc;
}
/**
* zl3073x_devlink_flash_update - Devlink flash update callback
* @devlink: devlink structure pointer
* @params: flashing parameters pointer
* @extack: netlink extack pointer to report errors
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_devlink_flash_update(struct devlink *devlink,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
{
struct zl3073x_dev *zldev = devlink_priv(devlink);
struct zl3073x_fw *zlfw;
int rc = 0;
zlfw = zl3073x_fw_load(zldev, params->fw->data, params->fw->size,
extack);
if (IS_ERR(zlfw)) {
zl3073x_devlink_flash_notify(zldev, "Failed to load firmware",
NULL, 0, 0);
rc = PTR_ERR(zlfw);
goto finish;
}
/* Stop normal operation and enter flash mode */
rc = zl3073x_devlink_flash_prepare(zldev, zlfw, extack);
if (rc)
goto finish;
rc = zl3073x_fw_flash(zldev, zlfw, extack);
if (rc) {
zl3073x_devlink_flash_finish(zldev, extack);
goto finish;
}
/* Resume normal mode */
rc = zl3073x_devlink_flash_finish(zldev, extack);
finish:
if (!IS_ERR(zlfw))
zl3073x_fw_free(zlfw);
zl3073x_devlink_flash_notify(zldev,
rc ? "Flashing failed" : "Flashing done",
NULL, 0, 0);
return rc;
}
static const struct devlink_ops zl3073x_devlink_ops = {
.info_get = zl3073x_devlink_info_get,
.reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT),
.reload_down = zl3073x_devlink_reload_down,
.reload_up = zl3073x_devlink_reload_up,
.flash_update = zl3073x_devlink_flash_update,
};
static void
zl3073x_devlink_free(void *ptr)
{
devlink_free(ptr);
}
/**
* zl3073x_devm_alloc - allocates zl3073x device structure
* @dev: pointer to device structure
*
* Allocates zl3073x device structure as device resource.
*
* Return: pointer to zl3073x device on success, error pointer on error
*/
struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev)
{
struct zl3073x_dev *zldev;
struct devlink *devlink;
int rc;
devlink = devlink_alloc(&zl3073x_devlink_ops, sizeof(*zldev), dev);
if (!devlink)
return ERR_PTR(-ENOMEM);
/* Add devres action to free devlink device */
rc = devm_add_action_or_reset(dev, zl3073x_devlink_free, devlink);
if (rc)
return ERR_PTR(rc);
zldev = devlink_priv(devlink);
zldev->dev = dev;
dev_set_drvdata(zldev->dev, zldev);
return zldev;
}
EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, "ZL3073X");
static int
zl3073x_devlink_param_clock_id_validate(struct devlink *devlink, u32 id,
union devlink_param_value *val,
struct netlink_ext_ack *extack)
{
if (!val->vu64) {
NL_SET_ERR_MSG_MOD(extack, "'clock_id' must be non-zero");
return -EINVAL;
}
return 0;
}
static const struct devlink_param zl3073x_devlink_params[] = {
DEVLINK_PARAM_GENERIC(CLOCK_ID, BIT(DEVLINK_PARAM_CMODE_DRIVERINIT),
NULL, NULL,
zl3073x_devlink_param_clock_id_validate),
};
static void
zl3073x_devlink_unregister(void *ptr)
{
struct devlink *devlink = priv_to_devlink(ptr);
devl_lock(devlink);
/* Unregister devlink params */
devl_params_unregister(devlink, zl3073x_devlink_params,
ARRAY_SIZE(zl3073x_devlink_params));
/* Unregister devlink instance */
devl_unregister(devlink);
devl_unlock(devlink);
}
/**
* zl3073x_devlink_register - register devlink instance and params
* @zldev: zl3073x device to register the devlink for
*
* Register the devlink instance and parameters associated with the device.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_devlink_register(struct zl3073x_dev *zldev)
{
struct devlink *devlink = priv_to_devlink(zldev);
union devlink_param_value value;
int rc;
devl_lock(devlink);
/* Register devlink params */
rc = devl_params_register(devlink, zl3073x_devlink_params,
ARRAY_SIZE(zl3073x_devlink_params));
if (rc) {
devl_unlock(devlink);
return rc;
}
value.vu64 = zldev->clock_id;
devl_param_driverinit_value_set(devlink,
DEVLINK_PARAM_GENERIC_ID_CLOCK_ID,
&value);
/* Register devlink instance */
devl_register(devlink);
devl_unlock(devlink);
/* Add devres action to unregister devlink device */
return devm_add_action_or_reset(zldev->dev, zl3073x_devlink_unregister,
zldev);
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_DEVLINK_H
#define _ZL3073X_DEVLINK_H
struct zl3073x_dev;
struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);
int zl3073x_devlink_register(struct zl3073x_dev *zldev);
void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg,
const char *component, u32 done, u32 total);
#endif /* _ZL3073X_DEVLINK_H */
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_DPLL_H
#define _ZL3073X_DPLL_H
#include <linux/dpll.h>
#include <linux/list.h>
#include "core.h"
/**
* struct zl3073x_dpll - ZL3073x DPLL sub-device structure
* @list: this DPLL list entry
* @dev: pointer to multi-function parent device
* @id: DPLL index
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
* @lock_status: last saved DPLL lock status
* @pins: list of pins
*/
struct zl3073x_dpll {
struct list_head list;
struct zl3073x_dev *dev;
u8 id;
u8 check_count;
bool phase_monitor;
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
enum dpll_lock_status lock_status;
struct list_head pins;
};
struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);
void zl3073x_dpll_free(struct zl3073x_dpll *zldpll);
int zl3073x_dpll_register(struct zl3073x_dpll *zldpll);
void zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll);
int zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev);
void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll);
#endif /* _ZL3073X_DPLL_H */
+665
View File
@@ -0,0 +1,665 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/minmax.h>
#include <linux/netlink.h>
#include <linux/sched/signal.h>
#include <linux/sizes.h>
#include <linux/sprintf.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <net/devlink.h>
#include "core.h"
#include "devlink.h"
#include "flash.h"
#define ZL_FLASH_ERR_PFX "FW update failed: "
#define ZL_FLASH_ERR_MSG(_extack, _msg, ...) \
NL_SET_ERR_MSG_FMT_MOD((_extack), ZL_FLASH_ERR_PFX _msg, \
## __VA_ARGS__)
/**
* zl3073x_flash_download - Download image block to device memory
* @zldev: zl3073x device structure
* @component: name of the component to be downloaded
* @addr: device memory target address
* @data: pointer to data to download
* @size: size of data to download
* @extack: netlink extack pointer to report errors
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_flash_download(struct zl3073x_dev *zldev, const char *component,
u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
#define ZL_CHECK_DELAY 5000 /* Check for interrupt each 5 seconds */
unsigned long check_time;
const void *ptr, *end;
int rc = 0;
dev_dbg(zldev->dev, "Downloading %zu bytes to device memory at 0x%0x\n",
size, addr);
check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY);
for (ptr = data, end = data + size; ptr < end; ptr += 4, addr += 4) {
/* Write current word to HW memory */
rc = zl3073x_write_hwreg(zldev, addr,
get_unaligned((u32 *)ptr));
if (rc) {
ZL_FLASH_ERR_MSG(extack,
"failed to write to memory at 0x%0x",
addr);
return rc;
}
if (time_is_before_jiffies(check_time)) {
if (signal_pending(current)) {
ZL_FLASH_ERR_MSG(extack,
"Flashing interrupted");
return -EINTR;
}
check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY);
}
/* Report status each 1 kB block */
if ((ptr - data) % 1024 == 0)
zl3073x_devlink_flash_notify(zldev, "Downloading image",
component, ptr - data,
size);
}
zl3073x_devlink_flash_notify(zldev, "Downloading image", component,
ptr - data, size);
dev_dbg(zldev->dev, "%zu bytes downloaded to device memory\n", size);
return rc;
}
/**
* zl3073x_flash_error_check - Check for flash utility errors
* @zldev: zl3073x device structure
* @extack: netlink extack pointer to report errors
*
* The function checks for errors detected by the flash utility and
* reports them if any were found.
*
* Return: 0 on success, -EIO when errors are detected
*/
static int
zl3073x_flash_error_check(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack)
{
u32 count, cause;
int rc;
rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_COUNT, &count);
if (rc)
return rc;
else if (!count)
return 0; /* No error */
rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause);
if (rc)
return rc;
/* Report errors */
ZL_FLASH_ERR_MSG(extack,
"utility error occurred: count=%u cause=0x%x", count,
cause);
return -EIO;
}
/**
* zl3073x_flash_wait_ready - Check or wait for utility to be ready to flash
* @zldev: zl3073x device structure
* @timeout_ms: timeout for the waiting
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_flash_wait_ready(struct zl3073x_dev *zldev, unsigned int timeout_ms)
{
#define ZL_FLASH_POLL_DELAY_MS 100
unsigned long timeout;
int rc, i;
dev_dbg(zldev->dev, "Waiting for flashing to be ready\n");
timeout = jiffies + msecs_to_jiffies(timeout_ms);
for (i = 0; time_is_after_jiffies(timeout); i++) {
u8 value;
/* Check for interrupt each 1s */
if (i > 9) {
if (signal_pending(current))
return -EINTR;
i = 0;
}
rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value);
if (rc)
return rc;
value = FIELD_GET(ZL_WRITE_FLASH_OP, value);
if (value == ZL_WRITE_FLASH_OP_DONE)
return 0; /* Successfully done */
msleep(ZL_FLASH_POLL_DELAY_MS);
}
return -ETIMEDOUT;
}
/**
* zl3073x_flash_cmd_wait - Perform flash operation and wait for finish
* @zldev: zl3073x device structure
* @operation: operation to perform
* @extack: netlink extack pointer to report errors
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation,
struct netlink_ext_ack *extack)
{
#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000 /* up to 1 minute */
#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000 /* up to 2 minutes */
u8 value;
int rc;
dev_dbg(zldev->dev, "Sending flash command: 0x%x\n", operation);
rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE1_TIMEOUT_MS);
if (rc)
return rc;
/* Issue the requested operation */
rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value);
if (rc)
return rc;
FIELD_MODIFY(ZL_WRITE_FLASH_OP, &value, operation);
rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value);
if (rc)
return rc;
/* Wait for command completion */
rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS);
if (rc)
return rc;
return zl3073x_flash_error_check(zldev, extack);
}
/**
* zl3073x_flash_get_sector_size - Get flash sector size
* @zldev: zl3073x device structure
* @sector_size: sector size returned by the function
*
* The function reads the flash sector size detected by flash utility and
* stores it into @sector_size.
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_flash_get_sector_size(struct zl3073x_dev *zldev, size_t *sector_size)
{
u8 flash_info;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_INFO, &flash_info);
if (rc)
return rc;
switch (FIELD_GET(ZL_FLASH_INFO_SECTOR_SIZE, flash_info)) {
case ZL_FLASH_INFO_SECTOR_4K:
*sector_size = SZ_4K;
break;
case ZL_FLASH_INFO_SECTOR_64K:
*sector_size = SZ_64K;
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
/**
* zl3073x_flash_block - Download and flash memory block
* @zldev: zl3073x device structure
* @component: component name
* @operation: flash operation to perform
* @page: destination flash page
* @addr: device memory address to load data
* @data: pointer to data to be flashed
* @size: size of data
* @extack: netlink extack pointer to report errors
*
* The function downloads the memory block given by the @data pointer and
* the size @size and flashes it into internal memory on flash page @page.
* The internal flash operation performed by the firmware is specified by
* the @operation parameter.
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_flash_block(struct zl3073x_dev *zldev, const char *component,
u32 operation, u32 page, u32 addr, const void *data,
size_t size, struct netlink_ext_ack *extack)
{
int rc;
/* Download block to device memory */
rc = zl3073x_flash_download(zldev, component, addr, data, size, extack);
if (rc)
return rc;
/* Set address to flash from */
rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr);
if (rc)
return rc;
/* Set size of block to flash */
rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size);
if (rc)
return rc;
/* Set destination page to flash */
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page);
if (rc)
return rc;
/* Set filling pattern */
rc = zl3073x_write_u32(zldev, ZL_REG_FILL_PATTERN, U32_MAX);
if (rc)
return rc;
zl3073x_devlink_flash_notify(zldev, "Flashing image", component, 0,
size);
dev_dbg(zldev->dev, "Flashing %zu bytes to page %u\n", size, page);
/* Execute sectors flash operation */
rc = zl3073x_flash_cmd_wait(zldev, operation, extack);
if (rc)
return rc;
zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size,
size);
return 0;
}
/**
* zl3073x_flash_sectors - Flash sectors
* @zldev: zl3073x device structure
* @component: component name
* @page: destination flash page
* @addr: device memory address to load data
* @data: pointer to data to be flashed
* @size: size of data
* @extack: netlink extack pointer to report errors
*
* The function flashes given @data with size of @size to the internal flash
* memory block starting from page @page. The function uses sector flash
* method and has to take into account the flash sector size reported by
* flashing utility. Input data are spliced into blocks according this
* sector size and each block is flashed separately.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
#define ZL_FLASH_MAX_BLOCK_SIZE 0x0001E000
#define ZL_FLASH_PAGE_SIZE 256
size_t max_block_size, block_size, sector_size;
const void *ptr, *end;
int rc;
/* Get flash sector size */
rc = zl3073x_flash_get_sector_size(zldev, &sector_size);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size");
return rc;
}
/* Determine max block size depending on sector size */
max_block_size = ALIGN_DOWN(ZL_FLASH_MAX_BLOCK_SIZE, sector_size);
for (ptr = data, end = data + size; ptr < end; ptr += block_size) {
char comp_str[32];
block_size = min_t(size_t, max_block_size, end - ptr);
/* Add suffix '-partN' if the requested component size is
* greater than max_block_size.
*/
if (max_block_size < size)
snprintf(comp_str, sizeof(comp_str), "%s-part%zu",
component, (ptr - data) / max_block_size + 1);
else
strscpy(comp_str, component);
/* Flash the memory block */
rc = zl3073x_flash_block(zldev, comp_str,
ZL_WRITE_FLASH_OP_SECTORS, page, addr,
ptr, block_size, extack);
if (rc)
goto finish;
/* Move to next page */
page += block_size / ZL_FLASH_PAGE_SIZE;
}
finish:
zl3073x_devlink_flash_notify(zldev,
rc ? "Flashing failed" : "Flashing done",
component, 0, 0);
return rc;
}
/**
* zl3073x_flash_page - Flash page
* @zldev: zl3073x device structure
* @component: component name
* @page: destination flash page
* @addr: device memory address to load data
* @data: pointer to data to be flashed
* @size: size of data
* @extack: netlink extack pointer to report errors
*
* The function flashes given @data with size of @size to the internal flash
* memory block starting with page @page.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
int rc;
/* Flash the memory block */
rc = zl3073x_flash_block(zldev, component, ZL_WRITE_FLASH_OP_PAGE, page,
addr, data, size, extack);
zl3073x_devlink_flash_notify(zldev,
rc ? "Flashing failed" : "Flashing done",
component, 0, 0);
return rc;
}
/**
* zl3073x_flash_page_copy - Copy flash page
* @zldev: zl3073x device structure
* @component: component name
* @src_page: source page to copy
* @dst_page: destination page
* @extack: netlink extack pointer to report errors
*
* The function copies one flash page specified by @src_page into the flash
* page specified by @dst_page.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component,
u32 src_page, u32 dst_page,
struct netlink_ext_ack *extack)
{
int rc;
/* Set source page to be copied */
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page);
if (rc)
return rc;
/* Set destination page for the copy */
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page);
if (rc)
return rc;
/* Perform copy operation */
rc = zl3073x_flash_cmd_wait(zldev, ZL_WRITE_FLASH_OP_COPY_PAGE, extack);
if (rc)
ZL_FLASH_ERR_MSG(extack, "Failed to copy page %u to page %u",
src_page, dst_page);
return rc;
}
/**
* zl3073x_flash_mode_verify - Check flash utility
* @zldev: zl3073x device structure
*
* Return: 0 if the flash utility is ready, <0 on error
*/
static int
zl3073x_flash_mode_verify(struct zl3073x_dev *zldev)
{
u8 family, release;
u32 hash;
int rc;
rc = zl3073x_read_u32(zldev, ZL_REG_FLASH_HASH, &hash);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_FAMILY, &family);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_RELEASE, &release);
if (rc)
return rc;
dev_dbg(zldev->dev,
"Flash utility check: hash 0x%08x, fam 0x%02x, rel 0x%02x\n",
hash, family, release);
/* Return success for correct family */
return (family == 0x21) ? 0 : -ENODEV;
}
static int
zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev)
{
u8 host_ctrl;
int rc;
/* Enable host control */
rc = zl3073x_read_u8(zldev, ZL_REG_HOST_CONTROL, &host_ctrl);
if (rc)
return rc;
host_ctrl |= ZL_HOST_CONTROL_ENABLE;
return zl3073x_write_u8(zldev, ZL_REG_HOST_CONTROL, host_ctrl);
}
/**
* zl3073x_flash_mode_enter - Switch the device to flash mode
* @zldev: zl3073x device structure
* @util_ptr: buffer with flash utility
* @util_size: size of buffer with flash utility
* @extack: netlink extack pointer to report errors
*
* The function prepares and switches the device into flash mode.
*
* The procedure:
* 1) Stop device CPU by specific HW register sequence
* 2) Download flash utility to device memory
* 3) Resume device CPU by specific HW register sequence
* 4) Check communication with flash utility
* 5) Enable host control necessary to access flash API
* 6) Check for potential error detected by the utility
*
* The API provided by normal firmware is not available in flash mode
* so the caller has to ensure that this API is not used in this mode.
*
* After performing flash operation the caller should call
* @zl3073x_flash_mode_leave to return back to normal operation.
*
* Return: 0 on success, <0 on error.
*/
int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr,
size_t util_size, struct netlink_ext_ack *extack)
{
/* Sequence to be written prior utility download */
static const struct zl3073x_hwreg_seq_item pre_seq[] = {
HWREG_SEQ_ITEM(0x80000400, 1, BIT(0), 0),
HWREG_SEQ_ITEM(0x80206340, 1, BIT(4), 0),
HWREG_SEQ_ITEM(0x10000000, 1, BIT(2), 0),
HWREG_SEQ_ITEM(0x10000024, 0x00000001, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000020, 0x00000001, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 1, BIT(10), 1000),
};
/* Sequence to be written after utility download */
static const struct zl3073x_hwreg_seq_item post_seq[] = {
HWREG_SEQ_ITEM(0x10400004, 0x000000C0, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400008, 0x00000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400010, 0x20000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400014, 0x20000004, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 1, GENMASK(10, 9), 0),
HWREG_SEQ_ITEM(0x10000020, 0x00000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 0, BIT(0), 1000),
};
int rc;
zl3073x_devlink_flash_notify(zldev, "Prepare flash mode", "utility",
0, 0);
/* Execure pre-load sequence */
rc = zl3073x_write_hwreg_seq(zldev, pre_seq, ARRAY_SIZE(pre_seq));
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot execute pre-load sequence");
goto error;
}
/* Download utility image to device memory */
rc = zl3073x_flash_download(zldev, "utility", 0x20000000, util_ptr,
util_size, extack);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot download flash utility");
goto error;
}
/* Execute post-load sequence */
rc = zl3073x_write_hwreg_seq(zldev, post_seq, ARRAY_SIZE(post_seq));
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot execute post-load sequence");
goto error;
}
/* Check that utility identifies itself correctly */
rc = zl3073x_flash_mode_verify(zldev);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "flash utility check failed");
goto error;
}
/* Enable host control */
rc = zl3073x_flash_host_ctrl_enable(zldev);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot enable host control");
goto error;
}
zl3073x_devlink_flash_notify(zldev, "Flash mode enabled", "utility",
0, 0);
return 0;
error:
zl3073x_flash_mode_leave(zldev, extack);
return rc;
}
/**
* zl3073x_flash_mode_leave - Leave flash mode
* @zldev: zl3073x device structure
* @extack: netlink extack pointer to report errors
*
* The function instructs the device to leave the flash mode and
* to return back to normal operation.
*
* The procedure:
* 1) Set reset flag
* 2) Reset the device CPU by specific HW register sequence
* 3) Wait for the device to be ready
* 4) Check the reset flag was cleared
*
* Return: 0 on success, <0 on error
*/
int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack)
{
/* Sequence to be written after flash */
static const struct zl3073x_hwreg_seq_item fw_reset_seq[] = {
HWREG_SEQ_ITEM(0x80000404, 1, BIT(0), 0),
HWREG_SEQ_ITEM(0x80000410, 1, BIT(0), 0),
};
u8 reset_status;
int rc;
zl3073x_devlink_flash_notify(zldev, "Leaving flash mode", "utility",
0, 0);
/* Read reset status register */
rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status);
if (rc)
return rc;
/* Set reset bit */
reset_status |= ZL_REG_RESET_STATUS_RESET;
/* Update reset status register */
rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status);
if (rc)
return rc;
/* We do not check the return value here as the sequence resets
* the device CPU and the last write always return an error.
*/
zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq));
/* Wait for the device to be ready */
msleep(500);
/* Read again the reset status register */
rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status);
if (rc)
return rc;
/* Check the reset bit was cleared */
if (reset_status & ZL_REG_RESET_STATUS_RESET) {
dev_err(zldev->dev,
"Reset not confirmed after switch to normal mode\n");
return -EINVAL;
}
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef __ZL3073X_FLASH_H
#define __ZL3073X_FLASH_H
#include <linux/types.h>
struct netlink_ext_ack;
struct zl3073x_dev;
int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr,
size_t util_size, struct netlink_ext_ack *extack);
int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack);
int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack);
int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component,
u32 src_page, u32 dst_page,
struct netlink_ext_ack *extack);
int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack);
#endif /* __ZL3073X_FLASH_H */
+419
View File
@@ -0,0 +1,419 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/array_size.h>
#include <linux/build_bug.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/netlink.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "core.h"
#include "flash.h"
#include "fw.h"
#define ZL3073X_FW_ERR_PFX "FW load failed: "
#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...) \
NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg, \
## __VA_ARGS__)
enum zl3073x_flash_type {
ZL3073X_FLASH_TYPE_NONE = 0,
ZL3073X_FLASH_TYPE_SECTORS,
ZL3073X_FLASH_TYPE_PAGE,
ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
};
struct zl3073x_fw_component_info {
const char *name;
size_t max_size;
enum zl3073x_flash_type flash_type;
u32 load_addr;
u32 dest_page;
u32 copy_page;
};
static const struct zl3073x_fw_component_info component_info[] = {
[ZL_FW_COMPONENT_UTIL] = {
.name = "utility",
.max_size = 0x4000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_NONE,
},
[ZL_FW_COMPONENT_FW1] = {
.name = "firmware1",
.max_size = 0x35000,
.load_addr = 0x20002000,
.flash_type = ZL3073X_FLASH_TYPE_SECTORS,
.dest_page = 0x020,
},
[ZL_FW_COMPONENT_FW2] = {
.name = "firmware2",
.max_size = 0x0040,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
.dest_page = 0x3e0,
.copy_page = 0x000,
},
[ZL_FW_COMPONENT_FW3] = {
.name = "firmware3",
.max_size = 0x0248,
.load_addr = 0x20000400,
.flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
.dest_page = 0x3e4,
.copy_page = 0x004,
},
[ZL_FW_COMPONENT_CFG0] = {
.name = "config0",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x3d0,
},
[ZL_FW_COMPONENT_CFG1] = {
.name = "config1",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x3c0,
},
[ZL_FW_COMPONENT_CFG2] = {
.name = "config2",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x3b0,
},
[ZL_FW_COMPONENT_CFG3] = {
.name = "config3",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x3a0,
},
[ZL_FW_COMPONENT_CFG4] = {
.name = "config4",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x390,
},
[ZL_FW_COMPONENT_CFG5] = {
.name = "config5",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x380,
},
[ZL_FW_COMPONENT_CFG6] = {
.name = "config6",
.max_size = 0x1000,
.load_addr = 0x20000000,
.flash_type = ZL3073X_FLASH_TYPE_PAGE,
.dest_page = 0x370,
},
};
/* Sanity check */
static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS);
/**
* zl3073x_fw_component_alloc - Alloc structure to hold firmware component
* @size: size of buffer to store data
*
* Return: pointer to allocated component structure or NULL on error.
*/
static struct zl3073x_fw_component *
zl3073x_fw_component_alloc(size_t size)
{
struct zl3073x_fw_component *comp;
comp = kzalloc_obj(*comp);
if (!comp)
return NULL;
comp->size = size;
comp->data = kzalloc(size, GFP_KERNEL);
if (!comp->data) {
kfree(comp);
return NULL;
}
return comp;
}
/**
* zl3073x_fw_component_free - Free allocated component structure
* @comp: pointer to allocated component
*/
static void
zl3073x_fw_component_free(struct zl3073x_fw_component *comp)
{
if (comp)
kfree(comp->data);
kfree(comp);
}
/**
* zl3073x_fw_component_id_get - Get ID for firmware component name
* @name: input firmware component name
*
* Return:
* - ZL3073X_FW_COMPONENT_* ID for known component name
* - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown
*/
static enum zl3073x_fw_component_id
zl3073x_fw_component_id_get(const char *name)
{
enum zl3073x_fw_component_id id;
for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++)
if (!strcasecmp(name, component_info[id].name))
return id;
return ZL_FW_COMPONENT_INVALID;
}
/**
* zl3073x_fw_component_load - Load component from firmware source
* @zldev: zl3073x device structure
* @pcomp: pointer to loaded component
* @psrc: data pointer to load component from
* @psize: remaining bytes in buffer
* @extack: netlink extack pointer to report errors
*
* The function allocates single firmware component and loads the data from
* the buffer specified by @psrc and @psize. Pointer to allocated component
* is stored in output @pcomp. Source data pointer @psrc and remaining bytes
* @psize are updated accordingly.
*
* Return:
* * 1 when component was allocated and loaded
* * 0 when there is no component to load
* * <0 on error
*/
static ssize_t
zl3073x_fw_component_load(struct zl3073x_dev *zldev,
struct zl3073x_fw_component **pcomp,
const char **psrc, size_t *psize,
struct netlink_ext_ack *extack)
{
const struct zl3073x_fw_component_info *info;
struct zl3073x_fw_component *comp = NULL;
struct device *dev = zldev->dev;
enum zl3073x_fw_component_id id;
char buf[32], name[16];
u32 count, size, *dest;
int pos, rc;
/* Fetch image name and size from input */
strscpy(buf, *psrc, min(sizeof(buf), *psize));
rc = sscanf(buf, "%15s %u %n", name, &count, &pos);
if (!rc) {
/* No more data */
return 0;
} else if (rc == 1 || count > U32_MAX / sizeof(u32)) {
ZL3073X_FW_ERR_MSG(extack, "invalid component size");
return -EINVAL;
}
*psrc += pos;
*psize -= pos;
dev_dbg(dev, "Firmware component '%s' found\n", name);
id = zl3073x_fw_component_id_get(name);
if (id == ZL_FW_COMPONENT_INVALID) {
ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name);
return -EINVAL;
}
info = &component_info[id];
size = count * sizeof(u32); /* get size in bytes */
/* Check image size validity */
if (size > component_info[id].max_size) {
ZL3073X_FW_ERR_MSG(extack,
"[%s] component is too big (%u bytes)\n",
info->name, size);
return -EINVAL;
}
dev_dbg(dev, "Indicated component image size: %u bytes\n", size);
/* Alloc component */
comp = zl3073x_fw_component_alloc(size);
if (!comp) {
ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory");
return -ENOMEM;
}
comp->id = id;
/* Load component data from firmware source */
for (dest = comp->data; count; count--, dest++) {
strscpy(buf, *psrc, min(sizeof(buf), *psize));
rc = sscanf(buf, "%x %n", dest, &pos);
if (!rc)
goto err_data;
*psrc += pos;
*psize -= pos;
}
*pcomp = comp;
return 1;
err_data:
ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name);
zl3073x_fw_component_free(comp);
return -ENODATA;
}
/**
* zl3073x_fw_free - Free allocated firmware
* @fw: firmware pointer
*
* The function frees existing firmware allocated by @zl3073x_fw_load.
*/
void zl3073x_fw_free(struct zl3073x_fw *fw)
{
size_t i;
if (!fw)
return;
for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++)
zl3073x_fw_component_free(fw->component[i]);
kfree(fw);
}
/**
* zl3073x_fw_load - Load all components from source
* @zldev: zl3073x device structure
* @data: source buffer pointer
* @size: size of source buffer
* @extack: netlink extack pointer to report errors
*
* The functions allocate firmware structure and loads all components from
* the given buffer specified by @data and @size.
*
* Return: pointer to firmware on success, error pointer on error
*/
struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data,
size_t size, struct netlink_ext_ack *extack)
{
struct zl3073x_fw_component *comp;
enum zl3073x_fw_component_id id;
struct zl3073x_fw *fw;
ssize_t rc;
/* Allocate firmware structure */
fw = kzalloc_obj(*fw);
if (!fw)
return ERR_PTR(-ENOMEM);
do {
/* Load single component */
rc = zl3073x_fw_component_load(zldev, &comp, &data, &size,
extack);
if (rc <= 0)
/* Everything was read or error occurred */
break;
id = comp->id;
/* Report error if the given component is present twice
* or more.
*/
if (fw->component[id]) {
ZL3073X_FW_ERR_MSG(extack,
"duplicate component '%s' detected",
component_info[id].name);
zl3073x_fw_component_free(comp);
rc = -EINVAL;
break;
}
fw->component[id] = comp;
} while (true);
if (rc) {
/* Free allocated firmware in case of error */
zl3073x_fw_free(fw);
return ERR_PTR(rc);
}
return fw;
}
/**
* zl3073x_fw_component_flash - Flash all components
* @zldev: zl3073x device structure
* @comp: pointer to components array
* @extack: netlink extack pointer to report errors
*
* Return: 0 in case of success or negative number otherwise.
*/
static int
zl3073x_fw_component_flash(struct zl3073x_dev *zldev,
struct zl3073x_fw_component *comp,
struct netlink_ext_ack *extack)
{
const struct zl3073x_fw_component_info *info;
int rc;
info = &component_info[comp->id];
switch (info->flash_type) {
case ZL3073X_FLASH_TYPE_NONE:
/* Non-flashable component - used for utility */
return 0;
case ZL3073X_FLASH_TYPE_SECTORS:
rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page,
info->load_addr, comp->data,
comp->size, extack);
break;
case ZL3073X_FLASH_TYPE_PAGE:
rc = zl3073x_flash_page(zldev, info->name, info->dest_page,
info->load_addr, comp->data, comp->size,
extack);
break;
case ZL3073X_FLASH_TYPE_PAGE_AND_COPY:
rc = zl3073x_flash_page(zldev, info->name, info->dest_page,
info->load_addr, comp->data, comp->size,
extack);
if (!rc)
rc = zl3073x_flash_page_copy(zldev, info->name,
info->dest_page,
info->copy_page, extack);
break;
}
if (rc)
ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'",
info->name);
return rc;
}
int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw,
struct netlink_ext_ack *extack)
{
int i, rc = 0;
for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) {
if (!zlfw->component[i])
continue; /* Component is not present */
rc = zl3073x_fw_component_flash(zldev, zlfw->component[i],
extack);
if (rc)
break;
}
return rc;
}
+52
View File
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_FW_H
#define _ZL3073X_FW_H
/*
* enum zl3073x_fw_component_id - Identifiers for possible flash components
*/
enum zl3073x_fw_component_id {
ZL_FW_COMPONENT_INVALID = -1,
ZL_FW_COMPONENT_UTIL = 0,
ZL_FW_COMPONENT_FW1,
ZL_FW_COMPONENT_FW2,
ZL_FW_COMPONENT_FW3,
ZL_FW_COMPONENT_CFG0,
ZL_FW_COMPONENT_CFG1,
ZL_FW_COMPONENT_CFG2,
ZL_FW_COMPONENT_CFG3,
ZL_FW_COMPONENT_CFG4,
ZL_FW_COMPONENT_CFG5,
ZL_FW_COMPONENT_CFG6,
ZL_FW_NUM_COMPONENTS
};
/**
* struct zl3073x_fw_component - Firmware component
* @id: Flash component ID
* @size: Size of the buffer
* @data: Pointer to buffer with component data
*/
struct zl3073x_fw_component {
enum zl3073x_fw_component_id id;
size_t size;
void *data;
};
/**
* struct zl3073x_fw - Firmware bundle
* @component: firmware components array
*/
struct zl3073x_fw {
struct zl3073x_fw_component *component[ZL_FW_NUM_COMPONENTS];
};
struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data,
size_t size, struct netlink_ext_ack *extack);
void zl3073x_fw_free(struct zl3073x_fw *fw);
int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw,
struct netlink_ext_ack *extack);
#endif /* _ZL3073X_FW_H */
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include "core.h"
static int zl3073x_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct zl3073x_dev *zldev;
zldev = zl3073x_devm_alloc(dev);
if (IS_ERR(zldev))
return PTR_ERR(zldev);
zldev->regmap = devm_regmap_init_i2c(client, &zl3073x_regmap_config);
if (IS_ERR(zldev->regmap))
return dev_err_probe(dev, PTR_ERR(zldev->regmap),
"Failed to initialize regmap\n");
return zl3073x_dev_probe(zldev);
}
static const struct i2c_device_id zl3073x_i2c_id[] = {
{ .name = "zl30731" },
{ .name = "zl30732" },
{ .name = "zl30733" },
{ .name = "zl30734" },
{ .name = "zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id);
static const struct of_device_id zl3073x_i2c_of_match[] = {
{ .compatible = "microchip,zl30731" },
{ .compatible = "microchip,zl30732" },
{ .compatible = "microchip,zl30733" },
{ .compatible = "microchip,zl30734" },
{ .compatible = "microchip,zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match);
static struct i2c_driver zl3073x_i2c_driver = {
.driver = {
.name = "zl3073x-i2c",
.of_match_table = zl3073x_i2c_of_match,
},
.probe = zl3073x_i2c_probe,
.id_table = zl3073x_i2c_id,
};
module_i2c_driver(zl3073x_i2c_driver);
MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
MODULE_DESCRIPTION("Microchip ZL3073x I2C driver");
MODULE_IMPORT_NS("ZL3073X");
MODULE_LICENSE("GPL");
+172
View File
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/string_choices.h>
#include <linux/types.h>
#include "core.h"
#include "out.h"
/**
* zl3073x_out_state_fetch - fetch output state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: output index to fetch state for
*
* Function fetches state of the given output from hardware and stores it
* for later use.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_out *out = &zldev->out[index];
int rc;
/* Read output configuration */
rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &out->ctrl);
if (rc)
return rc;
dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index,
str_enabled_disabled(zl3073x_out_is_enabled(out)),
zl3073x_out_synth_get(out));
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, &out->mode);
if (rc)
return rc;
dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index,
zl3073x_out_signal_format_get(out));
/* Read output divisor */
rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &out->div);
if (rc)
return rc;
if (!out->div) {
dev_err(zldev->dev, "Zero divisor for OUT%u got from device\n",
index);
return -EINVAL;
}
dev_dbg(zldev->dev, "OUT%u divisor: %u\n", index, out->div);
/* Read output width */
rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_WIDTH, &out->width);
if (rc)
return rc;
rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD,
&out->esync_n_period);
if (rc)
return rc;
if (!out->esync_n_period) {
dev_err(zldev->dev,
"Zero esync divisor for OUT%u got from device\n",
index);
return -EINVAL;
}
rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH,
&out->esync_n_width);
if (rc)
return rc;
rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP,
&out->phase_comp);
if (rc)
return rc;
return rc;
}
/**
* zl3073x_out_state_get - get current output state
* @zldev: pointer to zl3073x_dev structure
* @index: output index to get state for
*
* Return: pointer to given output state
*/
const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
u8 index)
{
return &zldev->out[index];
}
/**
* zl3073x_out_state_set - commit output state changes to hardware
* @zldev: pointer to zl3073x_dev structure
* @index: output index to set state for
* @out: desired output state
*
* Validates that invariant fields have not been modified, skips the HW
* write if the mutable configuration is unchanged, and otherwise writes
* only the changed cfg fields to hardware via the mailbox interface.
*
* Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
*/
int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_out *out)
{
struct zl3073x_out *dout = &zldev->out[index];
int rc;
/* Reject attempts to change invariant fields (set at fetch only) */
if (WARN_ON(memcmp(&dout->inv, &out->inv, sizeof(out->inv))))
return -EINVAL;
/* Skip HW write if configuration hasn't changed */
if (!memcmp(&dout->cfg, &out->cfg, sizeof(out->cfg)))
return 0;
guard(mutex)(&zldev->multiop_lock);
/* Read output configuration into mailbox */
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;
/* Update mailbox with changed values */
if (dout->div != out->div)
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, out->div);
if (!rc && dout->width != out->width)
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, out->width);
if (!rc && dout->esync_n_period != out->esync_n_period)
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD,
out->esync_n_period);
if (!rc && dout->esync_n_width != out->esync_n_width)
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH,
out->esync_n_width);
if (!rc && dout->mode != out->mode)
rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, out->mode);
if (!rc && dout->phase_comp != out->phase_comp)
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP,
out->phase_comp);
if (rc)
return rc;
/* Commit output configuration */
rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR,
ZL_REG_OUTPUT_MB_MASK, BIT(index));
if (rc)
return rc;
/* After successful commit store new state */
dout->cfg = out->cfg;
return 0;
}
+137
View File
@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_OUT_H
#define _ZL3073X_OUT_H
#include <linux/bitfield.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
struct zl3073x_dev;
/**
* struct zl3073x_out - output state
* @div: output divisor
* @width: output pulse width
* @esync_n_period: embedded sync or n-pin period (for n-div formats)
* @esync_n_width: embedded sync or n-pin pulse width
* @phase_comp: phase compensation
* @mode: output mode
* @ctrl: output control
*/
struct zl3073x_out {
struct_group(cfg, /* Config */
u32 div;
u32 width;
u32 esync_n_period;
u32 esync_n_width;
s32 phase_comp;
u8 mode;
);
struct_group(inv, /* Invariants */
u8 ctrl;
);
};
int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index);
const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
u8 index);
int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_out *out);
/**
* zl3073x_out_clock_type_get - get output clock type
* @out: pointer to out state
*
* Return: clock type of given output (ZL_OUTPUT_MODE_CLOCK_TYPE_*)
*/
static inline u8 zl3073x_out_clock_type_get(const struct zl3073x_out *out)
{
return FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode);
}
/**
* zl3073x_out_clock_type_set - set output clock type
* @out: pointer to out state
* @type: clock type (ZL_OUTPUT_MODE_CLOCK_TYPE_*)
*/
static inline void
zl3073x_out_clock_type_set(struct zl3073x_out *out, u8 type)
{
FIELD_MODIFY(ZL_OUTPUT_MODE_CLOCK_TYPE, &out->mode, type);
}
/**
* zl3073x_out_signal_format_get - get output signal format
* @out: pointer to out state
*
* Return: signal format of given output
*/
static inline u8 zl3073x_out_signal_format_get(const struct zl3073x_out *out)
{
return FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, out->mode);
}
/**
* zl3073x_out_is_diff - check if the given output is differential
* @out: pointer to out state
*
* Return: true if output is differential, false if output is single-ended
*/
static inline bool zl3073x_out_is_diff(const struct zl3073x_out *out)
{
switch (zl3073x_out_signal_format_get(out)) {
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS:
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF:
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM:
return true;
default:
break;
}
return false;
}
/**
* zl3073x_out_is_enabled - check if the given output is enabled
* @out: pointer to out state
*
* Return: true if output is enabled, false if output is disabled
*/
static inline bool zl3073x_out_is_enabled(const struct zl3073x_out *out)
{
return !!FIELD_GET(ZL_OUTPUT_CTRL_EN, out->ctrl);
}
/**
* zl3073x_out_is_ndiv - check if the given output is in N-div mode
* @out: pointer to out state
*
* Return: true if output is in N-div mode, false otherwise
*/
static inline bool zl3073x_out_is_ndiv(const struct zl3073x_out *out)
{
switch (zl3073x_out_signal_format_get(out)) {
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
return true;
default:
return false;
}
}
/**
* zl3073x_out_synth_get - get synth connected to given output
* @out: pointer to out state
*
* Return: index of synth connected to given output.
*/
static inline u8 zl3073x_out_synth_get(const struct zl3073x_out *out)
{
return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, out->ctrl);
}
#endif /* _ZL3073X_OUT_H */
+381
View File
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/array_size.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fwnode.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "core.h"
#include "prop.h"
/**
* zl3073x_pin_check_freq - verify frequency for given pin
* @zldev: pointer to zl3073x device
* @dir: pin direction
* @id: pin index
* @freq: frequency to check
*
* The function checks the given frequency is valid for the device. For input
* pins it checks that the frequency can be factorized using supported base
* frequencies. For output pins it checks that the frequency divides connected
* synth frequency without remainder.
*
* Return: true if the frequency is valid, false if not.
*/
static bool
zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir,
u8 id, u64 freq)
{
if (freq > U32_MAX)
goto err_inv_freq;
if (dir == DPLL_PIN_DIRECTION_INPUT) {
int rc;
/* Check if the frequency can be factorized */
rc = zl3073x_ref_freq_factorize(freq, NULL, NULL);
if (rc)
goto err_inv_freq;
} else {
u32 synth_freq;
u8 out, synth;
/* Get output pin synthesizer */
out = zl3073x_output_pin_out_get(id);
synth = zl3073x_dev_out_synth_get(zldev, out);
/* Get synth frequency */
synth_freq = zl3073x_dev_synth_freq_get(zldev, synth);
/* Check the frequency divides synth frequency */
if (synth_freq % (u32)freq)
goto err_inv_freq;
}
return true;
err_inv_freq:
dev_warn(zldev->dev,
"Unsupported frequency %llu Hz in firmware node\n", freq);
return false;
}
/**
* zl3073x_prop_pin_package_label_set - get package label for the pin
* @zldev: pointer to zl3073x device
* @props: pointer to pin properties
* @dir: pin direction
* @id: pin index
*
* Generates package label string and stores it into pin properties structure.
*
* Possible formats:
* REF<n> - differential input reference
* REF<n>P & REF<n>N - single-ended input reference (P or N pin)
* OUT<n> - differential output
* OUT<n>P & OUT<n>N - single-ended output (P or N pin)
*/
static void
zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev,
struct zl3073x_pin_props *props,
enum dpll_pin_direction dir, u8 id)
{
const char *prefix, *suffix;
bool is_diff;
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref;
prefix = "REF";
ref = zl3073x_input_pin_ref_get(id);
is_diff = zl3073x_dev_ref_is_diff(zldev, ref);
} else {
u8 out;
prefix = "OUT";
out = zl3073x_output_pin_out_get(id);
is_diff = zl3073x_dev_out_is_diff(zldev, out);
}
if (!is_diff)
suffix = zl3073x_is_p_pin(id) ? "P" : "N";
else
suffix = ""; /* No suffix for differential one */
snprintf(props->package_label, sizeof(props->package_label), "%s%u%s",
prefix, id / 2, suffix);
/* Set package_label pointer in DPLL core properties to generated
* string.
*/
props->dpll_props.package_label = props->package_label;
}
/**
* zl3073x_prop_pin_fwnode_get - get fwnode for given pin
* @zldev: pointer to zl3073x device
* @props: pointer to pin properties
* @dir: pin direction
* @id: pin index
*
* Return: 0 on success, -ENOENT if the firmware node does not exist
*/
static int
zl3073x_prop_pin_fwnode_get(struct zl3073x_dev *zldev,
struct zl3073x_pin_props *props,
enum dpll_pin_direction dir, u8 id)
{
struct fwnode_handle *pins_node, *pin_node;
const char *node_name;
if (dir == DPLL_PIN_DIRECTION_INPUT)
node_name = "input-pins";
else
node_name = "output-pins";
/* Get node containing input or output pins */
pins_node = device_get_named_child_node(zldev->dev, node_name);
if (!pins_node) {
dev_dbg(zldev->dev, "'%s' sub-node is missing\n", node_name);
return -ENOENT;
}
/* Enumerate child pin nodes and find the requested one */
fwnode_for_each_child_node(pins_node, pin_node) {
u32 reg;
if (fwnode_property_read_u32(pin_node, "reg", &reg))
continue;
if (id == reg)
break;
}
/* Release pin parent node */
fwnode_handle_put(pins_node);
/* Save found node */
props->fwnode = pin_node;
dev_dbg(zldev->dev, "Firmware node for %s %sfound\n",
props->package_label, pin_node ? "" : "NOT ");
return pin_node ? 0 : -ENOENT;
}
/**
* zl3073x_pin_props_get - get pin properties
* @zldev: pointer to zl3073x device
* @dir: pin direction
* @index: pin index
*
* The function looks for firmware node for the given pin if it is provided
* by the system firmware (DT or ACPI), allocates pin properties structure,
* generates package label string according pin type and optionally fetches
* board label, connection type, supported frequencies and esync capability
* from the firmware node if it does exist.
*
* Pointer that is returned by this function should be freed using
* @zl3073x_pin_props_put().
*
* Return:
* * pointer to allocated pin properties structure on success
* * error pointer in case of error
*/
struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
enum dpll_pin_direction dir,
u8 index)
{
struct dpll_pin_frequency *ranges;
struct zl3073x_pin_props *props;
int i, j, num_freqs = 0, rc;
u64 *freqs = NULL;
const char *type;
u32 curr_freq;
props = kzalloc_obj(*props);
if (!props)
return ERR_PTR(-ENOMEM);
/* Set default pin type and capabilities */
if (dir == DPLL_PIN_DIRECTION_INPUT) {
props->dpll_props.type = DPLL_PIN_TYPE_EXT;
props->dpll_props.capabilities =
DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
curr_freq = zl3073x_dev_ref_freq_get(zldev, index);
} else {
u8 out, synth;
u32 f;
props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
/* The output pin phase adjustment granularity equals half of
* the synth frequency count.
*/
out = zl3073x_output_pin_out_get(index);
synth = zl3073x_dev_out_synth_get(zldev, out);
f = 2 * zl3073x_dev_synth_freq_get(zldev, synth);
props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1;
curr_freq = zl3073x_dev_output_pin_freq_get(zldev, index);
}
props->dpll_props.phase_range.min = S32_MIN;
props->dpll_props.phase_range.max = S32_MAX;
zl3073x_prop_pin_package_label_set(zldev, props, dir, index);
/* Get firmware node for the given pin */
rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index);
if (rc)
goto skip_fwnode_props;
/* Look for label property and store the value as board label */
fwnode_property_read_string(props->fwnode, "label",
&props->dpll_props.board_label);
/* Look for pin type property and translate its value to DPLL
* pin type enum if it is present.
*/
if (!fwnode_property_read_string(props->fwnode, "connection-type",
&type)) {
if (!strcmp(type, "ext"))
props->dpll_props.type = DPLL_PIN_TYPE_EXT;
else if (!strcmp(type, "gnss"))
props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
else if (!strcmp(type, "int"))
props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
else if (!strcmp(type, "synce"))
props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
else if (!strcmp(type, "mux"))
props->dpll_props.type = DPLL_PIN_TYPE_MUX;
else
dev_warn(zldev->dev,
"Unknown or unsupported pin type '%s'\n",
type);
}
/* Check if the pin supports embedded sync control */
props->esync_control = fwnode_property_read_bool(props->fwnode,
"esync-control");
/* Read supported frequencies property if it is specified */
num_freqs = fwnode_property_count_u64(props->fwnode,
"supported-frequencies-hz");
if (num_freqs <= 0) {
num_freqs = 0;
goto skip_fwnode_props;
}
/* The firmware node specifies list of supported frequencies while
* DPLL core pin properties requires list of frequency ranges.
* So read the frequency list into temporary array.
*/
freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL);
if (!freqs) {
rc = -ENOMEM;
goto err_alloc_freqs;
}
/* Read frequencies list from firmware node */
fwnode_property_read_u64_array(props->fwnode,
"supported-frequencies-hz", freqs,
num_freqs);
skip_fwnode_props:
/* Allocate frequency ranges list - extra slot for current frequency */
ranges = kzalloc_objs(*ranges, num_freqs + 1);
if (!ranges) {
rc = -ENOMEM;
goto err_alloc_ranges;
}
/* Start with current frequency at index 0 */
ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq);
/* Add frequencies from firmware node, skipping current frequency
* and filtering out frequencies not representable by device
*/
for (i = 0, j = 1; i < num_freqs; i++) {
struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
if (freqs[i] == curr_freq)
continue;
if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) {
ranges[j] = freq;
j++;
}
}
/* Save number of freq ranges and pointer to them into pin properties */
props->dpll_props.freq_supported = ranges;
props->dpll_props.freq_supported_num = j;
/* Free temporary array */
kfree(freqs);
return props;
err_alloc_ranges:
kfree(freqs);
err_alloc_freqs:
fwnode_handle_put(props->fwnode);
kfree(props);
return ERR_PTR(rc);
}
/**
* zl3073x_pin_props_put - release pin properties
* @props: pin properties to free
*
* The function deallocates given pin properties structure.
*/
void zl3073x_pin_props_put(struct zl3073x_pin_props *props)
{
/* Free supported frequency ranges list if it is present */
kfree(props->dpll_props.freq_supported);
/* Put firmware handle if it is present */
if (props->fwnode)
fwnode_handle_put(props->fwnode);
kfree(props);
}
/**
* zl3073x_prop_dpll_type_get - get DPLL channel type
* @zldev: pointer to zl3073x device
* @index: DPLL channel index
*
* Return: DPLL type for given DPLL channel
*/
enum dpll_type
zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index)
{
const char *types[ZL3073X_MAX_CHANNELS];
int count;
/* Read dpll types property from firmware */
count = device_property_read_string_array(zldev->dev, "dpll-types",
types, ARRAY_SIZE(types));
/* Return default if property or entry for given channel is missing */
if (index >= count)
return DPLL_TYPE_PPS;
if (!strcmp(types[index], "pps"))
return DPLL_TYPE_PPS;
else if (!strcmp(types[index], "eec"))
return DPLL_TYPE_EEC;
dev_info(zldev->dev, "Unknown DPLL type '%s', using default\n",
types[index]);
return DPLL_TYPE_PPS; /* Default */
}
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_PROP_H
#define _ZL3073X_PROP_H
#include <linux/dpll.h>
#include "core.h"
struct fwnode_handle;
/**
* struct zl3073x_pin_props - pin properties
* @fwnode: pin firmware node
* @dpll_props: DPLL core pin properties
* @package_label: pin package label
* @esync_control: embedded sync support
*/
struct zl3073x_pin_props {
struct fwnode_handle *fwnode;
struct dpll_pin_properties dpll_props;
char package_label[8];
bool esync_control;
};
enum dpll_type zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index);
struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
enum dpll_pin_direction dir,
u8 index);
void zl3073x_pin_props_put(struct zl3073x_pin_props *props);
#endif /* _ZL3073X_PROP_H */
+247
View File
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/string_choices.h>
#include <linux/types.h>
#include "core.h"
#include "ref.h"
/**
* zl3073x_ref_freq_factorize - factorize given frequency
* @freq: input frequency
* @base: base frequency
* @mult: multiplier
*
* Checks if the given frequency can be factorized using one of the
* supported base frequencies. If so the base frequency and multiplier
* are stored into appropriate parameters if they are not NULL.
*
* Return: 0 on success, -EINVAL if the frequency cannot be factorized
*/
int
zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult)
{
static const u16 base_freqs[] = {
1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125,
128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000,
1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250,
6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250,
32000, 40000, 50000, 62500,
};
u32 div;
int i;
for (i = 0; i < ARRAY_SIZE(base_freqs); i++) {
div = freq / base_freqs[i];
if (div <= U16_MAX && (freq % base_freqs[i]) == 0) {
if (base)
*base = base_freqs[i];
if (mult)
*mult = div;
return 0;
}
}
return -EINVAL;
}
/**
* zl3073x_ref_state_update - update input reference status from HW
* @zldev: pointer to zl3073x_dev structure
* @index: input reference index
*
* Return: 0 on success, <0 on error
*/
int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_ref *ref = &zldev->ref[index];
return zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(index),
&ref->mon_status);
}
/**
* zl3073x_ref_state_fetch - fetch input reference state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: input reference index to fetch state for
*
* Function fetches state for the given input reference from hardware and
* stores it for later use.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_ref *ref = &zldev->ref[index];
int rc;
/* For differential type inputs the N-pin reference shares
* part of the configuration with the P-pin counterpart.
*/
if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(ref - 1)) {
struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/
/* Copy the shared items from the P-pin */
ref->cfg = p_ref->cfg;
ref->inv = p_ref->inv;
return 0; /* Finish - no non-shared items for now */
}
/* Read reference status */
rc = zl3073x_ref_state_update(zldev, index);
if (rc)
return rc;
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration */
rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
ZL_REG_REF_MB_MASK, BIT(index));
if (rc)
return rc;
/* Read ref_config register */
rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config);
if (rc)
return rc;
/* Read frequency related registers */
rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &ref->freq_base);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &ref->freq_mult);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &ref->freq_ratio_m);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &ref->freq_ratio_n);
if (rc)
return rc;
/* Read eSync and N-div rated registers */
rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &ref->esync_n_div);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref->sync_ctrl);
if (rc)
return rc;
/* Read phase compensation register */
if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) {
u32 val;
rc = zl3073x_read_u32(zldev, ZL_REG_REF_PHASE_OFFSET_COMP_32,
&val);
ref->phase_comp = val;
} else {
rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP,
&ref->phase_comp);
}
if (rc)
return rc;
dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index,
str_enabled_disabled(zl3073x_ref_is_enabled(ref)),
zl3073x_ref_is_diff(ref) ? "differential" : "single-ended");
return rc;
}
/**
* zl3073x_ref_state_get - get current input reference state
* @zldev: pointer to zl3073x_dev structure
* @index: input reference index to get state for
*
* Return: pointer to given input reference state
*/
const struct zl3073x_ref *
zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index)
{
return &zldev->ref[index];
}
/**
* zl3073x_ref_state_set - commit input reference state changes to hardware
* @zldev: pointer to zl3073x_dev structure
* @index: input reference index to set state for
* @ref: desired reference state
*
* Validates that invariant fields have not been modified, skips the HW
* write if the mutable configuration is unchanged, and otherwise writes
* only the changed cfg fields to hardware via the mailbox interface.
*
* Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
*/
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref)
{
struct zl3073x_ref *dref = &zldev->ref[index];
int rc;
/* Reject attempts to change invariant fields (set at init only) */
if (WARN_ON(memcmp(&dref->inv, &ref->inv, sizeof(ref->inv))))
return -EINVAL;
/* Skip HW write if configuration hasn't changed */
if (!memcmp(&dref->cfg, &ref->cfg, sizeof(ref->cfg)))
return 0;
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration into mailbox */
rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
ZL_REG_REF_MB_MASK, BIT(index));
if (rc)
return rc;
/* Update mailbox with changed values */
if (dref->freq_base != ref->freq_base)
rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE,
ref->freq_base);
if (!rc && dref->freq_mult != ref->freq_mult)
rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT,
ref->freq_mult);
if (!rc && dref->freq_ratio_m != ref->freq_ratio_m)
rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M,
ref->freq_ratio_m);
if (!rc && dref->freq_ratio_n != ref->freq_ratio_n)
rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N,
ref->freq_ratio_n);
if (!rc && dref->esync_n_div != ref->esync_n_div)
rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV,
ref->esync_n_div);
if (!rc && dref->sync_ctrl != ref->sync_ctrl)
rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL,
ref->sync_ctrl);
if (!rc && dref->phase_comp != ref->phase_comp) {
if (zl3073x_dev_is_ref_phase_comp_32bit(zldev))
rc = zl3073x_write_u32(zldev,
ZL_REG_REF_PHASE_OFFSET_COMP_32,
ref->phase_comp);
else
rc = zl3073x_write_u48(zldev,
ZL_REG_REF_PHASE_OFFSET_COMP,
ref->phase_comp);
}
if (rc)
return rc;
/* Commit reference configuration */
rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR,
ZL_REG_REF_MB_MASK, BIT(index));
if (rc)
return rc;
/* After successful commit store new state */
dref->cfg = ref->cfg;
return 0;
}
+191
View File
@@ -0,0 +1,191 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_REF_H
#define _ZL3073X_REF_H
#include <linux/bitfield.h>
#include <linux/math64.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
struct zl3073x_dev;
/**
* struct zl3073x_ref - input reference state
* @phase_comp: phase compensation
* @esync_n_div: divisor for embedded sync or n-divided signal formats
* @freq_base: frequency base
* @freq_mult: frequnecy multiplier
* @freq_ratio_m: FEC mode multiplier
* @freq_ratio_n: FEC mode divisor
* @sync_ctrl: reference sync control
* @config: reference config
* @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
struct zl3073x_ref {
struct_group(cfg, /* Configuration */
u64 phase_comp;
u32 esync_n_div;
u16 freq_base;
u16 freq_mult;
u16 freq_ratio_m;
u16 freq_ratio_n;
u8 sync_ctrl;
);
struct_group(inv, /* Invariants */
u8 config;
);
struct_group(stat, /* Status */
u32 meas_freq;
u8 mon_status;
);
};
int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index);
const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev,
u8 index);
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref);
int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
/**
* zl3073x_ref_meas_freq_get - get measured input frequency
* @ref: pointer to ref state
*
* Return: measured input frequency in Hz
*/
static inline u32
zl3073x_ref_meas_freq_get(const struct zl3073x_ref *ref)
{
return ref->meas_freq;
}
/**
* zl3073x_ref_freq_get - get given input reference frequency
* @ref: pointer to ref state
*
* Return: frequency of the given input reference
*/
static inline u32
zl3073x_ref_freq_get(const struct zl3073x_ref *ref)
{
return mul_u64_u32_div(ref->freq_base * ref->freq_mult,
ref->freq_ratio_m, ref->freq_ratio_n);
}
/**
* zl3073x_ref_freq_set - set given input reference frequency
* @ref: pointer to ref state
* @freq: frequency to be set
*
* Return: 0 on success, <0 when frequency cannot be factorized
*/
static inline int
zl3073x_ref_freq_set(struct zl3073x_ref *ref, u32 freq)
{
u16 base, mult;
int rc;
rc = zl3073x_ref_freq_factorize(freq, &base, &mult);
if (rc)
return rc;
ref->freq_base = base;
ref->freq_mult = mult;
ref->freq_ratio_m = 1;
ref->freq_ratio_n = 1;
return 0;
}
/**
* zl3073x_ref_sync_mode_get - get sync control mode
* @ref: pointer to ref state
*
* Return: sync control mode (ZL_REF_SYNC_CTRL_MODE_*)
*/
static inline u8
zl3073x_ref_sync_mode_get(const struct zl3073x_ref *ref)
{
return FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl);
}
/**
* zl3073x_ref_sync_mode_set - set sync control mode
* @ref: pointer to ref state
* @mode: sync control mode (ZL_REF_SYNC_CTRL_MODE_*)
*/
static inline void
zl3073x_ref_sync_mode_set(struct zl3073x_ref *ref, u8 mode)
{
FIELD_MODIFY(ZL_REF_SYNC_CTRL_MODE, &ref->sync_ctrl, mode);
}
/**
* zl3073x_ref_sync_pair_get - get sync pair reference index
* @ref: pointer to ref state
*
* Return: paired reference index
*/
static inline u8
zl3073x_ref_sync_pair_get(const struct zl3073x_ref *ref)
{
return FIELD_GET(ZL_REF_SYNC_CTRL_PAIR, ref->sync_ctrl);
}
/**
* zl3073x_ref_sync_pair_set - set sync pair reference index
* @ref: pointer to ref state
* @pair: paired reference index
*/
static inline void
zl3073x_ref_sync_pair_set(struct zl3073x_ref *ref, u8 pair)
{
FIELD_MODIFY(ZL_REF_SYNC_CTRL_PAIR, &ref->sync_ctrl, pair);
}
/**
* zl3073x_ref_is_diff - check if the given input reference is differential
* @ref: pointer to ref state
*
* Return: true if reference is differential, false if reference is single-ended
*/
static inline bool
zl3073x_ref_is_diff(const struct zl3073x_ref *ref)
{
return !!FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref->config);
}
/**
* zl3073x_ref_is_enabled - check if the given input reference is enabled
* @ref: pointer to ref state
*
* Return: true if input refernce is enabled, false otherwise
*/
static inline bool
zl3073x_ref_is_enabled(const struct zl3073x_ref *ref)
{
return !!FIELD_GET(ZL_REF_CONFIG_ENABLE, ref->config);
}
/**
* zl3073x_ref_is_status_ok - check the given input reference status
* @ref: pointer to ref state
*
* Return: true if the status is ok, false otherwise
*/
static inline bool
zl3073x_ref_is_status_ok(const struct zl3073x_ref *ref)
{
return ref->mon_status == ZL_REF_MON_STATUS_OK;
}
#endif /* _ZL3073X_REF_H */
+356
View File
@@ -0,0 +1,356 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_REGS_H
#define _ZL3073X_REGS_H
#include <linux/bitfield.h>
#include <linux/bits.h>
/*
* Hardware limits for ZL3073x chip family
*/
#define ZL3073X_MAX_CHANNELS 5
#define ZL3073X_NUM_REFS 10
#define ZL3073X_NUM_OUTS 10
#define ZL3073X_NUM_SYNTHS 5
#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS
#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
/*
* Register address structure:
* ===========================
* 25 19 18 16 15 7 6 0
* +------------------------------------------+
* | max_offset | size | page | page_offset |
* +------------------------------------------+
*
* page_offset ... <0x00..0x7F>
* page .......... HW page number
* size .......... register byte size (1, 2, 4 or 6)
* max_offset .... maximal offset for indexed registers
* (for non-indexed regs max_offset == page_offset)
*/
#define ZL_REG_OFFSET_MASK GENMASK(6, 0)
#define ZL_REG_PAGE_MASK GENMASK(15, 7)
#define ZL_REG_SIZE_MASK GENMASK(18, 16)
#define ZL_REG_MAX_OFFSET_MASK GENMASK(25, 19)
#define ZL_REG_ADDR_MASK GENMASK(15, 0)
#define ZL_REG_OFFSET(_reg) FIELD_GET(ZL_REG_OFFSET_MASK, _reg)
#define ZL_REG_PAGE(_reg) FIELD_GET(ZL_REG_PAGE_MASK, _reg)
#define ZL_REG_MAX_OFFSET(_reg) FIELD_GET(ZL_REG_MAX_OFFSET_MASK, _reg)
#define ZL_REG_SIZE(_reg) FIELD_GET(ZL_REG_SIZE_MASK, _reg)
#define ZL_REG_ADDR(_reg) FIELD_GET(ZL_REG_ADDR_MASK, _reg)
/**
* ZL_REG_IDX - define indexed register
* @_idx: index of register to access
* @_page: register page
* @_offset: register offset in page
* @_size: register byte size (1, 2, 4 or 6)
* @_items: number of register indices
* @_stride: stride between items in bytes
*
* All parameters except @_idx should be constant.
*/
#define ZL_REG_IDX(_idx, _page, _offset, _size, _items, _stride) \
(FIELD_PREP(ZL_REG_OFFSET_MASK, \
(_offset) + (_idx) * (_stride)) | \
FIELD_PREP_CONST(ZL_REG_PAGE_MASK, _page) | \
FIELD_PREP_CONST(ZL_REG_SIZE_MASK, _size) | \
FIELD_PREP_CONST(ZL_REG_MAX_OFFSET_MASK, \
(_offset) + ((_items) - 1) * (_stride)))
/**
* ZL_REG - define simple (non-indexed) register
* @_page: register page
* @_offset: register offset in page
* @_size: register byte size (1, 2, 4 or 6)
*
* All parameters should be constant.
*/
#define ZL_REG(_page, _offset, _size) \
ZL_REG_IDX(0, _page, _offset, _size, 1, 0)
/**************************
* Register Page 0, General
**************************/
#define ZL_REG_INFO ZL_REG(0, 0x00, 1)
#define ZL_INFO_READY BIT(7)
#define ZL_REG_ID ZL_REG(0, 0x01, 2)
#define ZL_REG_REVISION ZL_REG(0, 0x03, 2)
#define ZL_REG_FW_VER ZL_REG(0, 0x05, 2)
#define ZL_REG_CUSTOM_CONFIG_VER ZL_REG(0, 0x07, 4)
#define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1)
#define ZL_REG_RESET_STATUS_RESET BIT(0)
#define ZL_REG_DIE_TEMP_STATUS ZL_REG(0, 0x44, 2)
/*************************
* Register Page 2, Status
*************************/
#define ZL_REG_REF_MON_STATUS(_idx) \
ZL_REG_IDX(_idx, 2, 0x02, 1, ZL3073X_NUM_REFS, 1)
#define ZL_REF_MON_STATUS_OK 0
#define ZL_REF_MON_STATUS_LOS BIT(0)
#define ZL_REF_MON_STATUS_SCM BIT(1)
#define ZL_REF_MON_STATUS_CFM BIT(2)
#define ZL_REF_MON_STATUS_GST BIT(3)
#define ZL_REF_MON_STATUS_PFM BIT(4)
#define ZL_REF_MON_STATUS_ESYNC BIT(6)
#define ZL_REF_MON_STATUS_SPLIT_XO BIT(7)
#define ZL_REG_DPLL_MON_STATUS(_idx) \
ZL_REG_IDX(_idx, 2, 0x10, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_MON_STATUS_STATE GENMASK(1, 0)
#define ZL_DPLL_MON_STATUS_STATE_ACQUIRING 0
#define ZL_DPLL_MON_STATUS_STATE_LOCK 1
#define ZL_DPLL_MON_STATUS_STATE_HOLDOVER 2
#define ZL_DPLL_MON_STATUS_HO_READY BIT(2)
#define ZL_REG_DPLL_REFSEL_STATUS(_idx) \
ZL_REG_IDX(_idx, 2, 0x30, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_REFSEL_STATUS_REFSEL GENMASK(3, 0)
#define ZL_DPLL_REFSEL_STATUS_STATE GENMASK(6, 4)
#define ZL_DPLL_REFSEL_STATUS_STATE_LOCK 4
#define ZL_REG_REF_FREQ(_idx) \
ZL_REG_IDX(_idx, 2, 0x44, 4, ZL3073X_NUM_REFS, 4)
/**********************
* Register Page 4, Ref
**********************/
#define ZL_REG_REF_PHASE_ERR_READ_RQST ZL_REG(4, 0x0f, 1)
#define ZL_REF_PHASE_ERR_READ_RQST_RD BIT(0)
#define ZL_REG_REF_FREQ_MEAS_CTRL ZL_REG(4, 0x1c, 1)
#define ZL_REF_FREQ_MEAS_CTRL GENMASK(1, 0)
#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ 1
#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF 2
#define ZL_REF_FREQ_MEAS_CTRL_DPLL_FREQ_OFF 3
#define ZL_REG_REF_FREQ_MEAS_MASK_3_0 ZL_REG(4, 0x1d, 1)
#define ZL_REF_FREQ_MEAS_MASK_3_0(_ref) BIT(_ref)
#define ZL_REG_REF_FREQ_MEAS_MASK_4 ZL_REG(4, 0x1e, 1)
#define ZL_REF_FREQ_MEAS_MASK_4(_ref) BIT((_ref) - 8)
#define ZL_REG_DPLL_MEAS_REF_FREQ_CTRL ZL_REG(4, 0x1f, 1)
#define ZL_DPLL_MEAS_REF_FREQ_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_REF_FREQ_CTRL_IDX GENMASK(6, 4)
#define ZL_REG_REF_PHASE(_idx) \
ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6)
/***********************
* Register Page 5, DPLL
***********************/
#define ZL_REG_DPLL_MODE_REFSEL(_idx) \
ZL_REG_IDX(_idx, 5, 0x04, 1, ZL3073X_MAX_CHANNELS, 4)
#define ZL_DPLL_MODE_REFSEL_MODE GENMASK(2, 0)
#define ZL_DPLL_MODE_REFSEL_MODE_FREERUN 0
#define ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER 1
#define ZL_DPLL_MODE_REFSEL_MODE_REFLOCK 2
#define ZL_DPLL_MODE_REFSEL_MODE_AUTO 3
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
#define ZL_REG_DPLL_DF_READ(_idx) \
ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_DF_READ_SEM BIT(4)
#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
#define ZL_REG_DPLL_MEAS_IDX ZL_REG(5, 0x51, 1)
#define ZL_DPLL_MEAS_IDX GENMASK(2, 0)
#define ZL_REG_DPLL_PHASE_ERR_READ_MASK ZL_REG(5, 0x54, 1)
#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \
ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
/*******************************
* Register Pages 6-7, DPLL Data
*******************************/
#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
#define ZL_REG_DPLL_DF_OFFSET(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
/***********************************
* Register Page 9, Synth and Output
***********************************/
#define ZL_REG_SYNTH_CTRL(_idx) \
ZL_REG_IDX(_idx, 9, 0x00, 1, ZL3073X_NUM_SYNTHS, 1)
#define ZL_SYNTH_CTRL_EN BIT(0)
#define ZL_SYNTH_CTRL_DPLL_SEL GENMASK(6, 4)
#define ZL_REG_SYNTH_PHASE_SHIFT_CTRL ZL_REG(9, 0x1e, 1)
#define ZL_REG_SYNTH_PHASE_SHIFT_MASK ZL_REG(9, 0x1f, 1)
#define ZL_REG_SYNTH_PHASE_SHIFT_INTVL ZL_REG(9, 0x20, 1)
#define ZL_REG_SYNTH_PHASE_SHIFT_DATA ZL_REG(9, 0x21, 2)
#define ZL_REG_OUTPUT_CTRL(_idx) \
ZL_REG_IDX(_idx, 9, 0x28, 1, ZL3073X_NUM_OUTS, 1)
#define ZL_OUTPUT_CTRL_EN BIT(0)
#define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4)
/*******************************
* Register Page 10, Ref Mailbox
*******************************/
#define ZL_REG_REF_MB_MASK ZL_REG(10, 0x02, 2)
#define ZL_REG_REF_MB_SEM ZL_REG(10, 0x04, 1)
#define ZL_REF_MB_SEM_WR BIT(0)
#define ZL_REF_MB_SEM_RD BIT(1)
#define ZL_REG_REF_FREQ_BASE ZL_REG(10, 0x05, 2)
#define ZL_REG_REF_FREQ_MULT ZL_REG(10, 0x07, 2)
#define ZL_REG_REF_RATIO_M ZL_REG(10, 0x09, 2)
#define ZL_REG_REF_RATIO_N ZL_REG(10, 0x0b, 2)
#define ZL_REG_REF_CONFIG ZL_REG(10, 0x0d, 1)
#define ZL_REF_CONFIG_ENABLE BIT(0)
#define ZL_REF_CONFIG_DIFF_EN BIT(2)
#define ZL_REG_REF_PHASE_OFFSET_COMP ZL_REG(10, 0x28, 6)
#define ZL_REG_REF_PHASE_OFFSET_COMP_32 ZL_REG(10, 0x28, 4)
#define ZL_REG_REF_SYNC_CTRL ZL_REG(10, 0x2e, 1)
#define ZL_REF_SYNC_CTRL_MODE GENMASK(2, 0)
#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0
#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR 1
#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2
#define ZL_REF_SYNC_CTRL_PAIR GENMASK(7, 4)
#define ZL_REG_REF_ESYNC_DIV ZL_REG(10, 0x30, 4)
#define ZL_REF_ESYNC_DIV_1HZ 0
/********************************
* Register Page 12, DPLL Mailbox
********************************/
#define ZL_REG_DPLL_MB_MASK ZL_REG(12, 0x02, 2)
#define ZL_REG_DPLL_MB_SEM ZL_REG(12, 0x04, 1)
#define ZL_DPLL_MB_SEM_WR BIT(0)
#define ZL_DPLL_MB_SEM_RD BIT(1)
#define ZL_REG_DPLL_REF_PRIO(_idx) \
ZL_REG_IDX(_idx, 12, 0x52, 1, ZL3073X_NUM_REFS / 2, 1)
#define ZL_DPLL_REF_PRIO_REF_P GENMASK(3, 0)
#define ZL_DPLL_REF_PRIO_REF_N GENMASK(7, 4)
#define ZL_DPLL_REF_PRIO_MAX 14
#define ZL_DPLL_REF_PRIO_NONE 15
/*********************************
* Register Page 13, Synth Mailbox
*********************************/
#define ZL_REG_SYNTH_MB_MASK ZL_REG(13, 0x02, 2)
#define ZL_REG_SYNTH_MB_SEM ZL_REG(13, 0x04, 1)
#define ZL_SYNTH_MB_SEM_WR BIT(0)
#define ZL_SYNTH_MB_SEM_RD BIT(1)
#define ZL_REG_SYNTH_FREQ_BASE ZL_REG(13, 0x06, 2)
#define ZL_REG_SYNTH_FREQ_MULT ZL_REG(13, 0x08, 4)
#define ZL_REG_SYNTH_FREQ_M ZL_REG(13, 0x0c, 2)
#define ZL_REG_SYNTH_FREQ_N ZL_REG(13, 0x0e, 2)
/**********************************
* Register Page 14, Output Mailbox
**********************************/
#define ZL_REG_OUTPUT_MB_MASK ZL_REG(14, 0x02, 2)
#define ZL_REG_OUTPUT_MB_SEM ZL_REG(14, 0x04, 1)
#define ZL_OUTPUT_MB_SEM_WR BIT(0)
#define ZL_OUTPUT_MB_SEM_RD BIT(1)
#define ZL_REG_OUTPUT_MODE ZL_REG(14, 0x05, 1)
#define ZL_OUTPUT_MODE_CLOCK_TYPE GENMASK(2, 0)
#define ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL 0
#define ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC 1
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT GENMASK(7, 4)
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED 0
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS 1
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF 2
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM 3
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2 4
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P 5
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N 6
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_INV 7
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV 12
#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV 15
#define ZL_REG_OUTPUT_DIV ZL_REG(14, 0x0c, 4)
#define ZL_REG_OUTPUT_WIDTH ZL_REG(14, 0x10, 4)
#define ZL_REG_OUTPUT_ESYNC_PERIOD ZL_REG(14, 0x14, 4)
#define ZL_REG_OUTPUT_ESYNC_WIDTH ZL_REG(14, 0x18, 4)
#define ZL_REG_OUTPUT_PHASE_COMP ZL_REG(14, 0x20, 4)
/*
* Register Page 255 - HW registers access
*/
#define ZL_REG_HWREG_OP ZL_REG(0xff, 0x00, 1)
#define ZL_HWREG_OP_WRITE 0x28
#define ZL_HWREG_OP_READ 0x29
#define ZL_HWREG_OP_PENDING BIT(1)
#define ZL_REG_HWREG_ADDR ZL_REG(0xff, 0x04, 4)
#define ZL_REG_HWREG_WRITE_DATA ZL_REG(0xff, 0x08, 4)
#define ZL_REG_HWREG_READ_DATA ZL_REG(0xff, 0x0c, 4)
/*
* Registers available in flash mode
*/
#define ZL_REG_FLASH_HASH ZL_REG(0, 0x78, 4)
#define ZL_REG_FLASH_FAMILY ZL_REG(0, 0x7c, 1)
#define ZL_REG_FLASH_RELEASE ZL_REG(0, 0x7d, 1)
#define ZL_REG_HOST_CONTROL ZL_REG(1, 0x02, 1)
#define ZL_HOST_CONTROL_ENABLE BIT(0)
#define ZL_REG_IMAGE_START_ADDR ZL_REG(1, 0x04, 4)
#define ZL_REG_IMAGE_SIZE ZL_REG(1, 0x08, 4)
#define ZL_REG_FLASH_INDEX_READ ZL_REG(1, 0x0c, 4)
#define ZL_REG_FLASH_INDEX_WRITE ZL_REG(1, 0x10, 4)
#define ZL_REG_FILL_PATTERN ZL_REG(1, 0x14, 4)
#define ZL_REG_WRITE_FLASH ZL_REG(1, 0x18, 1)
#define ZL_WRITE_FLASH_OP GENMASK(2, 0)
#define ZL_WRITE_FLASH_OP_DONE 0x0
#define ZL_WRITE_FLASH_OP_SECTORS 0x2
#define ZL_WRITE_FLASH_OP_PAGE 0x3
#define ZL_WRITE_FLASH_OP_COPY_PAGE 0x4
#define ZL_REG_FLASH_INFO ZL_REG(2, 0x00, 1)
#define ZL_FLASH_INFO_SECTOR_SIZE GENMASK(3, 0)
#define ZL_FLASH_INFO_SECTOR_4K 0
#define ZL_FLASH_INFO_SECTOR_64K 1
#define ZL_REG_ERROR_COUNT ZL_REG(2, 0x04, 4)
#define ZL_REG_ERROR_CAUSE ZL_REG(2, 0x08, 4)
#define ZL_REG_OP_STATE ZL_REG(2, 0x14, 1)
#define ZL_OP_STATE_NO_COMMAND 0
#define ZL_OP_STATE_PENDING 1
#define ZL_OP_STATE_DONE 2
#endif /* _ZL3073X_REGS_H */
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include "core.h"
static int zl3073x_spi_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct zl3073x_dev *zldev;
zldev = zl3073x_devm_alloc(dev);
if (IS_ERR(zldev))
return PTR_ERR(zldev);
zldev->regmap = devm_regmap_init_spi(spi, &zl3073x_regmap_config);
if (IS_ERR(zldev->regmap))
return dev_err_probe(dev, PTR_ERR(zldev->regmap),
"Failed to initialize regmap\n");
return zl3073x_dev_probe(zldev);
}
static const struct spi_device_id zl3073x_spi_id[] = {
{ "zl30731" },
{ "zl30732" },
{ "zl30733" },
{ "zl30734" },
{ "zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
static const struct of_device_id zl3073x_spi_of_match[] = {
{ .compatible = "microchip,zl30731" },
{ .compatible = "microchip,zl30732" },
{ .compatible = "microchip,zl30733" },
{ .compatible = "microchip,zl30734" },
{ .compatible = "microchip,zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match);
static struct spi_driver zl3073x_spi_driver = {
.driver = {
.name = "zl3073x-spi",
.of_match_table = zl3073x_spi_of_match,
},
.probe = zl3073x_spi_probe,
.id_table = zl3073x_spi_id,
};
module_spi_driver(zl3073x_spi_driver);
MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
MODULE_DESCRIPTION("Microchip ZL3073x SPI driver");
MODULE_IMPORT_NS("ZL3073X");
MODULE_LICENSE("GPL");
+87
View File
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/string_choices.h>
#include <linux/types.h>
#include "core.h"
#include "synth.h"
/**
* zl3073x_synth_state_fetch - fetch synth state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: synth index to fetch state for
*
* Function fetches state of the given synthesizer from the hardware and
* stores it for later use.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_synth *synth = &zldev->synth[index];
int rc;
/* Read synth control register */
rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth->ctrl);
if (rc)
return rc;
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, &synth->freq_base);
if (rc)
return rc;
rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &synth->freq_mult);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &synth->freq_m);
if (rc)
return rc;
rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &synth->freq_n);
if (rc)
return rc;
/* Check denominator for zero to avoid div by 0 */
if (!synth->freq_n) {
dev_err(zldev->dev,
"Zero divisor for SYNTH%u retrieved from device\n",
index);
return -EINVAL;
}
dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index,
zl3073x_synth_freq_get(synth));
return rc;
}
/**
* zl3073x_synth_state_get - get current synth state
* @zldev: pointer to zl3073x_dev structure
* @index: synth index to get state for
*
* Return: pointer to given synth state
*/
const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev,
u8 index)
{
return &zldev->synth[index];
}
+72
View File
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ZL3073X_SYNTH_H
#define _ZL3073X_SYNTH_H
#include <linux/bitfield.h>
#include <linux/math64.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
struct zl3073x_dev;
/**
* struct zl3073x_synth - synthesizer state
* @freq_mult: frequency multiplier
* @freq_base: frequency base
* @freq_m: frequency numerator
* @freq_n: frequency denominator
* @ctrl: synth control
*/
struct zl3073x_synth {
struct_group(inv, /* Invariants */
u32 freq_mult;
u16 freq_base;
u16 freq_m;
u16 freq_n;
u8 ctrl;
);
};
int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id);
const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev,
u8 synth_id);
/**
* zl3073x_synth_dpll_get - get DPLL ID the synth is driven by
* @synth: pointer to synth state
*
* Return: ID of DPLL the given synthetizer is driven by
*/
static inline u8 zl3073x_synth_dpll_get(const struct zl3073x_synth *synth)
{
return FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth->ctrl);
}
/**
* zl3073x_synth_freq_get - get synth current freq
* @synth: pointer to synth state
*
* Return: frequency of given synthetizer
*/
static inline u32 zl3073x_synth_freq_get(const struct zl3073x_synth *synth)
{
return mul_u64_u32_div(synth->freq_base * synth->freq_m,
synth->freq_mult, synth->freq_n);
}
/**
* zl3073x_synth_is_enabled - check if the given synth is enabled
* @synth: pointer to synth state
*
* Return: true if synth is enabled, false otherwise
*/
static inline bool zl3073x_synth_is_enabled(const struct zl3073x_synth *synth)
{
return FIELD_GET(ZL_SYNTH_CTRL_EN, synth->ctrl);
}
#endif /* _ZL3073X_SYNTH_H */