mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 09:04:39 +02:00
Merge branch 'add-microchip-zl3073x-support-part-1'
Ivan Vecera says: ==================== Add Microchip ZL3073x support (part 1) Add support for Microchip Azurite DPLL/PTP/SyncE chip family that provides DPLL and PTP functionality. This series bring first part that adds the core functionality and basic DPLL support. The next part of the series will bring additional DPLL functionality like eSync support, phase offset and frequency offset reporting and phase adjustments. Testing was done by myself and by Prathosh Satish on Microchip EDS2 development board with ZL30732 DPLL chip connected over I2C bus. ==================== Link: https://patch.msgid.link/20250704182202.1641943-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
11b5d56d37
76
Documentation/devicetree/bindings/dpll/dpll-device.yaml
Normal file
76
Documentation/devicetree/bindings/dpll/dpll-device.yaml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/dpll/dpll-device.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Digital Phase-Locked Loop (DPLL) Device
|
||||
|
||||
maintainers:
|
||||
- Ivan Vecera <ivecera@redhat.com>
|
||||
|
||||
description:
|
||||
Digital Phase-Locked Loop (DPLL) device is used for precise clock
|
||||
synchronization in networking and telecom hardware. The device can
|
||||
have one or more channels (DPLLs) and one or more physical input and
|
||||
output pins. Each DPLL channel can either produce pulse-per-clock signal
|
||||
or drive ethernet equipment clock. The type of each channel can be
|
||||
indicated by dpll-types property.
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^dpll(@.*)?$"
|
||||
|
||||
"#address-cells":
|
||||
const: 0
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
dpll-types:
|
||||
description: List of DPLL channel types, one per DPLL instance.
|
||||
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
|
||||
items:
|
||||
enum: [pps, eec]
|
||||
|
||||
input-pins:
|
||||
type: object
|
||||
description: DPLL input pins
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
"#address-cells":
|
||||
const: 1
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^pin@[0-9a-f]+$":
|
||||
$ref: /schemas/dpll/dpll-pin.yaml
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
output-pins:
|
||||
type: object
|
||||
description: DPLL output pins
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
"#address-cells":
|
||||
const: 1
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^pin@[0-9]+$":
|
||||
$ref: /schemas/dpll/dpll-pin.yaml
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
additionalProperties: true
|
||||
45
Documentation/devicetree/bindings/dpll/dpll-pin.yaml
Normal file
45
Documentation/devicetree/bindings/dpll/dpll-pin.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/dpll/dpll-pin.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: DPLL Pin
|
||||
|
||||
maintainers:
|
||||
- Ivan Vecera <ivecera@redhat.com>
|
||||
|
||||
description: |
|
||||
The DPLL pin is either a physical input or output pin that is provided
|
||||
by a DPLL( Digital Phase-Locked Loop) device. The pin is identified by
|
||||
its physical order number that is stored in reg property and can have
|
||||
an additional set of properties like supported (allowed) frequencies,
|
||||
label, type and may support embedded sync.
|
||||
|
||||
Note that the pin in this context has nothing to do with pinctrl.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description: Hardware index of the DPLL pin.
|
||||
maxItems: 1
|
||||
|
||||
connection-type:
|
||||
description: Connection type of the pin
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum: [ext, gnss, int, mux, synce]
|
||||
|
||||
esync-control:
|
||||
description: Indicates whether the pin supports embedded sync functionality.
|
||||
type: boolean
|
||||
|
||||
label:
|
||||
description: String exposed as the pin board label
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
|
||||
supported-frequencies-hz:
|
||||
description: List of supported frequencies for this pin, expressed in Hz.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
115
Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml
Normal file
115
Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/dpll/microchip,zl30731.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Microchip Azurite DPLL device
|
||||
|
||||
maintainers:
|
||||
- Ivan Vecera <ivecera@redhat.com>
|
||||
|
||||
description:
|
||||
Microchip Azurite DPLL (ZL3073x) is a family of DPLL devices that
|
||||
provides up to 5 independent DPLL channels, up to 10 differential or
|
||||
single-ended inputs and 10 differential or 20 single-ended outputs.
|
||||
These devices support both I2C and SPI interfaces.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- microchip,zl30731
|
||||
- microchip,zl30732
|
||||
- microchip,zl30733
|
||||
- microchip,zl30734
|
||||
- microchip,zl30735
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/dpll/dpll-device.yaml#
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dpll@70 {
|
||||
compatible = "microchip,zl30732";
|
||||
reg = <0x70>;
|
||||
dpll-types = "pps", "eec";
|
||||
|
||||
input-pins {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pin@0 { /* REF0P */
|
||||
reg = <0>;
|
||||
connection-type = "ext";
|
||||
label = "Input 0";
|
||||
supported-frequencies-hz = /bits/ 64 <1 1000>;
|
||||
};
|
||||
};
|
||||
|
||||
output-pins {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pin@3 { /* OUT1N */
|
||||
reg = <3>;
|
||||
connection-type = "gnss";
|
||||
esync-control;
|
||||
label = "Output 1";
|
||||
supported-frequencies-hz = /bits/ 64 <1 10000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
- |
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dpll@70 {
|
||||
compatible = "microchip,zl30731";
|
||||
reg = <0x70>;
|
||||
spi-max-frequency = <12500000>;
|
||||
|
||||
dpll-types = "pps";
|
||||
|
||||
input-pins {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pin@0 { /* REF0P */
|
||||
reg = <0>;
|
||||
connection-type = "ext";
|
||||
label = "Input 0";
|
||||
supported-frequencies-hz = /bits/ 64 <1 1000>;
|
||||
};
|
||||
};
|
||||
|
||||
output-pins {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pin@3 { /* OUT1N */
|
||||
reg = <3>;
|
||||
connection-type = "gnss";
|
||||
esync-control;
|
||||
label = "Output 1";
|
||||
supported-frequencies-hz = /bits/ 64 <1 10000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
@ -140,3 +140,6 @@ own name.
|
|||
* - ``enable_phc``
|
||||
- Boolean
|
||||
- Enable PHC (PTP Hardware Clock) functionality in the device.
|
||||
* - ``clock_id``
|
||||
- u64
|
||||
- Clock ID used by the device for registering DPLL devices and pins.
|
||||
|
|
|
|||
|
|
@ -98,3 +98,4 @@ parameters, info versions, and other features it supports.
|
|||
iosm
|
||||
octeontx2
|
||||
sfc
|
||||
zl3073x
|
||||
|
|
|
|||
51
Documentation/networking/devlink/zl3073x.rst
Normal file
51
Documentation/networking/devlink/zl3073x.rst
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=======================
|
||||
zl3073x devlink support
|
||||
=======================
|
||||
|
||||
This document describes the devlink features implemented by the ``zl3073x``
|
||||
device driver.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
.. list-table:: Generic parameters implemented
|
||||
:widths: 5 5 90
|
||||
|
||||
* - Name
|
||||
- Mode
|
||||
- Notes
|
||||
* - ``clock_id``
|
||||
- driverinit
|
||||
- Set the clock ID that is used by the driver for registering DPLL devices
|
||||
and pins.
|
||||
|
||||
Info versions
|
||||
=============
|
||||
|
||||
The ``zl3073x`` driver reports the following versions
|
||||
|
||||
.. list-table:: devlink info versions implemented
|
||||
:widths: 5 5 5 90
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Example
|
||||
- Description
|
||||
* - ``asic.id``
|
||||
- fixed
|
||||
- 1E94
|
||||
- Chip identification number
|
||||
* - ``asic.rev``
|
||||
- fixed
|
||||
- 300
|
||||
- Chip revision number
|
||||
* - ``fw``
|
||||
- running
|
||||
- 7006
|
||||
- Firmware version number
|
||||
* - ``custom_cfg``
|
||||
- running
|
||||
- 1.3.0.1
|
||||
- Device configuration version customized by OEM
|
||||
10
MAINTAINERS
10
MAINTAINERS
|
|
@ -7335,6 +7335,8 @@ M: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
|
|||
M: Jiri Pirko <jiri@resnulli.us>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/dpll/dpll-device.yaml
|
||||
F: Documentation/devicetree/bindings/dpll/dpll-pin.yaml
|
||||
F: Documentation/driver-api/dpll.rst
|
||||
F: drivers/dpll/*
|
||||
F: include/linux/dpll.h
|
||||
|
|
@ -16512,6 +16514,14 @@ L: linux-wireless@vger.kernel.org
|
|||
S: Supported
|
||||
F: drivers/net/wireless/microchip/
|
||||
|
||||
MICROCHIP ZL3073X DRIVER
|
||||
M: Ivan Vecera <ivecera@redhat.com>
|
||||
M: Prathosh Satish <Prathosh.Satish@microchip.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml
|
||||
F: drivers/dpll/zl3073x/
|
||||
|
||||
MICROSEMI MIPS SOCS
|
||||
M: Alexandre Belloni <alexandre.belloni@bootlin.com>
|
||||
M: UNGLinuxDriver@microchip.com
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ source "drivers/pps/Kconfig"
|
|||
|
||||
source "drivers/ptp/Kconfig"
|
||||
|
||||
source "drivers/dpll/Kconfig"
|
||||
|
||||
source "drivers/pinctrl/Kconfig"
|
||||
|
||||
source "drivers/gpio/Kconfig"
|
||||
|
|
@ -245,6 +247,4 @@ source "drivers/hte/Kconfig"
|
|||
|
||||
source "drivers/cdx/Kconfig"
|
||||
|
||||
source "drivers/dpll/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
|||
|
|
@ -3,5 +3,11 @@
|
|||
# Generic DPLL drivers configuration
|
||||
#
|
||||
|
||||
menu "DPLL device support"
|
||||
|
||||
config DPLL
|
||||
bool
|
||||
|
||||
source "drivers/dpll/zl3073x/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@ obj-$(CONFIG_DPLL) += dpll.o
|
|||
dpll-y += dpll_core.o
|
||||
dpll-y += dpll_netlink.o
|
||||
dpll-y += dpll_nl.o
|
||||
|
||||
obj-$(CONFIG_ZL3073X) += zl3073x/
|
||||
|
|
|
|||
38
drivers/dpll/zl3073x/Kconfig
Normal file
38
drivers/dpll/zl3073x/Kconfig
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config ZL3073X
|
||||
tristate "Microchip Azurite DPLL/PTP/SyncE devices"
|
||||
depends on NET
|
||||
select DPLL
|
||||
select NET_DEVLINK
|
||||
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 && ZL3073X
|
||||
select REGMAP_I2C
|
||||
default m
|
||||
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 SPI && ZL3073X
|
||||
select REGMAP_SPI
|
||||
default m
|
||||
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.
|
||||
10
drivers/dpll/zl3073x/Makefile
Normal file
10
drivers/dpll/zl3073x/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_ZL3073X) += zl3073x.o
|
||||
zl3073x-objs := core.o devlink.o dpll.o prop.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
|
||||
859
drivers/dpll/zl3073x/core.c
Normal file
859
drivers/dpll/zl3073x/core.c
Normal file
|
|
@ -0,0 +1,859 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/sprintf.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <net/devlink.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "devlink.h"
|
||||
#include "dpll.h"
|
||||
#include "regs.h"
|
||||
|
||||
/* Chip IDs for zl30731 */
|
||||
static const u16 zl30731_ids[] = {
|
||||
0x0E93,
|
||||
0x1E93,
|
||||
0x2E93,
|
||||
};
|
||||
|
||||
const struct zl3073x_chip_info zl30731_chip_info = {
|
||||
.ids = zl30731_ids,
|
||||
.num_ids = ARRAY_SIZE(zl30731_ids),
|
||||
.num_channels = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, "ZL3073X");
|
||||
|
||||
/* Chip IDs for zl30732 */
|
||||
static const u16 zl30732_ids[] = {
|
||||
0x0E30,
|
||||
0x0E94,
|
||||
0x1E94,
|
||||
0x1F60,
|
||||
0x2E94,
|
||||
0x3FC4,
|
||||
};
|
||||
|
||||
const struct zl3073x_chip_info zl30732_chip_info = {
|
||||
.ids = zl30732_ids,
|
||||
.num_ids = ARRAY_SIZE(zl30732_ids),
|
||||
.num_channels = 2,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, "ZL3073X");
|
||||
|
||||
/* Chip IDs for zl30733 */
|
||||
static const u16 zl30733_ids[] = {
|
||||
0x0E95,
|
||||
0x1E95,
|
||||
0x2E95,
|
||||
};
|
||||
|
||||
const struct zl3073x_chip_info zl30733_chip_info = {
|
||||
.ids = zl30733_ids,
|
||||
.num_ids = ARRAY_SIZE(zl30733_ids),
|
||||
.num_channels = 3,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, "ZL3073X");
|
||||
|
||||
/* Chip IDs for zl30734 */
|
||||
static const u16 zl30734_ids[] = {
|
||||
0x0E96,
|
||||
0x1E96,
|
||||
0x2E96,
|
||||
};
|
||||
|
||||
const struct zl3073x_chip_info zl30734_chip_info = {
|
||||
.ids = zl30734_ids,
|
||||
.num_ids = ARRAY_SIZE(zl30734_ids),
|
||||
.num_channels = 4,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, "ZL3073X");
|
||||
|
||||
/* Chip IDs for zl30735 */
|
||||
static const u16 zl30735_ids[] = {
|
||||
0x0E97,
|
||||
0x1E97,
|
||||
0x2E97,
|
||||
};
|
||||
|
||||
const struct zl3073x_chip_info zl30735_chip_info = {
|
||||
.ids = zl30735_ids,
|
||||
.num_ids = ARRAY_SIZE(zl30735_ids),
|
||||
.num_channels = 5,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, "ZL3073X");
|
||||
|
||||
#define ZL_RANGE_OFFSET 0x80
|
||||
#define ZL_PAGE_SIZE 0x80
|
||||
#define ZL_NUM_PAGES 15
|
||||
#define ZL_PAGE_SEL 0x7F
|
||||
#define ZL_PAGE_SEL_MASK GENMASK(3, 0)
|
||||
#define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE)
|
||||
|
||||
/* Regmap range configuration */
|
||||
static const struct regmap_range_cfg zl3073x_regmap_range = {
|
||||
.range_min = ZL_RANGE_OFFSET,
|
||||
.range_max = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
|
||||
.selector_reg = ZL_PAGE_SEL,
|
||||
.selector_mask = ZL_PAGE_SEL_MASK,
|
||||
.selector_shift = 0,
|
||||
.window_start = 0,
|
||||
.window_len = ZL_PAGE_SIZE,
|
||||
};
|
||||
|
||||
static bool
|
||||
zl3073x_is_volatile_reg(struct device *dev __maybe_unused, unsigned int reg)
|
||||
{
|
||||
/* Only page selector is non-volatile */
|
||||
return reg != ZL_PAGE_SEL;
|
||||
}
|
||||
|
||||
const struct regmap_config zl3073x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
|
||||
.ranges = &zl3073x_regmap_range,
|
||||
.num_ranges = 1,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
.volatile_reg = zl3073x_is_volatile_reg,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X");
|
||||
|
||||
/**
|
||||
* zl3073x_ref_freq_factorize - factorize given frequency
|
||||
* @freq: input frequency
|
||||
* @base: base frequency
|
||||
* @mult: multiplier
|
||||
*
|
||||
* Checks if the given frequency can be factorized using one of the
|
||||
* supported base frequencies. If so the base frequency and multiplier
|
||||
* are stored into appropriate parameters if they are not NULL.
|
||||
*
|
||||
* Return: 0 on success, -EINVAL if the frequency cannot be factorized
|
||||
*/
|
||||
int
|
||||
zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult)
|
||||
{
|
||||
static const u16 base_freqs[] = {
|
||||
1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125,
|
||||
128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000,
|
||||
1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250,
|
||||
6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250,
|
||||
32000, 40000, 50000, 62500,
|
||||
};
|
||||
u32 div;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(base_freqs); i++) {
|
||||
div = freq / base_freqs[i];
|
||||
|
||||
if (div <= U16_MAX && (freq % base_freqs[i]) == 0) {
|
||||
if (base)
|
||||
*base = base_freqs[i];
|
||||
if (mult)
|
||||
*mult = div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static bool
|
||||
zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size)
|
||||
{
|
||||
/* Check that multiop lock is held when accessing registers
|
||||
* from page 10 and above.
|
||||
*/
|
||||
if (ZL_REG_PAGE(reg) >= 10)
|
||||
lockdep_assert_held(&zldev->multiop_lock);
|
||||
|
||||
/* Check the index is in valid range for indexed register */
|
||||
if (ZL_REG_OFFSET(reg) > ZL_REG_MAX_OFFSET(reg)) {
|
||||
dev_err(zldev->dev, "Index out of range for reg 0x%04lx\n",
|
||||
ZL_REG_ADDR(reg));
|
||||
return false;
|
||||
}
|
||||
/* Check the requested size corresponds to register size */
|
||||
if (ZL_REG_SIZE(reg) != size) {
|
||||
dev_err(zldev->dev, "Invalid size %zu for reg 0x%04lx\n",
|
||||
size, ZL_REG_ADDR(reg));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val,
|
||||
size_t size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!zl3073x_check_reg(zldev, reg, size))
|
||||
return -EINVAL;
|
||||
|
||||
/* Map the register address to virtual range */
|
||||
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
||||
|
||||
rc = regmap_bulk_read(zldev->regmap, reg, val, size);
|
||||
if (rc) {
|
||||
dev_err(zldev->dev, "Failed to read reg 0x%04x: %pe\n", reg,
|
||||
ERR_PTR(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val,
|
||||
size_t size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!zl3073x_check_reg(zldev, reg, size))
|
||||
return -EINVAL;
|
||||
|
||||
/* Map the register address to virtual range */
|
||||
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
||||
|
||||
rc = regmap_bulk_write(zldev->regmap, reg, val, size);
|
||||
if (rc) {
|
||||
dev_err(zldev->dev, "Failed to write reg 0x%04x: %pe\n", reg,
|
||||
ERR_PTR(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_read_u8 - read value from 8bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Reads value from given 8bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val)
|
||||
{
|
||||
return zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_write_u8 - write value to 16bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Writes value into given 8bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val)
|
||||
{
|
||||
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_read_u16 - read value from 16bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Reads value from given 16bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
||||
if (!rc)
|
||||
be16_to_cpus(val);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_write_u16 - write value to 16bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Writes value into given 16bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val)
|
||||
{
|
||||
cpu_to_be16s(&val);
|
||||
|
||||
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_read_u32 - read value from 32bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Reads value from given 32bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
||||
if (!rc)
|
||||
be32_to_cpus(val);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_write_u32 - write value to 32bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Writes value into given 32bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val)
|
||||
{
|
||||
cpu_to_be32s(&val);
|
||||
|
||||
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_read_u48 - read value from 48bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Reads value from given 48bit register.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val)
|
||||
{
|
||||
u8 buf[6];
|
||||
int rc;
|
||||
|
||||
rc = zl3073x_read_reg(zldev, reg, buf, sizeof(buf));
|
||||
if (!rc)
|
||||
*val = get_unaligned_be48(buf);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_write_u48 - write value to 48bit register
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to write to
|
||||
* @val: value to write
|
||||
*
|
||||
* Writes value into given 48bit register.
|
||||
* The value must be from the interval -S48_MIN to U48_MAX.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
|
||||
{
|
||||
u8 buf[6];
|
||||
|
||||
/* Check the value belongs to <S48_MIN, U48_MAX>
|
||||
* Any value >= S48_MIN has bits 47..63 set.
|
||||
*/
|
||||
if (val > GENMASK_ULL(47, 0) && val < GENMASK_ULL(63, 47)) {
|
||||
dev_err(zldev->dev, "Value 0x%0llx out of range\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
put_unaligned_be48(val, buf);
|
||||
|
||||
return zl3073x_write_reg(zldev, reg, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_poll_zero_u8 - wait for register to be cleared by device
|
||||
* @zldev: zl3073x device pointer
|
||||
* @reg: register to poll (has to be 8bit register)
|
||||
* @mask: bit mask for polling
|
||||
*
|
||||
* Waits for bits specified by @mask in register @reg value to be cleared
|
||||
* by the device.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
|
||||
{
|
||||
/* Register polling sleep & timeout */
|
||||
#define ZL_POLL_SLEEP_US 10
|
||||
#define ZL_POLL_TIMEOUT_US 2000000
|
||||
unsigned int val;
|
||||
|
||||
/* Check the register is 8bit */
|
||||
if (ZL_REG_SIZE(reg) != 1) {
|
||||
dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n",
|
||||
ZL_REG_ADDR(reg));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Map the register address to virtual range */
|
||||
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
||||
|
||||
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
|
||||
ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
|
||||
unsigned int mask_reg, u16 mask_val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Set mask for the operation */
|
||||
rc = zl3073x_write_u16(zldev, mask_reg, mask_val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Trigger the operation */
|
||||
rc = zl3073x_write_u8(zldev, op_reg, op_val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Wait for the operation to actually finish */
|
||||
return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_ref_state_fetch - get input reference state
|
||||
* @zldev: pointer to zl3073x_dev structure
|
||||
* @index: input reference index to fetch state for
|
||||
*
|
||||
* Function fetches information for the given input reference that are
|
||||
* invariant and stores them for later use.
|
||||
*
|
||||
* Return: 0 on success, <0 on error
|
||||
*/
|
||||
static int
|
||||
zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
struct zl3073x_ref *input = &zldev->ref[index];
|
||||
u8 ref_config;
|
||||
int rc;
|
||||
|
||||
/* If the input is differential then the configuration for N-pin
|
||||
* reference is ignored and P-pin config is used for both.
|
||||
*/
|
||||
if (zl3073x_is_n_pin(index) &&
|
||||
zl3073x_ref_is_diff(zldev, index - 1)) {
|
||||
input->enabled = zl3073x_ref_is_enabled(zldev, index - 1);
|
||||
input->diff = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
guard(mutex)(&zldev->multiop_lock);
|
||||
|
||||
/* Read reference configuration */
|
||||
rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
|
||||
ZL_REG_REF_MB_MASK, BIT(index));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Read ref_config register */
|
||||
rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref_config);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
input->enabled = FIELD_GET(ZL_REF_CONFIG_ENABLE, ref_config);
|
||||
input->diff = FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref_config);
|
||||
|
||||
dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index,
|
||||
str_enabled_disabled(input->enabled),
|
||||
input->diff ? "differential" : "single-ended");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_out_state_fetch - get output state
|
||||
* @zldev: pointer to zl3073x_dev structure
|
||||
* @index: output index to fetch state for
|
||||
*
|
||||
* Function fetches information for the given output (not output pin)
|
||||
* that are invariant and stores them for later use.
|
||||
*
|
||||
* Return: 0 on success, <0 on error
|
||||
*/
|
||||
static int
|
||||
zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
struct zl3073x_out *out = &zldev->out[index];
|
||||
u8 output_ctrl, output_mode;
|
||||
int rc;
|
||||
|
||||
/* Read output configuration */
|
||||
rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &output_ctrl);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Store info about output enablement and synthesizer the output
|
||||
* is connected to.
|
||||
*/
|
||||
out->enabled = FIELD_GET(ZL_OUTPUT_CTRL_EN, output_ctrl);
|
||||
out->synth = FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, output_ctrl);
|
||||
|
||||
dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index,
|
||||
str_enabled_disabled(out->enabled), out->synth);
|
||||
|
||||
guard(mutex)(&zldev->multiop_lock);
|
||||
|
||||
/* Read output configuration */
|
||||
rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
|
||||
ZL_REG_OUTPUT_MB_MASK, BIT(index));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Read output_mode */
|
||||
rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Extract and store output signal format */
|
||||
out->signal_format = FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT,
|
||||
output_mode);
|
||||
|
||||
dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index,
|
||||
out->signal_format);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_synth_state_fetch - get synth state
|
||||
* @zldev: pointer to zl3073x_dev structure
|
||||
* @index: synth index to fetch state for
|
||||
*
|
||||
* Function fetches information for the given synthesizer that are
|
||||
* invariant and stores them for later use.
|
||||
*
|
||||
* Return: 0 on success, <0 on error
|
||||
*/
|
||||
static int
|
||||
zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
struct zl3073x_synth *synth = &zldev->synth[index];
|
||||
u16 base, m, n;
|
||||
u8 synth_ctrl;
|
||||
u32 mult;
|
||||
int rc;
|
||||
|
||||
/* Read synth control register */
|
||||
rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth_ctrl);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Store info about synth enablement and DPLL channel the synth is
|
||||
* driven by.
|
||||
*/
|
||||
synth->enabled = FIELD_GET(ZL_SYNTH_CTRL_EN, synth_ctrl);
|
||||
synth->dpll = FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth_ctrl);
|
||||
|
||||
dev_dbg(zldev->dev, "SYNTH%u is %s and driven by DPLL%u\n", index,
|
||||
str_enabled_disabled(synth->enabled), synth->dpll);
|
||||
|
||||
guard(mutex)(&zldev->multiop_lock);
|
||||
|
||||
/* Read synth configuration */
|
||||
rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD,
|
||||
ZL_REG_SYNTH_MB_MASK, BIT(index));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* The output frequency is determined by the following formula:
|
||||
* base * multiplier * numerator / denominator
|
||||
*
|
||||
* Read registers with these values
|
||||
*/
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &base);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &mult);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &m);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &n);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Check denominator for zero to avoid div by 0 */
|
||||
if (!n) {
|
||||
dev_err(zldev->dev,
|
||||
"Zero divisor for SYNTH%u retrieved from device\n",
|
||||
index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Compute and store synth frequency */
|
||||
zldev->synth[index].freq = div_u64(mul_u32_u32(base * m, mult), n);
|
||||
|
||||
dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index,
|
||||
zldev->synth[index].freq);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
|
||||
{
|
||||
int rc;
|
||||
u8 i;
|
||||
|
||||
for (i = 0; i < ZL3073X_NUM_REFS; i++) {
|
||||
rc = zl3073x_ref_state_fetch(zldev, i);
|
||||
if (rc) {
|
||||
dev_err(zldev->dev,
|
||||
"Failed to fetch input state: %pe\n",
|
||||
ERR_PTR(rc));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
|
||||
rc = zl3073x_synth_state_fetch(zldev, i);
|
||||
if (rc) {
|
||||
dev_err(zldev->dev,
|
||||
"Failed to fetch synth state: %pe\n",
|
||||
ERR_PTR(rc));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ZL3073X_NUM_OUTS; i++) {
|
||||
rc = zl3073x_out_state_fetch(zldev, i);
|
||||
if (rc) {
|
||||
dev_err(zldev->dev,
|
||||
"Failed to fetch output state: %pe\n",
|
||||
ERR_PTR(rc));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void
|
||||
zl3073x_dev_periodic_work(struct kthread_work *work)
|
||||
{
|
||||
struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev,
|
||||
work.work);
|
||||
struct zl3073x_dpll *zldpll;
|
||||
|
||||
list_for_each_entry(zldpll, &zldev->dplls, list)
|
||||
zl3073x_dpll_changes_check(zldpll);
|
||||
|
||||
/* Run twice a second */
|
||||
kthread_queue_delayed_work(zldev->kworker, &zldev->work,
|
||||
msecs_to_jiffies(500));
|
||||
}
|
||||
|
||||
static void zl3073x_dev_dpll_fini(void *ptr)
|
||||
{
|
||||
struct zl3073x_dpll *zldpll, *next;
|
||||
struct zl3073x_dev *zldev = ptr;
|
||||
|
||||
/* Stop monitoring thread */
|
||||
if (zldev->kworker) {
|
||||
kthread_cancel_delayed_work_sync(&zldev->work);
|
||||
kthread_destroy_worker(zldev->kworker);
|
||||
zldev->kworker = NULL;
|
||||
}
|
||||
|
||||
/* Release DPLLs */
|
||||
list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) {
|
||||
zl3073x_dpll_unregister(zldpll);
|
||||
list_del(&zldpll->list);
|
||||
zl3073x_dpll_free(zldpll);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
|
||||
{
|
||||
struct kthread_worker *kworker;
|
||||
struct zl3073x_dpll *zldpll;
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
INIT_LIST_HEAD(&zldev->dplls);
|
||||
|
||||
/* Initialize all DPLLs */
|
||||
for (i = 0; i < num_dplls; i++) {
|
||||
zldpll = zl3073x_dpll_alloc(zldev, i);
|
||||
if (IS_ERR(zldpll)) {
|
||||
dev_err_probe(zldev->dev, PTR_ERR(zldpll),
|
||||
"Failed to alloc DPLL%u\n", i);
|
||||
rc = PTR_ERR(zldpll);
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = zl3073x_dpll_register(zldpll);
|
||||
if (rc) {
|
||||
dev_err_probe(zldev->dev, rc,
|
||||
"Failed to register DPLL%u\n", i);
|
||||
zl3073x_dpll_free(zldpll);
|
||||
goto error;
|
||||
}
|
||||
|
||||
list_add_tail(&zldpll->list, &zldev->dplls);
|
||||
}
|
||||
|
||||
/* Perform initial firmware fine phase correction */
|
||||
rc = zl3073x_dpll_init_fine_phase_adjust(zldev);
|
||||
if (rc) {
|
||||
dev_err_probe(zldev->dev, rc,
|
||||
"Failed to init fine phase correction\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Initialize monitoring thread */
|
||||
kthread_init_delayed_work(&zldev->work, zl3073x_dev_periodic_work);
|
||||
kworker = kthread_run_worker(0, "zl3073x-%s", dev_name(zldev->dev));
|
||||
if (IS_ERR(kworker)) {
|
||||
rc = PTR_ERR(kworker);
|
||||
goto error;
|
||||
}
|
||||
|
||||
zldev->kworker = kworker;
|
||||
kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0);
|
||||
|
||||
/* Add devres action to release DPLL related resources */
|
||||
rc = devm_add_action_or_reset(zldev->dev, zl3073x_dev_dpll_fini, zldev);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
zl3073x_dev_dpll_fini(zldev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_dev_probe - initialize zl3073x device
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @chip_info: chip info based on compatible
|
||||
*
|
||||
* Common initialization of zl3073x device structure.
|
||||
*
|
||||
* Returns: 0 on success, <0 on error
|
||||
*/
|
||||
int zl3073x_dev_probe(struct zl3073x_dev *zldev,
|
||||
const struct zl3073x_chip_info *chip_info)
|
||||
{
|
||||
u16 id, revision, fw_ver;
|
||||
unsigned int i;
|
||||
u32 cfg_ver;
|
||||
int rc;
|
||||
|
||||
/* Read chip ID */
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Check it matches */
|
||||
for (i = 0; i < chip_info->num_ids; i++) {
|
||||
if (id == chip_info->ids[i])
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == chip_info->num_ids) {
|
||||
return dev_err_probe(zldev->dev, -ENODEV,
|
||||
"Unknown or non-match chip ID: 0x%0x\n",
|
||||
id);
|
||||
}
|
||||
|
||||
/* Read revision, firmware version and custom config version */
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
dev_dbg(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n", id,
|
||||
revision, fw_ver);
|
||||
dev_dbg(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
|
||||
FIELD_GET(GENMASK(31, 24), cfg_ver),
|
||||
FIELD_GET(GENMASK(23, 16), cfg_ver),
|
||||
FIELD_GET(GENMASK(15, 8), cfg_ver),
|
||||
FIELD_GET(GENMASK(7, 0), cfg_ver));
|
||||
|
||||
/* Generate random clock ID as the device has not such property that
|
||||
* could be used for this purpose. A user can later change this value
|
||||
* using devlink.
|
||||
*/
|
||||
zldev->clock_id = get_random_u64();
|
||||
|
||||
/* Initialize mutex for operations where multiple reads, writes
|
||||
* and/or polls are required to be done atomically.
|
||||
*/
|
||||
rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
|
||||
if (rc)
|
||||
return dev_err_probe(zldev->dev, rc,
|
||||
"Failed to initialize mutex\n");
|
||||
|
||||
/* Fetch device state */
|
||||
rc = zl3073x_dev_state_fetch(zldev);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Register DPLL channels */
|
||||
rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Register the devlink instance and parameters */
|
||||
rc = zl3073x_devlink_register(zldev);
|
||||
if (rc)
|
||||
return dev_err_probe(zldev->dev, rc,
|
||||
"Failed to register devlink instance\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, "ZL3073X");
|
||||
|
||||
MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
|
||||
MODULE_DESCRIPTION("Microchip ZL3073x core driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
367
drivers/dpll/zl3073x/core.h
Normal file
367
drivers/dpll/zl3073x/core.h
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _ZL3073X_CORE_H
|
||||
#define _ZL3073X_CORE_H
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "regs.h"
|
||||
|
||||
struct device;
|
||||
struct regmap;
|
||||
struct zl3073x_dpll;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* struct zl3073x_ref - input reference invariant info
|
||||
* @enabled: input reference is enabled or disabled
|
||||
* @diff: true if input reference is differential
|
||||
*/
|
||||
struct zl3073x_ref {
|
||||
bool enabled;
|
||||
bool diff;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct zl3073x_out - output invariant info
|
||||
* @enabled: out is enabled or disabled
|
||||
* @synth: synthesizer the out is connected to
|
||||
* @signal_format: out signal format
|
||||
*/
|
||||
struct zl3073x_out {
|
||||
bool enabled;
|
||||
u8 synth;
|
||||
u8 signal_format;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct zl3073x_synth - synthesizer invariant info
|
||||
* @freq: synthesizer frequency
|
||||
* @dpll: ID of DPLL the synthesizer is driven by
|
||||
* @enabled: synth is enabled or disabled
|
||||
*/
|
||||
struct zl3073x_synth {
|
||||
u32 freq;
|
||||
u8 dpll;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct zl3073x_dev - zl3073x device
|
||||
* @dev: pointer to device
|
||||
* @regmap: regmap to access device registers
|
||||
* @multiop_lock: to serialize multiple register operations
|
||||
* @clock_id: clock id of the device
|
||||
* @ref: array of input references' invariants
|
||||
* @out: array of outs' invariants
|
||||
* @synth: array of synths' invariants
|
||||
* @dplls: list of DPLLs
|
||||
* @kworker: thread for periodic work
|
||||
* @work: periodic work
|
||||
*/
|
||||
struct zl3073x_dev {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct mutex multiop_lock;
|
||||
u64 clock_id;
|
||||
|
||||
/* Invariants */
|
||||
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
|
||||
struct zl3073x_out out[ZL3073X_NUM_OUTS];
|
||||
struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
|
||||
|
||||
/* DPLL channels */
|
||||
struct list_head dplls;
|
||||
|
||||
/* Monitor */
|
||||
struct kthread_worker *kworker;
|
||||
struct kthread_delayed_work work;
|
||||
};
|
||||
|
||||
struct zl3073x_chip_info {
|
||||
const u16 *ids;
|
||||
size_t num_ids;
|
||||
int num_channels;
|
||||
};
|
||||
|
||||
extern const struct zl3073x_chip_info zl30731_chip_info;
|
||||
extern const struct zl3073x_chip_info zl30732_chip_info;
|
||||
extern const struct zl3073x_chip_info zl30733_chip_info;
|
||||
extern const struct zl3073x_chip_info zl30734_chip_info;
|
||||
extern const struct zl3073x_chip_info zl30735_chip_info;
|
||||
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,
|
||||
const struct zl3073x_chip_info *chip_info);
|
||||
|
||||
/**********************
|
||||
* Registers operations
|
||||
**********************/
|
||||
|
||||
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);
|
||||
|
||||
/*****************
|
||||
* Misc operations
|
||||
*****************/
|
||||
|
||||
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
|
||||
|
||||
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_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_ref_is_diff(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->ref[index].diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_ref_is_enabled - check if the given input reference is enabled
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @index: input reference index
|
||||
*
|
||||
* Return: true if input refernce is enabled, false otherwise
|
||||
*/
|
||||
static inline bool
|
||||
zl3073x_ref_is_enabled(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->ref[index].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_synth_dpll_get - get DPLL ID the synth is driven by
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @index: synth index
|
||||
*
|
||||
* Return: ID of DPLL the given synthetizer is driven by
|
||||
*/
|
||||
static inline u8
|
||||
zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->synth[index].dpll;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_synth_freq_get - get synth current freq
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @index: synth index
|
||||
*
|
||||
* Return: frequency of given synthetizer
|
||||
*/
|
||||
static inline u32
|
||||
zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->synth[index].freq;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_synth_is_enabled - check if the given synth is enabled
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @index: synth index
|
||||
*
|
||||
* Return: true if synth is enabled, false otherwise
|
||||
*/
|
||||
static inline bool
|
||||
zl3073x_synth_is_enabled(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->synth[index].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_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_out_synth_get(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->out[index].synth;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_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_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
u8 synth;
|
||||
|
||||
/* Output is enabled only if associated synth is enabled */
|
||||
synth = zl3073x_out_synth_get(zldev, index);
|
||||
if (zl3073x_synth_is_enabled(zldev, synth))
|
||||
return zldev->out[index].enabled;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_out_signal_format_get - get output signal format
|
||||
* @zldev: pointer to zl3073x device
|
||||
* @index: output index
|
||||
*
|
||||
* Return: signal format of given output
|
||||
*/
|
||||
static inline u8
|
||||
zl3073x_out_signal_format_get(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
return zldev->out[index].signal_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_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_out_dpll_get(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
u8 synth;
|
||||
|
||||
/* Get synthesizer connected to given output */
|
||||
synth = zl3073x_out_synth_get(zldev, index);
|
||||
|
||||
/* Return DPLL that drives the synth */
|
||||
return zl3073x_synth_dpll_get(zldev, synth);
|
||||
}
|
||||
|
||||
/**
|
||||
* zl3073x_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_out_is_diff(struct zl3073x_dev *zldev, u8 index)
|
||||
{
|
||||
switch (zl3073x_out_signal_format_get(zldev, index)) {
|
||||
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_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_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id)
|
||||
{
|
||||
u8 output = zl3073x_output_pin_out_get(id);
|
||||
|
||||
/* Check if the whole output is enabled */
|
||||
if (!zl3073x_out_is_enabled(zldev, output))
|
||||
return false;
|
||||
|
||||
/* Check signal format */
|
||||
switch (zl3073x_out_signal_format_get(zldev, output)) {
|
||||
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 */
|
||||
259
drivers/dpll/zl3073x/devlink.c
Normal file
259
drivers/dpll/zl3073x/devlink.c
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
// 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 "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);
|
||||
struct zl3073x_dpll *zldpll;
|
||||
|
||||
if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Unregister all DPLLs */
|
||||
list_for_each_entry(zldpll, &zldev->dplls, list)
|
||||
zl3073x_dpll_unregister(zldpll);
|
||||
|
||||
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;
|
||||
struct zl3073x_dpll *zldpll;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Re-register all DPLLs */
|
||||
list_for_each_entry(zldpll, &zldev->dplls, list) {
|
||||
rc = zl3073x_dpll_register(zldpll);
|
||||
if (rc)
|
||||
dev_warn(zldev->dev,
|
||||
"Failed to re-register DPLL%u\n", zldpll->id);
|
||||
}
|
||||
|
||||
*actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
12
drivers/dpll/zl3073x/devlink.h
Normal file
12
drivers/dpll/zl3073x/devlink.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/* 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);
|
||||
|
||||
#endif /* _ZL3073X_DEVLINK_H */
|
||||
1504
drivers/dpll/zl3073x/dpll.c
Normal file
1504
drivers/dpll/zl3073x/dpll.c
Normal file
File diff suppressed because it is too large
Load Diff
42
drivers/dpll/zl3073x/dpll.h
Normal file
42
drivers/dpll/zl3073x/dpll.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* 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
|
||||
* @refsel_mode: reference selection mode
|
||||
* @forced_ref: selected reference in forced reference lock mode
|
||||
* @dpll_dev: pointer to registered DPLL device
|
||||
* @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 refsel_mode;
|
||||
u8 forced_ref;
|
||||
struct dpll_device *dpll_dev;
|
||||
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 */
|
||||
76
drivers/dpll/zl3073x/i2c.c
Normal file
76
drivers/dpll/zl3073x/i2c.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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, i2c_get_match_data(client));
|
||||
}
|
||||
|
||||
static const struct i2c_device_id zl3073x_i2c_id[] = {
|
||||
{
|
||||
.name = "zl30731",
|
||||
.driver_data = (kernel_ulong_t)&zl30731_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30732",
|
||||
.driver_data = (kernel_ulong_t)&zl30732_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30733",
|
||||
.driver_data = (kernel_ulong_t)&zl30733_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30734",
|
||||
.driver_data = (kernel_ulong_t)&zl30734_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30735",
|
||||
.driver_data = (kernel_ulong_t)&zl30735_chip_info,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id);
|
||||
|
||||
static const struct of_device_id zl3073x_i2c_of_match[] = {
|
||||
{ .compatible = "microchip,zl30731", .data = &zl30731_chip_info },
|
||||
{ .compatible = "microchip,zl30732", .data = &zl30732_chip_info },
|
||||
{ .compatible = "microchip,zl30733", .data = &zl30733_chip_info },
|
||||
{ .compatible = "microchip,zl30734", .data = &zl30734_chip_info },
|
||||
{ .compatible = "microchip,zl30735", .data = &zl30735_chip_info },
|
||||
{ /* 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");
|
||||
358
drivers/dpll/zl3073x/prop.c
Normal file
358
drivers/dpll/zl3073x/prop.c
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
// 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_out_synth_get(zldev, out);
|
||||
|
||||
/* Get synth frequency */
|
||||
synth_freq = zl3073x_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_ref_is_diff(zldev, ref);
|
||||
} else {
|
||||
u8 out;
|
||||
|
||||
prefix = "OUT";
|
||||
out = zl3073x_output_pin_out_get(id);
|
||||
is_diff = zl3073x_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", ®))
|
||||
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, rc;
|
||||
const char *type;
|
||||
u64 *freqs;
|
||||
|
||||
props = kzalloc(sizeof(*props), GFP_KERNEL);
|
||||
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;
|
||||
} else {
|
||||
props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
|
||||
}
|
||||
|
||||
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)
|
||||
return props; /* Return if it does not exist */
|
||||
|
||||
/* 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
|
||||
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)
|
||||
/* Return if the property does not exist or number is 0 */
|
||||
return 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);
|
||||
|
||||
/* Allocate frequency ranges list and fill it */
|
||||
ranges = kcalloc(num_freqs, sizeof(*ranges), GFP_KERNEL);
|
||||
if (!ranges) {
|
||||
rc = -ENOMEM;
|
||||
goto err_alloc_ranges;
|
||||
}
|
||||
|
||||
/* Convert list of frequencies to list of frequency ranges but
|
||||
* filter-out frequencies that are not representable by device
|
||||
*/
|
||||
for (i = 0, j = 0; i < num_freqs; i++) {
|
||||
struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
|
||||
|
||||
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
drivers/dpll/zl3073x/prop.h
Normal file
34
drivers/dpll/zl3073x/prop.h
Normal 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 */
|
||||
208
drivers/dpll/zl3073x/regs.h
Normal file
208
drivers/dpll/zl3073x/regs.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _ZL3073X_REGS_H
|
||||
#define _ZL3073X_REGS_H
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
|
||||
/*
|
||||
* 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_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)
|
||||
|
||||
/*************************
|
||||
* 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 /* all bits zeroed */
|
||||
|
||||
#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
|
||||
|
||||
/***********************
|
||||
* 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)
|
||||
|
||||
/***********************************
|
||||
* 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)
|
||||
|
||||
/********************************
|
||||
* 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_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)
|
||||
|
||||
#endif /* _ZL3073X_REGS_H */
|
||||
76
drivers/dpll/zl3073x/spi.c
Normal file
76
drivers/dpll/zl3073x/spi.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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, spi_get_device_match_data(spi));
|
||||
}
|
||||
|
||||
static const struct spi_device_id zl3073x_spi_id[] = {
|
||||
{
|
||||
.name = "zl30731",
|
||||
.driver_data = (kernel_ulong_t)&zl30731_chip_info
|
||||
},
|
||||
{
|
||||
.name = "zl30732",
|
||||
.driver_data = (kernel_ulong_t)&zl30732_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30733",
|
||||
.driver_data = (kernel_ulong_t)&zl30733_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30734",
|
||||
.driver_data = (kernel_ulong_t)&zl30734_chip_info,
|
||||
},
|
||||
{
|
||||
.name = "zl30735",
|
||||
.driver_data = (kernel_ulong_t)&zl30735_chip_info,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
|
||||
|
||||
static const struct of_device_id zl3073x_spi_of_match[] = {
|
||||
{ .compatible = "microchip,zl30731", .data = &zl30731_chip_info },
|
||||
{ .compatible = "microchip,zl30732", .data = &zl30732_chip_info },
|
||||
{ .compatible = "microchip,zl30733", .data = &zl30733_chip_info },
|
||||
{ .compatible = "microchip,zl30734", .data = &zl30734_chip_info },
|
||||
{ .compatible = "microchip,zl30735", .data = &zl30735_chip_info },
|
||||
{ /* 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");
|
||||
|
|
@ -425,6 +425,7 @@ enum devlink_param_type {
|
|||
DEVLINK_PARAM_TYPE_U8 = DEVLINK_VAR_ATTR_TYPE_U8,
|
||||
DEVLINK_PARAM_TYPE_U16 = DEVLINK_VAR_ATTR_TYPE_U16,
|
||||
DEVLINK_PARAM_TYPE_U32 = DEVLINK_VAR_ATTR_TYPE_U32,
|
||||
DEVLINK_PARAM_TYPE_U64 = DEVLINK_VAR_ATTR_TYPE_U64,
|
||||
DEVLINK_PARAM_TYPE_STRING = DEVLINK_VAR_ATTR_TYPE_STRING,
|
||||
DEVLINK_PARAM_TYPE_BOOL = DEVLINK_VAR_ATTR_TYPE_FLAG,
|
||||
};
|
||||
|
|
@ -433,6 +434,7 @@ union devlink_param_value {
|
|||
u8 vu8;
|
||||
u16 vu16;
|
||||
u32 vu32;
|
||||
u64 vu64;
|
||||
char vstr[__DEVLINK_PARAM_MAX_STRING_VALUE];
|
||||
bool vbool;
|
||||
};
|
||||
|
|
@ -523,6 +525,7 @@ enum devlink_param_generic_id {
|
|||
DEVLINK_PARAM_GENERIC_ID_IO_EQ_SIZE,
|
||||
DEVLINK_PARAM_GENERIC_ID_EVENT_EQ_SIZE,
|
||||
DEVLINK_PARAM_GENERIC_ID_ENABLE_PHC,
|
||||
DEVLINK_PARAM_GENERIC_ID_CLOCK_ID,
|
||||
|
||||
/* add new param generic ids above here*/
|
||||
__DEVLINK_PARAM_GENERIC_ID_MAX,
|
||||
|
|
@ -584,6 +587,9 @@ enum devlink_param_generic_id {
|
|||
#define DEVLINK_PARAM_GENERIC_ENABLE_PHC_NAME "enable_phc"
|
||||
#define DEVLINK_PARAM_GENERIC_ENABLE_PHC_TYPE DEVLINK_PARAM_TYPE_BOOL
|
||||
|
||||
#define DEVLINK_PARAM_GENERIC_CLOCK_ID_NAME "clock_id"
|
||||
#define DEVLINK_PARAM_GENERIC_CLOCK_ID_TYPE DEVLINK_PARAM_TYPE_U64
|
||||
|
||||
#define DEVLINK_PARAM_GENERIC(_id, _cmodes, _get, _set, _validate) \
|
||||
{ \
|
||||
.id = DEVLINK_PARAM_GENERIC_ID_##_id, \
|
||||
|
|
|
|||
|
|
@ -97,6 +97,11 @@ static const struct devlink_param devlink_param_generic[] = {
|
|||
.name = DEVLINK_PARAM_GENERIC_ENABLE_PHC_NAME,
|
||||
.type = DEVLINK_PARAM_GENERIC_ENABLE_PHC_TYPE,
|
||||
},
|
||||
{
|
||||
.id = DEVLINK_PARAM_GENERIC_ID_CLOCK_ID,
|
||||
.name = DEVLINK_PARAM_GENERIC_CLOCK_ID_NAME,
|
||||
.type = DEVLINK_PARAM_GENERIC_CLOCK_ID_TYPE,
|
||||
},
|
||||
};
|
||||
|
||||
static int devlink_param_generic_verify(const struct devlink_param *param)
|
||||
|
|
@ -200,6 +205,11 @@ devlink_nl_param_value_fill_one(struct sk_buff *msg,
|
|||
if (nla_put_u32(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu32))
|
||||
goto value_nest_cancel;
|
||||
break;
|
||||
case DEVLINK_PARAM_TYPE_U64:
|
||||
if (devlink_nl_put_u64(msg, DEVLINK_ATTR_PARAM_VALUE_DATA,
|
||||
val.vu64))
|
||||
goto value_nest_cancel;
|
||||
break;
|
||||
case DEVLINK_PARAM_TYPE_STRING:
|
||||
if (nla_put_string(msg, DEVLINK_ATTR_PARAM_VALUE_DATA,
|
||||
val.vstr))
|
||||
|
|
@ -434,6 +444,11 @@ devlink_param_value_get_from_info(const struct devlink_param *param,
|
|||
return -EINVAL;
|
||||
value->vu32 = nla_get_u32(param_data);
|
||||
break;
|
||||
case DEVLINK_PARAM_TYPE_U64:
|
||||
if (nla_len(param_data) != sizeof(u64))
|
||||
return -EINVAL;
|
||||
value->vu64 = nla_get_u64(param_data);
|
||||
break;
|
||||
case DEVLINK_PARAM_TYPE_STRING:
|
||||
len = strnlen(nla_data(param_data), nla_len(param_data));
|
||||
if (len == nla_len(param_data) ||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user