* [PATCH 00/28] Add Microchip ZL3073x support
@ 2025-04-07 17:28 Ivan Vecera
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
` (28 more replies)
0 siblings, 29 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andy Shevchenko, Andrew Morton, Michal Schmidt,
devicetree, linux-kernel, linux-hardening
This series adds support for Microchip Azurite DPLL/PTP/SyncE chip
family. These chips provide DPLL and PTP functionality, so the series
adds the common MFD driver that provides an access to the bus that can
be either I2C or SPI. The second part of the series is DPLL driver that
covers DPLL functionality. The PTP support will be added by separate
series as well as flashing capability.
All functionality was tested by myself and by Prathosh Satish on
Microchip EDS2 development board with ZL30732 DPLL chip connected over
I2C bus.
Patch breakdown
===============
Patch 1 - Basic support for I2C, SPI and regmap
Patch 2 - Devlink registration
Patches 3-4 - Helpers for accessing device registers
Patches 5-6 - Component versions reporting via devlink dev info
Patches 7-8 - Helpers for accessing register mailboxes
Patch 9 - Clock ID generation for DPLL driver
Patch 10 - Export strnchrnul function for modules
(used by next patch)
Patch 11 - Support for MFG config initialization file
Patch 12 - Fetch invariant register values used by DPLL and later by
PTP driver
Patch 13 - Basic DPLL driver with required only functionality
Patch 14 - Registration of DPLL sub-devices by MFD driver
Patch 15 - Device tree bindings for DPLL device and pin
Patch 16 - Device tree bindings for ZL3073x device
Patch 17 - Read DPLL device types from firmware (DT,ACPI...)
Patch 18 - Read basic pin properties from firmware
Patch 19 - Implementation of input pin selection for DPLL in manual mode
Patch 20 - Implementation of getting/setting priority for input pins
Patch 21 - Implementation of input pin state setting for DPLL in auto mode
Patch 22 - Implementation of getting/setting frequency for input pins
Patch 23 - Implementation of getting/setting frequency for output pins
Patch 24 - Read pin supported frequencies from firmware
Patch 25 - Implementation of getting phase offset from input pins
Patch 26 - Implementation of getting/setting phase adjust for both
pin types
Patch 27 - Implementation of getting/setting embedded sync frequency
for both pin types
Patch 28 - Implementation of getting fractional frequency offset for
input pins
Ivan Vecera (28):
mfd: Add Microchip ZL3073x support
mfd: zl3073x: Register itself as devlink device
mfd: zl3073x: Add register access helpers
mfd: zl3073x: Add macros for device registers access
mfd: zl3073x: Add components versions register defs
mfd: zl3073x: Implement devlink device info
mfd: zl3073x: Add macro to wait for register value bits to be cleared
mfd: zl3073x: Add functions to work with register mailboxes
mfd: zl3073x: Add clock_id field
lib: Allow modules to use strnchrnul
mfd: zl3073x: Load mfg file into HW if it is present
mfd: zl3073x: Fetch invariants during probe
dpll: Add Microchip ZL3073x DPLL driver
mfd: zl3073x: Register DPLL sub-device during init
dt-bindings: dpll: Add device tree bindings for DPLL device and pin
dt-bindings: dpll: Add support for Microchip Azurite chip family
dpll: zl3073x: Read DPLL types from firmware
dpll: zl3073x: Read optional pin properties from firmware
dpll: zl3073x: Implement input pin selection in manual mode
dpll: zl3073x: Add support to get/set priority on input pins
dpll: zl3073x: Implement input pin state setting in automatic mode
dpll: zl3073x: Add support to get/set frequency on input pins
dpll: zl3073x: Add support to get/set frequency on output pins
dpll: zl3073x: Read pin supported frequencies from firmware
dpll: zl3073x: Add support to get phase offset on input pins
dpll: zl3073x: Add support to get/set phase adjust on pins
dpll: zl3073x: Add support to get/set esync on pins
dpll: zl3073x: Add support to get fractional frequency offset on input
pins
.../devicetree/bindings/dpll/dpll-device.yaml | 84 +
.../devicetree/bindings/dpll/dpll-pin.yaml | 43 +
.../bindings/dpll/microchip,zl3073x.yaml | 74 +
MAINTAINERS | 12 +
drivers/dpll/Kconfig | 16 +
drivers/dpll/Makefile | 2 +
drivers/dpll/dpll_zl3073x.c | 2768 +++++++++++++++++
drivers/mfd/Kconfig | 33 +
drivers/mfd/Makefile | 5 +
drivers/mfd/zl3073x-core.c | 840 +++++
drivers/mfd/zl3073x-i2c.c | 71 +
drivers/mfd/zl3073x-spi.c | 72 +
drivers/mfd/zl3073x.h | 13 +
include/linux/mfd/zl3073x.h | 335 ++
lib/string.c | 1 +
15 files changed, 4369 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dpll/dpll-device.yaml
create mode 100644 Documentation/devicetree/bindings/dpll/dpll-pin.yaml
create mode 100644 Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
create mode 100644 drivers/dpll/dpll_zl3073x.c
create mode 100644 drivers/mfd/zl3073x-core.c
create mode 100644 drivers/mfd/zl3073x-i2c.c
create mode 100644 drivers/mfd/zl3073x-spi.c
create mode 100644 drivers/mfd/zl3073x.h
create mode 100644 include/linux/mfd/zl3073x.h
--
2.48.1
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:53 ` Krzysztof Kozlowski
2025-04-07 18:05 ` Andy Shevchenko
2025-04-07 17:28 ` [PATCH 02/28] mfd: zl3073x: Register itself as devlink device Ivan Vecera
` (27 subsequent siblings)
28 siblings, 2 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
This adds base MFD driver for Microchip Azurite ZL3073x chip family.
These chips provide DPLL and PHC (PTP) functionality and they can
be connected over I2C or SPI bus.
The MFD driver provide basic communication and synchronization
over the bus and common functionality that are used by the DPLL
driver (later in this series) and by the PTP driver (will be
added later).
The chip family is characterized by following properties:
* 2 separate DPLL units (channels)
* 5 synthesizers
* 10 input pins (references)
* 10 outputs
* 20 output pins (output pin pair shares one output)
* Each reference and output can act in differential or single-ended
mode (reference or output in differential mode consumes 2 pins)
* Each output is connected to one of the synthesizers
* Each synthesizer is driven by one of the DPLL unit
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
MAINTAINERS | 8 +++++
drivers/mfd/Kconfig | 30 ++++++++++++++++
drivers/mfd/Makefile | 5 +++
drivers/mfd/zl3073x-core.c | 70 ++++++++++++++++++++++++++++++++++++
drivers/mfd/zl3073x-i2c.c | 70 ++++++++++++++++++++++++++++++++++++
drivers/mfd/zl3073x-spi.c | 71 +++++++++++++++++++++++++++++++++++++
drivers/mfd/zl3073x.h | 13 +++++++
include/linux/mfd/zl3073x.h | 15 ++++++++
8 files changed, 282 insertions(+)
create mode 100644 drivers/mfd/zl3073x-core.c
create mode 100644 drivers/mfd/zl3073x-i2c.c
create mode 100644 drivers/mfd/zl3073x-spi.c
create mode 100644 drivers/mfd/zl3073x.h
create mode 100644 include/linux/mfd/zl3073x.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 4c5c2e2c12787..c69a69d862310 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15994,6 +15994,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: drivers/mfd/zl3073x*
+F: include/linux/mfd/zl3073x.h
+
MICROSEMI MIPS SOCS
M: Alexandre Belloni <alexandre.belloni@bootlin.com>
M: UNGLinuxDriver@microchip.com
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 22b9363100394..30b36e3ee8f7f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2422,5 +2422,35 @@ config MFD_UPBOARD_FPGA
To compile this driver as a module, choose M here: the module will be
called upboard-fpga.
+config MFD_ZL3073X_CORE
+ tristate
+ select MFD_CORE
+
+config MFD_ZL3073X_I2C
+ tristate "Microchip Azurite DPLL/PTP/SyncE with I2C"
+ depends on I2C
+ select MFD_ZL3073X_CORE
+ select REGMAP_I2C
+ help
+ Support for Microchip Azurite DPLL/PTP/SyncE chip family. This option
+ supports I2C as the control interface.
+
+ This driver provides common support for accessing the device.
+ Additional drivers must be enabled in order to use the functionality
+ of the device.
+
+config MFD_ZL3073X_SPI
+ tristate "Microchip Azurite DPLL/PTP/SyncE with SPI"
+ depends on SPI
+ select MFD_ZL3073X_CORE
+ select REGMAP_SPI
+ help
+ Support for Microchip Azurite DPLL/PTP/SyncE chip family. This option
+ supports SPI as the control interface.
+
+ This driver provides common support for accessing the device.
+ Additional drivers must be enabled in order to use the functionality
+ of the device.
+
endmenu
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 948cbdf42a18b..76e2babc1538f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -290,3 +290,8 @@ obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o
obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
+
+zl3073x-y := zl3073x-core.o
+obj-$(CONFIG_MFD_ZL3073X_CORE) += zl3073x.o
+obj-$(CONFIG_MFD_ZL3073X_I2C) += zl3073x-i2c.o
+obj-$(CONFIG_MFD_ZL3073X_SPI) += zl3073x-spi.o
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
new file mode 100644
index 0000000000000..67a9d5a0e2d8c
--- /dev/null
+++ b/drivers/mfd/zl3073x-core.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include "zl3073x.h"
+
+/*
+ * Regmap ranges
+ */
+#define ZL3073x_PAGE_SIZE 128
+#define ZL3073x_NUM_PAGES 16
+#define ZL3073x_PAGE_SEL 0x7F
+
+static const struct regmap_range_cfg zl3073x_regmap_ranges[] = {
+ {
+ .range_min = 0,
+ .range_max = ZL3073x_NUM_PAGES * ZL3073x_PAGE_SIZE,
+ .selector_reg = ZL3073x_PAGE_SEL,
+ .selector_mask = GENMASK(3, 0),
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = ZL3073x_PAGE_SIZE,
+ },
+};
+
+/*
+ * Regmap config
+ */
+const struct regmap_config zl3073x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ZL3073x_NUM_PAGES * ZL3073x_PAGE_SIZE,
+ .ranges = zl3073x_regmap_ranges,
+ .num_ranges = ARRAY_SIZE(zl3073x_regmap_ranges),
+};
+
+/**
+ * zl3073x_get_regmap_config - return pointer to regmap config
+ *
+ * Returns pointer to regmap config
+ */
+const struct regmap_config *zl3073x_get_regmap_config(void)
+{
+ return &zl3073x_regmap_config;
+}
+EXPORT_SYMBOL_NS_GPL(zl3073x_get_regmap_config, "ZL3073X");
+
+struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
+{
+ struct zl3073x_dev *zldev;
+
+ return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
+}
+EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
+
+int zl3073x_dev_init(struct zl3073x_dev *zldev)
+{
+ devm_mutex_init(zldev->dev, &zldev->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
+
+void zl3073x_dev_exit(struct zl3073x_dev *zldev)
+{
+}
+EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
+
+MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
+MODULE_DESCRIPTION("Microchip ZL3073x core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/zl3073x-i2c.c b/drivers/mfd/zl3073x-i2c.c
new file mode 100644
index 0000000000000..8c8b2ba176766
--- /dev/null
+++ b/drivers/mfd/zl3073x-i2c.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "zl3073x.h"
+
+static const struct i2c_device_id zl3073x_i2c_id[] = {
+ { "zl3073x-i2c", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id);
+
+static const struct of_device_id zl3073x_i2c_of_match[] = {
+ { .compatible = "microchip,zl3073x-i2c" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match);
+
+static int zl3073x_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct i2c_device_id *id;
+ struct zl3073x_dev *zldev;
+ int rc = 0;
+
+ zldev = zl3073x_dev_alloc(dev);
+ if (!zldev)
+ return -ENOMEM;
+
+ id = i2c_client_get_device_id(client);
+ zldev->dev = dev;
+
+ zldev->regmap = devm_regmap_init_i2c(client,
+ zl3073x_get_regmap_config());
+ if (IS_ERR(zldev->regmap)) {
+ rc = PTR_ERR(zldev->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", rc);
+ return rc;
+ }
+
+ i2c_set_clientdata(client, zldev);
+
+ return zl3073x_dev_init(zldev);
+}
+
+static void zl3073x_i2c_remove(struct i2c_client *client)
+{
+ struct zl3073x_dev *zldev;
+
+ zldev = i2c_get_clientdata(client);
+ zl3073x_dev_exit(zldev);
+}
+
+static struct i2c_driver zl3073x_i2c_driver = {
+ .driver = {
+ .name = "zl3073x-i2c",
+ .of_match_table = of_match_ptr(zl3073x_i2c_of_match),
+ },
+ .probe = zl3073x_i2c_probe,
+ .remove = zl3073x_i2c_remove,
+ .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");
diff --git a/drivers/mfd/zl3073x-spi.c b/drivers/mfd/zl3073x-spi.c
new file mode 100644
index 0000000000000..a6b9a366a7585
--- /dev/null
+++ b/drivers/mfd/zl3073x-spi.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+#include "zl3073x.h"
+
+static const struct spi_device_id zl3073x_spi_id[] = {
+ { "zl3073x-spi", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
+
+static const struct of_device_id zl3073x_spi_of_match[] = {
+ { .compatible = "microchip,zl3073x-spi" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match);
+
+static int zl3073x_spi_probe(struct spi_device *spidev)
+{
+ struct device *dev = &spidev->dev;
+ const struct spi_device_id *id;
+ struct zl3073x_dev *zldev;
+ int rc;
+
+ zldev = zl3073x_dev_alloc(dev);
+ if (!zldev)
+ return -ENOMEM;
+
+ id = spi_get_device_id(spidev);
+ zldev->dev = dev;
+
+ zldev->regmap = devm_regmap_init_spi(spidev,
+ zl3073x_get_regmap_config());
+ if (IS_ERR(zldev->regmap)) {
+ rc = PTR_ERR(zldev->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", rc);
+ return rc;
+ }
+
+ spi_set_drvdata(spidev, zldev);
+
+ return zl3073x_dev_init(zldev);
+}
+
+static void zl3073x_spi_remove(struct spi_device *spidev)
+{
+ struct zl3073x_dev *zldev;
+
+ zldev = spi_get_drvdata(spidev);
+ zl3073x_dev_exit(zldev);
+}
+
+static struct spi_driver zl3073x_spi_driver = {
+ .driver = {
+ .name = "zl3073x-spi",
+ .of_match_table = of_match_ptr(zl3073x_spi_of_match),
+ },
+ .probe = zl3073x_spi_probe,
+ .remove = zl3073x_spi_remove,
+ .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");
diff --git a/drivers/mfd/zl3073x.h b/drivers/mfd/zl3073x.h
new file mode 100644
index 0000000000000..582cb40d681d3
--- /dev/null
+++ b/drivers/mfd/zl3073x.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __ZL3073X_CORE_H
+#define __ZL3073X_CORE_H
+
+#include <linux/mfd/zl3073x.h>
+
+struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev);
+int zl3073x_dev_init(struct zl3073x_dev *zldev);
+void zl3073x_dev_exit(struct zl3073x_dev *zldev);
+const struct regmap_config *zl3073x_get_regmap_config(void);
+
+#endif /* __ZL3073X_CORE_H */
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
new file mode 100644
index 0000000000000..7b80c3059b5f3
--- /dev/null
+++ b/include/linux/mfd/zl3073x.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __LINUX_MFD_ZL3073X_H
+#define __LINUX_MFD_ZL3073X_H
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+struct zl3073x_dev {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+};
+
+#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 02/28] mfd: zl3073x: Register itself as devlink device
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 20:57 ` Andrew Lunn
2025-04-07 17:28 ` [PATCH 03/28] mfd: zl3073x: Add register access helpers Ivan Vecera
` (26 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Use devlink_alloc() to alloc zl3073x_dev structure and register
the device as a devlink device. Follow-up patches add support for
devlink device info reporting and devlink flash interface will
be later used for flashing firmware and configuration.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/Kconfig | 3 +++
drivers/mfd/zl3073x-core.c | 27 +++++++++++++++++++++++++--
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 30b36e3ee8f7f..a838d5dca4579 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2424,11 +2424,13 @@ config MFD_UPBOARD_FPGA
config MFD_ZL3073X_CORE
tristate
+ select NET_DEVLINK
select MFD_CORE
config MFD_ZL3073X_I2C
tristate "Microchip Azurite DPLL/PTP/SyncE with I2C"
depends on I2C
+ depends on NET
select MFD_ZL3073X_CORE
select REGMAP_I2C
help
@@ -2441,6 +2443,7 @@ config MFD_ZL3073X_I2C
config MFD_ZL3073X_SPI
tristate "Microchip Azurite DPLL/PTP/SyncE with SPI"
+ depends on NET
depends on SPI
select MFD_ZL3073X_CORE
select REGMAP_SPI
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 67a9d5a0e2d8c..71454f683eab0 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
+#include <net/devlink.h>
#include "zl3073x.h"
/*
@@ -44,24 +45,46 @@ const struct regmap_config *zl3073x_get_regmap_config(void)
}
EXPORT_SYMBOL_NS_GPL(zl3073x_get_regmap_config, "ZL3073X");
+static const struct devlink_ops zl3073x_devlink_ops = {
+};
+
+static void zl3073x_devlink_free(void *ptr)
+{
+ devlink_free(ptr);
+}
+
struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
{
- struct zl3073x_dev *zldev;
+ struct devlink *devlink;
- return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
+ devlink = devlink_alloc(&zl3073x_devlink_ops,
+ sizeof(struct zl3073x_dev), dev);
+ if (!devlink)
+ return NULL;
+
+ if (devm_add_action_or_reset(dev, zl3073x_devlink_free, devlink))
+ return NULL;
+
+ return devlink_priv(devlink);
}
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
int zl3073x_dev_init(struct zl3073x_dev *zldev)
{
+ struct devlink *devlink;
+
devm_mutex_init(zldev->dev, &zldev->lock);
+ devlink = priv_to_devlink(zldev);
+ devlink_register(devlink);
+
return 0;
}
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
void zl3073x_dev_exit(struct zl3073x_dev *zldev)
{
+ devlink_unregister(priv_to_devlink(zldev));
}
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 03/28] mfd: zl3073x: Add register access helpers
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
2025-04-07 17:28 ` [PATCH 02/28] mfd: zl3073x: Register itself as devlink device Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 21:03 ` Andrew Lunn
2025-04-07 17:28 ` [PATCH 04/28] mfd: zl3073x: Add macros for device registers access Ivan Vecera
` (25 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Add helpers zl3073x_{read,write}_reg() to access device registers.
These functions have to be called with device lock that can be taken
by zl3073x_{lock,unlock}() or a caller can use defined guard.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 88 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/zl3073x.h | 32 ++++++++++++++
2 files changed, 120 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 71454f683eab0..39d4c8608a740 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
+#include <linux/unaligned.h>
#include <net/devlink.h>
#include "zl3073x.h"
@@ -45,6 +46,93 @@ const struct regmap_config *zl3073x_get_regmap_config(void)
}
EXPORT_SYMBOL_NS_GPL(zl3073x_get_regmap_config, "ZL3073X");
+/**
+ * zl3073x_read_reg - Read value from device register
+ * @zldev: device structure pointer
+ * @reg: register to be read
+ * @len: number of bytes to read
+ * @value: pointer to place to store value read from the register
+ *
+ * Caller has to hold the device lock that can be obtained
+ * by zl3073x_lock().
+ *
+ * Returns 0 in case of success or negative value otherwise
+ */
+int zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg,
+ unsigned int len, void *value)
+{
+ u8 buf[6];
+ int rc;
+
+ lockdep_assert_held(&zldev->lock);
+
+ rc = regmap_bulk_read(zldev->regmap, reg, buf, len);
+ if (rc)
+ return rc;
+
+ switch (len) {
+ case 1:
+ *(u8 *)value = buf[0];
+ break;
+ case 2:
+ *(u16 *)value = get_unaligned_be16(buf);
+ break;
+ case 4:
+ *(u32 *)value = get_unaligned_be32(buf);
+ break;
+ case 6:
+ *(u64 *)value = get_unaligned_be48(buf);
+ break;
+ default:
+ WARN(true, "Unsupported register size: %u\n", len);
+ break;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(zl3073x_read_reg);
+
+/**
+ * zl3073x_write_reg - Write value to device register
+ * @zldev: device structure pointer
+ * @reg: register to be written
+ * @len: number of bytes to write
+ * @value: pointer to value to write to the register
+ *
+ * Caller has to hold the device lock that can be obtained
+ * by zl3073x_lock().
+ *
+ * Returns 0 in case of success or negative value otherwise
+ */
+int zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg,
+ unsigned int len, const void *value)
+{
+ u8 buf[6];
+
+ lockdep_assert_held(&zldev->lock);
+
+ switch (len) {
+ case 1:
+ buf[0] = *(u8 *)value;
+ break;
+ case 2:
+ put_unaligned_be16(*(u16 *)value, buf);
+ break;
+ case 4:
+ put_unaligned_be32(*(u32 *)value, buf);
+ break;
+ case 6:
+ put_unaligned_be48(*(u64 *)value, buf);
+ break;
+ default:
+ WARN(true, "Unsupported register size: %u\n", len);
+ break;
+ }
+
+ return regmap_bulk_write(zldev->regmap, reg, buf, len);
+}
+EXPORT_SYMBOL_GPL(zl3073x_write_reg);
+
static const struct devlink_ops zl3073x_devlink_ops = {
};
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 7b80c3059b5f3..0156f9044d79d 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -12,4 +12,36 @@ struct zl3073x_dev {
struct mutex lock;
};
+/**
+ * zl3073x_lock - Lock the device
+ * @zldev: device structure pointer
+ *
+ * Caller has to take this lock when it needs to access device registers.
+ */
+static inline void zl3073x_lock(struct zl3073x_dev *zldev)
+{
+ mutex_lock(&zldev->lock);
+}
+
+/**
+ * zl3073x_unlock - Unlock the device
+ * @zldev: device structure pointer
+ *
+ * Caller unlocks the device when it does not need to access device
+ * registers anymore.
+ */
+static inline void zl3073x_unlock(struct zl3073x_dev *zldev)
+{
+ mutex_unlock(&zldev->lock);
+}
+
+DEFINE_GUARD(zl3073x, struct zl3073x_dev *, zl3073x_lock(_T),
+ zl3073x_unlock(_T));
+
+int zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg,
+ unsigned int len, void *value);
+
+int zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg,
+ unsigned int len, const void *value);
+
#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 04/28] mfd: zl3073x: Add macros for device registers access
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (2 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 03/28] mfd: zl3073x: Add register access helpers Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 05/28] mfd: zl3073x: Add components versions register defs Ivan Vecera
` (24 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Add several macros to access device registers. These macros
defines a couple of static inline functions to ease an access
device registers. There are two types of registers, the 1st type
is a simple one that is defined by an address and size and the 2nd
type is indexed register that is defined by base address, type,
number of register instances and address stride between instances.
Examples:
__ZL3073X_REG_DEF(reg1, 0x1234, 4, u32);
__ZL3073X_REG_IDX_DEF(idx_reg2, 0x1234, 2, u16, 4, 0x10);
this defines the following functions:
int zl3073x_read_reg1(struct zl3073x_dev *dev, u32 *value);
int zl3073x_write_reg1(struct zl3073x_dev *dev, u32 value);
int zl3073x_read_idx_reg2(struct zl3073x_dev *dev, unsigned int idx,
u32 *value);
int zl3073x_write_idx_reg2(struct zl3073x_dev *dev, unsigned int idx,
u32 value);
There are also several shortcut macros to define registers with
certain bit widths: 8, 16, 32 and 48 bits.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
include/linux/mfd/zl3073x.h | 99 +++++++++++++++++++++++++++++++++++++
1 file changed, 99 insertions(+)
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 0156f9044d79d..3524426f0e3ba 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -44,4 +44,103 @@ int zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg,
int zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg,
unsigned int len, const void *value);
+/**
+ * __ZL3073X_REG_DEF - Define a device register helpers
+ * @_name: register name
+ * @_addr: register address
+ * @_len: size of register value in bytes
+ * @_type: type of register value
+ *
+ * The macro defines helper functions for particular device register
+ * to access it.
+ *
+ * Example:
+ * __ZL3073X_REG_DEF(sample_reg, 0x1234, 4, u32)
+ *
+ * generates static inline functions:
+ * int zl3073x_read_sample_reg(struct zl3073x_dev *dev, u32 *value);
+ * int zl3073x_write_sample_reg(struct zl3073x_dev *dev, u32 value);
+ *
+ * Note that these functions have to be called with the device lock
+ * taken.
+ */
+#define __ZL3073X_REG_DEF(_name, _addr, _len, _type) \
+typedef _type zl3073x_##_name##_t; \
+static inline \
+int zl3073x_read_##_name(struct zl3073x_dev *zldev, _type * value) \
+{ \
+ return zl3073x_read_reg(zldev, _addr, _len, value); \
+} \
+static inline \
+int zl3073x_write_##_name(struct zl3073x_dev *zldev, _type value) \
+{ \
+ return zl3073x_write_reg(zldev, _addr, _len, &value); \
+}
+
+/**
+ * __ZL3073X_REG_IDX_DEF - Define an indexed device register helpers
+ * @_name: register name
+ * @_addr: register address
+ * @_len: size of register value in bytes
+ * @_type: type of register value
+ * @_num: number of register instances
+ * @_stride: address stride between instances
+ *
+ * The macro defines helper functions for particular indexed device
+ * register to access it.
+ *
+ * Example:
+ * __ZL3073X_REG_IDX_DEF(sample_reg, 0x1234, 2, u16, 4, 0x10)
+ *
+ * generates static inline functions:
+ * int zl3073x_read_sample_reg(struct zl3073x_dev *dev, unsigned int idx,
+ * u32 *value);
+ * int zl3073x_write_sample_reg(struct zl3073x_dev *dev, unsigned int idx,
+ * u32 value);
+ *
+ * Note that these functions have to be called with the device lock
+ * taken.
+ */
+#define __ZL3073X_REG_IDX_DEF(_name, _addr, _len, _type, _num, _stride) \
+typedef _type zl3073x_##_name##_t; \
+static inline \
+int zl3073x_read_##_name(struct zl3073x_dev *zldev, unsigned int idx, \
+ _type * value) \
+{ \
+ WARN_ON(idx >= (_num)); \
+ return zl3073x_read_reg(zldev, (_addr) + idx * (_stride), _len, \
+ value); \
+} \
+static inline \
+int zl3073x_write_##_name(struct zl3073x_dev *zldev, unsigned int idx, \
+ _type value) \
+{ \
+ WARN_ON(idx >= (_num)); \
+ return zl3073x_write_reg(zldev, (_addr) + idx * (_stride), \
+ _len, &value); \
+}
+
+/*
+ * Add register definition shortcuts for 8, 16, 32 and 48 bits
+ */
+#define ZL3073X_REG8_DEF(_name, _addr) __ZL3073X_REG_DEF(_name, _addr, 1, u8)
+#define ZL3073X_REG16_DEF(_name, _addr) __ZL3073X_REG_DEF(_name, _addr, 2, u16)
+#define ZL3073X_REG32_DEF(_name, _addr) __ZL3073X_REG_DEF(_name, _addr, 4, u32)
+#define ZL3073X_REG48_DEF(_name, _addr) __ZL3073X_REG_DEF(_name, _addr, 6, u64)
+
+/*
+ * Add indexed register definition shortcuts for 8, 16, 32 and 48 bits
+ */
+#define ZL3073X_REG8_IDX_DEF(_name, _addr, _num, _stride) \
+ __ZL3073X_REG_IDX_DEF(_name, _addr, 1, u8, _num, _stride)
+
+#define ZL3073X_REG16_IDX_DEF(_name, _addr, _num, _stride) \
+ __ZL3073X_REG_IDX_DEF(_name, _addr, 2, u16, _num, _stride)
+
+#define ZL3073X_REG32_IDX_DEF(_name, _addr, _num, _stride) \
+ __ZL3073X_REG_IDX_DEF(_name, _addr, 4, u32, _num, _stride)
+
+#define ZL3073X_REG48_IDX_DEF(_name, _addr, _num, _stride) \
+ __ZL3073X_REG_IDX_DEF(_name, _addr, 6, u64, _num, _stride)
+
#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (3 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 04/28] mfd: zl3073x: Add macros for device registers access Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 21:09 ` Andrew Lunn
2025-04-07 17:28 ` [PATCH 06/28] mfd: zl3073x: Implement devlink device info Ivan Vecera
` (23 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Add register definitions for components versions and report them
during probe.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 39d4c8608a740..b3091b00cffa8 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -1,10 +1,19 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/unaligned.h>
#include <net/devlink.h>
#include "zl3073x.h"
+/*
+ * Register Map Page 0, General
+ */
+ZL3073X_REG16_DEF(id, 0x0001);
+ZL3073X_REG16_DEF(revision, 0x0003);
+ZL3073X_REG16_DEF(fw_ver, 0x0005);
+ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
+
/*
* Regmap ranges
*/
@@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
int zl3073x_dev_init(struct zl3073x_dev *zldev)
{
+ u16 id, revision, fw_ver;
struct devlink *devlink;
+ u32 cfg_ver;
+ int rc;
devm_mutex_init(zldev->dev, &zldev->lock);
+ scoped_guard(zl3073x, zldev) {
+ rc = zl3073x_read_id(zldev, &id);
+ if (rc)
+ return rc;
+ rc = zl3073x_read_revision(zldev, &revision);
+ if (rc)
+ return rc;
+ rc = zl3073x_read_fw_ver(zldev, &fw_ver);
+ if (rc)
+ return rc;
+ rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
+ if (rc)
+ return rc;
+ }
+
+ dev_info(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n",
+ id, revision, fw_ver);
+ dev_info(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));
+
devlink = priv_to_devlink(zldev);
devlink_register(devlink);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 06/28] mfd: zl3073x: Implement devlink device info
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (4 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 05/28] mfd: zl3073x: Add components versions register defs Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 07/28] mfd: zl3073x: Add macro to wait for register value bits to be cleared Ivan Vecera
` (22 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Provide ASIC ID, ASIC revision, firmware version and optionally
custom config version through devlink device info.
Sample output:
# devlink dev
i2c/1-0070
# devlink dev info
i2c/1-0070:
driver zl3073x-i2c
versions:
fixed:
asic.id 1E94
asic.rev 300
fw 7006
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 73 ++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index b3091b00cffa8..33e76666e5694 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -142,7 +142,80 @@ int zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg,
}
EXPORT_SYMBOL_GPL(zl3073x_write_reg);
+/**
+ * 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
+ *
+ * Returns 0 in case of success or negative value otherwise
+ */
+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;
+
+ guard(zl3073x)(zldev);
+
+ rc = zl3073x_read_id(zldev, &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_revision(zldev, &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_fw_ver(zldev, &fw_ver);
+ if (rc)
+ return rc;
+
+ snprintf(buf, sizeof(buf), "%u", fw_ver);
+ rc = devlink_info_version_fixed_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW,
+ buf);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
+ if (rc)
+ return rc;
+
+ /* No custom config version */
+ if (cfg_ver == U32_MAX)
+ return rc;
+
+ 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));
+
+ rc = devlink_info_version_running_put(req, "cfg.custom_ver", buf);
+
+ return rc;
+}
+
static const struct devlink_ops zl3073x_devlink_ops = {
+ .info_get = zl3073x_devlink_info_get,
};
static void zl3073x_devlink_free(void *ptr)
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 07/28] mfd: zl3073x: Add macro to wait for register value bits to be cleared
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (5 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 06/28] mfd: zl3073x: Implement devlink device info Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 08/28] mfd: zl3073x: Add functions to work with register mailboxes Ivan Vecera
` (21 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Sometimes in communication with the device is necessary to set
certain bit(s) in certain register and then the driver has to
wait until these bits are cleared by the device.
Add the macro for this functionality, it will be used by later
commits.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
include/linux/mfd/zl3073x.h | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 3524426f0e3ba..15dfb0d8bf3cb 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -143,4 +143,33 @@ int zl3073x_write_##_name(struct zl3073x_dev *zldev, unsigned int idx, \
#define ZL3073X_REG48_IDX_DEF(_name, _addr, _num, _stride) \
__ZL3073X_REG_IDX_DEF(_name, _addr, 6, u64, _num, _stride)
+/**
+ * zl3073x_wait_clear_bits - wait for specific bits to be cleared
+ * _zldev: pointer to device structure
+ * _reg: register name
+ * _bits: bits that should be cleared
+ * _index: optional index for indexed register
+ *
+ * The macro waits up to @READ_TIMEOUT_US microseconds for @_bits in @_reg
+ * to be cleared.
+ *
+ * Returns:
+ * -ETIMEDOUT: if timeout occurred
+ * <0: for other errors occurred during communication
+ * 0: success
+ */
+#define READ_SLEEP_US 10
+#define READ_TIMEOUT_US 2000000
+#define zl3073x_wait_clear_bits(_zldev, _reg, _bits, _index...) \
+({ \
+ zl3073x_##_reg##_t __val; \
+ int __rc; \
+ if (read_poll_timeout(zl3073x_read_##_reg, __rc, \
+ __rc || !((_bits) & __val), \
+ READ_SLEEP_US, READ_TIMEOUT_US, false, \
+ _zldev, ##_index, &__val)) \
+ __rc = -ETIMEDOUT; \
+ __rc; \
+})
+
#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 08/28] mfd: zl3073x: Add functions to work with register mailboxes
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (6 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 07/28] mfd: zl3073x: Add macro to wait for register value bits to be cleared Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 09/28] mfd: zl3073x: Add clock_id field Ivan Vecera
` (20 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Registers present in page 10 and higher are organized in so-called
register mailboxes. These mailboxes are used to read and write
configuration of particular object (dpll, output, reference & synth).
Each of these register pages contains mask register that is used by
the driver to indicate an index of requested object to work with and
also semaphore register to indicate what operation is requested.
The rest of registers in the particular register page are latch
registers that are filled by the firmware during read operation or by
the driver prior write operation.
For read operation the driver...
1) ... updates the mailbox mask register with index of particular object
2) ... sets the mailbox semaphore register read bit
3) ... waits for the semaphore register read bit to be cleared by FW
4) ... reads the configuration from latch registers
For write operation the driver...
1) ... writes the requested configuration to latch registers
2) ... sets the mailbox mask register for the DPLL to be updated
3) ... sets the mailbox semaphore register bit for the write operation
4) ... waits for the semaphore register bit to be cleared by FW
Add functions to read and write mailboxes for all supported object types.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 185 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/zl3073x.h | 12 +++
2 files changed, 197 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 33e76666e5694..feb139b550571 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -14,6 +14,47 @@ ZL3073X_REG16_DEF(revision, 0x0003);
ZL3073X_REG16_DEF(fw_ver, 0x0005);
ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
+/*
+ * Register Map Page 10, Ref Mailbox
+ */
+ZL3073X_REG16_DEF(ref_mb_mask, 0x502);
+#define REF_MB_MASK GENMASK(9, 0)
+
+ZL3073X_REG8_DEF(ref_mb_sem, 0x504);
+#define REF_MB_SEM_WR BIT(0)
+#define REF_MB_SEM_RD BIT(1)
+
+/*
+ * Register Map Page 12, DPLL Mailbox
+ */
+ZL3073X_REG16_DEF(dpll_mb_mask, 0x602);
+
+ZL3073X_REG8_DEF(dpll_mb_sem, 0x604);
+#define DPLL_MB_SEM_WR BIT(0)
+#define DPLL_MB_SEM_RD BIT(1)
+
+/*
+ * Register Map Page 13, Synth Mailbox
+ */
+ZL3073X_REG16_DEF(synth_mb_mask, 0x682);
+
+ZL3073X_REG8_DEF(synth_mb_sem, 0x684);
+#define SYNTH_MB_SEM_WR BIT(0)
+#define SYNTH_MB_SEM_RD BIT(1)
+
+ZL3073X_REG16_DEF(synth_freq_base, 0x686);
+ZL3073X_REG32_DEF(synth_freq_mult, 0x688);
+ZL3073X_REG16_DEF(synth_freq_m, 0x68c);
+ZL3073X_REG16_DEF(synth_freq_n, 0x68e);
+
+/*
+ * Register Map Page 14, Output Mailbox
+ */
+ZL3073X_REG16_DEF(output_mb_mask, 0x702);
+ZL3073X_REG8_DEF(output_mb_sem, 0x704);
+#define OUTPUT_MB_SEM_WR BIT(0)
+#define OUTPUT_MB_SEM_RD BIT(1)
+
/*
* Regmap ranges
*/
@@ -142,6 +183,150 @@ int zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg,
}
EXPORT_SYMBOL_GPL(zl3073x_write_reg);
+/**
+ * ZL3073X_MB_OP - perform an operation on mailbox of certain type
+ * @_zldev: pointer to device structure
+ * @_type: type of mailbox (dpll, ref or output)
+ * @_index: object index of given type
+ * @_op: operation to perform
+ *
+ * Performs the requested operation on mailbox of certain type and
+ * returns 0 in case of success or negative value otherwise.
+ */
+#define ZL3073X_MB_OP(_zldev, _type, _index, _op) \
+({ \
+ struct zl3073x_dev *__zldev = (_zldev); \
+ u16 __mask = BIT(_index); \
+ u8 __op = (_op); \
+ int __rc; \
+ do { \
+ /* Select requested index in mask register */ \
+ __rc = zl3073x_write_##_type##_mb_mask(__zldev, __mask);\
+ if (__rc) \
+ break; \
+ /* Select requested command */ \
+ __rc = zl3073x_write_##_type##_mb_sem(__zldev, __op); \
+ if (__rc) \
+ break; \
+ /* Wait for the command to actually finish */ \
+ __rc = zl3073x_wait_clear_bits(__zldev, _type##_mb_sem, \
+ __op); \
+ } while (0); \
+ __rc; \
+})
+
+/**
+ * zl3073x_mb_dpll_read - read given DPLL configuration to mailbox
+ * @zldev: pointer to device structure
+ * @index: DPLL index
+ *
+ * Reads configuration of given DPLL into DPLL mailbox and returns 0
+ * in case of success or negative value otherwise.
+ */
+int zl3073x_mb_dpll_read(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, dpll, index, DPLL_MB_SEM_RD);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_dpll_read);
+
+/**
+ * zl3073x_mb_dpll_write - write given DPLL configuration from mailbox
+ * @zldev: pointer to device structure
+ * @index: DPLL index
+ *
+ * Writes (commits) configuration of given DPLL from DPLL mailbox and
+ * returns 0 in case of success or negative value otherwise.
+ */
+int zl3073x_mb_dpll_write(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, dpll, index, DPLL_MB_SEM_WR);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_dpll_write);
+
+/**
+ * zl3073x_mb_output_read - read given output configuration to mailbox
+ * @zldev: pointer to device structure
+ * @index: output index
+ *
+ * Reads configuration of given output into output mailbox and returns 0
+ * in case of success or negative value otherwise.
+ */
+int zl3073x_mb_output_read(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, output, index, OUTPUT_MB_SEM_RD);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_output_read);
+
+/**
+ * zl3073x_mb_output_write - write given output configuration from mailbox
+ * @zldev: pointer to device structure
+ * @index: DPLL index
+ *
+ * Writes (commits) configuration of given output from output mailbox and
+ * returns 0 in case of success or negative value otherwise.
+ */
+int zl3073x_mb_output_write(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, output, index, OUTPUT_MB_SEM_WR);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_output_write);
+
+/**
+ * zl3073x_mb_ref_read - read given reference configuration to mailbox
+ * @zldev: pointer to device structure
+ * @index: reference index
+ *
+ * Reads configuration of given reference into reference mailbox and
+ * returns 0 in case of success or negative value otherwise.
+ */
+int zl3073x_mb_ref_read(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, ref, index, REF_MB_SEM_RD);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_ref_read);
+
+/**
+ * zl3073x_mb_ref_write - write given reference configuration from mailbox
+ * @zldev: pointer to device structure
+ * @index: reference index
+ *
+ * Writes (commits) configuration of given reference from reference mailbox
+ * and returns 0 in case of success or negative value otherwise.
+ */
+int zl3073x_mb_ref_write(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, ref, index, REF_MB_SEM_WR);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_ref_write);
+
+/**
+ * zl3073x_mb_synth_read - read given synth configuration to mailbox
+ * @zldev: pointer to device structure
+ * @index: synth index
+ *
+ * Reads configuration of given synth into synth mailbox and returns 0
+ * in case of success or negative value otherwise.
+ */
+int zl3073x_mb_synth_read(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, synth, index, SYNTH_MB_SEM_RD);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_synth_read);
+
+/**
+ * zl3073x_mb_synth_write - write given synth configuration from mailbox
+ * @zldev: pointer to device structure
+ * @index: synth index
+ *
+ * Writes (commits) configuration of given synth from reference mailbox
+ * and returns 0 in case of success or negative value otherwise.
+ */
+int zl3073x_mb_synth_write(struct zl3073x_dev *zldev, u8 index)
+{
+ return ZL3073X_MB_OP(zldev, synth, index, SYNTH_MB_SEM_WR);
+}
+EXPORT_SYMBOL_GPL(zl3073x_mb_synth_write);
+
/**
* zl3073x_devlink_info_get - Devlink device info callback
* @devlink: devlink structure pointer
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 15dfb0d8bf3cb..436f79f2fda63 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -172,4 +172,16 @@ int zl3073x_write_##_name(struct zl3073x_dev *zldev, unsigned int idx, \
__rc; \
})
+/*
+ * Mailbox operations
+ */
+int zl3073x_mb_dpll_read(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_dpll_write(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_output_read(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_output_write(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_ref_read(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_ref_write(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_synth_read(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_mb_synth_write(struct zl3073x_dev *zldev, u8 index);
+
#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 09/28] mfd: zl3073x: Add clock_id field
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (7 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 08/28] mfd: zl3073x: Add functions to work with register mailboxes Ivan Vecera
@ 2025-04-07 17:28 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 10/28] lib: Allow modules to use strnchrnul Ivan Vecera
` (19 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:28 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Later commits that add support for DPLL functionality need a clock ID
for DPLL device registration. To generate such ID use chip ID read
during device initialization for this. For the case where are
multiple zl3073x based chips the chip ID is shifted and lower
bits are filled by an unique value. For I2C case it is I2C device
address and for SPI case it is chip-select value.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 5 ++++-
drivers/mfd/zl3073x-i2c.c | 3 ++-
drivers/mfd/zl3073x-spi.c | 3 ++-
drivers/mfd/zl3073x.h | 2 +-
include/linux/mfd/zl3073x.h | 1 +
5 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index feb139b550571..5570de58c46e4 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -424,7 +424,7 @@ struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
}
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
-int zl3073x_dev_init(struct zl3073x_dev *zldev)
+int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
{
u16 id, revision, fw_ver;
struct devlink *devlink;
@@ -448,6 +448,9 @@ int zl3073x_dev_init(struct zl3073x_dev *zldev)
return rc;
}
+ /* Use chip ID and given dev ID as clock ID */
+ zldev->clock_id = ((u64)id << 8) | dev_id;
+
dev_info(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n",
id, revision, fw_ver);
dev_info(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
diff --git a/drivers/mfd/zl3073x-i2c.c b/drivers/mfd/zl3073x-i2c.c
index 8c8b2ba176766..ae7079d9359c1 100644
--- a/drivers/mfd/zl3073x-i2c.c
+++ b/drivers/mfd/zl3073x-i2c.c
@@ -41,7 +41,8 @@ static int zl3073x_i2c_probe(struct i2c_client *client)
i2c_set_clientdata(client, zldev);
- return zl3073x_dev_init(zldev);
+ /* Initialize device and use I2C address as dev ID */
+ return zl3073x_dev_init(zldev, client->addr);
}
static void zl3073x_i2c_remove(struct i2c_client *client)
diff --git a/drivers/mfd/zl3073x-spi.c b/drivers/mfd/zl3073x-spi.c
index a6b9a366a7585..6877ca1664111 100644
--- a/drivers/mfd/zl3073x-spi.c
+++ b/drivers/mfd/zl3073x-spi.c
@@ -42,7 +42,8 @@ static int zl3073x_spi_probe(struct spi_device *spidev)
spi_set_drvdata(spidev, zldev);
- return zl3073x_dev_init(zldev);
+ /* Initialize device and use SPI chip select value as dev ID */
+ return zl3073x_dev_init(zldev, spi_get_chipselect(spidev, 0));
}
static void zl3073x_spi_remove(struct spi_device *spidev)
diff --git a/drivers/mfd/zl3073x.h b/drivers/mfd/zl3073x.h
index 582cb40d681d3..04612313d32a9 100644
--- a/drivers/mfd/zl3073x.h
+++ b/drivers/mfd/zl3073x.h
@@ -6,7 +6,7 @@
#include <linux/mfd/zl3073x.h>
struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev);
-int zl3073x_dev_init(struct zl3073x_dev *zldev);
+int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id);
void zl3073x_dev_exit(struct zl3073x_dev *zldev);
const struct regmap_config *zl3073x_get_regmap_config(void);
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 436f79f2fda63..a18eddbc03709 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -9,6 +9,7 @@
struct zl3073x_dev {
struct device *dev;
struct regmap *regmap;
+ u64 clock_id;
struct mutex lock;
};
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 10/28] lib: Allow modules to use strnchrnul
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (8 preceding siblings ...)
2025-04-07 17:28 ` [PATCH 09/28] mfd: zl3073x: Add clock_id field Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:50 ` Kees Cook
2025-04-07 17:31 ` [PATCH 11/28] mfd: zl3073x: Load mfg file into HW if it is present Ivan Vecera
` (18 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Commit 0bee0cece2a6a ("lib/string: add strnchrnul()") added the
mentioned function but did not export it so it cannot be used by
modules.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
lib/string.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/string.c b/lib/string.c
index eb4486ed40d25..824b3aac86de0 100644
--- a/lib/string.c
+++ b/lib/string.c
@@ -363,6 +363,7 @@ char *strnchrnul(const char *s, size_t count, int c)
s++;
return (char *)s;
}
+EXPORT_SYMBOL(strnchrnul);
#ifndef __HAVE_ARCH_STRRCHR
/**
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 11/28] mfd: zl3073x: Load mfg file into HW if it is present
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (9 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 10/28] lib: Allow modules to use strnchrnul Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 12/28] mfd: zl3073x: Fetch invariants during probe Ivan Vecera
` (17 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Add support for loading mfg file that can be provided
by a user. The mfg file can be generated by Microchip
tool and contains snippets of device configuration
that is different from the one stored in the flash
memory inside the chip.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 106 +++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 5570de58c46e4..9920c5329d50f 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -424,6 +424,108 @@ struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
}
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
+static int zl3073x_fw_parse_line(struct zl3073x_dev *zldev, const char *line)
+{
+#define ZL3073X_FW_WHITESPACES_SIZE 3
+#define ZL3073X_FW_COMMAND_SIZE 1
+ const char *ptr = line;
+ char *endp;
+ u32 delay;
+ u16 addr;
+ u8 val;
+
+ switch (ptr[0]) {
+ case 'X':
+ /* The line looks like this:
+ * X , ADDR , VAL
+ * Where:
+ * - X means that is a command that needs to be executed
+ * - ADDR represents the addr and is always 2 bytes and the
+ * value is in hex, for example 0x0232
+ * - VAL represents the value that is written and is always 1
+ * byte and the value is in hex, for example 0x12
+ */
+ ptr += ZL3073X_FW_COMMAND_SIZE;
+ ptr += ZL3073X_FW_WHITESPACES_SIZE;
+ addr = simple_strtoul(ptr, &endp, 16);
+
+ ptr = endp;
+ ptr += ZL3073X_FW_WHITESPACES_SIZE;
+ val = simple_strtoul(ptr, NULL, 16);
+
+ /* Write requested value to given register */
+ return zl3073x_write_reg(zldev, addr, 1, &val);
+ case 'W':
+ /* The line looks like this:
+ * W , DELAY
+ * Where:
+ * - W means that is a wait command
+ * - DELAY represents the delay in microseconds and the value
+ * is in decimal
+ */
+ ptr += ZL3073X_FW_COMMAND_SIZE;
+ ptr += ZL3073X_FW_WHITESPACES_SIZE;
+ delay = simple_strtoul(ptr, NULL, 10);
+
+ fsleep(delay);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+#define ZL3073X_MFG_FILE "microchip/zl3073x.mfg"
+
+static void zl3073x_fw_load(struct zl3073x_dev *zldev)
+{
+ const struct firmware *fw;
+ const char *ptr, *end;
+ char buf[128];
+ int rc;
+
+ rc = firmware_request_nowarn(&fw, ZL3073X_MFG_FILE, zldev->dev);
+ if (rc)
+ return;
+
+ dev_info(zldev->dev, "Applying mfg file %s...\n", ZL3073X_MFG_FILE);
+
+ guard(zl3073x)(zldev);
+
+ ptr = fw->data;
+ end = ptr + fw->size;
+ while (ptr < end) {
+ /* Get next end of the line or end of buffer */
+ char *eol = strnchrnul(ptr, end - ptr, '\n');
+ size_t len = eol - ptr;
+
+ /* Check line length */
+ if (len >= sizeof(buf)) {
+ dev_err(zldev->dev, "Line in firmware is too long\n");
+ return;
+ }
+
+ /* Copy line from buffer */
+ memcpy(buf, ptr, len);
+ buf[len] = '\0';
+
+ /* Parse and process the line */
+ rc = zl3073x_fw_parse_line(zldev, buf);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to parse firmware line: %pe\n",
+ ERR_PTR(rc));
+ break;
+ }
+
+ /* Move to next line */
+ ptr = eol + 1;
+ }
+
+ release_firmware(fw);
+}
+
int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
{
u16 id, revision, fw_ver;
@@ -451,6 +553,9 @@ int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
/* Use chip ID and given dev ID as clock ID */
zldev->clock_id = ((u64)id << 8) | dev_id;
+ /* Load mfg file if present */
+ zl3073x_fw_load(zldev);
+
dev_info(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n",
id, revision, fw_ver);
dev_info(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
@@ -475,3 +580,4 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
MODULE_DESCRIPTION("Microchip ZL3073x core driver");
MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(ZL3073X_MFG_FILE);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 12/28] mfd: zl3073x: Fetch invariants during probe
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (10 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 11/28] mfd: zl3073x: Load mfg file into HW if it is present Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 13/28] dpll: Add Microchip ZL3073x DPLL driver Ivan Vecera
` (16 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Several configuration parameters will not be changed in runtime so we
can load them during probe to avoid excessive reads from the hardware.
The following parameters are read and stored for later use:
* synthesizers' frequencies and associated DPLL channel
* input pins' enablement and type (single-ended or differential)
* outputs'associated synths, signal format and enablement
These parameters will be frequently read by the DPLL driver from this
series and later by PHC/PTP sub-device driver.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 241 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/zl3073x.h | 142 +++++++++++++++++++++
2 files changed, 383 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 9920c5329d50f..9ed405a62fa86 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -14,6 +14,20 @@ ZL3073X_REG16_DEF(revision, 0x0003);
ZL3073X_REG16_DEF(fw_ver, 0x0005);
ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
+/*
+ * Register Map Page 9, Synth and Output
+ */
+ZL3073X_REG8_IDX_DEF(synth_ctrl, 0x480, ZL3073X_NUM_SYNTHS, 1);
+#define SYNTH_CTRL_EN BIT(0)
+#define SYNTH_CTRL_DPLL_SEL GENMASK(6, 4)
+
+ZL3073X_REG8_IDX_DEF(output_ctrl, 0x4a8, ZL3073X_NUM_OUTPUTS, 1);
+#define OUTPUT_CTRL_EN BIT(0)
+#define OUTPUT_CTRL_STOP BIT(1)
+#define OUTPUT_CTRL_STOP_HIGH BIT(2)
+#define OUTPUT_CTRL_STOP_HZ BIT(3)
+#define OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4)
+
/*
* Register Map Page 10, Ref Mailbox
*/
@@ -24,6 +38,10 @@ ZL3073X_REG8_DEF(ref_mb_sem, 0x504);
#define REF_MB_SEM_WR BIT(0)
#define REF_MB_SEM_RD BIT(1)
+ZL3073X_REG8_DEF(ref_config, 0x50d);
+#define REF_CONFIG_ENABLE BIT(0)
+#define REF_CONFIG_DIFF_EN BIT(2)
+
/*
* Register Map Page 12, DPLL Mailbox
*/
@@ -55,6 +73,9 @@ ZL3073X_REG8_DEF(output_mb_sem, 0x704);
#define OUTPUT_MB_SEM_WR BIT(0)
#define OUTPUT_MB_SEM_RD BIT(1)
+ZL3073X_REG8_DEF(output_mode, 0x705);
+#define OUTPUT_MODE_SIGNAL_FORMAT GENMASK(7, 4)
+
/*
* Regmap ranges
*/
@@ -526,6 +547,219 @@ static void zl3073x_fw_load(struct zl3073x_dev *zldev)
release_firmware(fw);
}
+/**
+ * zl3073x_input_state_fetch - get input state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input pin index to fetch state for
+ *
+ * Function fetches information for the given input reference that are
+ * invariant and stores them for later use.
+ *
+ * Returns 0 in case of success or negative value in error case.
+ */
+static int
+zl3073x_input_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_input *input;
+ u8 ref_config;
+ int rc;
+
+ if (index >= ZL3073X_NUM_INPUTS)
+ return -EINVAL;
+
+ input = &zldev->input[index];
+
+ /* 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_input_is_diff(zldev, index-1)) {
+ input->enabled = zl3073x_input_is_enabled(zldev, index-1);
+ input->diff = true;
+
+ return 0;
+ }
+
+ /* Read reference configuration into mailbox */
+ rc = zl3073x_mb_ref_read(zldev, index);
+ if (rc)
+ return rc;
+
+ /* Read reference config register */
+ rc = zl3073x_read_ref_config(zldev, &ref_config);
+ if (rc)
+ return rc;
+
+ /* Store info about input reference enablement and if it is
+ * configured in differential mode or not.
+ */
+ input->enabled = FIELD_GET(REF_CONFIG_ENABLE, ref_config);
+ input->diff = FIELD_GET(REF_CONFIG_DIFF_EN, ref_config);
+
+ return rc;
+}
+
+/**
+ * zl3073x_output_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.
+ *
+ * Returns 0 in case of success or negative value in error case.
+ */
+static int
+zl3073x_output_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_output *output;
+ u8 output_ctrl, output_mode;
+ int rc;
+
+ if (index >= ZL3073X_NUM_OUTPUTS)
+ return -EINVAL;
+
+ output = &zldev->output[index];
+
+ /* Read output control register */
+ rc = zl3073x_read_output_ctrl(zldev, index, &output_ctrl);
+ if (rc)
+ return rc;
+
+ /* Store info about output enablement and synthesizer the output
+ * is connected to.
+ */
+ output->enabled = FIELD_GET(OUTPUT_CTRL_EN, output_ctrl);
+ output->synth = FIELD_GET(OUTPUT_CTRL_SYNTH_SEL, output_ctrl);
+
+ /* Load given output config into mailbox */
+ rc = zl3073x_mb_output_read(zldev, index);
+ if (rc)
+ return rc;
+
+ /* Read output mode from mailbox */
+ rc = zl3073x_read_output_mode(zldev, &output_mode);
+ if (rc)
+ return rc;
+
+ /* Extract and store output signal format */
+ output->signal_format = FIELD_GET(OUTPUT_MODE_SIGNAL_FORMAT,
+ output_mode);
+
+ 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.
+ *
+ * Returns 0 in case of success or negative value in error case.
+ */
+static int
+zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ u16 base, numerator, denominator;
+ u8 synth_ctrl;
+ u32 mult;
+ int rc;
+
+ /* Read synth control register */
+ rc = zl3073x_read_synth_ctrl(zldev, index, &synth_ctrl);
+ if (rc)
+ return rc;
+
+ /* Extract and store DPLL channel the synth is driven by */
+ zldev->synth[index].dpll = FIELD_GET(SYNTH_CTRL_DPLL_SEL, synth_ctrl);
+
+ dev_dbg(zldev->dev, "SYNTH%u is connected to DPLL%u\n", index,
+ zldev->synth[index].dpll);
+
+ /* Read synth configuration into mailbox */
+ rc = zl3073x_mb_synth_read(zldev, index);
+ if (rc)
+ return rc;
+
+ /* The output frequency is determined by the following formula:
+ * base * multiplier * numerator / denominator
+ *
+ * Therefore get all this number and calculate the output frequency
+ */
+ rc = zl3073x_read_synth_freq_base(zldev, &base);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_synth_freq_mult(zldev, &mult);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_synth_freq_m(zldev, &numerator);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_synth_freq_n(zldev, &denominator);
+ if (rc)
+ return rc;
+
+ /* Check denominator for zero to avoid div by 0 */
+ if (!denominator) {
+ dev_err(zldev->dev,
+ "Zero divisor for SYNTH%u retrieved from device\n",
+ index);
+ return -ENODEV;
+ }
+
+ /* Compute and store synth frequency */
+ zldev->synth[index].freq = mul_u64_u32_div(mul_u32_u32(base, mult),
+ numerator, denominator);
+
+ dev_dbg(zldev->dev, "SYNTH%u frequency: %llu 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_INPUTS; i++) {
+ rc = zl3073x_input_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_OUTPUTS; i++) {
+ rc = zl3073x_output_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch output state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
{
u16 id, revision, fw_ver;
@@ -556,6 +790,13 @@ int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
/* Load mfg file if present */
zl3073x_fw_load(zldev);
+ /* Fetch device state */
+ scoped_guard(zl3073x, zldev) {
+ rc = zl3073x_dev_state_fetch(zldev);
+ if (rc)
+ return rc;
+ }
+
dev_info(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n",
id, revision, fw_ver);
dev_info(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index a18eddbc03709..825e6706dc974 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -6,11 +6,49 @@
#include <linux/device.h>
#include <linux/regmap.h>
+/*
+ * Hardware limits for ZL3073x chip family
+ */
+#define ZL3073X_NUM_INPUTS 10
+#define ZL3073X_NUM_OUTPUTS 10
+#define ZL3073X_NUM_SYNTHS 5
+
+struct zl3073x_input {
+ bool enabled;
+ bool diff;
+};
+
+struct zl3073x_output {
+ bool enabled;
+ u8 synth;
+ u8 signal_format;
+#define OUTPUT_MODE_SIGNAL_FORMAT_DISABLED 0
+#define OUTPUT_MODE_SIGNAL_FORMAT_LVDS 1
+#define OUTPUT_MODE_SIGNAL_FORMAT_DIFFERENTIAL 2
+#define OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM 3
+#define OUTPUT_MODE_SIGNAL_FORMAT_TWO 4
+#define OUTPUT_MODE_SIGNAL_FORMAT_ONE_P 5
+#define OUTPUT_MODE_SIGNAL_FORMAT_ONE_N 6
+#define OUTPUT_MODE_SIGNAL_FORMAT_TWO_INV 7
+#define OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV 12
+#define OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV 15
+};
+
+struct zl3073x_synth {
+ u64 freq;
+ u8 dpll;
+};
+
struct zl3073x_dev {
struct device *dev;
struct regmap *regmap;
u64 clock_id;
struct mutex lock;
+
+ /* Invariants */
+ struct zl3073x_input input[ZL3073X_NUM_INPUTS];
+ struct zl3073x_output output[ZL3073X_NUM_OUTPUTS];
+ struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
};
/**
@@ -185,4 +223,108 @@ int zl3073x_mb_ref_write(struct zl3073x_dev *zldev, u8 index);
int zl3073x_mb_synth_read(struct zl3073x_dev *zldev, u8 index);
int zl3073x_mb_synth_write(struct zl3073x_dev *zldev, u8 index);
+static inline
+bool zl3073x_is_n_pin(u8 index)
+{
+ /* P-pins indices are even while N-pins are odd */
+ return index & 1;
+}
+
+static inline
+bool zl3073x_is_p_pin(u8 index)
+{
+ return !zl3073x_is_n_pin(index);
+}
+
+/**
+ * zl3073x_input_is_diff - check if the given input ref is differential
+ * @zldev: device structure pointer
+ * @index: output index
+ *
+ * Returns true if the given input ref is differential
+ */
+static inline
+bool zl3073x_input_is_diff(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->input[index].diff;
+}
+
+/**
+ * zl3073x_input_is_enabled - check if the given input ref is enabled
+ * @zldev: device structure pointer
+ * @index: output index
+ *
+ * Returns true if the given input ref is enabled
+ */
+static inline
+bool zl3073x_input_is_enabled(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->input[index].enabled;
+}
+
+/**
+ * zl3073x_output_is_enabled - check if the given output is enabled
+ * @zldev: device structure pointer
+ * @index: output index
+ *
+ * Returns true if the given output is enabled
+ */
+static inline
+u8 zl3073x_output_is_enabled(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->output[index].enabled;
+}
+
+/**
+ * zl3073x_output_signal_format_get - get output signal format
+ * @zldev: device structure pointer
+ * @index: output index
+ *
+ * Returns signal format of given output
+ */
+static inline
+u8 zl3073x_output_signal_format_get(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->output[index].signal_format;
+}
+
+/**
+ * zl3073x_output_synth_get - get synth connected to given output
+ * @zldev: device structure pointer
+ * @index: output index
+ *
+ * Returns index of synth connected to given output.
+ */
+static inline
+u8 zl3073x_output_synth_get(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->output[index].synth;
+}
+
+/**
+ * zl3073x_synth_dpll_get - get dpll id where the synth is connected to
+ * @zldev: device structure pointer
+ * @index: synth order number
+ *
+ * Returns frequency of given synthetizer
+ */
+static inline
+u64 zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->synth[index].dpll;
+}
+
+/**
+ * zl3073x_synth_freq_get - get synth current freq
+ * @zldev: device structure pointer
+ * @index: synth order number
+ *
+ * Returns frequency of given synthetizer
+ */
+static inline
+u64 zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index)
+{
+ return zldev->synth[index].freq;
+}
+
#endif /* __LINUX_MFD_ZL3073X_H */
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 13/28] dpll: Add Microchip ZL3073x DPLL driver
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (11 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 12/28] mfd: zl3073x: Fetch invariants during probe Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 14/28] mfd: zl3073x: Register DPLL sub-device during init Ivan Vecera
` (15 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
Add a DPLL driver for Microchip Azurite chip family with basic
functionality where only required DPLL and pin callbacks are
implemented. Other features are added by subsequent patches.
The driver controls the sub-devices created by zl3073x MFD driver and
each DPLL sub-device represents one of the DPLL channels that are
provided by the device.
For each sub-device the driver registers a DPLL device and also input and
output pins. Number of registered pins depend on their configuration
that is stored in flash memory inside the device. The input pins can be
configured as differential or single-ended ones. If the input/output
is configured as differential then P&N pins form one input/output and only
1 input/output pin is registered. For single-ended case the number of
registered pins is up to 2 depending on whether the pin (P or N) is
enabled or disabled in configuration.
Each DPLL channel (sub-device) can drive up to 5 synthesizers whose
output is connected to up to 10 output pin pairs (P & N). Because output
pin pairs shares synthesizers where each is driven by different DPLL
channel, the driver does not support to change state of output pins.
So the output pins are registered only for DPLL device they are
connected to (based on stored configuration) and are always reported as
connected. This does not apply to the case of input pins where any of
them can be a reference for any DPLL channel.
The driver also creates a kworker task to monitor DPLL channel and input
pins changes and to notify about them DPLL core. Output pins are not
monitored as their parameters are not changed asynchronously by the
device.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
MAINTAINERS | 1 +
drivers/dpll/Kconfig | 16 +
drivers/dpll/Makefile | 2 +
drivers/dpll/dpll_zl3073x.c | 1073 +++++++++++++++++++++++++++++++++++
include/linux/mfd/zl3073x.h | 5 +
5 files changed, 1097 insertions(+)
create mode 100644 drivers/dpll/dpll_zl3073x.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c69a69d862310..3d542440d0b2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15999,6 +15999,7 @@ M: Ivan Vecera <ivecera@redhat.com>
M: Prathosh Satish <Prathosh.Satish@microchip.com>
L: netdev@vger.kernel.org
S: Supported
+F: drivers/dpll/dpll_zl3073x*
F: drivers/mfd/zl3073x*
F: include/linux/mfd/zl3073x.h
diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig
index 20607ed542435..efd867f338dfc 100644
--- a/drivers/dpll/Kconfig
+++ b/drivers/dpll/Kconfig
@@ -3,5 +3,21 @@
# Generic DPLL drivers configuration
#
+menu "DPLL support"
+
config DPLL
bool
+
+config DPLL_ZL3073X
+ tristate "Microchip Azurite DPLL driver"
+ depends on MFD_ZL3073X_CORE
+ select DPLL
+ help
+ This driver adds support for DPLL exposed by Microchip Azurite
+ chip family.
+
+ The devices handled by this driver are created by MFD zl3073x
+ driver as sub-devices for each DPLL channel that is present
+ in the device.
+
+endmenu
diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile
index 2e5b278501105..9f7c99261e74d 100644
--- a/drivers/dpll/Makefile
+++ b/drivers/dpll/Makefile
@@ -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_DPLL_ZL3073X) += dpll_zl3073x.o
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
new file mode 100644
index 0000000000000..34bd6964fe001
--- /dev/null
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -0,0 +1,1073 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/dpll.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/zl3073x.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+
+/*
+ * Register Map Page 2, Status
+ */
+ZL3073X_REG8_IDX_DEF(ref_mon_status, 0x102,
+ ZL3073X_NUM_INPUT_PINS, 1);
+#define REF_MON_STATUS_LOS_FAIL BIT(0)
+#define REF_MON_STATUS_SCM_FAIL BIT(1)
+#define REF_MON_STATUS_CFM_FAIL BIT(2)
+#define REF_MON_STATUS_GST_FAIL BIT(3)
+#define REF_MON_STATUS_PFM_FAIL BIT(4)
+#define REF_MON_STATUS_ESYNC_FAIL BIT(6)
+#define REF_MON_STATUS_SPLIT_XO_FAIL BIT(7)
+#define REF_MON_STATUS_OK 0 /* all bits zeroed */
+
+ZL3073X_REG8_IDX_DEF(dpll_mon_status, 0x110, ZL3073X_NUM_CHANNELS, 1);
+#define DPLL_MON_STATUS_LOCK BIT(0)
+#define DPLL_MON_STATUS_HO BIT(1)
+#define DPLL_MON_STATUS_HO_READY BIT(2)
+
+ZL3073X_REG8_IDX_DEF(dpll_refsel_status, 0x130, ZL3073X_NUM_CHANNELS, 1);
+#define DPLL_REFSEL_STATUS_REFSEL GENMASK(3, 0)
+#define DPLL_REFSEL_STATUS_STATE GENMASK(6, 4)
+#define DPLL_REFSEL_STATUS_STATE_FREERUN 0
+#define DPLL_REFSEL_STATUS_STATE_HOLDOVER 1
+#define DPLL_REFSEL_STATUS_STATE_FASTLOCK 2
+#define DPLL_REFSEL_STATUS_STATE_ACQUIRING 3
+#define DPLL_REFSEL_STATUS_STATE_LOCK 4
+
+/*
+ * Register Map Page 5, DPLL
+ */
+ZL3073X_REG8_IDX_DEF(dpll_mode_refsel, 0x284, ZL3073X_NUM_CHANNELS, 4);
+#define DPLL_MODE_REFSEL_MODE GENMASK(2, 0)
+#define DPLL_MODE_REFSEL_MODE_FREERUN 0
+#define DPLL_MODE_REFSEL_MODE_HOLDOVER 1
+#define DPLL_MODE_REFSEL_MODE_REFLOCK 2
+#define DPLL_MODE_REFSEL_MODE_AUTO 3
+#define DPLL_MODE_REFSEL_MODE_NCO 4
+#define DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+
+/*
+ * Register Map Page 9, Synth and Output
+ */
+ZL3073X_REG8_DEF(synth_phase_shift_ctrl, 0x49e);
+ZL3073X_REG8_DEF(synth_phase_shift_mask, 0x49f);
+ZL3073X_REG8_DEF(synth_phase_shift_intvl, 0x4a0);
+ZL3073X_REG16_DEF(synth_phase_shift_data, 0x4a1);
+
+/*
+ * Register Map Page 12, DPLL Mailbox
+ */
+ZL3073X_REG8_IDX_DEF(dpll_ref_prio, 0x652,
+ ZL3073X_NUM_INPUT_PINS / 2, 1);
+#define DPLL_REF_PRIO_REF_P GENMASK(3, 0)
+#define DPLL_REF_PRIO_REF_N GENMASK(7, 4)
+#define DPLL_REF_PRIO_MAX 14
+#define DPLL_REF_PRIO_NONE 15 /* non-selectable */
+
+#define ZL3073X_REF_NONE ZL3073X_NUM_INPUT_PINS
+#define ZL3073X_REF_IS_VALID(_ref) ((_ref) != ZL3073X_REF_NONE)
+
+/**
+ * struct zl3073x_dpll_pin_info - DPLL pin info
+ * @props: DPLL core pin properties
+ * @package_label: pin package label
+ */
+struct zl3073x_dpll_pin_info {
+ struct dpll_pin_properties props;
+ char package_label[8];
+};
+
+/**
+ * struct zl3073x_dpll_pin - DPLL pin
+ * @dpll_pin: pointer to registered dpll_pin
+ * @index: index in zl3073x_dpll.pins array
+ * @prio: pin priority <0, 14>
+ * @selectable: pin is selectable in automatic mode
+ * @pin_state: last saved pin state
+ */
+struct zl3073x_dpll_pin {
+ struct dpll_pin *dpll_pin;
+ u8 index;
+ u8 prio;
+ bool selectable;
+ enum dpll_pin_state pin_state;
+};
+
+/**
+ * struct zl3073x_dpll - ZL3073x DPLL sub-device structure
+ * @dev: device pointer
+ * @mfd: 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: array of pins
+ * @kworker: thread for periodic work
+ * @work: periodic work
+ */
+struct zl3073x_dpll {
+ struct device *dev;
+ struct zl3073x_dev *mfd;
+ int id;
+ u8 refsel_mode;
+ u8 forced_ref;
+ struct dpll_device *dpll_dev;
+ enum dpll_lock_status lock_status;
+ struct zl3073x_dpll_pin pins[ZL3073X_NUM_PINS];
+
+ struct kthread_worker *kworker;
+ struct kthread_delayed_work work;
+};
+
+#define pin_to_dpll(_pin) \
+ container_of((_pin), struct zl3073x_dpll, pins[(_pin)->index])
+
+#define pin_to_dev(_pin) \
+ pin_to_dpll(_pin)->mfd
+
+/**
+ * zl3073x_dpll_is_input_pin - check if the pin is input one
+ * @pin: pin to check
+ *
+ * Returns true if the pin is input or false if output one.
+ */
+static bool
+zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin)
+{
+ /* Output pins are stored in zl3073x_dpll.pins first and input
+ * pins follow.
+ */
+ if (pin->index >= ZL3073X_NUM_OUTPUT_PINS)
+ return true;
+
+ return false;
+}
+
+/**
+ * zl3073x_dpll_pin_index_get - get pin HW index
+ * @pin: pin pointer
+ *
+ * Returns index of the pin from the HW point of view.
+ */
+static u8
+zl3073x_dpll_pin_index_get(struct zl3073x_dpll_pin *pin)
+{
+ if (zl3073x_dpll_is_input_pin(pin))
+ return pin->index - ZL3073X_NUM_OUTPUT_PINS;
+
+ return pin->index;
+}
+
+/**
+ * zl3073x_dpll_is_n_pin - check if the pin is N-pin
+ * @pin: pin to check
+ *
+ * Returns true if the pin is N-pin or false if output one.
+ */
+static bool
+zl3073x_dpll_is_n_pin(struct zl3073x_dpll_pin *pin)
+{
+ /* P-pins indices are even while N-pins are odd */
+ return zl3073x_is_n_pin(zl3073x_dpll_pin_index_get(pin));
+}
+
+/**
+ * zl3073x_dpll_is_p_pin - check if the pin is P-pin
+ * @pin: pin to check
+ *
+ * Returns true if the pin is P-pin or false if output one.
+ */
+static bool
+zl3073x_dpll_is_p_pin(struct zl3073x_dpll_pin *pin)
+{
+ return zl3073x_is_p_pin(zl3073x_dpll_pin_index_get(pin));
+}
+
+/**
+ * zl3073x_dpll_output_pin_output_get - get output index for given output pin
+ * @pin: pointer to pin
+ *
+ * Returns output index for the given output pin
+ */
+static u8
+zl3073x_dpll_output_pin_output_get(struct zl3073x_dpll_pin *pin)
+{
+ WARN_ON(zl3073x_dpll_is_input_pin(pin));
+
+ return zl3073x_dpll_pin_index_get(pin) / 2;
+}
+
+static int
+zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *pin = pin_priv;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ *direction = DPLL_PIN_DIRECTION_INPUT;
+ else
+ *direction = DPLL_PIN_DIRECTION_OUTPUT;
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_selected_ref_get - get currently selected reference
+ * @zldpll: pointer to zl3073x_dpll
+ * @ref: place to store selected reference
+ *
+ * Check for currently selected reference the DPLL should be locked to
+ * and stores its index to given @ref.
+ *
+ * Return 0 in case of success or negative value otherwise.
+ */
+static int
+zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
+{
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 state, value;
+ int rc;
+
+ switch (zldpll->refsel_mode) {
+ case DPLL_MODE_REFSEL_MODE_AUTO:
+ /* For automatic mode read refsel_status register */
+ rc = zl3073x_read_dpll_refsel_status(zldev, zldpll->id, &value);
+ if (rc)
+ return rc;
+
+ /* Extract reference state */
+ state = FIELD_GET(DPLL_REFSEL_STATUS_STATE, value);
+
+ /* Return the reference only if the DPLL is locked to it */
+ if (state == DPLL_REFSEL_STATUS_STATE_LOCK)
+ *ref = FIELD_GET(DPLL_REFSEL_STATUS_REFSEL, value);
+ else
+ *ref = ZL3073X_REF_NONE;
+ break;
+ case DPLL_MODE_REFSEL_MODE_REFLOCK:
+ /* For manual mode return stored value */
+ *ref = zldpll->forced_ref;
+ break;
+ default:
+ /* For other modes like NCO, freerun... there is no input ref */
+ *ref = ZL3073X_REF_NONE;
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_connected_ref_get - get currently connected reference
+ * @zldpll: pointer to zl3073x_dpll
+ * @ref: place to store selected reference
+ *
+ * Looks for currently connected the DPLL is locked to and stores its index
+ * to given @ref.
+ *
+ * Return 0 in case of success or negative value otherwise.
+ */
+static int
+zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
+{
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ int rc;
+
+ /* Get currently selected input reference */
+ rc = zl3073x_dpll_selected_ref_get(zldpll, ref);
+ if (rc)
+ return rc;
+
+ if (ZL3073X_REF_IS_VALID(*ref)) {
+ u8 ref_status;
+
+ /* Read the reference monitor status */
+ rc = zl3073x_read_ref_mon_status(zldev, *ref, &ref_status);
+ if (rc)
+ return rc;
+
+ /* If the monitor indicates an error nothing is connected */
+ if (ref_status != REF_MON_STATUS_OK)
+ *ref = ZL3073X_REF_NONE;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_ref_prio_get - get priority for given input pin
+ * @pin: pointer to pin
+ * @prio: place to store priority
+ *
+ * Reads current priority for the given input pin and stores the value
+ * to @prio.
+ *
+ * Returns 0 in case of success or negative value otherwise.
+ */
+static int
+zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 ref_id, ref_prio;
+ int rc;
+
+ /* Read DPLL configuration into mailbox */
+ rc = zl3073x_mb_dpll_read(zldev, zldpll->id);
+ if (rc)
+ return rc;
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read ref prio nibble */
+ rc = zl3073x_read_dpll_ref_prio(zldev, ref_id / 2, &ref_prio);
+ if (rc)
+ return rc;
+
+ /* Select nibble according pin type */
+ if (zl3073x_dpll_is_p_pin(pin))
+ *prio = FIELD_GET(DPLL_REF_PRIO_REF_P, ref_prio);
+ else
+ *prio = FIELD_GET(DPLL_REF_PRIO_REF_N, ref_prio);
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 ref_id, ref_conn, ref_status;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Get currently connected reference */
+ rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn);
+ if (rc)
+ return rc;
+
+ if (ref_id == ref_conn) {
+ *state = DPLL_PIN_STATE_CONNECTED;
+ return 0;
+ }
+
+ /* If the DPLL is running in automatic mode and the reference is
+ * selectable and its monitor does not report any error then report
+ * pin as selectable.
+ */
+ if (zldpll->refsel_mode == DPLL_MODE_REFSEL_MODE_AUTO &&
+ pin->selectable) {
+ /* Read reference monitor status */
+ rc = zl3073x_read_ref_mon_status(zldev, ref_id, &ref_status);
+ if (rc)
+ return rc;
+
+ /* If the monitor indicates errors report the reference
+ * as disconnected
+ */
+ if (ref_status == REF_MON_STATUS_OK) {
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ return 0;
+ }
+ }
+
+ /* Otherwise report the pin as disconnected */
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ /* If the output pin is registered then it is always connected */
+ *state = DPLL_PIN_STATE_CONNECTED;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_lock_status *status,
+ enum dpll_lock_status_error *status_error,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 mon_status;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ rc = zl3073x_read_dpll_mon_status(zldev, zldpll->id, &mon_status);
+
+ if (rc)
+ return rc;
+
+ if (FIELD_GET(DPLL_MON_STATUS_LOCK, mon_status)) {
+ if (FIELD_GET(DPLL_MON_STATUS_HO_READY, mon_status))
+ *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ;
+ else
+ *status = DPLL_LOCK_STATUS_LOCKED;
+ } else if (FIELD_GET(DPLL_MON_STATUS_HO, mon_status)) {
+ *status = DPLL_LOCK_STATUS_HOLDOVER;
+ } else {
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ }
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_mode *mode, struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ switch (zldpll->refsel_mode) {
+ case DPLL_MODE_REFSEL_MODE_FREERUN:
+ case DPLL_MODE_REFSEL_MODE_HOLDOVER:
+ case DPLL_MODE_REFSEL_MODE_NCO:
+ case DPLL_MODE_REFSEL_MODE_REFLOCK:
+ /* Use MANUAL for device FREERUN, HOLDOVER, NCO and
+ * REFLOCK modes
+ */
+ *mode = DPLL_MODE_MANUAL;
+ break;
+ case DPLL_MODE_REFSEL_MODE_AUTO:
+ /* Use AUTO for device AUTO mode */
+ *mode = DPLL_MODE_AUTOMATIC;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
+ .direction_get = zl3073x_dpll_pin_direction_get,
+ .state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
+};
+
+static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
+ .direction_get = zl3073x_dpll_pin_direction_get,
+ .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
+};
+
+static const struct dpll_device_ops zl3073x_dpll_device_ops = {
+ .lock_status_get = zl3073x_dpll_lock_status_get,
+ .mode_get = zl3073x_dpll_mode_get,
+};
+
+/**
+ * zl3073x_dpll_pin_info_package_label_set - generate package label for the pin
+ * @pin: pointer to pin
+ * @pin_info: pointer to pin info structure
+ *
+ * Generates package label string and stores it into pin info 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_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
+ struct zl3073x_dpll_pin_info *pin_info)
+{
+ struct zl3073x_dev *zldev = pin_to_dpll(pin)->mfd;
+ char suffix;
+ u8 idx;
+
+ suffix = zl3073x_dpll_is_p_pin(pin) ? 'P' : 'N';
+
+ if (zl3073x_dpll_is_input_pin(pin)) {
+ idx = zl3073x_dpll_pin_index_get(pin);
+
+ if (zl3073x_input_is_diff(zldev, idx))
+ /* For differential use REF<n> */
+ snprintf(pin_info->package_label,
+ sizeof(pin_info->package_label),
+ "REF%u", idx / 2);
+ else
+ /* For single-ended use REF<n>P/N */
+ snprintf(pin_info->package_label,
+ sizeof(pin_info->package_label),
+ "REF%u%c", idx / 2, suffix);
+ } else {
+ idx = zl3073x_dpll_output_pin_output_get(pin);
+
+ switch (zl3073x_output_signal_format_get(zldev, idx)) {
+ case OUTPUT_MODE_SIGNAL_FORMAT_LVDS:
+ case OUTPUT_MODE_SIGNAL_FORMAT_DIFFERENTIAL:
+ case OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM:
+ /* For differential use OUT<n> */
+ snprintf(pin_info->package_label,
+ sizeof(pin_info->package_label), "OUT%u", idx);
+ break;
+ default:
+ /* For single-ended use OUT<n>P/N */
+ snprintf(pin_info->package_label,
+ sizeof(pin_info->package_label), "OUT%u%c",
+ idx, suffix);
+ break;
+ }
+ }
+
+ /* Set package_label pointer in DPLL core properties to generated
+ * string.
+ */
+ pin_info->props.package_label = pin_info->package_label;
+}
+
+/**
+ * zl3073x_dpll_pin_info_get - get pin info
+ * @pin: pin whose info is returned
+ *
+ * The function allocates pin info structure, generates package label
+ * string according pin type and its order number.
+ *
+ * Returns pointer to allocated pin info structure that has to be freed
+ * by @zl3073x_dpll_pin_info_put by the caller and in case of error
+ * then error pointer is returned.
+ */
+static struct zl3073x_dpll_pin_info *
+zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll_pin_info *pin_info;
+
+ /* Allocate pin info structure */
+ pin_info = kzalloc(sizeof(*pin_info), GFP_KERNEL);
+ if (!pin_info)
+ return ERR_PTR(-ENOMEM);
+
+ /* Set pin type */
+ if (zl3073x_dpll_is_input_pin(pin))
+ pin_info->props.type = DPLL_PIN_TYPE_EXT;
+ else
+ pin_info->props.type = DPLL_PIN_TYPE_GNSS;
+
+ pin_info->props.phase_range.min = S32_MIN;
+ pin_info->props.phase_range.max = S32_MAX;
+
+ /* Generate package label for the given pin */
+ zl3073x_dpll_pin_info_package_label_set(pin, pin_info);
+
+ return pin_info;
+}
+
+/**
+ * zl3073x_dpll_pin_info_put - free pin info
+ * @pin_info: pin info to free
+ *
+ * The function deallocates given pin info structure.
+ */
+static void
+zl3073x_dpll_pin_info_put(struct zl3073x_dpll_pin_info *pin_info)
+{
+ /* Free the pin info structure itself */
+ kfree(pin_info);
+}
+
+static int
+zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct zl3073x_dpll_pin_info *pin_info;
+ const struct dpll_pin_ops *ops;
+ int rc;
+
+ /* Get pin info */
+ pin_info = zl3073x_dpll_pin_info_get(pin);
+ if (IS_ERR(pin_info))
+ return PTR_ERR(pin_info);
+
+ /* Create or get existing DPLL pin */
+ pin->dpll_pin = dpll_pin_get(zldpll->mfd->clock_id, pin->index,
+ THIS_MODULE, &pin_info->props);
+ if (IS_ERR(pin->dpll_pin)) {
+ rc = PTR_ERR(pin->dpll_pin);
+ goto err_pin_get;
+ }
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ ops = &zl3073x_dpll_input_pin_ops;
+ else
+ ops = &zl3073x_dpll_output_pin_ops;
+
+ /* Register the pin */
+ rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, ops, pin);
+ if (rc)
+ goto err_register;
+
+ /* Free pin info */
+ zl3073x_dpll_pin_info_put(pin_info);
+
+ return 0;
+
+err_register:
+ dpll_pin_put(pin->dpll_pin);
+ pin->dpll_pin = NULL;
+err_pin_get:
+ zl3073x_dpll_pin_info_put(pin_info);
+
+ return rc;
+}
+
+static void
+zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ const struct dpll_pin_ops *ops;
+
+ if (IS_ERR_OR_NULL(pin->dpll_pin))
+ return;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ ops = &zl3073x_dpll_input_pin_ops;
+ else
+ ops = &zl3073x_dpll_output_pin_ops;
+
+ /* Unregister the pin */
+ dpll_pin_unregister(zldpll->dpll_dev, pin->dpll_pin, ops, pin);
+
+ dpll_pin_put(pin->dpll_pin);
+ pin->dpll_pin = NULL;
+}
+
+static int
+zl3073x_dpll_register_input_pin(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 ref;
+
+ /* Get index of the pin */
+ ref = zl3073x_dpll_pin_index_get(pin);
+
+ /* If the ref is differential then register only for the P-pin */
+ if (zl3073x_input_is_diff(zldev, ref) && zl3073x_dpll_is_n_pin(pin)) {
+ dev_dbg(zldev->dev, "INPUT%u is differential, skipping N-pin\n",
+ ref);
+ return 0;
+ }
+
+ /* If the ref is disabled then skip registration */
+ if (!zl3073x_input_is_enabled(zldev, ref)) {
+ dev_dbg(zldev->dev, "INPUT%u is disabled\n", ref);
+ return 0;
+ }
+
+ scoped_guard(zl3073x, zldev) {
+ int rc;
+
+ rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio);
+ if (rc)
+ return rc;
+ }
+
+ if (pin->prio == DPLL_REF_PRIO_NONE) {
+ /* Clamp priority to max value and make pin non-selectable */
+ pin->prio = DPLL_REF_PRIO_MAX;
+ pin->selectable = false;
+ } else {
+ /* Mark pin as selectable */
+ pin->selectable = true;
+ }
+
+ /* Register the pin */
+ return zl3073x_dpll_pin_register(pin);
+}
+
+static int
+zl3073x_dpll_register_output_pin(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 dpll, output, synth;
+
+ /* Get output id for the pin and synth where it is connected to */
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ synth = zl3073x_output_synth_get(zldev, output);
+
+ /* Get DPLL channel the synth is associated with */
+ dpll = zl3073x_synth_dpll_get(zldev, synth);
+
+ /* If the output's synth is connected to different DPLL channel
+ * then skip registration.
+ */
+ if (dpll != zldpll->id) {
+ dev_dbg(zldev->dev, "OUTPUT%u is driven by different DPLL\n",
+ output);
+ return 0;
+ }
+
+ /* If the output is disabled then skip registration */
+ if (!zl3073x_output_is_enabled(zldev, output)) {
+ dev_dbg(zldev->dev, "OUTPUT%u is disabled\n", output);
+ return 0;
+ }
+
+ /* Check ouput's signal format */
+ switch (zldev->output[output].signal_format) {
+ case OUTPUT_MODE_SIGNAL_FORMAT_DISABLED:
+ /* Output is disabled, nothing to register */
+ dev_dbg(zldev->dev, "OUTPUT%u is disabled by signal format\n",
+ output);
+ return 0;
+
+ case OUTPUT_MODE_SIGNAL_FORMAT_LVDS:
+ case OUTPUT_MODE_SIGNAL_FORMAT_DIFFERENTIAL:
+ case OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM:
+ /* Output is differential, skip registration for N-pin */
+ if (zl3073x_dpll_is_n_pin(pin)) {
+ dev_dbg(zldev->dev,
+ "OUTPUT%u is differential, skipping N-pin\n",
+ output);
+ return 0;
+ }
+ break;
+
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_INV:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV:
+ /* Output is two single ended outputs, continue with
+ * registration.
+ */
+ break;
+
+ case OUTPUT_MODE_SIGNAL_FORMAT_ONE_P:
+ /* Output is one single ended P-pin output */
+ if (zl3073x_dpll_is_n_pin(pin)) {
+ dev_dbg(zldev->dev,
+ "OUTPUT%u is P-pin only, skipping N-pin\n",
+ output);
+ return 0;
+ }
+ break;
+ case OUTPUT_MODE_SIGNAL_FORMAT_ONE_N:
+ /* Output is one single ended N-pin output */
+ if (zl3073x_dpll_is_p_pin(pin)) {
+ dev_dbg(zldev->dev,
+ "OUTPUT%u is N-pin only, skipping P-pin\n",
+ output);
+ return 0;
+ }
+ break;
+ default:
+ dev_warn(zldev->dev, "Unknown output mode signal format: %u\n",
+ zldev->output[output].signal_format);
+ return 0;
+ }
+
+ /* Register the pin */
+ return zl3073x_dpll_pin_register(pin);
+}
+
+static int
+zl3073x_dpll_register_pins(struct zl3073x_dpll *zldpll)
+{
+ int i, rc;
+
+ for (i = 0; i < ZL3073X_NUM_PINS; i++) {
+ struct zl3073x_dpll_pin *pin = &zldpll->pins[i];
+
+ pin->index = i;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ rc = zl3073x_dpll_register_input_pin(pin);
+ else
+ rc = zl3073x_dpll_register_output_pin(pin);
+
+ if (rc)
+ goto err_register;
+ }
+
+ return 0;
+
+err_register:
+ while (i--)
+ zl3073x_dpll_pin_unregister(&zldpll->pins[i]);
+
+ return rc;
+}
+
+static void
+zl3073x_dpll_unregister_pins(struct zl3073x_dpll *zldpll)
+{
+ int i;
+
+ for (i = 0; i < ZL3073X_NUM_PINS; i++)
+ zl3073x_dpll_pin_unregister(&zldpll->pins[i]);
+}
+
+static int
+zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ int rc;
+
+ scoped_guard(zl3073x, zldev) {
+ u8 dpll_mode_refsel;
+
+ /* Read DPLL mode and forcibly selected reference */
+ rc = zl3073x_read_dpll_mode_refsel(zldev, zldpll->id,
+ &dpll_mode_refsel);
+ if (rc)
+ return rc;
+
+ /* Extract mode and selected input reference */
+ zldpll->refsel_mode = FIELD_GET(DPLL_MODE_REFSEL_MODE,
+ dpll_mode_refsel);
+ zldpll->forced_ref = FIELD_GET(DPLL_MODE_REFSEL_REF,
+ dpll_mode_refsel);
+ }
+
+ zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id,
+ THIS_MODULE);
+ if (IS_ERR(zldpll->dpll_dev))
+ return PTR_ERR(zldpll->dpll_dev);
+
+ rc = dpll_device_register(zldpll->dpll_dev, DPLL_TYPE_PPS,
+ &zl3073x_dpll_device_ops, zldpll);
+ if (rc) {
+ dpll_device_put(zldpll->dpll_dev);
+ zldpll->dpll_dev = NULL;
+ }
+
+ return rc;
+}
+
+static void
+zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)
+{
+ if (IS_ERR_OR_NULL(zldpll->dpll_dev))
+ return;
+
+ dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops,
+ zldpll);
+ dpll_device_put(zldpll->dpll_dev);
+ zldpll->dpll_dev = NULL;
+}
+
+static int
+zl3073x_dpll_init(struct zl3073x_dpll *zldpll)
+{
+ int rc;
+
+ rc = zl3073x_dpll_register(zldpll);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_dpll_register_pins(zldpll);
+ if (rc)
+ zl3073x_dpll_unregister(zldpll);
+
+ return rc;
+}
+
+static void
+zl3073x_dpll_periodic_work(struct kthread_work *work)
+{
+ struct zl3073x_dpll *zldpll = container_of(work, struct zl3073x_dpll,
+ work.work);
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ enum dpll_lock_status lock_status;
+ int i, rc;
+
+ /* Get current lock status for the DPLL */
+ rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
+ &lock_status, NULL, NULL);
+ if (rc) {
+ dev_err(zldpll->mfd->dev,
+ "Failed to get DPLL lock status: %pe", ERR_PTR(rc));
+ goto out;
+ }
+
+ /* If lock status was changed then notify DPLL core */
+ if (zldpll->lock_status != lock_status) {
+ zldpll->lock_status = lock_status;
+ dpll_device_change_ntf(zldpll->dpll_dev);
+ }
+
+ /* Output pins change checks are not necessary because output states
+ * are constant.
+ */
+ for (i = 0; i < ZL3073X_NUM_INPUT_PINS; i++) {
+ struct zl3073x_dpll_pin *pin;
+ enum dpll_pin_state state;
+
+ /* Input pins starts are stored after output pins */
+ pin = &zldpll->pins[ZL3073X_NUM_OUTPUT_PINS + i];
+
+ /* Skip non-registered pins */
+ if (!pin->dpll_pin)
+ continue;
+
+ rc = zl3073x_dpll_input_pin_state_on_dpll_get(pin->dpll_pin,
+ pin,
+ zldpll->dpll_dev,
+ zldpll, &state,
+ NULL);
+ if (rc)
+ goto out;
+
+ if (state != pin->pin_state) {
+ dev_dbg(zldev->dev,
+ "INPUT%u state changed to %u\n",
+ zl3073x_dpll_pin_index_get(pin), state);
+ pin->pin_state = state;
+ dpll_pin_change_ntf(pin->dpll_pin);
+ }
+ }
+
+out:
+ /* Run twice a second */
+ kthread_queue_delayed_work(zldpll->kworker, &zldpll->work,
+ msecs_to_jiffies(500));
+}
+
+static int
+zl3073x_dpll_init_worker(struct zl3073x_dpll *zldpll)
+{
+ struct kthread_worker *kworker;
+
+ kthread_init_delayed_work(&zldpll->work, zl3073x_dpll_periodic_work);
+ kworker = kthread_run_worker(0, "zl3073x-%s", dev_name(zldpll->dev));
+ if (IS_ERR(kworker))
+ return PTR_ERR(kworker);
+
+ zldpll->kworker = kworker;
+ kthread_queue_delayed_work(zldpll->kworker, &zldpll->work, 0);
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ int rc;
+
+ guard(zl3073x)(zldpll->mfd);
+
+ rc = zl3073x_write_synth_phase_shift_mask(zldev, 0x1f);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_synth_phase_shift_intvl(zldev, 0x01);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_synth_phase_shift_data(zldev, 0xffff);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_synth_phase_shift_ctrl(zldev, 0x01);
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_probe(struct platform_device *pdev)
+{
+ struct zl3073x_dpll *zldpll;
+ int rc;
+
+ zldpll = devm_kzalloc(&pdev->dev, sizeof(*zldpll), GFP_KERNEL);
+ if (!zldpll)
+ return -ENOMEM;
+
+ zldpll->dev = &pdev->dev;
+ zldpll->mfd = dev_get_drvdata(pdev->dev.parent);
+ zldpll->id = pdev->mfd_cell->id;
+
+ rc = zl3073x_dpll_init(zldpll);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_dpll_init_worker(zldpll);
+ if (rc)
+ goto err_init_worker;
+
+ platform_set_drvdata(pdev, zldpll);
+
+ /* Initial firmware fine phase correction */
+ rc = zl3073x_dpll_init_fine_phase_adjust(zldpll);
+ if (rc)
+ goto err_init_phase_adjust;
+
+ return rc;
+
+err_init_phase_adjust:
+ kthread_cancel_delayed_work_sync(&zldpll->work);
+ kthread_destroy_worker(zldpll->kworker);
+err_init_worker:
+ zl3073x_dpll_unregister_pins(zldpll);
+ zl3073x_dpll_unregister(zldpll);
+
+ return rc;
+}
+
+static void
+zl3073x_dpll_remove(struct platform_device *pdev)
+{
+ struct zl3073x_dpll *zldpll = platform_get_drvdata(pdev);
+
+ /* Stop worker */
+ kthread_cancel_delayed_work_sync(&zldpll->work);
+ kthread_destroy_worker(zldpll->kworker);
+
+ /* Unregister all pins and dpll */
+ zl3073x_dpll_unregister_pins(zldpll);
+ zl3073x_dpll_unregister(zldpll);
+}
+
+static const struct platform_device_id zl3073x_dpll_platform_id[] = {
+ { "zl3073x-dpll", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, zl3073x_dpll_platform_id);
+
+static struct platform_driver zl3073x_dpll_driver = {
+ .driver = {
+ .name = "zl3073x-dpll",
+ },
+ .probe = zl3073x_dpll_probe,
+ .remove = zl3073x_dpll_remove,
+ .id_table = zl3073x_dpll_platform_id,
+};
+
+module_platform_driver(zl3073x_dpll_driver);
+
+MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
+MODULE_AUTHOR("Tariq Haddad <tariq.haddad@microchip.com>");
+MODULE_DESCRIPTION("Microchip ZL3073x DPLL driver");
+MODULE_IMPORT_NS("ZL3073X");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/zl3073x.h b/include/linux/mfd/zl3073x.h
index 825e6706dc974..3eaa5d96ca9af 100644
--- a/include/linux/mfd/zl3073x.h
+++ b/include/linux/mfd/zl3073x.h
@@ -9,9 +9,14 @@
/*
* Hardware limits for ZL3073x chip family
*/
+#define ZL3073X_NUM_CHANNELS 2
#define ZL3073X_NUM_INPUTS 10
#define ZL3073X_NUM_OUTPUTS 10
#define ZL3073X_NUM_SYNTHS 5
+#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_INPUTS
+#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTPUTS * 2)
+#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
+ ZL3073X_NUM_OUTPUT_PINS)
struct zl3073x_input {
bool enabled;
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 14/28] mfd: zl3073x: Register DPLL sub-device during init
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (12 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 13/28] dpll: Add Microchip ZL3073x DPLL driver Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin Ivan Vecera
` (14 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Register DPLL sub-devices to expose this functionality provided
by ZL3073x chip family. Each sub-device represents one of the provided
DPLL channels.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/mfd/zl3073x-core.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
index 9ed405a62fa86..8ac59133bc54a 100644
--- a/drivers/mfd/zl3073x-core.c
+++ b/drivers/mfd/zl3073x-core.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
+#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/unaligned.h>
#include <net/devlink.h>
@@ -760,6 +761,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
return rc;
}
+static const struct mfd_cell zl3073x_devs[] = {
+ MFD_CELL_BASIC("zl3073x-dpll", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("zl3073x-dpll", NULL, NULL, 0, 1),
+};
+
int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
{
u16 id, revision, fw_ver;
@@ -805,6 +811,16 @@ int zl3073x_dev_init(struct zl3073x_dev *zldev, u8 dev_id)
FIELD_GET(GENMASK(15, 8), cfg_ver),
FIELD_GET(GENMASK(7, 0), cfg_ver));
+ /* Add DPLL sub-device cells */
+ rc = devm_mfd_add_devices(zldev->dev, PLATFORM_DEVID_AUTO, zl3073x_devs,
+ ARRAY_SIZE(zl3073x_devs), NULL, 0, NULL);
+ if (rc) {
+ dev_err(zldev->dev, "Failed to add sub-devices: %pe\n",
+ ERR_PTR(rc));
+
+ return rc;
+ }
+
devlink = priv_to_devlink(zldev);
devlink_register(devlink);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (13 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 14/28] mfd: zl3073x: Register DPLL sub-device during init Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 18:01 ` Krzysztof Kozlowski
2025-04-07 17:31 ` [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family Ivan Vecera
` (13 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
This adds DT bindings schema for DPLL (device phase-locked loop)
device and associated pin. The schema follows existing DPLL core API
and should be used to expose information that should be provided
by platform firmware.
The schema for DPLL device describe a DPLL chip that can contain
one or more DPLLs (channels) and platform can specify their types.
For now 'pps' and 'eec' types supported and these values are mapped
to DPLL core's enums.
The DPLL device can have optionally 'input-pins' and 'output-pins'
sub-nodes that contain pin sub-nodes.
These pin sub-nodes follows schema for dpll-pin and can contain
information about the particular pin.
The pin contains the following properties:
* reg - pin HW index (physical pin number of given type)
* label - string that is used as board label by DPLL core
* type - string that indicates pin type (mapped to DPLL core pin type)
* esync-control - boolean that indicates whether embeddded sync control
is allowed for this pin
* supported-frequencies - list of 64bit values that represents frequencies
that are allowed to be configured for the pin
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
.../devicetree/bindings/dpll/dpll-device.yaml | 84 +++++++++++++++++++
.../devicetree/bindings/dpll/dpll-pin.yaml | 43 ++++++++++
MAINTAINERS | 2 +
3 files changed, 129 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dpll/dpll-device.yaml
create mode 100644 Documentation/devicetree/bindings/dpll/dpll-pin.yaml
diff --git a/Documentation/devicetree/bindings/dpll/dpll-device.yaml b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
new file mode 100644
index 0000000000000..e6c309abb857f
--- /dev/null
+++ b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
@@ -0,0 +1,84 @@
+# 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 are used for precise clock
+ synchronization in networking and telecom hardware. The device can
+ have one or more channels (DPLLs) and one or more input and output
+ pins. Each DPLL channel can either produce pulse-per-clock signal
+ or drive ethernet equipment clock. The type of each channel is
+ indicated by dpll-types property.
+
+properties:
+ $nodename:
+ pattern: "^dpll(@.*)?$"
+
+ "#address-cells":
+ const: 0
+
+ "#size-cells":
+ const: 0
+
+ num-dplls:
+ description: Number of DPLL channels in this device.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 1
+
+ dpll-types:
+ description: List of DPLL 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-9]+$":
+ $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"
+
+dependentRequired:
+ dpll-types: [ num-dplls ]
+
+additionalProperties: true
diff --git a/Documentation/devicetree/bindings/dpll/dpll-pin.yaml b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
new file mode 100644
index 0000000000000..9aea8ceabb5af
--- /dev/null
+++ b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
@@ -0,0 +1,43 @@
+# 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: |
+ Schema for defining input and output pins of a Digital Phase-Locked Loop (DPLL).
+ Each pin can have a set of supported frequencies, label, type and may support
+ embedded sync.
+
+properties:
+ reg:
+ description: Hardware index of the pin.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ 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:
+ description: List of supported frequencies for this pin, expressed in Hz.
+ $ref: /schemas/types.yaml#/definitions/uint64-array
+
+ type:
+ description: Type of the pin
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: [ext, gnss, int, mux, synce]
+
+
+required:
+ - reg
+
+additionalProperties: false
diff --git a/MAINTAINERS b/MAINTAINERS
index 3d542440d0b2b..eaf2576a9b746 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7194,6 +7194,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
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (14 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 18:04 ` Krzysztof Kozlowski
2025-04-07 17:31 ` [PATCH 17/28] dpll: zl3073x: Read DPLL types from firmware Ivan Vecera
` (12 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
This adds DT bindings schema for Microchip Azurite DPLL chip family.
These bindings are used by zl3073x driver and specifies this device
that can be connected either to I2C or SPI bus.
The schema inherits existing dpll-device and dpll-pin schemas.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
.../bindings/dpll/microchip,zl3073x.yaml | 74 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 75 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
new file mode 100644
index 0000000000000..38a6cc00bc026
--- /dev/null
+++ b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dpll/microchip,zl3073x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip Azurite DPLL device
+
+maintainers:
+ - Ivan Vecera <ivecera@redhat.com>
+
+properties:
+ compatible:
+ enum:
+ - microchip,zl3073x-i2c
+ - microchip,zl3073x-spi
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: /schemas/dpll/dpll-device.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dpll@70 {
+ compatible = "microchip,zl3073x-i2c";
+ #address-cells = <0>;
+ #size-cells = <0>;
+ reg = <0x70>;
+ status = "okay";
+
+ num-dplls = <2>;
+ dpll-types = "pps", "eec";
+
+ input-pins {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pin@0 { /* REF0P */
+ reg = <0>;
+
+ label = "Input 0";
+ type = "ext";
+ supported-frequencies = /bits/ 64 <1 1000>;
+ };
+ };
+
+ output-pins {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pin@3 { /* OUT1N */
+ reg = <3>;
+
+ label = "Output 1";
+ type = "gnss";
+ esync-control;
+ supported-frequencies = /bits/ 64 <1 10000>;
+ };
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index eaf2576a9b746..ec86bec05c40c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16001,6 +16001,7 @@ 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,zl3073x.yaml
F: drivers/dpll/dpll_zl3073x*
F: drivers/mfd/zl3073x*
F: include/linux/mfd/zl3073x.h
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 17/28] dpll: zl3073x: Read DPLL types from firmware
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (15 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 18/28] dpll: zl3073x: Read optional pin properties " Ivan Vecera
` (11 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
System firmware (DT, ACPI...) can provide a type for each DPLL. The types
are stored in property 'dpll-types' as string array and possible values
'pps' and 'eec' are mapped to DPLL enums DPLL_TYPE_PPS and DPLL_TYPE_EEC.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 34bd6964fe001..3ff53a333a6e9 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -6,6 +6,7 @@
#include <linux/mfd/zl3073x.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
/*
* Register Map Page 2, Status
@@ -825,6 +826,36 @@ zl3073x_dpll_unregister_pins(struct zl3073x_dpll *zldpll)
zl3073x_dpll_pin_unregister(&zldpll->pins[i]);
}
+static enum dpll_type
+zl3073x_dpll_type_get(struct zl3073x_dpll *zldpll)
+{
+ const char *types[ZL3073X_NUM_CHANNELS];
+ enum dpll_type type;
+ int rc;
+
+ /* Set default */
+ type = DPLL_TYPE_PPS;
+
+ /* Read dpll types property from firmware */
+ rc = device_property_read_string_array(zldpll->mfd->dev, "dpll-types",
+ types, ARRAY_SIZE(types));
+
+ /* It is not present or property does not exist, use default */
+ if (rc <= zldpll->id)
+ return type;
+
+ if (!strcmp(types[zldpll->id], "pps"))
+ type = DPLL_TYPE_PPS;
+ else if (!strcmp(types[zldpll->id], "eec"))
+ type = DPLL_TYPE_EEC;
+ else
+ dev_info(zldpll->mfd->dev,
+ "Unknown dpll type '%s', using default\n",
+ types[zldpll->id]);
+
+ return type;
+}
+
static int
zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
{
@@ -852,7 +883,8 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
if (IS_ERR(zldpll->dpll_dev))
return PTR_ERR(zldpll->dpll_dev);
- rc = dpll_device_register(zldpll->dpll_dev, DPLL_TYPE_PPS,
+ rc = dpll_device_register(zldpll->dpll_dev,
+ zl3073x_dpll_type_get(zldpll),
&zl3073x_dpll_device_ops, zldpll);
if (rc) {
dpll_device_put(zldpll->dpll_dev);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 18/28] dpll: zl3073x: Read optional pin properties from firmware
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (16 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 17/28] dpll: zl3073x: Read DPLL types from firmware Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 18:06 ` Krzysztof Kozlowski
2025-04-07 17:31 ` [PATCH 19/28] dpll: zl3073x: Implement input pin selection in manual mode Ivan Vecera
` (10 subsequent siblings)
28 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
The firmware (DT, ACPI...) can specify properties for particular
pins. Input pins are stored in 'input-pins' sub-node and output
pins in 'output-pins'. Each pin is represented by separate node
in 'input-pins' or 'output-pins'.
The properties that are supported:
* reg - integer that specifies pin index
* label - string that is used by driver as board label
* type - string that indicates pin type
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 102 ++++++++++++++++++++++++++++++++++--
1 file changed, 98 insertions(+), 4 deletions(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 3ff53a333a6e9..cf2cdd6dec263 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -73,10 +73,12 @@ ZL3073X_REG8_IDX_DEF(dpll_ref_prio, 0x652,
* struct zl3073x_dpll_pin_info - DPLL pin info
* @props: DPLL core pin properties
* @package_label: pin package label
+ * @fwnode: pin firmware node
*/
struct zl3073x_dpll_pin_info {
struct dpll_pin_properties props;
char package_label[8];
+ struct fwnode_handle *fwnode;
};
/**
@@ -482,6 +484,62 @@ static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.mode_get = zl3073x_dpll_mode_get,
};
+/**
+ * zl3073x_dpll_pin_fwnode_get - get fwnode for given pin
+ * pin: pointer to pin structure
+ *
+ * The caller is responsible for calling fwnode_handle_put() on the returned
+ * fwnode pointer.
+ *
+ * Returns the firmware node for the given pin if it is present or
+ * NULL if it is missing.
+ */
+static struct fwnode_handle *
+zl3073x_dpll_pin_fwnode_get(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct fwnode_handle *pins_node, *pin_node;
+ const char *node_name;
+ u8 idx;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ node_name = "input-pins";
+ else
+ node_name = "output-pins";
+
+ /* Get node containing input or output pins */
+ pins_node = device_get_named_child_node(zldpll->mfd->dev, node_name);
+ if (!pins_node) {
+ dev_dbg(zldpll->mfd->dev, "'%s' sub-node is missing\n",
+ node_name);
+ return NULL;
+ }
+
+ /* Get pin HW index */
+ idx = zl3073x_dpll_pin_index_get(pin);
+
+ /* Enumerate 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 (idx == reg)
+ break;
+ }
+
+ /* Release pin parent node */
+ fwnode_handle_put(pins_node);
+
+ dev_dbg(zldpll->mfd->dev, "Firmware node for %s%u%c %sfound\n",
+ zl3073x_dpll_is_input_pin(pin) ? "REF" : "OUT", idx / 2,
+ zl3073x_dpll_is_p_pin(pin) ? 'P' : 'N',
+ pin_node ? "" : "NOT ");
+
+ return pin_node;
+}
+
/**
* zl3073x_dpll_pin_info_package_label_set - generate package label for the pin
* @pin: pointer to pin
@@ -548,8 +606,10 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
* zl3073x_dpll_pin_info_get - get pin info
* @pin: pin whose info is returned
*
- * The function allocates pin info structure, generates package label
- * string according pin type and its order number.
+ * The function looks for firmware node for the given pin if it is provided
+ * by the system firmware (DT or ACPI), allocates pin info structure,
+ * generates package label string according pin type and its order number
+ * and optionally fetches board label from the firmware node if it exists.
*
* Returns pointer to allocated pin info structure that has to be freed
* by @zl3073x_dpll_pin_info_put by the caller and in case of error
@@ -558,14 +618,16 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
static struct zl3073x_dpll_pin_info *
zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
{
+ struct zl3073x_dev *zldev = pin_to_dev(pin);
struct zl3073x_dpll_pin_info *pin_info;
+ const char *pin_type;
/* Allocate pin info structure */
pin_info = kzalloc(sizeof(*pin_info), GFP_KERNEL);
if (!pin_info)
return ERR_PTR(-ENOMEM);
- /* Set pin type */
+ /* Set default pin type */
if (zl3073x_dpll_is_input_pin(pin))
pin_info->props.type = DPLL_PIN_TYPE_EXT;
else
@@ -577,6 +639,34 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
/* Generate package label for the given pin */
zl3073x_dpll_pin_info_package_label_set(pin, pin_info);
+ /* Get firmware node for the given pin */
+ pin_info->fwnode = zl3073x_dpll_pin_fwnode_get(pin);
+ if (!pin_info->fwnode)
+ /* Return if it does not exist */
+ return pin_info;
+
+ /* Look for label property and store the value as board label */
+ fwnode_property_read_string(pin_info->fwnode, "label",
+ &pin_info->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(pin_info->fwnode, "type", &pin_type)) {
+ if (!strcmp(pin_type, "ext"))
+ pin_info->props.type = DPLL_PIN_TYPE_EXT;
+ else if (!strcmp(pin_type, "gnss"))
+ pin_info->props.type = DPLL_PIN_TYPE_GNSS;
+ else if (!strcmp(pin_type, "int"))
+ pin_info->props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+ else if (!strcmp(pin_type, "synce"))
+ pin_info->props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
+ else
+ dev_warn(zldev->dev,
+ "Unknown or unsupported pin type '%s'\n",
+ pin_type);
+ }
+
return pin_info;
}
@@ -584,11 +674,15 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
* zl3073x_dpll_pin_info_put - free pin info
* @pin_info: pin info to free
*
- * The function deallocates given pin info structure.
+ * The function deallocates given pin info structure and firmware node handle.
*/
static void
zl3073x_dpll_pin_info_put(struct zl3073x_dpll_pin_info *pin_info)
{
+ /* Put firmware handle if it is present */
+ if (pin_info->fwnode)
+ fwnode_handle_put(pin_info->fwnode);
+
/* Free the pin info structure itself */
kfree(pin_info);
}
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 19/28] dpll: zl3073x: Implement input pin selection in manual mode
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (17 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 18/28] dpll: zl3073x: Read optional pin properties " Ivan Vecera
@ 2025-04-07 17:31 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 20/28] dpll: zl3073x: Add support to get/set priority on input pins Ivan Vecera
` (9 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:31 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
Implement input pin state setting if the DPLL is running in manual mode.
The driver indicates manual mode if the DPLL mode is one of ref-lock,
forced-holdover, freerun and NCO (numerically controlled oscillator).
Use these modes to implement input pin state change between connected
and disconnected states. When the user set the particular pin as
connected the driver marks this input pin as forced reference and
switches the DPLL mode to ref-lock. When the use set the pin as
disconnected the driver switches the DPLL to freerun or forced holdover
mode. The switch to holdover mode is done if the DPLL has holdover
capability (e.g is currently locked with holdover acquired).
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 114 +++++++++++++++++++++++++++++++++++-
1 file changed, 111 insertions(+), 3 deletions(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index cf2cdd6dec263..ad2a8d383daaf 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -274,6 +274,68 @@ zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
*
* Return 0 in case of success or negative value otherwise.
*/
+static int
+zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
+{
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 mode, mode_refsel;
+ int rc;
+
+ mode = zldpll->refsel_mode;
+
+ switch (mode) {
+ case DPLL_MODE_REFSEL_MODE_REFLOCK: /* Manual mode with ref selected */
+ if (ref == ZL3073X_REF_NONE) {
+ switch (zldpll->lock_status) {
+ case DPLL_LOCK_STATUS_LOCKED_HO_ACQ:
+ case DPLL_LOCK_STATUS_HOLDOVER:
+ /* Switch to forced holdover */
+ mode = DPLL_MODE_REFSEL_MODE_HOLDOVER;
+ break;
+ default:
+ /* Switch to freerun */
+ mode = DPLL_MODE_REFSEL_MODE_FREERUN;
+ break;
+ }
+ /* Keep selected reference */
+ ref = zldpll->forced_ref;
+ } else if (ref == zldpll->forced_ref) {
+ /* No register update - same mode and same ref */
+ return 0;
+ }
+ break;
+ case DPLL_MODE_REFSEL_MODE_FREERUN: /* Manual mode without no ref */
+ case DPLL_MODE_REFSEL_MODE_HOLDOVER:
+ if (ref == ZL3073X_REF_NONE)
+ /* No register update - keep current mode */
+ return 0;
+
+ /* Switch to reflock mode and update ref selection */
+ mode = DPLL_MODE_REFSEL_MODE_REFLOCK;
+ break;
+ default:
+ /* For other modes like automatic or NCO ref cannot be selected
+ * manually
+ */
+ return -EOPNOTSUPP;
+ }
+
+ /* Build mode_refsel value */
+ mode_refsel = FIELD_PREP(DPLL_MODE_REFSEL_MODE, mode) |
+ FIELD_PREP(DPLL_MODE_REFSEL_REF, ref);
+
+ /* Update dpll_mode_refsel register */
+ rc = zl3073x_write_dpll_mode_refsel(zldev, zldpll->id, mode_refsel);
+ if (rc)
+ return rc;
+
+ /* Store new mode and forced reference */
+ zldpll->refsel_mode = mode;
+ zldpll->forced_ref = ref;
+
+ return rc;
+}
+
static int
zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
{
@@ -396,6 +458,48 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
return 0;
}
+static int
+zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 new_ref;
+ int rc;
+
+ switch (zldpll->refsel_mode) {
+ case DPLL_MODE_REFSEL_MODE_REFLOCK:
+ case DPLL_MODE_REFSEL_MODE_FREERUN:
+ case DPLL_MODE_REFSEL_MODE_HOLDOVER:
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ /* Choose the pin as new selected reference */
+ new_ref = zl3073x_dpll_pin_index_get(pin);
+ } else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+ /* No reference */
+ new_ref = ZL3073X_REF_NONE;
+ } else {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Invalid pin state for manual mode");
+ return -EINVAL;
+ }
+
+ rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref);
+ break;
+ default:
+ /* In other modes we cannot change input reference */
+ NL_SET_ERR_MSG(extack,
+ "Pin state cannot be changed in current mode");
+ rc = -EOPNOTSUPP;
+ break;
+ }
+
+ return rc;
+}
+
static int
zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -472,6 +576,7 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
+ .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set,
};
static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
@@ -627,11 +732,14 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
if (!pin_info)
return ERR_PTR(-ENOMEM);
- /* Set default pin type */
- if (zl3073x_dpll_is_input_pin(pin))
+ /* Set default pin type and capabilities */
+ if (zl3073x_dpll_is_input_pin(pin)) {
pin_info->props.type = DPLL_PIN_TYPE_EXT;
- else
+ pin_info->props.capabilities =
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ } else {
pin_info->props.type = DPLL_PIN_TYPE_GNSS;
+ }
pin_info->props.phase_range.min = S32_MIN;
pin_info->props.phase_range.max = S32_MAX;
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 20/28] dpll: zl3073x: Add support to get/set priority on input pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (18 preceding siblings ...)
2025-04-07 17:31 ` [PATCH 19/28] dpll: zl3073x: Implement input pin selection in manual mode Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 21/28] dpll: zl3073x: Implement input pin state setting in automatic mode Ivan Vecera
` (8 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support for getting and setting input pin priority. Implement
required callbacks and set appropriate capability for input pins.
Although the pin priority make sense only if the DPLL is running in
automatic mode we have to expose this capability unconditionally because
input pins (references) are shared between both DPLLs where on of them
can run in automatic mode while the other one not.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 82 +++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index ad2a8d383daaf..072a33ec12399 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -403,6 +403,45 @@ zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
return rc;
}
+static int
+zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio)
+{
+ struct zl3073x_dpll *zldpll = pin_to_dpll(pin);
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ u8 idx, ref_prio;
+ int rc;
+
+ /* Read DPLL configuration into mailbox */
+ rc = zl3073x_mb_dpll_read(zldev, zldpll->id);
+ if (rc)
+ return rc;
+
+ /* Get index of the pin */
+ idx = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read ref prio nibble */
+ rc = zl3073x_read_dpll_ref_prio(zldev, idx / 2, &ref_prio);
+ if (rc)
+ return rc;
+
+ /* Update nibble according pin type */
+ if (zl3073x_dpll_is_p_pin(pin)) {
+ ref_prio &= ~DPLL_REF_PRIO_REF_P;
+ ref_prio |= FIELD_PREP(DPLL_REF_PRIO_REF_P, prio);
+ } else {
+ ref_prio &= ~DPLL_REF_PRIO_REF_N;
+ ref_prio |= FIELD_PREP(DPLL_REF_PRIO_REF_N, prio);
+ }
+
+ /* Write the updated priority value */
+ rc = zl3073x_write_dpll_ref_prio(zldev, idx / 2, ref_prio);
+ if (rc)
+ return rc;
+
+ /* Update channel configuration from mailbox */
+ return zl3073x_mb_dpll_write(zldev, zldpll->id);
+}
+
static int
zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -500,6 +539,46 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
return rc;
}
+static int
+zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ u32 *prio, struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *pin = pin_priv;
+
+ *prio = pin->prio;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ u32 prio, struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ int rc;
+
+ if (prio > DPLL_REF_PRIO_MAX)
+ return -EINVAL;
+
+ /* If the pin is selectable then update HW registers */
+ if (pin->selectable) {
+ guard(zl3073x)(zldev);
+
+ rc = zl3073x_dpll_ref_prio_set(pin, prio);
+ if (rc)
+ return rc;
+ }
+
+ /* Save priority */
+ pin->prio = prio;
+
+ return 0;
+}
+
static int
zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -575,6 +654,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
+ .prio_get = zl3073x_dpll_input_pin_prio_get,
+ .prio_set = zl3073x_dpll_input_pin_prio_set,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
.state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set,
};
@@ -736,6 +817,7 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
if (zl3073x_dpll_is_input_pin(pin)) {
pin_info->props.type = DPLL_PIN_TYPE_EXT;
pin_info->props.capabilities =
+ DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
} else {
pin_info->props.type = DPLL_PIN_TYPE_GNSS;
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 21/28] dpll: zl3073x: Implement input pin state setting in automatic mode
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (19 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 20/28] dpll: zl3073x: Add support to get/set priority on input pins Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 22/28] dpll: zl3073x: Add support to get/set frequency on input pins Ivan Vecera
` (7 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
This implements input pin state setting when the DPLL is running in
automatic mode. Unlike manual mode, the DPLL mode switching is not used
here and the implementation uses special priority value (15) to make
the given pin non-selectable.
When the user sets state of the pin as disconnected the driver
internally sets its priority in HW to 15 that prevents the DPLL to
choose this input pin. Conversely, if the pin status is set to
selectable, the driver sets the pin priority in HW to the original saved
value.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 072a33ec12399..192e0e56fcdde 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -528,6 +528,37 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref);
break;
+
+ case DPLL_MODE_REFSEL_MODE_AUTO:
+ if (state == DPLL_PIN_STATE_SELECTABLE) {
+ if (pin->selectable)
+ return 0; /* Pin is already selectable */
+
+ /* Restore pin priority in HW */
+ rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
+ if (rc)
+ return rc;
+
+ /* Mark pin as selectable */
+ pin->selectable = true;
+ } else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+ if (!pin->selectable)
+ return 0; /* Pin is already disconnected */
+
+ /* Set pin priority to none in HW */
+ rc = zl3073x_dpll_ref_prio_set(pin, DPLL_REF_PRIO_NONE);
+ if (rc)
+ return rc;
+
+ /* Mark pin as non-selectable */
+ pin->selectable = false;
+ } else {
+ NL_SET_ERR_MSG(extack,
+ "Invalid pin state for automatic mode");
+ return -EINVAL;
+ }
+ break;
+
default:
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 22/28] dpll: zl3073x: Add support to get/set frequency on input pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (20 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 21/28] dpll: zl3073x: Implement input pin state setting in automatic mode Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 23/28] dpll: zl3073x: Add support to get/set frequency on output pins Ivan Vecera
` (6 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
Add support to get/set frequency from/to input pins. The frequency
for input pins (references) is computed in the device according this
formula:
freq = base_freq * multiplier * (nominator / denominator)
where the base_freq comes from the list of supported base frequencies
and other parameters are arbitrary numbers. All these parameters are
16-bit unsigned integers.
Implement necessary callbacks for input pins and helper function
to factorize a frequency into a base frequency and a multiplier
(nominator and denominator are used as 1 for now).
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 179 ++++++++++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 192e0e56fcdde..965664da9371d 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -56,6 +56,14 @@ ZL3073X_REG8_DEF(synth_phase_shift_mask, 0x49f);
ZL3073X_REG8_DEF(synth_phase_shift_intvl, 0x4a0);
ZL3073X_REG16_DEF(synth_phase_shift_data, 0x4a1);
+/*
+ * Register Map Page 10, Ref Mailbox
+ */
+ZL3073X_REG16_DEF(ref_freq_base, 0x505);
+ZL3073X_REG16_DEF(ref_freq_mult, 0x507);
+ZL3073X_REG16_DEF(ref_ratio_m, 0x509);
+ZL3073X_REG16_DEF(ref_ratio_n, 0x50b);
+
/*
* Register Map Page 12, DPLL Mailbox
*/
@@ -218,6 +226,175 @@ zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv,
return 0;
}
+/**
+ * zl3073x_dpll_input_ref_frequency_factorize - factorize given frequency
+ * @freq: input frequency
+ * @base_freq: 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 and
+ * returns 0. If the frequency cannot be factorized then the function
+ * returns -EINVAL.
+ */
+static int
+zl3073x_dpll_input_ref_frequency_factorize(u64 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, rem;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(base_freqs); i++) {
+ div = div_u64_rem(freq, base_freqs[i], &rem);
+ if (!rem && div <= U16_MAX) {
+ if (base)
+ *base = base_freqs[i];
+ if (mult)
+ *mult = div;
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * zl3073x_dpll_input_ref_frequency_get - get input reference frequency
+ * zldev: pointer to device structure
+ * ref_id: reference id
+ * frequency: pointer to variable to store frequency
+ *
+ * Context: zl3073x_dev.lock has to be held
+ *
+ * Reads frequency of given input reference.
+ *
+ * Returns 0 in case of success or negative value if error occurred
+ */
+static int
+zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dev *zldev, u8 ref_id,
+ u64 *frequency)
+{
+ u16 base_freq, mult, num, denom;
+ int rc;
+
+ /* Read reference configuration into mailbox */
+ rc = zl3073x_mb_ref_read(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ /* Read base frequency */
+ rc = zl3073x_read_ref_freq_base(zldev, &base_freq);
+ if (rc)
+ return rc;
+
+ /* Read multiplier */
+ rc = zl3073x_read_ref_freq_mult(zldev, &mult);
+ if (rc)
+ return rc;
+
+ /* Write numerator */
+ rc = zl3073x_read_ref_ratio_m(zldev, &num);
+ if (rc)
+ return rc;
+
+ /* Write denominator */
+ rc = zl3073x_read_ref_ratio_n(zldev, &denom);
+ if (rc)
+ return rc;
+
+ /* Sanity check that HW has not returned zero denominator */
+ if (!denom) {
+ dev_err(zldev->dev,
+ "Zero divisor for ref %u frequency got from device\n",
+ ref_id);
+ return -EINVAL;
+ }
+
+ *frequency = mul_u64_u32_div(base_freq * mult, num, denom);
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 ref_id;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read and return ref frequency */
+ return zl3073x_dpll_input_ref_frequency_get(zldev, ref_id, frequency);
+}
+
+static int
+zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u16 base_freq, mult;
+ u8 ref_id;
+ int rc;
+
+ /* Get base frequency and multiplier for the requested frequency */
+ rc = zl3073x_dpll_input_ref_frequency_factorize(frequency, &base_freq,
+ &mult);
+ if (rc)
+ return rc;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Write base frequency */
+ rc = zl3073x_write_ref_freq_base(zldev, base_freq);
+ if (rc)
+ return rc;
+
+ /* Write multiplier */
+ rc = zl3073x_write_ref_freq_mult(zldev, mult);
+ if (rc)
+ return rc;
+
+ /* Write numerator */
+ rc = zl3073x_write_ref_ratio_m(zldev, 1);
+ if (rc)
+ return rc;
+
+ /* Write denominator */
+ rc = zl3073x_write_ref_ratio_n(zldev, 1);
+ if (rc)
+ return rc;
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Update reference configuration from mailbox */
+ return zl3073x_mb_ref_write(zldev, ref_id);
+}
+
/**
* zl3073x_dpll_selected_ref_get - get currently selected reference
* @zldpll: pointer to zl3073x_dpll
@@ -685,6 +862,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
+ .frequency_get = zl3073x_dpll_input_pin_frequency_get,
+ .frequency_set = zl3073x_dpll_input_pin_frequency_set,
.prio_get = zl3073x_dpll_input_pin_prio_get,
.prio_set = zl3073x_dpll_input_pin_prio_set,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 23/28] dpll: zl3073x: Add support to get/set frequency on output pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (21 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 22/28] dpll: zl3073x: Add support to get/set frequency on input pins Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 24/28] dpll: zl3073x: Read pin supported frequencies from firmware Ivan Vecera
` (5 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support to get/set frequencies for output pins. The frequency
for output pin is determined by the frequency of synthesizer the output
pin is connected to and divisor of the output to which is the given pin
belongs. The resulting frequency of the P-pin and the N-pin from this
output pair depends on the signal format of this output pair.
The device supports so-called N-divided signal formats where for the
N-pin there is an additional divisor. The frequencies for both pins from
such output pair are computed:
P-pin-freq = synth_freq / output_div
N-pin-freq = synth_freq / output_div / n_div
For other signal-format types both P and N pin have the same frequency
based only synth frequency and output divisor.
Implement output pin callbacks to get and set frequency. The frequency
setting for the output non-N-divided signal format is simple as we have
to compute just new output divisor. For N-divided formats it is more
complex because by changing of output divisor we change frequency for
both P and N pins. In this case if we are changing frequency for P-pin
we have to compute also new N-divisor for N-pin to keep its current
frequency. From this and the above it follows that the frequency of
the N-pin cannot be higher than the frequency of the P-pin and the
callback must take this limitation into account.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 248 ++++++++++++++++++++++++++++++++++++
1 file changed, 248 insertions(+)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 965664da9371d..07a547aaee0f1 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -74,6 +74,14 @@ ZL3073X_REG8_IDX_DEF(dpll_ref_prio, 0x652,
#define DPLL_REF_PRIO_MAX 14
#define DPLL_REF_PRIO_NONE 15 /* non-selectable */
+/*
+ * Register Map Page 14, Output Mailbox
+ */
+ZL3073X_REG32_DEF(output_div, 0x70c);
+ZL3073X_REG32_DEF(output_width, 0x710);
+ZL3073X_REG32_DEF(output_ndiv_period, 0x714);
+ZL3073X_REG32_DEF(output_ndiv_width, 0x718);
+
#define ZL3073X_REF_NONE ZL3073X_NUM_INPUT_PINS
#define ZL3073X_REF_IS_VALID(_ref) ((_ref) != ZL3073X_REF_NONE)
@@ -787,6 +795,244 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
return 0;
}
+static u8
+zl3073x_dpll_pin_synth_get(struct zl3073x_dpll_pin *pin)
+{
+ u8 output = zl3073x_dpll_output_pin_output_get(pin);
+
+ return zl3073x_output_synth_get(pin_to_dev(pin), output);
+}
+
+static int
+zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 output, signal_format, synth;
+ u64 synth_freq;
+ u32 output_div;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ synth = zl3073x_dpll_pin_synth_get(pin);
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Get divisor */
+ rc = zl3073x_read_output_div(zldev, &output_div);
+ if (rc)
+ return rc;
+
+ /* Check output divisor for zero */
+ if (!output_div) {
+ dev_err(zldev->dev,
+ "Zero divisor for output %u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Read used signal format for the given output */
+ signal_format = zl3073x_output_signal_format_get(zldev, output);
+
+ switch (signal_format) {
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV:
+ /* In case of divided format we have to distiguish between
+ * given output pin type.
+ */
+ if (zl3073x_dpll_is_p_pin(pin)) {
+ /* For P-pin the resulting frequency is computed as
+ * simple division of synth frequency and output
+ * divisor.
+ */
+ *frequency = div_u64(synth_freq, output_div);
+ } else {
+ /* For N-pin we have to divide additionally by
+ * divisor stored in output_ndiv_period register
+ * that is used as N-pin divisor for these modes.
+ */
+ u64 divisor;
+ u32 ndiv;
+
+ rc = zl3073x_read_output_ndiv_period(zldev, &ndiv);
+ if (rc)
+ return rc;
+
+ /* Check N-pin divisor for zero */
+ if (!ndiv) {
+ dev_err(zldev->dev,
+ "Zero N-pin divisor for output %u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Compute final divisor for N-pin */
+ divisor = mul_u32_u32(output_div, ndiv);
+ *frequency = div64_u64(synth_freq, divisor);
+ }
+ break;
+ default:
+ /* In other modes the resulting frequency is computed as
+ * division of synth frequency and output divisor.
+ */
+ *frequency = div_u64(synth_freq, output_div);
+ break;
+ }
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u32 output_n_freq, output_p_freq;
+ u8 output, signal_format, synth;
+ u32 cur_div, new_div, n_div;
+ u64 rem, synth_freq;
+ int rc;
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ synth = zl3073x_dpll_pin_synth_get(pin);
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Compute new divisor and check the remainder to be zero as
+ * the requested frequency has to divide synthesizer frequency
+ */
+ new_div = (u32)div64_u64_rem(synth_freq, frequency, &rem);
+ if (rem) {
+ dev_err(zldev->dev,
+ "The requested frequency must divide %llu Hz\n",
+ synth_freq);
+ return -EINVAL;
+ }
+
+ guard(zl3073x)(zldev);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Get used signal format for the given output */
+ signal_format = zl3073x_output_signal_format_get(zldev, output);
+
+ /* Check signal format */
+ if (signal_format != OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV &&
+ signal_format != OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV) {
+ /* For non N-divided signal formats the frequency is computed
+ * as division of synth frequency and output divisor.
+ */
+ rc = zl3073x_write_output_div(zldev, new_div);
+ if (rc)
+ return rc;
+
+ /* For 50/50 duty cycle the divisor is equal to width */
+ rc = zl3073x_write_output_width(zldev, new_div);
+ if (rc)
+ return rc;
+
+ /* Update output configuration from mailbox */
+ return zl3073x_mb_output_write(zldev, output);
+ }
+
+ /* For N-divided signal format get current divisor */
+ rc = zl3073x_read_output_div(zldev, &cur_div);
+ if (rc)
+ return rc;
+
+ /* Check output divisor for zero */
+ if (!cur_div) {
+ dev_err(zldev->dev,
+ "Zero divisor for output %u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Compute current output frequency for P-pin */
+ output_p_freq = (u32)div_u64(synth_freq, cur_div);
+
+ /* Get N-pin divisor */
+ rc = zl3073x_read_output_ndiv_period(zldev, &n_div);
+ if (rc)
+ return rc;
+
+ /* Check N-pin divisor for zero */
+ if (!n_div) {
+ dev_err(zldev->dev,
+ "Zero N-pin divisor for output %u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Compute current N-pin frequency */
+ output_n_freq = output_p_freq / n_div;
+
+ if (zl3073x_dpll_is_p_pin(pin)) {
+ /* We are going to change output frequency for P-pin but
+ * if the requested frequency is less than current N-pin
+ * frequency then indicate a failure as we are not able
+ * to compute N-pin divisor to keep its frequency unchanged.
+ */
+ if (frequency <= output_n_freq)
+ return -EINVAL;
+
+ /* Update the register with new divisor */
+ rc = zl3073x_write_output_div(zldev, new_div);
+ if (rc)
+ return rc;
+
+ /* For 50/50 duty cycle the divisor is equal to width */
+ rc = zl3073x_write_output_width(zldev, new_div);
+ if (rc)
+ return rc;
+
+ /* Compute new divisor for N-pin */
+ n_div = (u32)div_u64(frequency, output_n_freq);
+ } else {
+ /* We are going to change frequency of N-pin but if
+ * the requested freq is greater or equal than freq of P-pin
+ * in the output pair we cannot compute divisor for the N-pin.
+ * In this case indicate a failure.
+ */
+ if (output_p_freq <= frequency)
+ return -EINVAL;
+
+ /* Compute new divisor for N-pin */
+ n_div = output_p_freq / (u32)frequency;
+ }
+
+ /* Update divisor for the N-pin */
+ rc = zl3073x_write_output_ndiv_period(zldev, n_div);
+ if (rc)
+ return rc;
+
+ /* For 50/50 duty cycle the divisor is equal to width */
+ rc = zl3073x_write_output_ndiv_width(zldev, n_div);
+ if (rc)
+ return rc;
+
+ /* Update output configuration from mailbox */
+ return zl3073x_mb_output_write(zldev, output);
+}
+
static int
zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -872,6 +1118,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
+ .frequency_get = zl3073x_dpll_output_pin_frequency_get,
+ .frequency_set = zl3073x_dpll_output_pin_frequency_set,
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 24/28] dpll: zl3073x: Read pin supported frequencies from firmware
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (22 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 23/28] dpll: zl3073x: Add support to get/set frequency on output pins Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 25/28] dpll: zl3073x: Add support to get phase offset on input pins Ivan Vecera
` (4 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
The firmware (DT, ACPI...) can specify what frequencies are supported
for particular pins. Load the frequencies from the appropriate property
and use them during pin registation. The unsupported frequencies that
cannot be represented in device are filtered out.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 113 +++++++++++++++++++++++++++++++++++-
1 file changed, 112 insertions(+), 1 deletion(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 07a547aaee0f1..c920904008e22 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -1246,6 +1246,50 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
pin_info->props.package_label = pin_info->package_label;
}
+/**
+ * zl3073x_dpll_check_frequency - verify frequency for given pin
+ * @pin: pointer to pin
+ * @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.
+ *
+ * Returns true if the frequency is valid or false if not.
+ */
+static bool
+zl3073x_dpll_check_frequency(struct zl3073x_dpll_pin *pin, u64 freq)
+{
+ if (zl3073x_dpll_is_input_pin(pin)) {
+ u16 base, mult;
+ int rc;
+
+ /* Check if the frequency can be factorized */
+ rc = zl3073x_dpll_input_ref_frequency_factorize(freq, &base,
+ &mult);
+ if (!rc)
+ return true;
+ } else {
+ struct zl3073x_dev *zldev = pin_to_dev(pin);
+ u64 synth_freq, rem;
+ u8 synth;
+
+ /* Get output pin synthesizer */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Check the frequency divides synth frequency */
+ div64_u64_rem(synth_freq, freq, &rem);
+ if (!rem)
+ return true;
+ }
+
+ return false;
+}
+
/**
* zl3073x_dpll_pin_info_get - get pin info
* @pin: pin whose info is returned
@@ -1253,7 +1297,8 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
* The function looks for firmware node for the given pin if it is provided
* by the system firmware (DT or ACPI), allocates pin info structure,
* generates package label string according pin type and its order number
- * and optionally fetches board label from the firmware node if it exists.
+ * and optionally fetches board label and supported frequencies from
+ * the firmware node if they exist.
*
* Returns pointer to allocated pin info structure that has to be freed
* by @zl3073x_dpll_pin_info_put by the caller and in case of error
@@ -1264,7 +1309,10 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
{
struct zl3073x_dev *zldev = pin_to_dev(pin);
struct zl3073x_dpll_pin_info *pin_info;
+ struct dpll_pin_frequency *ranges;
+ int i, j, num_freqs, rc;
const char *pin_type;
+ u64 *freqs;
/* Allocate pin info structure */
pin_info = kzalloc(sizeof(*pin_info), GFP_KERNEL);
@@ -1315,7 +1363,67 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
pin_type);
}
+ /* Read supported frequencies property if it is specified */
+ num_freqs = fwnode_property_count_u64(pin_info->fwnode,
+ "supported-frequencies");
+ if (num_freqs <= 0)
+ /* Return if the property does not exist or number is 0 */
+ return pin_info;
+
+ /* 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(pin_info->fwnode,
+ "supported-frequencies", 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_dpll_check_frequency(pin, freqs[i])) {
+ ranges[j] = freq;
+ j++;
+ } else {
+ dev_warn(zldev->dev,
+ "Unsupported frequency %llu Hz in firmware node\n",
+ freqs[i]);
+ }
+ }
+
+ /* Save number of freq ranges and pointer to them into pin properties */
+ pin_info->props.freq_supported = ranges;
+ pin_info->props.freq_supported_num = j;
+
+ /* Free temporary array */
+ kfree(freqs);
+
return pin_info;
+
+err_alloc_ranges:
+ kfree(freqs);
+err_alloc_freqs:
+ fwnode_handle_put(pin_info->fwnode);
+ kfree(pin_info);
+
+ return ERR_PTR(rc);
}
/**
@@ -1327,6 +1435,9 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
static void
zl3073x_dpll_pin_info_put(struct zl3073x_dpll_pin_info *pin_info)
{
+ /* Free supported frequency ranges list if it is present */
+ kfree(pin_info->props.freq_supported);
+
/* Put firmware handle if it is present */
if (pin_info->fwnode)
fwnode_handle_put(pin_info->fwnode);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 25/28] dpll: zl3073x: Add support to get phase offset on input pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (23 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 24/28] dpll: zl3073x: Read pin supported frequencies from firmware Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:32 ` [PATCH 26/28] dpll: zl3073x: Add support to get/set phase adjust on pins Ivan Vecera
` (3 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support to get phase offset for the input pins. Implement
the appropriate callback that performs DPLL to reference phase
error measurement and reports the measured value. If the DPLL is
currently locked to different reference with higher frequency
then the phase offset is modded to the period of the signal
the DPLL is locked to.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 156 +++++++++++++++++++++++++++++++++++-
1 file changed, 155 insertions(+), 1 deletion(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index c920904008e22..3b28d229dd4be 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -36,6 +36,15 @@ ZL3073X_REG8_IDX_DEF(dpll_refsel_status, 0x130, ZL3073X_NUM_CHANNELS, 1);
#define DPLL_REFSEL_STATUS_STATE_ACQUIRING 3
#define DPLL_REFSEL_STATUS_STATE_LOCK 4
+/*
+ * Register Map Page 4, Ref
+ */
+ZL3073X_REG8_DEF(ref_phase_err_read_rqst, 0x20f);
+#define REF_PHASE_ERR_READ_RQST_RD BIT(0)
+
+ZL3073X_REG48_IDX_DEF(ref_phase, 0x220,
+ ZL3073X_NUM_INPUT_PINS, 6);
+
/*
* Register Map Page 5, DPLL
*/
@@ -48,6 +57,13 @@ ZL3073X_REG8_IDX_DEF(dpll_mode_refsel, 0x284, ZL3073X_NUM_CHANNELS, 4);
#define DPLL_MODE_REFSEL_MODE_NCO 4
#define DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+ZL3073X_REG8_DEF(dpll_meas_ctrl, 0x2d0);
+#define DPLL_MEAS_CTRL_EN BIT(0)
+#define DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
+
+ZL3073X_REG8_DEF(dpll_meas_idx, 0x2d1);
+#define DPLL_MEAS_IDX_IDX GENMASK(2, 0)
+
/*
* Register Map Page 9, Synth and Output
*/
@@ -104,6 +120,7 @@ struct zl3073x_dpll_pin_info {
* @prio: pin priority <0, 14>
* @selectable: pin is selectable in automatic mode
* @pin_state: last saved pin state
+ * @phase_offset: last saved pin phase offset
*/
struct zl3073x_dpll_pin {
struct dpll_pin *dpll_pin;
@@ -111,6 +128,7 @@ struct zl3073x_dpll_pin {
u8 prio;
bool selectable;
enum dpll_pin_state pin_state;
+ s64 phase_offset;
};
/**
@@ -558,6 +576,120 @@ zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
*
* Returns 0 in case of success or negative value otherwise.
*/
+static int
+zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s64 *phase_offset,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 dpll_meas_ctrl, dpll_meas_idx;
+ u8 conn_ref, ref_id, ref_status;
+ s64 ref_phase;
+ int rc;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Wait for reading to be ready */
+ rc = zl3073x_wait_clear_bits(zldev, ref_phase_err_read_rqst,
+ REF_PHASE_ERR_READ_RQST_RD);
+ if (rc)
+ return rc;
+
+ /* Read measurement control register */
+ rc = zl3073x_read_dpll_meas_ctrl(zldev, &dpll_meas_ctrl);
+ if (rc)
+ return rc;
+
+ /* Enable measurement */
+ dpll_meas_ctrl |= DPLL_MEAS_CTRL_EN;
+
+ /* Update measurement control register with new values */
+ rc = zl3073x_write_dpll_meas_ctrl(zldev, dpll_meas_ctrl);
+ if (rc)
+ return rc;
+
+ /* Set measurement index to channel index */
+ dpll_meas_idx = FIELD_PREP(DPLL_MEAS_IDX_IDX, zldpll->id);
+ rc = zl3073x_write_dpll_meas_idx(zldev, dpll_meas_idx);
+ if (rc)
+ return rc;
+
+ /* Request read of the current phase error measurements */
+ rc = zl3073x_write_ref_phase_err_read_rqst(zldev,
+ REF_PHASE_ERR_READ_RQST_RD);
+ if (rc)
+ return rc;
+
+ /* Wait for confirmation from the device */
+ rc = zl3073x_wait_clear_bits(zldev, ref_phase_err_read_rqst,
+ REF_PHASE_ERR_READ_RQST_RD);
+ if (rc)
+ return rc;
+
+ /* Read DPLL-to-REF phase measurement */
+ rc = zl3073x_read_ref_phase(zldev, ref_id, &ref_phase);
+ if (rc)
+ return rc;
+
+ /* Perform sign extension for 48bit signed value */
+ ref_phase = sign_extend64(ref_phase, 47);
+
+ /* Register units are 0.01 ps -> convert it to ps */
+ ref_phase = div_s64(ref_phase, 100);
+
+ /* Get currently connected reference */
+ rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref);
+ if (rc)
+ return rc;
+
+ /* Get this pin monitor status */
+ rc = zl3073x_read_ref_mon_status(zldev, ref_id, &ref_status);
+ if (rc)
+ return rc;
+
+ /* The DPLL being locked to a higher freq than the current ref
+ * the phase offset is modded to the period of the signal
+ * the dpll is locked to.
+ */
+ if (ZL3073X_REF_IS_VALID(conn_ref) && conn_ref != ref_id &&
+ ref_status == REF_MON_STATUS_OK) {
+ u64 conn_freq, ref_freq;
+
+ /* Get frequency of connected ref */
+ rc = zl3073x_dpll_input_ref_frequency_get(zldev, conn_ref,
+ &conn_freq);
+ if (rc)
+ return rc;
+
+ /* Get frequency of given ref */
+ rc = zl3073x_dpll_input_ref_frequency_get(zldev, ref_id,
+ &ref_freq);
+ if (rc)
+ return rc;
+
+ if (conn_freq > ref_freq) {
+ s64 conn_period;
+ int div_factor;
+
+ conn_period = (s64)div_u64(PSEC_PER_SEC, conn_freq);
+ div_factor = div64_s64(ref_phase, conn_period);
+ ref_phase -= conn_period * div_factor;
+ }
+ }
+
+ *phase_offset = ref_phase;
+
+ return rc;
+}
+
static int
zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
{
@@ -1110,6 +1242,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
+ .phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
.prio_get = zl3073x_dpll_input_pin_prio_get,
.prio_set = zl3073x_dpll_input_pin_prio_set,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
@@ -1805,6 +1938,8 @@ zl3073x_dpll_periodic_work(struct kthread_work *work)
for (i = 0; i < ZL3073X_NUM_INPUT_PINS; i++) {
struct zl3073x_dpll_pin *pin;
enum dpll_pin_state state;
+ s64 phase_offset;
+ bool pin_changed;
/* Input pins starts are stored after output pins */
pin = &zldpll->pins[ZL3073X_NUM_OUTPUT_PINS + i];
@@ -1821,13 +1956,32 @@ zl3073x_dpll_periodic_work(struct kthread_work *work)
if (rc)
goto out;
+ rc = zl3073x_dpll_input_pin_phase_offset_get(pin->dpll_pin,
+ pin,
+ zldpll->dpll_dev,
+ zldpll,
+ &phase_offset,
+ NULL);
+ if (rc)
+ goto out;
+
if (state != pin->pin_state) {
dev_dbg(zldev->dev,
"INPUT%u state changed to %u\n",
zl3073x_dpll_pin_index_get(pin), state);
pin->pin_state = state;
- dpll_pin_change_ntf(pin->dpll_pin);
+ pin_changed = true;
}
+ if (phase_offset != pin->phase_offset) {
+ dev_dbg(zldev->dev,
+ "INPUT%u phase offset changed to %llu\n",
+ pin->index, phase_offset);
+ pin->phase_offset = phase_offset;
+ pin_changed = true;
+ }
+
+ if (pin_changed)
+ dpll_pin_change_ntf(pin->dpll_pin);
}
out:
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 26/28] dpll: zl3073x: Add support to get/set phase adjust on pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (24 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 25/28] dpll: zl3073x: Add support to get phase offset on input pins Ivan Vecera
@ 2025-04-07 17:32 ` Ivan Vecera
2025-04-07 17:33 ` [PATCH 27/28] dpll: zl3073x: Add support to get/set esync " Ivan Vecera
` (2 subsequent siblings)
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:32 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support to get/set phase adjustment for both input and output
pins. The phase adjustment is implemented using reference and output
phase offset compensation registers. For input pins the adjustment value
can be arbitrary number but for outputs the value has to be a multiple
of half synthesizer clock cycles.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 182 ++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 3b28d229dd4be..b1f38f000899b 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -79,6 +79,7 @@ ZL3073X_REG16_DEF(ref_freq_base, 0x505);
ZL3073X_REG16_DEF(ref_freq_mult, 0x507);
ZL3073X_REG16_DEF(ref_ratio_m, 0x509);
ZL3073X_REG16_DEF(ref_ratio_n, 0x50b);
+ZL3073X_REG48_DEF(ref_phase_compensation, 0x528);
/*
* Register Map Page 12, DPLL Mailbox
@@ -98,6 +99,8 @@ ZL3073X_REG32_DEF(output_width, 0x710);
ZL3073X_REG32_DEF(output_ndiv_period, 0x714);
ZL3073X_REG32_DEF(output_ndiv_width, 0x718);
+ZL3073X_REG32_DEF(output_phase_compensation, 0x720);
+
#define ZL3073X_REF_NONE ZL3073X_NUM_INPUT_PINS
#define ZL3073X_REF_IS_VALID(_ref) ((_ref) != ZL3073X_REF_NONE)
@@ -690,6 +693,85 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
return rc;
}
+static int
+zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ s64 phase_comp;
+ u8 ref_id;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read reference configuration into mailbox */
+ rc = zl3073x_mb_ref_read(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ /* Read current phase offset compensation */
+ rc = zl3073x_read_ref_phase_compensation(zldev, &phase_comp);
+ if (rc)
+ return rc;
+
+ /* Perform sign extension for 48bit signed value */
+ phase_comp = sign_extend64(phase_comp, 47);
+
+ /* Reverse two's complement negation applied during set and convert
+ * to 32bit signed int
+ */
+ *phase_adjust = (s32) -phase_comp;
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ s64 phase_comp;
+ u8 ref_id;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ /* The value in the register is stored as two's complement negation
+ * of requested value.
+ */
+ phase_comp = (s64) -phase_adjust;
+
+ /* Write the requested value into the compensation register */
+ rc = zl3073x_write_ref_phase_compensation(zldev, phase_comp);
+ if (rc)
+ return rc;
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Update reference configuration from mailbox */
+ rc = zl3073x_mb_ref_write(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
static int
zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
{
@@ -1165,6 +1247,102 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
return zl3073x_mb_output_write(zldev, output);
}
+static int
+zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 output, synth;
+ u64 synth_freq;
+ s32 phase_comp;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ synth = zl3073x_dpll_pin_synth_get(pin);
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Read current output phase compensation */
+ rc = zl3073x_read_output_phase_compensation(zldev, &phase_comp);
+ if (rc)
+ return rc;
+
+ /* Value in register is expressed in half synth clock cycles */
+ phase_comp *= (int)div_u64(PSEC_PER_SEC, 2 * synth_freq);
+
+ /* Reverse two's complement negation applied during 'set' */
+ *phase_adjust = -phase_comp;
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ int half_synth_cycle;
+ u8 output, synth;
+ u64 synth_freq;
+ int phase_comp;
+ int rc;
+
+ /* Get attached synth */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth's frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Value in register is expressed in half synth clock cycles so
+ * the given phase adjustment a multiple of half synth clock.
+ */
+ half_synth_cycle = (int)div_u64(PSEC_PER_SEC, 2 * synth_freq);
+
+ if ((phase_adjust % half_synth_cycle) != 0) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "Phase adjustment value has to be multiple of %d",
+ half_synth_cycle);
+ return -EINVAL;
+ }
+ phase_adjust /= half_synth_cycle;
+
+ guard(zl3073x)(zldev);
+
+ /* The value in the register is stored as two's complement negation
+ * of requested value.
+ */
+ phase_comp = -phase_adjust;
+
+ /* Write the requested value into the compensation register */
+ rc = zl3073x_write_output_phase_compensation(zldev, phase_comp);
+ if (rc)
+ return rc;
+
+ /* Update output configuration from mailbox */
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ rc = zl3073x_mb_output_write(zldev, output);
+
+ return rc;
+}
+
static int
zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1243,6 +1421,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
+ .phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get,
+ .phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
.prio_get = zl3073x_dpll_input_pin_prio_get,
.prio_set = zl3073x_dpll_input_pin_prio_set,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
@@ -1253,6 +1433,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.frequency_get = zl3073x_dpll_output_pin_frequency_get,
.frequency_set = zl3073x_dpll_output_pin_frequency_set,
+ .phase_adjust_get = zl3073x_dpll_output_pin_phase_adjust_get,
+ .phase_adjust_set = zl3073x_dpll_output_pin_phase_adjust_set,
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 27/28] dpll: zl3073x: Add support to get/set esync on pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (25 preceding siblings ...)
2025-04-07 17:32 ` [PATCH 26/28] dpll: zl3073x: Add support to get/set phase adjust on pins Ivan Vecera
@ 2025-04-07 17:33 ` Ivan Vecera
2025-04-07 17:33 ` [PATCH 28/28] dpll: zl3073x: Add support to get fractional frequency offset on input pins Ivan Vecera
2025-04-07 18:06 ` [PATCH 00/28] Add Microchip ZL3073x support Andy Shevchenko
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:33 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support to get/set embedded sync for both input and output
pins. The DPLL is able to lock on input reference when the embedded sync
frequency is 1 PPS and pulse width 25%. The esync on outputs are more
versatille and theoretically supports any esync frequency that divides
current output frequency but for now support the same that supported on
input pins (1 PPS & 25% pulse).
Note that for the output pins the esync divisor shares the same register
used for N-divided signal formats. Due to this the esync cannot be
enabled on outputs configured with one of the N-divided signal formats.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 370 +++++++++++++++++++++++++++++++++++-
1 file changed, 368 insertions(+), 2 deletions(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index b1f38f000899b..e1d7f6d4c3d57 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -81,6 +81,14 @@ ZL3073X_REG16_DEF(ref_ratio_m, 0x509);
ZL3073X_REG16_DEF(ref_ratio_n, 0x50b);
ZL3073X_REG48_DEF(ref_phase_compensation, 0x528);
+ZL3073X_REG8_DEF(ref_sync_ctrl, 0x52e);
+#define REF_SYNC_CTRL_MODE GENMASK(2, 0)
+#define REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0
+#define REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2
+
+ZL3073X_REG32_DEF(ref_esync_div, 0x530);
+#define REF_ESYNC_DIV_1HZ 0
+
/*
* Register Map Page 12, DPLL Mailbox
*/
@@ -94,10 +102,17 @@ ZL3073X_REG8_IDX_DEF(dpll_ref_prio, 0x652,
/*
* Register Map Page 14, Output Mailbox
*/
+ZL3073X_REG8_DEF(output_mode, 0x705);
+#define OUTPUT_MODE_CLOCK_TYPE GENMASK(2, 0)
+#define OUTPUT_MODE_CLOCK_TYPE_NORMAL 0
+#define OUTPUT_MODE_CLOCK_TYPE_ESYNC 1
+
ZL3073X_REG32_DEF(output_div, 0x70c);
ZL3073X_REG32_DEF(output_width, 0x710);
-ZL3073X_REG32_DEF(output_ndiv_period, 0x714);
-ZL3073X_REG32_DEF(output_ndiv_width, 0x718);
+ZL3073X_REG32_DEF(output_esync_period, 0x714);
+ZL3073X_REG32_DEF(output_ndiv_period, 0x714); /* alias for previous */
+ZL3073X_REG32_DEF(output_esync_width, 0x718);
+ZL3073X_REG32_DEF(output_ndiv_width, 0x718); /* alias for previous */
ZL3073X_REG32_DEF(output_phase_compensation, 0x720);
@@ -122,6 +137,7 @@ struct zl3073x_dpll_pin_info {
* @index: index in zl3073x_dpll.pins array
* @prio: pin priority <0, 14>
* @selectable: pin is selectable in automatic mode
+ * @esync_control: embedded sync is controllable
* @pin_state: last saved pin state
* @phase_offset: last saved pin phase offset
*/
@@ -130,6 +146,7 @@ struct zl3073x_dpll_pin {
u8 index;
u8 prio;
bool selectable;
+ bool esync_control;
enum dpll_pin_state pin_state;
s64 phase_offset;
};
@@ -213,6 +230,13 @@ zl3073x_dpll_is_n_pin(struct zl3073x_dpll_pin *pin)
return zl3073x_is_n_pin(zl3073x_dpll_pin_index_get(pin));
}
+/*
+ * Supported esync ranges for input and for output per output pair type
+ */
+static const struct dpll_pin_frequency esync_freq_ranges[] = {
+ DPLL_PIN_FREQUENCY_RANGE(0, 1),
+};
+
/**
* zl3073x_dpll_is_p_pin - check if the pin is P-pin
* @pin: pin to check
@@ -352,6 +376,128 @@ zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dev *zldev, u8 ref_id,
return rc;
}
+static int
+zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ struct dpll_pin_esync *esync,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 sync_mode, ref_id, ref_sync_ctrl;
+ u32 esync_div;
+ u64 ref_freq;
+ int rc;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Get reference frequency */
+ rc = zl3073x_dpll_input_ref_frequency_get(zldev, ref_id, &ref_freq);
+ if (rc)
+ return rc;
+
+ /* Get ref sync mode */
+ rc = zl3073x_read_ref_sync_ctrl(zldev, &ref_sync_ctrl);
+ if (rc)
+ return rc;
+
+ sync_mode = FIELD_GET(REF_SYNC_CTRL_MODE, ref_sync_ctrl);
+
+ /* Get esync divisor */
+ rc = zl3073x_read_ref_esync_div(zldev, &esync_div);
+ if (rc)
+ return rc;
+
+ switch (sync_mode) {
+ case REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75:
+ esync->freq = (esync_div == REF_ESYNC_DIV_1HZ) ? 1 : 0;
+ esync->pulse = 25;
+ break;
+ default:
+ esync->freq = 0;
+ esync->pulse = 50;
+ break;
+ }
+
+ /* If the pin supports esync control expose its range but only
+ * if the current reference frequency is > 1 Hz.
+ */
+ if (pin->esync_control && ref_freq > 1) {
+ esync->range = esync_freq_ranges;
+ esync->range_num = ARRAY_SIZE(esync_freq_ranges);
+ } else {
+ esync->range = NULL;
+ esync->range_num = 0;
+ }
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 freq,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 ref_id, ref_sync_ctrl, sync_mode;
+ int rc;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read reference configuration into mailbox */
+ rc = zl3073x_mb_ref_read(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ /* Read ref sync control */
+ rc = zl3073x_read_ref_sync_ctrl(zldev, &ref_sync_ctrl);
+ if (rc)
+ return rc;
+
+ /* Use freq == 0 to disable esync */
+ if (!freq)
+ sync_mode = REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
+ else
+ sync_mode = REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75;
+
+ ref_sync_ctrl &= REF_SYNC_CTRL_MODE;
+ ref_sync_ctrl |= FIELD_PREP(REF_SYNC_CTRL_MODE, sync_mode);
+
+ /* Update ref sync control register */
+ rc = zl3073x_write_ref_sync_ctrl(zldev, ref_sync_ctrl);
+ if (rc)
+ return rc;
+
+ if (freq) {
+ /* 1 Hz is only supported frequnecy currently */
+ rc = zl3073x_write_ref_esync_div(zldev, REF_ESYNC_DIV_1HZ);
+ if (rc)
+ return rc;
+ }
+
+ /* Update reference configuration from mailbox */
+ rc = zl3073x_mb_ref_write(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
static int
zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1017,6 +1163,218 @@ zl3073x_dpll_pin_synth_get(struct zl3073x_dpll_pin *pin)
return zl3073x_output_synth_get(pin_to_dev(pin), output);
}
+static int
+zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ struct dpll_pin_esync *esync,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u32 esync_period, esync_width, output_div;
+ u8 clock_type, output, output_mode, synth;
+ u64 synth_freq;
+ int rc;
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+
+ /* If N-division is enabled, esync is not supported. The register used
+ * for N-division is also used for the esync divider so both cannot
+ * be used.
+ */
+ switch (zl3073x_output_signal_format_get(zldev, output)) {
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV:
+ return -EOPNOTSUPP;
+ default:
+ break;
+ }
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Read output mode */
+ rc = zl3073x_read_output_mode(zldev, &output_mode);
+ if (rc)
+ return rc;
+
+ /* Read output divisor */
+ rc = zl3073x_read_output_div(zldev, &output_div);
+ if (rc)
+ return rc;
+
+ /* Check output divisor for zero */
+ if (!output_div) {
+ dev_err(zldev->dev,
+ "Zero divisor for OUTPUT%u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Get synth attached to output pin */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ clock_type = FIELD_GET(OUTPUT_MODE_CLOCK_TYPE, output_mode);
+ if (clock_type != OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
+ /* No need to read esync data if it is not enabled */
+ esync->freq = 0;
+ esync->pulse = 50;
+
+ goto finish;
+ }
+
+ /* Read esync period */
+ rc = zl3073x_read_output_esync_period(zldev, &esync_period);
+ if (rc)
+ return rc;
+
+ /* Check esync divisor for zero */
+ if (!esync_period) {
+ dev_err(zldev->dev,
+ "Zero esync divisor for OUTPUT%u got from device\n",
+ output);
+ return -EINVAL;
+ }
+
+ /* Get esync pulse width in units of half synth cycles */
+ rc = zl3073x_read_output_esync_width(zldev, &esync_width);
+ if (rc)
+ return rc;
+
+ /* Compute esync frequency:
+ * esync_freq = synth_freq / output_div / esync_period;
+ * ... = synth_freq / (output_div * esync_period);
+ */
+ esync->freq = div64_u64(synth_freq,
+ mul_u32_u32(output_div, esync_period));
+
+ /* By comparing the esync_pulse_width to the half of the pulse width
+ * the esync pulse percentage can be determined.
+ * Note that half pulse width is in units of half synth cycles, which
+ * is why it reduces down to be output_div.
+ */
+ esync->pulse = (50 * esync_width) / output_div;
+
+finish:
+ /* Set supported esync ranges if the pin supports esync control and
+ * if the output frequency is > 1 Hz.
+ */
+ if (pin->esync_control && div_u64(synth_freq, output_div) > 1) {
+ esync->range = esync_freq_ranges;
+ esync->range_num = ARRAY_SIZE(esync_freq_ranges);
+ } else {
+ esync->range = NULL;
+ esync->range_num = 0;
+ }
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 freq,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u32 esync_period, esync_width, output_div;
+ u8 clock_type, output, output_mode, synth;
+ u64 synth_freq;
+ int rc;
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+
+ /* If N-division is enabled, esync is not supported. The register used
+ * for N-division is also used for the esync divider so both cannot
+ * be used.
+ */
+ switch (zl3073x_output_signal_format_get(zldev, output)) {
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV:
+ case OUTPUT_MODE_SIGNAL_FORMAT_TWO_N_DIV_INV:
+ return -EOPNOTSUPP;
+ default:
+ break;
+ }
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Read output mode */
+ rc = zl3073x_read_output_mode(zldev, &output_mode);
+ if (rc)
+ return rc;
+
+ /* Select clock type */
+ if (freq)
+ clock_type = OUTPUT_MODE_CLOCK_TYPE_ESYNC;
+ else
+ clock_type = OUTPUT_MODE_CLOCK_TYPE_NORMAL;
+
+ /* Update clock type in ref sync control */
+ output_mode &= ~OUTPUT_MODE_CLOCK_TYPE;
+ output_mode |= FIELD_PREP(OUTPUT_MODE_CLOCK_TYPE, clock_type);
+ rc = zl3073x_write_output_mode(zldev, output_mode);
+ if (rc)
+ return rc;
+
+ /* If esync is being disabled just write mailbox and finish */
+ if (!freq)
+ goto write_mailbox;
+
+ /* Read output divisor */
+ rc = zl3073x_read_output_div(zldev, &output_div);
+ if (rc)
+ return rc;
+
+ /* Get synth attached to output pin */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Compute and update esync period */
+ esync_period = (u32)div64_u64(synth_freq, freq * output_div);
+ rc = zl3073x_write_output_esync_period(zldev, esync_period);
+ if (rc)
+ return rc;
+
+ /* Half of the period in units of 1/2 synth cycle can be represented by
+ * the output_div. To get the supported esync pulse width of 25% of the
+ * period the output_div can just be divided by 2. Note that this
+ * assumes that output_div is even, otherwise some resolution will be
+ * lost.
+ */
+ esync_width = output_div / 2;
+ rc = zl3073x_write_output_esync_width(zldev, esync_width);
+ if (rc)
+ return rc;
+
+write_mailbox:
+ /* Update output configuration from mailbox */
+ rc = zl3073x_mb_output_write(zldev, output);
+
+ return rc;
+}
+
static int
zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1418,6 +1776,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
+ .esync_get = zl3073x_dpll_input_pin_esync_get,
+ .esync_set = zl3073x_dpll_input_pin_esync_set,
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
@@ -1431,6 +1791,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
+ .esync_get = zl3073x_dpll_output_pin_esync_get,
+ .esync_set = zl3073x_dpll_output_pin_esync_set,
.frequency_get = zl3073x_dpll_output_pin_frequency_get,
.frequency_set = zl3073x_dpll_output_pin_frequency_set,
.phase_adjust_get = zl3073x_dpll_output_pin_phase_adjust_get,
@@ -1678,6 +2040,10 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
pin_type);
}
+ /* Check if the pin supports embedded sync control */
+ pin->esync_control = fwnode_property_read_bool(pin_info->fwnode,
+ "esync-control");
+
/* Read supported frequencies property if it is specified */
num_freqs = fwnode_property_count_u64(pin_info->fwnode,
"supported-frequencies");
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 28/28] dpll: zl3073x: Add support to get fractional frequency offset on input pins
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (26 preceding siblings ...)
2025-04-07 17:33 ` [PATCH 27/28] dpll: zl3073x: Add support to get/set esync " Ivan Vecera
@ 2025-04-07 17:33 ` Ivan Vecera
2025-04-07 18:06 ` [PATCH 00/28] Add Microchip ZL3073x support Andy Shevchenko
28 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-07 17:33 UTC (permalink / raw)
To: netdev
Cc: Michal Schmidt, Prathosh Satish, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Kees Cook,
Andy Shevchenko, Andrew Morton, devicetree, linux-kernel,
linux-hardening
This adds support to get fractional frequency offset for input pins.
Implement the appropriate callback that performs reference frequency
measurement and reports the frequency offset between the DPLL and
the reference.
Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 110 +++++++++++++++++++++++++++++++++++-
1 file changed, 109 insertions(+), 1 deletion(-)
diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index e1d7f6d4c3d57..f5a58c3bab382 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -36,12 +36,31 @@ ZL3073X_REG8_IDX_DEF(dpll_refsel_status, 0x130, ZL3073X_NUM_CHANNELS, 1);
#define DPLL_REFSEL_STATUS_STATE_ACQUIRING 3
#define DPLL_REFSEL_STATUS_STATE_LOCK 4
+ZL3073X_REG32_IDX_DEF(ref_freq, 0x144,
+ ZL3073X_NUM_INPUT_PINS, 4);
+
/*
* Register Map Page 4, Ref
*/
ZL3073X_REG8_DEF(ref_phase_err_read_rqst, 0x20f);
#define REF_PHASE_ERR_READ_RQST_RD BIT(0)
+ZL3073X_REG8_DEF(ref_freq_meas_ctrl, 0x21c);
+#define REF_FREQ_MEAS_CTRL_LATCH GENMASK(1, 0)
+#define REF_FREQ_MEAS_CTRL_LATCH_REF_FREQ 1
+#define REF_FREQ_MEAS_CTRL_LATCH_REF_FREQ_OFF 2
+#define REF_FREQ_MEAS_CTRL_LATCH_DPLL_FREQ_OFF 3
+
+ZL3073X_REG8_DEF(ref_freq_meas_mask_3_0, 0x21d);
+#define REF_FREQ_MEAS_MASK_3_0(_ref) BIT(_ref)
+
+ZL3073X_REG8_DEF(ref_freq_meas_mask_4, 0x21e);
+#define REF_FREQ_MEAS_MASK_4(_ref) BIT((_ref) - 8)
+
+ZL3073X_REG8_DEF(dpll_meas_ref_freq_ctrl, 0x21f);
+#define DPLL_MEAS_REF_FREQ_CTRL_EN BIT(0)
+#define DPLL_MEAS_REF_FREQ_CTRL_IDX GENMASK(6, 4)
+
ZL3073X_REG48_IDX_DEF(ref_phase, 0x220,
ZL3073X_NUM_INPUT_PINS, 6);
@@ -140,6 +159,7 @@ struct zl3073x_dpll_pin_info {
* @esync_control: embedded sync is controllable
* @pin_state: last saved pin state
* @phase_offset: last saved pin phase offset
+ * @freq_offset: last saved fractional frequency offset
*/
struct zl3073x_dpll_pin {
struct dpll_pin *dpll_pin;
@@ -149,6 +169,7 @@ struct zl3073x_dpll_pin {
bool esync_control;
enum dpll_pin_state pin_state;
s64 phase_offset;
+ s64 freq_offset;
};
/**
@@ -498,6 +519,79 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
return rc;
}
+static int
+zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ s64 *ffo, struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 dpll_meas_ref_freq_ctrl, ref_id;
+ u8 ref_freq_meas_ctrl, ref_mask;
+ s32 freq_offset;
+ int rc;
+
+ /* Take device lock */
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Wait for being ready */
+ rc = zl3073x_wait_clear_bits(zldev, ref_freq_meas_ctrl,
+ REF_FREQ_MEAS_CTRL_LATCH);
+ if (rc)
+ return rc;
+
+ /* Select channel index in the mask and enable freq measurement */
+ dpll_meas_ref_freq_ctrl =
+ DPLL_MEAS_REF_FREQ_CTRL_EN |
+ FIELD_PREP(DPLL_MEAS_REF_FREQ_CTRL_IDX, zldpll->id);
+
+ rc = zl3073x_write_dpll_meas_ref_freq_ctrl(zldev,
+ dpll_meas_ref_freq_ctrl);
+ if (rc)
+ return rc;
+
+ /* Set reference mask
+ * REF0P,REF0N..REF3P,REF3N are set in ref_freq_meas_mask_3_0 register
+ * REF4P and REF4N are set in ref_freq_meas_mask_4 register
+ */
+ if (ref_id < 8) {
+ ref_mask = REF_FREQ_MEAS_MASK_3_0(ref_id);
+ rc = zl3073x_write_ref_freq_meas_mask_3_0(zldev, ref_mask);
+ } else {
+ ref_mask = REF_FREQ_MEAS_MASK_4(ref_id);
+ rc = zl3073x_write_ref_freq_meas_mask_4(zldev, ref_mask);
+ }
+ if (rc)
+ return rc;
+
+ /* Request a reading of the frequency offset between the DPLL and
+ * the reference
+ */
+ ref_freq_meas_ctrl = REF_FREQ_MEAS_CTRL_LATCH_DPLL_FREQ_OFF;
+ rc = zl3073x_write_ref_freq_meas_ctrl(zldev, ref_freq_meas_ctrl);
+ if (rc)
+ return rc;
+
+ /* Wait for the command to actually finish */
+ rc = zl3073x_wait_clear_bits(zldev, ref_freq_meas_ctrl,
+ REF_FREQ_MEAS_CTRL_LATCH);
+ if (rc)
+ return rc;
+
+ /* Read the frequency offset between DPLL and reference */
+ rc = zl3073x_read_ref_freq(zldev, ref_id, &freq_offset);
+ if (rc)
+ return rc;
+
+ *ffo = freq_offset;
+
+ return rc;
+}
+
static int
zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1778,6 +1872,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set,
+ .ffo_get = zl3073x_dpll_input_pin_ffo_get,
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
@@ -2484,9 +2579,9 @@ zl3073x_dpll_periodic_work(struct kthread_work *work)
* are constant.
*/
for (i = 0; i < ZL3073X_NUM_INPUT_PINS; i++) {
+ s64 freq_offset, phase_offset;
struct zl3073x_dpll_pin *pin;
enum dpll_pin_state state;
- s64 phase_offset;
bool pin_changed;
/* Input pins starts are stored after output pins */
@@ -2513,6 +2608,12 @@ zl3073x_dpll_periodic_work(struct kthread_work *work)
if (rc)
goto out;
+ rc = zl3073x_dpll_input_pin_ffo_get(pin->dpll_pin, pin,
+ zldpll->dpll_dev, zldpll,
+ &freq_offset, NULL);
+ if (rc)
+ goto out;
+
if (state != pin->pin_state) {
dev_dbg(zldev->dev,
"INPUT%u state changed to %u\n",
@@ -2527,6 +2628,13 @@ zl3073x_dpll_periodic_work(struct kthread_work *work)
pin->phase_offset = phase_offset;
pin_changed = true;
}
+ if (freq_offset != pin->freq_offset) {
+ dev_dbg(zldev->dev,
+ "INPUT%u frequency offset changed to %llu\n",
+ pin->index, freq_offset);
+ pin->freq_offset = freq_offset;
+ pin_changed = true;
+ }
if (pin_changed)
dpll_pin_change_ntf(pin->dpll_pin);
--
2.48.1
^ permalink raw reply related [flat|nested] 70+ messages in thread
* Re: [PATCH 10/28] lib: Allow modules to use strnchrnul
2025-04-07 17:31 ` [PATCH 10/28] lib: Allow modules to use strnchrnul Ivan Vecera
@ 2025-04-07 17:50 ` Kees Cook
0 siblings, 0 replies; 70+ messages in thread
From: Kees Cook @ 2025-04-07 17:50 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Andy Shevchenko, Andrew Morton,
devicetree, linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:31:40PM +0200, Ivan Vecera wrote:
> Commit 0bee0cece2a6a ("lib/string: add strnchrnul()") added the
> mentioned function but did not export it so it cannot be used by
> modules.
>
> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Acked-by: Kees Cook <kees@kernel.org>
--
Kees Cook
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
@ 2025-04-07 17:53 ` Krzysztof Kozlowski
2025-04-09 6:31 ` Ivan Vecera
2025-04-07 18:05 ` Andy Shevchenko
1 sibling, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-07 17:53 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07/04/2025 19:28, Ivan Vecera wrote:
> This adds base MFD driver for Microchip Azurite ZL3073x chip family.
Please do not use "This commit/patch/change", but imperative mood. See
longer explanation here:
https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
> These chips provide DPLL and PHC (PTP) functionality and they can
> be connected over I2C or SPI bus.
>
...
> +/**
> + * zl3073x_get_regmap_config - return pointer to regmap config
> + *
> + * Returns pointer to regmap config
> + */
> +const struct regmap_config *zl3073x_get_regmap_config(void)
> +{
> + return &zl3073x_regmap_config;
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_get_regmap_config, "ZL3073X");
> +
> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
> +{
> + struct zl3073x_dev *zldev;
> +
> + return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
> +
> +int zl3073x_dev_init(struct zl3073x_dev *zldev)
> +{
> + devm_mutex_init(zldev->dev, &zldev->lock);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
> +
> +void zl3073x_dev_exit(struct zl3073x_dev *zldev)
> +{
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
Why do you add empty exports?
> diff --git a/drivers/mfd/zl3073x-spi.c b/drivers/mfd/zl3073x-spi.c
> new file mode 100644
> index 0000000000000..a6b9a366a7585
> --- /dev/null
> +++ b/drivers/mfd/zl3073x-spi.c
> @@ -0,0 +1,71 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/spi/spi.h>
> +#include "zl3073x.h"
> +
> +static const struct spi_device_id zl3073x_spi_id[] = {
> + { "zl3073x-spi", },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
> +
> +static const struct of_device_id zl3073x_spi_of_match[] = {
> + { .compatible = "microchip,zl3073x-spi" },
You need bindings. If they are somewhere in this patchset then you need
correct order so before users (see DT submitting patches).
> +static void zl3073x_spi_remove(struct spi_device *spidev)
> +{
> + struct zl3073x_dev *zldev;
> +
> + zldev = spi_get_drvdata(spidev);
> + zl3073x_dev_exit(zldev);
> +}
> +
> +static struct spi_driver zl3073x_spi_driver = {
> + .driver = {
> + .name = "zl3073x-spi",
> + .of_match_table = of_match_ptr(zl3073x_spi_of_match),
Drop of_match_ptr, you have warnings here.
> + },
> + .probe = zl3073x_spi_probe,
> + .remove = zl3073x_spi_remove,
> + .id_table = zl3073x_spi_id,
> +};
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin
2025-04-07 17:31 ` [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin Ivan Vecera
@ 2025-04-07 18:01 ` Krzysztof Kozlowski
2025-04-07 18:10 ` Krzysztof Kozlowski
` (2 more replies)
0 siblings, 3 replies; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-07 18:01 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07/04/2025 19:31, Ivan Vecera wrote:
> This adds DT bindings schema for DPLL (device phase-locked loop)
Please do not use "This commit/patch/change", but imperative mood. See
longer explanation here:
https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
A nit, subject: drop second/last, redundant "device tree bindings for".
The "dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
> device and associated pin. The schema follows existing DPLL core API
What is core API in terms of Devicetree?
> and should be used to expose information that should be provided
> by platform firmware.
>
> The schema for DPLL device describe a DPLL chip that can contain
> one or more DPLLs (channels) and platform can specify their types.
> For now 'pps' and 'eec' types supported and these values are mapped
> to DPLL core's enums.
Describe entire hardware, not what is supported.
>
> The DPLL device can have optionally 'input-pins' and 'output-pins'
> sub-nodes that contain pin sub-nodes.
>
> These pin sub-nodes follows schema for dpll-pin and can contain
> information about the particular pin.
Describe the hardware, not the schema. We can read the contents of
patch. What we cannot read is the hardware and why you are making all
these choices.
>
> The pin contains the following properties:
> * reg - pin HW index (physical pin number of given type)
> * label - string that is used as board label by DPLL core
> * type - string that indicates pin type (mapped to DPLL core pin type)
> * esync-control - boolean that indicates whether embeddded sync control
> is allowed for this pin
> * supported-frequencies - list of 64bit values that represents frequencies
> that are allowed to be configured for the pin
Drop. Describe the hardware.
>
> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Did this really happen?
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> .../devicetree/bindings/dpll/dpll-device.yaml | 84 +++++++++++++++++++
> .../devicetree/bindings/dpll/dpll-pin.yaml | 43 ++++++++++
> MAINTAINERS | 2 +
> 3 files changed, 129 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dpll/dpll-device.yaml
> create mode 100644 Documentation/devicetree/bindings/dpll/dpll-pin.yaml
Filenames matching compatibles... unless this is common schema, but
commit description did not mention it.
>
> diff --git a/Documentation/devicetree/bindings/dpll/dpll-device.yaml b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
> new file mode 100644
> index 0000000000000..e6c309abb857f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
> @@ -0,0 +1,84 @@
> +# 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: |
Do not need '|' unless you need to preserve formatting.
> + Digital Phase-Locked Loop (DPLL) device are used for precise clock
> + synchronization in networking and telecom hardware. The device can
> + have one or more channels (DPLLs) and one or more input and output
> + pins. Each DPLL channel can either produce pulse-per-clock signal
> + or drive ethernet equipment clock. The type of each channel is
> + indicated by dpll-types property.
> +
> +properties:
> + $nodename:
> + pattern: "^dpll(@.*)?$"
> +
> + "#address-cells":
> + const: 0
> +
> + "#size-cells":
> + const: 0
Why do you need these cells?
> +
> + num-dplls:
> + description: Number of DPLL channels in this device.
Why this is not deducible from compatible?
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 1
> +
> + dpll-types:
> + description: List of DPLL types, one per DPLL instance.
> + $ref: /schemas/types.yaml#/definitions/non-unique-string-array
> + items:
> + enum: [pps, eec]
Why this is not deducible from compatible?
> +
> + input-pins:
> + type: object
> + description: DPLL input pins
> + unevaluatedProperties: false
So this is all for pinctrl? Or something else? Could not figure out from
commit msg. This does not help me either.
> +
> + properties:
> + "#address-cells":
> + const: 1
Why?
> + "#size-cells":
> + const: 0
Why? I don't see these being used.
> +
> + patternProperties:
> + "^pin@[0-9]+$":
> + $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"
> +
> +dependentRequired:
> + dpll-types: [ num-dplls ]
> +
> +additionalProperties: true
> diff --git a/Documentation/devicetree/bindings/dpll/dpll-pin.yaml b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
> new file mode 100644
> index 0000000000000..9aea8ceabb5af
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
> @@ -0,0 +1,43 @@
> +# 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: |
> + Schema for defining input and output pins of a Digital Phase-Locked Loop (DPLL).
> + Each pin can have a set of supported frequencies, label, type and may support
> + embedded sync.
> +
> +properties:
> + reg:
> + description: Hardware index of the pin.
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + 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:
> + description: List of supported frequencies for this pin, expressed in Hz.
> + $ref: /schemas/types.yaml#/definitions/uint64-array
Use common property suffixes and drop ref.
> +
> + type:
> + description: Type of the pin
> + $ref: /schemas/types.yaml#/definitions/string
> + enum: [ext, gnss, int, mux, synce]
> +
> +
Just one blank line
I bet that half of my questions could be answered with proper hardware
description which is missing in commit msg and binding description.
Instead your commit msg explains schema which makes no sense - I
mentioned, we can read the schema.
> +required:
> + - reg
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-07 17:31 ` [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family Ivan Vecera
@ 2025-04-07 18:04 ` Krzysztof Kozlowski
2025-04-09 7:19 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-07 18:04 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07/04/2025 19:31, Ivan Vecera wrote:
> This adds DT bindings schema for Microchip Azurite DPLL chip family.
Please do not use "This commit/patch/change", but imperative mood. See
longer explanation here:
https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
> These bindings are used by zl3073x driver and specifies this device
> that can be connected either to I2C or SPI bus.
Bindings are for hardware, not driver. Explain the hardware.
>
> The schema inherits existing dpll-device and dpll-pin schemas.
>
Do not explain what schema does - we see it. Explain the hardware which
we do not see here, because we (or to be precise: I) know nothing about.
> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> .../bindings/dpll/microchip,zl3073x.yaml | 74 +++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 75 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
>
> diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
> new file mode 100644
> index 0000000000000..38a6cc00bc026
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
> @@ -0,0 +1,74 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dpll/microchip,zl3073x.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microchip Azurite DPLL device
> +
> +maintainers:
> + - Ivan Vecera <ivecera@redhat.com>
> +
> +properties:
> + compatible:
> + enum:
> + - microchip,zl3073x-i2c
> + - microchip,zl3073x-spi
1. No, you do not get two compatibles. Only one.
2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
> +
> + reg:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> +
> +allOf:
> + - $ref: /schemas/dpll/dpll-device.yaml
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + dpll@70 {
> + compatible = "microchip,zl3073x-i2c";
> + #address-cells = <0>;
> + #size-cells = <0>;
Again, why do you need them if you are not using these two?
> + reg = <0x70>;
> + status = "okay";
Drop
Also, follow DTS coding style and order properties properly.
> +
> + num-dplls = <2>;
> + dpll-types = "pps", "eec";
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
2025-04-07 17:53 ` Krzysztof Kozlowski
@ 2025-04-07 18:05 ` Andy Shevchenko
2025-04-09 6:40 ` Ivan Vecera
1 sibling, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-07 18:05 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:28:28PM +0200, Ivan Vecera wrote:
> This adds base MFD driver for Microchip Azurite ZL3073x chip family.
> These chips provide DPLL and PHC (PTP) functionality and they can
> be connected over I2C or SPI bus.
>
> The MFD driver provide basic communication and synchronization
> over the bus and common functionality that are used by the DPLL
> driver (later in this series) and by the PTP driver (will be
> added later).
>
> The chip family is characterized by following properties:
> * 2 separate DPLL units (channels)
> * 5 synthesizers
> * 10 input pins (references)
> * 10 outputs
> * 20 output pins (output pin pair shares one output)
> * Each reference and output can act in differential or single-ended
> mode (reference or output in differential mode consumes 2 pins)
> * Each output is connected to one of the synthesizers
> * Each synthesizer is driven by one of the DPLL unit
.
The comments below are applicable to entire series, take your time and fix
*all* stylic and header issues before sending v2.
...
+ array_size.h
+ bits.h
+ device/devres.h
> +#include <linux/module.h>
This file uses *much* amore than that.
+ regmap.h
> +#include "zl3073x.h"
...
> +/*
> + * Regmap ranges
> + */
> +#define ZL3073x_PAGE_SIZE 128
> +#define ZL3073x_NUM_PAGES 16
> +#define ZL3073x_PAGE_SEL 0x7F
> +
> +static const struct regmap_range_cfg zl3073x_regmap_ranges[] = {
> + {
> + .range_min = 0,
Are you sure you get the idea of virtual address pages here?
> + .range_max = ZL3073x_NUM_PAGES * ZL3073x_PAGE_SIZE,
> + .selector_reg = ZL3073x_PAGE_SEL,
> + .selector_mask = GENMASK(3, 0),
> + .selector_shift = 0,
> + .window_start = 0,
> + .window_len = ZL3073x_PAGE_SIZE,
> + },
> +};
...
> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
> +{
> + struct zl3073x_dev *zldev;
> +
> + return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
> +
> +int zl3073x_dev_init(struct zl3073x_dev *zldev)
> +{
> + devm_mutex_init(zldev->dev, &zldev->lock);
Missing check.
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
> +
> +void zl3073x_dev_exit(struct zl3073x_dev *zldev)
> +{
> +}
> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
What's the point in these stubs?
...
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
No usual driver should include kernel.h, please follow IWYU principle.
> +#include <linux/module.h>
Again, this is just a random list of headers, see above and follow the IWYU
principle.
> +#include "zl3073x.h"
...
> +static const struct i2c_device_id zl3073x_i2c_id[] = {
> + { "zl3073x-i2c", },
Redundant inner comma.
> + { /* sentinel */ },
No comma for the sentinel.
> +};
...
> +static const struct of_device_id zl3073x_i2c_of_match[] = {
> + { .compatible = "microchip,zl3073x-i2c" },
> + { /* sentinel */ },
No comma.
> +};
> +static int zl3073x_i2c_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + const struct i2c_device_id *id;
> + struct zl3073x_dev *zldev;
> + int rc = 0;
Useless assignment.
> + zldev = zl3073x_dev_alloc(dev);
> + if (!zldev)
> + return -ENOMEM;
> +
> + id = i2c_client_get_device_id(client);
> + zldev->dev = dev;
> +
> + zldev->regmap = devm_regmap_init_i2c(client,
> + zl3073x_get_regmap_config());
It's perfectly a single line.
> + if (IS_ERR(zldev->regmap)) {
> + rc = PTR_ERR(zldev->regmap);
> + dev_err(dev, "Failed to allocate register map: %d\n", rc);
> + return rc;
return dev_err_probe(...);
> + }
> +
> + i2c_set_clientdata(client, zldev);
> +
> + return zl3073x_dev_init(zldev);
> +}
...
> +static void zl3073x_i2c_remove(struct i2c_client *client)
> +{
> + struct zl3073x_dev *zldev;
> +
> + zldev = i2c_get_clientdata(client);
Just make it one line definition + assignment.
> + zl3073x_dev_exit(zldev);
This is a red flag and because you haven't properly named the calls (i.e. devm
to show that they are only for probe stage and use managed resources) this is
not easy to catch.
> +}
> +
> +static struct i2c_driver zl3073x_i2c_driver = {
> + .driver = {
> + .name = "zl3073x-i2c",
> + .of_match_table = of_match_ptr(zl3073x_i2c_of_match),
Please, never use of_match_ptr() or ACPI_PTR() in a new code.
> + },
> + .probe = zl3073x_i2c_probe,
> + .remove = zl3073x_i2c_remove,
> + .id_table = zl3073x_i2c_id,
> +};
> +
Redundant blank line.
> +module_i2c_driver(zl3073x_i2c_driver);
...
> +#include <linux/kernel.h>
Just no. You should know what you are doing in the driver.
> +#include <linux/module.h>
> +#include <linux/of.h>
Just no. Use agnostic APIs.
> +#include <linux/spi/spi.h>
> +#include "zl3073x.h"
...
> +static const struct spi_device_id zl3073x_spi_id[] = {
> + { "zl3073x-spi", },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
> +
> +static const struct of_device_id zl3073x_spi_of_match[] = {
> + { .compatible = "microchip,zl3073x-spi" },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match);
Move the above closer to its user.
...
> +static int zl3073x_spi_probe(struct spi_device *spidev)
Usual name is spi for the above, it's shorter and allows to tidy up the code.
And below same comments as for i2c part of the driver.
...
> +#ifndef __ZL3073X_CORE_H
> +#define __ZL3073X_CORE_H
> +#include <linux/mfd/zl3073x.h>
How is it used here, please?
> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev);
> +int zl3073x_dev_init(struct zl3073x_dev *zldev);
> +void zl3073x_dev_exit(struct zl3073x_dev *zldev);
> +const struct regmap_config *zl3073x_get_regmap_config(void);
> +
> +#endif /* __ZL3073X_CORE_H */
...
> +#ifndef __LINUX_MFD_ZL3073X_H
> +#define __LINUX_MFD_ZL3073X_H
> +#include <linux/device.h>
> +#include <linux/regmap.h>
Ditto. Two unused headers and one which must be included is missed.
> +struct zl3073x_dev {
> + struct device *dev;
> + struct regmap *regmap;
> + struct mutex lock;
> +};
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 00/28] Add Microchip ZL3073x support
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
` (27 preceding siblings ...)
2025-04-07 17:33 ` [PATCH 28/28] dpll: zl3073x: Add support to get fractional frequency offset on input pins Ivan Vecera
@ 2025-04-07 18:06 ` Andy Shevchenko
28 siblings, 0 replies; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-07 18:06 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andrew Morton, Michal Schmidt, devicetree,
linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:28:27PM +0200, Ivan Vecera wrote:
> This series adds support for Microchip Azurite DPLL/PTP/SyncE chip
> family. These chips provide DPLL and PTP functionality, so the series
> adds the common MFD driver that provides an access to the bus that can
> be either I2C or SPI. The second part of the series is DPLL driver that
> covers DPLL functionality. The PTP support will be added by separate
> series as well as flashing capability.
>
> All functionality was tested by myself and by Prathosh Satish on
> Microchip EDS2 development board with ZL30732 DPLL chip connected over
> I2C bus.
>
> Patch breakdown
> ===============
> Patch 1 - Basic support for I2C, SPI and regmap
Was enough to me to see that this need more work. Just take your time and do
internal review. It seems like one is missed. I believe you have a lot of
experienced kernel maintainers and contributors who can help you with that.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 18/28] dpll: zl3073x: Read optional pin properties from firmware
2025-04-07 17:31 ` [PATCH 18/28] dpll: zl3073x: Read optional pin properties " Ivan Vecera
@ 2025-04-07 18:06 ` Krzysztof Kozlowski
2025-04-09 7:22 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-07 18:06 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07/04/2025 19:31, Ivan Vecera wrote:
> +
> + if (zl3073x_dpll_is_input_pin(pin))
> + node_name = "input-pins";
> + else
> + node_name = "output-pins";
> +
> + /* Get node containing input or output pins */
> + pins_node = device_get_named_child_node(zldpll->mfd->dev, node_name);
> + if (!pins_node) {
> + dev_dbg(zldpll->mfd->dev, "'%s' sub-node is missing\n",
> + node_name);
> + return NULL;
> + }
> +
> + /* Get pin HW index */
> + idx = zl3073x_dpll_pin_index_get(pin);
> +
> + /* Enumerate 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 (idx == reg)
> + break;
Where do you drop the reference to pin_node?
> + }
> +
> + /* Release pin parent node */
> + fwnode_handle_put(pins_node);
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin
2025-04-07 18:01 ` Krzysztof Kozlowski
@ 2025-04-07 18:10 ` Krzysztof Kozlowski
2025-04-08 5:19 ` Michal Schmidt
2025-04-09 7:09 ` Ivan Vecera
2 siblings, 0 replies; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-07 18:10 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07/04/2025 20:01, Krzysztof Kozlowski wrote:
>> +
>> + input-pins:
>> + type: object
>> + description: DPLL input pins
>> + unevaluatedProperties: false
>
> So this is all for pinctrl? Or something else? Could not figure out from
> commit msg. This does not help me either.
>
>> +
>> + properties:
>> + "#address-cells":
>> + const: 1
>
> Why?
Ah, I see being used.
>
>> + "#size-cells":
>> + const: 0
>
> Why? I don't see these being used.
And this as well.
>
>> +
>> + patternProperties:
>> + "^pin@[0-9]+$":
>> + $ref: /schemas/dpll/dpll-pin.yaml
>> + unevaluatedProperties: false
>> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 02/28] mfd: zl3073x: Register itself as devlink device
2025-04-07 17:28 ` [PATCH 02/28] mfd: zl3073x: Register itself as devlink device Ivan Vecera
@ 2025-04-07 20:57 ` Andrew Lunn
2025-04-09 6:41 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andrew Lunn @ 2025-04-07 20:57 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:28:29PM +0200, Ivan Vecera wrote:
> Use devlink_alloc() to alloc zl3073x_dev structure and register
> the device as a devlink device. Follow-up patches add support for
> devlink device info reporting and devlink flash interface will
> be later used for flashing firmware and configuration.
>
> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> drivers/mfd/Kconfig | 3 +++
> drivers/mfd/zl3073x-core.c | 27 +++++++++++++++++++++++++--
> 2 files changed, 28 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 30b36e3ee8f7f..a838d5dca4579 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2424,11 +2424,13 @@ config MFD_UPBOARD_FPGA
>
> config MFD_ZL3073X_CORE
> tristate
> + select NET_DEVLINK
> select MFD_CORE
>
> config MFD_ZL3073X_I2C
> tristate "Microchip Azurite DPLL/PTP/SyncE with I2C"
> depends on I2C
> + depends on NET
> select MFD_ZL3073X_CORE
> select REGMAP_I2C
> help
> @@ -2441,6 +2443,7 @@ config MFD_ZL3073X_I2C
>
> config MFD_ZL3073X_SPI
> tristate "Microchip Azurite DPLL/PTP/SyncE with SPI"
> + depends on NET
It seems odd that the SPI and I2C drivers need net? It is the core
which is doing devlink stuff.
Andrew
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 03/28] mfd: zl3073x: Add register access helpers
2025-04-07 17:28 ` [PATCH 03/28] mfd: zl3073x: Add register access helpers Ivan Vecera
@ 2025-04-07 21:03 ` Andrew Lunn
2025-04-09 6:42 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andrew Lunn @ 2025-04-07 21:03 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:28:30PM +0200, Ivan Vecera wrote:
> Add helpers zl3073x_{read,write}_reg() to access device registers.
> These functions have to be called with device lock that can be taken
> by zl3073x_{lock,unlock}() or a caller can use defined guard.
regmap has locking. It would be good to explain in detail why regmap
locking is not sufficient.
Andrew
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-07 17:28 ` [PATCH 05/28] mfd: zl3073x: Add components versions register defs Ivan Vecera
@ 2025-04-07 21:09 ` Andrew Lunn
2025-04-09 6:44 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andrew Lunn @ 2025-04-07 21:09 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
> Add register definitions for components versions and report them
> during probe.
>
> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
> 1 file changed, 35 insertions(+)
>
> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
> index 39d4c8608a740..b3091b00cffa8 100644
> --- a/drivers/mfd/zl3073x-core.c
> +++ b/drivers/mfd/zl3073x-core.c
> @@ -1,10 +1,19 @@
> // SPDX-License-Identifier: GPL-2.0-only
>
> +#include <linux/bitfield.h>
> #include <linux/module.h>
> #include <linux/unaligned.h>
> #include <net/devlink.h>
> #include "zl3073x.h"
>
> +/*
> + * Register Map Page 0, General
> + */
> +ZL3073X_REG16_DEF(id, 0x0001);
> +ZL3073X_REG16_DEF(revision, 0x0003);
> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
> +
> /*
> * Regmap ranges
> */
> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>
> int zl3073x_dev_init(struct zl3073x_dev *zldev)
> {
> + u16 id, revision, fw_ver;
> struct devlink *devlink;
> + u32 cfg_ver;
> + int rc;
>
> devm_mutex_init(zldev->dev, &zldev->lock);
>
> + scoped_guard(zl3073x, zldev) {
Why the scoped_guard? The locking scheme you have seems very opaque.
> + rc = zl3073x_read_id(zldev, &id);
> + if (rc)
> + return rc;
> + rc = zl3073x_read_revision(zldev, &revision);
> + if (rc)
> + return rc;
> + rc = zl3073x_read_fw_ver(zldev, &fw_ver);
> + if (rc)
> + return rc;
> + rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
> + if (rc)
> + return rc;
Could a parallel operation change the ID? Upgrade the firmware
version?
Andrew
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin
2025-04-07 18:01 ` Krzysztof Kozlowski
2025-04-07 18:10 ` Krzysztof Kozlowski
@ 2025-04-08 5:19 ` Michal Schmidt
2025-04-09 7:09 ` Ivan Vecera
2 siblings, 0 replies; 70+ messages in thread
From: Michal Schmidt @ 2025-04-08 5:19 UTC (permalink / raw)
To: Krzysztof Kozlowski, Ivan Vecera
Cc: netdev, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On Mon, Apr 7, 2025 at 8:02 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 07/04/2025 19:31, Ivan Vecera wrote:
> > This adds DT bindings schema for DPLL (device phase-locked loop)
> > Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>
> Did this really happen?
We have been through several iterations of the patchset internally. I
pointed out numerous bugs in the driver code and suggested cleanups.
However, I know very little about DT, so in v2 we should omit my
Reviewed-by on the DT patches.
Michal
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-07 17:53 ` Krzysztof Kozlowski
@ 2025-04-09 6:31 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 6:31 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07. 04. 25 7:53 odp., Krzysztof Kozlowski wrote:
> On 07/04/2025 19:28, Ivan Vecera wrote:
>> This adds base MFD driver for Microchip Azurite ZL3073x chip family.
>
> Please do not use "This commit/patch/change", but imperative mood. See
> longer explanation here:
> https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
Will fix in v2.
>> These chips provide DPLL and PHC (PTP) functionality and they can
>> be connected over I2C or SPI bus.
>>
>
> ...
>
>> +/**
>> + * zl3073x_get_regmap_config - return pointer to regmap config
>> + *
>> + * Returns pointer to regmap config
>> + */
>> +const struct regmap_config *zl3073x_get_regmap_config(void)
>> +{
>> + return &zl3073x_regmap_config;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_get_regmap_config, "ZL3073X");
>> +
>> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
>> +{
>> + struct zl3073x_dev *zldev;
>> +
>> + return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>> +
>> +int zl3073x_dev_init(struct zl3073x_dev *zldev)
>> +{
>> + devm_mutex_init(zldev->dev, &zldev->lock);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
>> +
>> +void zl3073x_dev_exit(struct zl3073x_dev *zldev)
>> +{
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
>
> Why do you add empty exports?
It is filled in the later commits but yeah I will include the function
once it will be necessary.
>> diff --git a/drivers/mfd/zl3073x-spi.c b/drivers/mfd/zl3073x-spi.c
>> new file mode 100644
>> index 0000000000000..a6b9a366a7585
>> --- /dev/null
>> +++ b/drivers/mfd/zl3073x-spi.c
>> @@ -0,0 +1,71 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/spi/spi.h>
>> +#include "zl3073x.h"
>> +
>> +static const struct spi_device_id zl3073x_spi_id[] = {
>> + { "zl3073x-spi", },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
>> +
>> +static const struct of_device_id zl3073x_spi_of_match[] = {
>> + { .compatible = "microchip,zl3073x-spi" },
>
>
> You need bindings. If they are somewhere in this patchset then you need
> correct order so before users (see DT submitting patches).
Yes, there are. I will reorder the patches in v2 and also split the
whole series into several ones.
>> +static void zl3073x_spi_remove(struct spi_device *spidev)
>> +{
>> + struct zl3073x_dev *zldev;
>> +
>> + zldev = spi_get_drvdata(spidev);
>> + zl3073x_dev_exit(zldev);
>> +}
>> +
>> +static struct spi_driver zl3073x_spi_driver = {
>> + .driver = {
>> + .name = "zl3073x-spi",
>> + .of_match_table = of_match_ptr(zl3073x_spi_of_match),
>
> Drop of_match_ptr, you have warnings here.
Ack, will avoid.
>> + },
>> + .probe = zl3073x_spi_probe,
>> + .remove = zl3073x_spi_remove,
>> + .id_table = zl3073x_spi_id,
>> +};
>> +
>
>
>
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-07 18:05 ` Andy Shevchenko
@ 2025-04-09 6:40 ` Ivan Vecera
2025-04-14 6:36 ` Andy Shevchenko
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 6:40 UTC (permalink / raw)
To: Andy Shevchenko
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07. 04. 25 8:05 odp., Andy Shevchenko wrote:
> On Mon, Apr 07, 2025 at 07:28:28PM +0200, Ivan Vecera wrote:
>> This adds base MFD driver for Microchip Azurite ZL3073x chip family.
>> These chips provide DPLL and PHC (PTP) functionality and they can
>> be connected over I2C or SPI bus.
>>
>> The MFD driver provide basic communication and synchronization
>> over the bus and common functionality that are used by the DPLL
>> driver (later in this series) and by the PTP driver (will be
>> added later).
>>
>> The chip family is characterized by following properties:
>> * 2 separate DPLL units (channels)
>> * 5 synthesizers
>> * 10 input pins (references)
>> * 10 outputs
>> * 20 output pins (output pin pair shares one output)
>> * Each reference and output can act in differential or single-ended
>> mode (reference or output in differential mode consumes 2 pins)
>> * Each output is connected to one of the synthesizers
>> * Each synthesizer is driven by one of the DPLL unit
> .
> The comments below are applicable to entire series, take your time and fix
> *all* stylic and header issues before sending v2.
>
> ...
>
> + array_size.h
> + bits.h
>
> + device/devres.h
>
>> +#include <linux/module.h>
>
> This file uses *much* amore than that.
>
> + regmap.h
>
>
>> +#include "zl3073x.h"
>
> ...
>
Will fix in the next series.
>> +/*
>> + * Regmap ranges
>> + */
>> +#define ZL3073x_PAGE_SIZE 128
>> +#define ZL3073x_NUM_PAGES 16
>> +#define ZL3073x_PAGE_SEL 0x7F
>> +
>> +static const struct regmap_range_cfg zl3073x_regmap_ranges[] = {
>> + {
>> + .range_min = 0,
>
> Are you sure you get the idea of virtual address pages here?
What is wrong here?
I have a device that uses 7-bit addresses and have 16 register pages.
Each pages is from 0x00-0x7f and register 0x7f is used as page selector
where bits 0-3 select the page.
>> + .range_max = ZL3073x_NUM_PAGES * ZL3073x_PAGE_SIZE,
>> + .selector_reg = ZL3073x_PAGE_SEL,
>> + .selector_mask = GENMASK(3, 0),
>> + .selector_shift = 0,
>> + .window_start = 0,
>> + .window_len = ZL3073x_PAGE_SIZE,
>> + },
>> +};
>
> ...
>
>> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev)
>> +{
>> + struct zl3073x_dev *zldev;
>> +
>> + return devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>> +
>> +int zl3073x_dev_init(struct zl3073x_dev *zldev)
>> +{
>> + devm_mutex_init(zldev->dev, &zldev->lock);
>
> Missing check.
Will fix.
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_init, "ZL3073X");
>> +
>> +void zl3073x_dev_exit(struct zl3073x_dev *zldev)
>> +{
>> +}
>> +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_exit, "ZL3073X");
>
> What's the point in these stubs?
This function is used and filled later. I will drop it here and
introduce when it will be necessary.
>> +#include <linux/i2c.h>
>
>> +#include <linux/kernel.h>
>
> No usual driver should include kernel.h, please follow IWYU principle.
Will follow IWYU in v2.
>> +#include <linux/module.h>
>
> Again, this is just a random list of headers, see above and follow the IWYU
> principle.
Ditto.
>> +#include "zl3073x.h"
>
> ...
>
>> +static const struct i2c_device_id zl3073x_i2c_id[] = {
>> + { "zl3073x-i2c", },
>
> Redundant inner comma.
Ack
>> + { /* sentinel */ },
>
> No comma for the sentinel.
>
>> +};
Ack
>> +static const struct of_device_id zl3073x_i2c_of_match[] = {
>> + { .compatible = "microchip,zl3073x-i2c" },
>> + { /* sentinel */ },
Ack
>> +};
>
>> +static int zl3073x_i2c_probe(struct i2c_client *client)
>> +{
>> + struct device *dev = &client->dev;
>> + const struct i2c_device_id *id;
>> + struct zl3073x_dev *zldev;
>
>> + int rc = 0;
>
> Useless assignment.
Sorry for that, it was originally necessary.
Will drop.
>> + zldev = zl3073x_dev_alloc(dev);
>> + if (!zldev)
>> + return -ENOMEM;
>> +
>> + id = i2c_client_get_device_id(client);
>> + zldev->dev = dev;
>> +
>> + zldev->regmap = devm_regmap_init_i2c(client,
>> + zl3073x_get_regmap_config());
>
> It's perfectly a single line.
I tried to follow strictly 80 chars/line. Will fix.
>> + if (IS_ERR(zldev->regmap)) {
>> + rc = PTR_ERR(zldev->regmap);
>> + dev_err(dev, "Failed to allocate register map: %d\n", rc);
>> + return rc;
>
> return dev_err_probe(...);
Will change.
>> + }
>> +
>> + i2c_set_clientdata(client, zldev);
>> +
>> + return zl3073x_dev_init(zldev);
>> +}
>
> ...
>
>> +static void zl3073x_i2c_remove(struct i2c_client *client)
>> +{
>
>> + struct zl3073x_dev *zldev;
>> +
>> + zldev = i2c_get_clientdata(client);
>
> Just make it one line definition + assignment.
Ack
>> + zl3073x_dev_exit(zldev);
>
> This is a red flag and because you haven't properly named the calls (i.e. devm
> to show that they are only for probe stage and use managed resources) this is
> not easy to catch.
Will rename zl3073x_dev_alloc() to zl3073x_devm_alloc() to indicate that
devres is used... Probably will drop zl3073x_dev_exit() entirely and
take care of devlink unregistration by devres way.
>> +}
>> +
>> +static struct i2c_driver zl3073x_i2c_driver = {
>> + .driver = {
>> + .name = "zl3073x-i2c",
>> + .of_match_table = of_match_ptr(zl3073x_i2c_of_match),
>
> Please, never use of_match_ptr() or ACPI_PTR() in a new code.
Ack
>> + },
>> + .probe = zl3073x_i2c_probe,
>> + .remove = zl3073x_i2c_remove,
>> + .id_table = zl3073x_i2c_id,
>> +};
>
>> +
>
> Redundant blank line.
>
>> +module_i2c_driver(zl3073x_i2c_driver);
>
> ...
>
>> +#include <linux/kernel.h>
>
> Just no. You should know what you are doing in the driver.
Will fix.
>> +#include <linux/module.h>
>
>> +#include <linux/of.h>
Ack
>> +#include <linux/spi/spi.h>
>> +#include "zl3073x.h"
>
> ...
>
>> +static const struct spi_device_id zl3073x_spi_id[] = {
>> + { "zl3073x-spi", },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
>> +
>> +static const struct of_device_id zl3073x_spi_of_match[] = {
>> + { .compatible = "microchip,zl3073x-spi" },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match);
>
> Move the above closer to its user.
Ack
>> +static int zl3073x_spi_probe(struct spi_device *spidev)
>
> Usual name is spi for the above, it's shorter and allows to tidy up the code.
>
> And below same comments as for i2c part of the driver.
OK, will fix.
>> +#ifndef __ZL3073X_CORE_H
>> +#define __ZL3073X_CORE_H
>
>> +#include <linux/mfd/zl3073x.h>
>
> How is it used here, please?
Will change to forward declaration.
>> +struct zl3073x_dev *zl3073x_dev_alloc(struct device *dev);
>> +int zl3073x_dev_init(struct zl3073x_dev *zldev);
>> +void zl3073x_dev_exit(struct zl3073x_dev *zldev);
>> +const struct regmap_config *zl3073x_get_regmap_config(void);
>> +
>> +#endif /* __ZL3073X_CORE_H */
>
> ...
>
>> +#ifndef __LINUX_MFD_ZL3073X_H
>> +#define __LINUX_MFD_ZL3073X_H
>
>> +#include <linux/device.h>
>> +#include <linux/regmap.h>
>
> Ditto. Two unused headers and one which must be included is missed.
The same, forward declaration and inclusion of <linux/mutex.h>
>> +struct zl3073x_dev {
>> + struct device *dev;
>> + struct regmap *regmap;
>> + struct mutex lock;
>> +};
Thank you Andy for the review.
I.
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 02/28] mfd: zl3073x: Register itself as devlink device
2025-04-07 20:57 ` Andrew Lunn
@ 2025-04-09 6:41 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 6:41 UTC (permalink / raw)
To: Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 07. 04. 25 10:57 odp., Andrew Lunn wrote:
> On Mon, Apr 07, 2025 at 07:28:29PM +0200, Ivan Vecera wrote:
>> Use devlink_alloc() to alloc zl3073x_dev structure and register
>> the device as a devlink device. Follow-up patches add support for
>> devlink device info reporting and devlink flash interface will
>> be later used for flashing firmware and configuration.
>>
>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> ---
>> drivers/mfd/Kconfig | 3 +++
>> drivers/mfd/zl3073x-core.c | 27 +++++++++++++++++++++++++--
>> 2 files changed, 28 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>> index 30b36e3ee8f7f..a838d5dca4579 100644
>> --- a/drivers/mfd/Kconfig
>> +++ b/drivers/mfd/Kconfig
>> @@ -2424,11 +2424,13 @@ config MFD_UPBOARD_FPGA
>>
>> config MFD_ZL3073X_CORE
>> tristate
>> + select NET_DEVLINK
>> select MFD_CORE
>>
>> config MFD_ZL3073X_I2C
>> tristate "Microchip Azurite DPLL/PTP/SyncE with I2C"
>> depends on I2C
>> + depends on NET
>> select MFD_ZL3073X_CORE
>> select REGMAP_I2C
>> help
>> @@ -2441,6 +2443,7 @@ config MFD_ZL3073X_I2C
>>
>> config MFD_ZL3073X_SPI
>> tristate "Microchip Azurite DPLL/PTP/SyncE with SPI"
>> + depends on NET
>
> It seems odd that the SPI and I2C drivers need net? It is the core
> which is doing devlink stuff.
>
> Andrew
Will move this under MFD_ZL3073X_CORE.
Thank you.
I.
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 03/28] mfd: zl3073x: Add register access helpers
2025-04-07 21:03 ` Andrew Lunn
@ 2025-04-09 6:42 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 6:42 UTC (permalink / raw)
To: Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 07. 04. 25 11:03 odp., Andrew Lunn wrote:
> On Mon, Apr 07, 2025 at 07:28:30PM +0200, Ivan Vecera wrote:
>> Add helpers zl3073x_{read,write}_reg() to access device registers.
>> These functions have to be called with device lock that can be taken
>> by zl3073x_{lock,unlock}() or a caller can use defined guard.
>
> regmap has locking. It would be good to explain in detail why regmap
> locking is not sufficient.
>
> Andrew
>
Yes, it is not sufficient. I will describe this in v2 commit description.
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-07 21:09 ` Andrew Lunn
@ 2025-04-09 6:44 ` Ivan Vecera
2025-04-10 7:11 ` Krzysztof Kozlowski
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 6:44 UTC (permalink / raw)
To: Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 07. 04. 25 11:09 odp., Andrew Lunn wrote:
> On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
>> Add register definitions for components versions and report them
>> during probe.
>>
>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> ---
>> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
>> 1 file changed, 35 insertions(+)
>>
>> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
>> index 39d4c8608a740..b3091b00cffa8 100644
>> --- a/drivers/mfd/zl3073x-core.c
>> +++ b/drivers/mfd/zl3073x-core.c
>> @@ -1,10 +1,19 @@
>> // SPDX-License-Identifier: GPL-2.0-only
>>
>> +#include <linux/bitfield.h>
>> #include <linux/module.h>
>> #include <linux/unaligned.h>
>> #include <net/devlink.h>
>> #include "zl3073x.h"
>>
>> +/*
>> + * Register Map Page 0, General
>> + */
>> +ZL3073X_REG16_DEF(id, 0x0001);
>> +ZL3073X_REG16_DEF(revision, 0x0003);
>> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
>> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
>> +
>> /*
>> * Regmap ranges
>> */
>> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>>
>> int zl3073x_dev_init(struct zl3073x_dev *zldev)
>> {
>> + u16 id, revision, fw_ver;
>> struct devlink *devlink;
>> + u32 cfg_ver;
>> + int rc;
>>
>> devm_mutex_init(zldev->dev, &zldev->lock);
>>
>> + scoped_guard(zl3073x, zldev) {
>
> Why the scoped_guard? The locking scheme you have seems very opaque.
We are read the HW registers in this block and the access is protected
by this device lock. Regmap locking will be disabled in v2 as this is
not sufficient.
>> + rc = zl3073x_read_id(zldev, &id);
>> + if (rc)
>> + return rc;
>> + rc = zl3073x_read_revision(zldev, &revision);
>> + if (rc)
>> + return rc;
>> + rc = zl3073x_read_fw_ver(zldev, &fw_ver);
>> + if (rc)
>> + return rc;
>> + rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
>> + if (rc)
>> + return rc;
>
> Could a parallel operation change the ID? Upgrade the firmware
> version?
>
> Andrew
No, but register access functions require the device lock to be held.
See above.
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin
2025-04-07 18:01 ` Krzysztof Kozlowski
2025-04-07 18:10 ` Krzysztof Kozlowski
2025-04-08 5:19 ` Michal Schmidt
@ 2025-04-09 7:09 ` Ivan Vecera
2 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 7:09 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07. 04. 25 8:01 odp., Krzysztof Kozlowski wrote:
> On 07/04/2025 19:31, Ivan Vecera wrote:
>> This adds DT bindings schema for DPLL (device phase-locked loop)
>
> Please do not use "This commit/patch/change", but imperative mood. See
> longer explanation here:
> https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
>
> A nit, subject: drop second/last, redundant "device tree bindings for".
> The "dt-bindings" prefix is already stating that these are bindings.
> See also:
> https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
Will fix this in v2.
>> device and associated pin. The schema follows existing DPLL core API
>
> What is core API in terms of Devicetree?
>
>> and should be used to expose information that should be provided
>> by platform firmware.
>>
>> The schema for DPLL device describe a DPLL chip that can contain
>> one or more DPLLs (channels) and platform can specify their types.
>> For now 'pps' and 'eec' types supported and these values are mapped
>> to DPLL core's enums.
>
> Describe entire hardware, not what is supported.
Ack
>>
>> The DPLL device can have optionally 'input-pins' and 'output-pins'
>> sub-nodes that contain pin sub-nodes.
>>
>> These pin sub-nodes follows schema for dpll-pin and can contain
>> information about the particular pin.
>
> Describe the hardware, not the schema. We can read the contents of
> patch. What we cannot read is the hardware and why you are making all
> these choices.
OK
>>
>> The pin contains the following properties:
>> * reg - pin HW index (physical pin number of given type)
>> * label - string that is used as board label by DPLL core
>> * type - string that indicates pin type (mapped to DPLL core pin type)
>> * esync-control - boolean that indicates whether embeddded sync control
>> is allowed for this pin
>> * supported-frequencies - list of 64bit values that represents frequencies
>> that are allowed to be configured for the pin
>
> Drop. Describe the hardware.
>
>
>>
>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>
> Did this really happen?
>
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> ---
>> .../devicetree/bindings/dpll/dpll-device.yaml | 84 +++++++++++++++++++
>> .../devicetree/bindings/dpll/dpll-pin.yaml | 43 ++++++++++
>> MAINTAINERS | 2 +
>> 3 files changed, 129 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/dpll/dpll-device.yaml
>> create mode 100644 Documentation/devicetree/bindings/dpll/dpll-pin.yaml
>
> Filenames matching compatibles... unless this is common schema, but
> commit description did not mention it.
Yes, this is common schema to describe a common properties of DPLL
device that is inherited by a concrete HW implementation (next patch).
>>
>> diff --git a/Documentation/devicetree/bindings/dpll/dpll-device.yaml b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
>> new file mode 100644
>> index 0000000000000..e6c309abb857f
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/dpll/dpll-device.yaml
>> @@ -0,0 +1,84 @@
>> +# 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: |
>
> Do not need '|' unless you need to preserve formatting.
OK
>> + Digital Phase-Locked Loop (DPLL) device are used for precise clock
>> + synchronization in networking and telecom hardware. The device can
>> + have one or more channels (DPLLs) and one or more input and output
>> + pins. Each DPLL channel can either produce pulse-per-clock signal
>> + or drive ethernet equipment clock. The type of each channel is
>> + indicated by dpll-types property.
>> +
>> +properties:
>> + $nodename:
>> + pattern: "^dpll(@.*)?$"
>> +
>> + "#address-cells":
>> + const: 0
>> +
>> + "#size-cells":
>> + const: 0
>
> Why do you need these cells?
There are 'input-pins' and 'output-pins' sub-nodes that do not use '@'
suffix and 'reg' property. They can be specified only once so address
nor size do not make sense.
>> +
>> + num-dplls:
>> + description: Number of DPLL channels in this device.
>
> Why this is not deducible from compatible?
Yes, it is. Concrete HW implementation should know this.
Will drop.
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + minimum: 1
>> +
>> + dpll-types:
>> + description: List of DPLL types, one per DPLL instance.
>> + $ref: /schemas/types.yaml#/definitions/non-unique-string-array
>> + items:
>> + enum: [pps, eec]
>
> Why this is not deducible from compatible?
The compatible does not specify how the DPLL channels are used.
Particular hardware schematics/wiring specify the type of the channel usage.
>> +
>> + input-pins:
>> + type: object
>> + description: DPLL input pins
>> + unevaluatedProperties: false
>
> So this is all for pinctrl? Or something else? Could not figure out from
> commit msg. This does not help me either.
No, these pins have nothing to do with pinctrl.
Will describe in next version.
>> +
>> + properties:
>> + "#address-cells":
>> + const: 1
>
> Why?
>
>> + "#size-cells":
>> + const: 0
>
> Why? I don't see these being used.
The pin has '@' suffix and 'reg' property that specifies the HW index of
the pin. (e.g pin@3 under output-pins is the 3rd physical output pin).
>> +
>> + patternProperties:
>> + "^pin@[0-9]+$":
>> + $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"
>> +
>> +dependentRequired:
>> + dpll-types: [ num-dplls ]
>> +
>> +additionalProperties: true
>> diff --git a/Documentation/devicetree/bindings/dpll/dpll-pin.yaml b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
>> new file mode 100644
>> index 0000000000000..9aea8ceabb5af
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml
>> @@ -0,0 +1,43 @@
>> +# 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: |
>> + Schema for defining input and output pins of a Digital Phase-Locked Loop (DPLL).
>> + Each pin can have a set of supported frequencies, label, type and may support
>> + embedded sync.
>> +
>> +properties:
>> + reg:
>> + description: Hardware index of the pin.
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> +
>> + 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:
>> + description: List of supported frequencies for this pin, expressed in Hz.
>> + $ref: /schemas/types.yaml#/definitions/uint64-array
>
> Use common property suffixes and drop ref.
Should I use 'supported-frequencies-hz'? If so... This property unit
type is specified [1] as uint32-matrix, can I use this for list of
uint64 numbers?
[1]
https://github.com/devicetree-org/dt-schema/blob/dd3e3dce83607661f2831a8fac9112fae5ebe6cd/dtschema/schemas/property-units.yaml#L56
>> +
>> + type:
>> + description: Type of the pin
>> + $ref: /schemas/types.yaml#/definitions/string
>> + enum: [ext, gnss, int, mux, synce]
>> +
>> +
>
> Just one blank line
Ack
> I bet that half of my questions could be answered with proper hardware
> description which is missing in commit msg and binding description.
> Instead your commit msg explains schema which makes no sense - I
> mentioned, we can read the schema.
>> +required:
>> + - reg
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-07 18:04 ` Krzysztof Kozlowski
@ 2025-04-09 7:19 ` Ivan Vecera
2025-04-10 7:01 ` Krzysztof Kozlowski
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 7:19 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07. 04. 25 8:04 odp., Krzysztof Kozlowski wrote:
> On 07/04/2025 19:31, Ivan Vecera wrote:
>> This adds DT bindings schema for Microchip Azurite DPLL chip family.
>
> Please do not use "This commit/patch/change", but imperative mood. See
> longer explanation here:
> https://elixir.bootlin.com/linux/v5.17.1/source/Documentation/process/submitting-patches.rst#L95
>
>> These bindings are used by zl3073x driver and specifies this device
>> that can be connected either to I2C or SPI bus.
>
> Bindings are for hardware, not driver. Explain the hardware.
OK
>>
>> The schema inherits existing dpll-device and dpll-pin schemas.
>>
>
> Do not explain what schema does - we see it. Explain the hardware which
> we do not see here, because we (or to be precise: I) know nothing about.
OK
>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> ---
>> .../bindings/dpll/microchip,zl3073x.yaml | 74 +++++++++++++++++++
>> MAINTAINERS | 1 +
>> 2 files changed, 75 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
>> new file mode 100644
>> index 0000000000000..38a6cc00bc026
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/dpll/microchip,zl3073x.yaml
>> @@ -0,0 +1,74 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/dpll/microchip,zl3073x.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Microchip Azurite DPLL device
>> +
>> +maintainers:
>> + - Ivan Vecera <ivecera@redhat.com>
>> +
>> +properties:
>> + compatible:
>> + enum:
>> + - microchip,zl3073x-i2c
>> + - microchip,zl3073x-spi
>
> 1. No, you do not get two compatibles. Only one.
Will split to two files, one for i2c and one for spi.
> 2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
Microchip refers to the ZL3073x as a family of compatible DPLL chips
with the same features. There is no need to introduce separate
compatible string for each of them.
>> +
>> + reg:
>> + maxItems: 1
>> +
>> +required:
>> + - compatible
>> + - reg
>> +
>> +allOf:
>> + - $ref: /schemas/dpll/dpll-device.yaml
>> +
>> +unevaluatedProperties: false
>> +
>> +examples:
>> + - |
>> + i2c {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + dpll@70 {
>> + compatible = "microchip,zl3073x-i2c";
>
>> + #address-cells = <0>;
>> + #size-cells = <0>;
>
> Again, why do you need them if you are not using these two?
The dpll-device.yaml defines them as required. Shouldn't they be
specified explicitly?
>> + reg = <0x70>;
>> + status = "okay";
>
> Drop
OK
> Also, follow DTS coding style and order properties properly.
>
>> +
>> + num-dplls = <2>;
>> + dpll-types = "pps", "eec";
>> +
Ack
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 18/28] dpll: zl3073x: Read optional pin properties from firmware
2025-04-07 18:06 ` Krzysztof Kozlowski
@ 2025-04-09 7:22 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-09 7:22 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 07. 04. 25 8:06 odp., Krzysztof Kozlowski wrote:
> On 07/04/2025 19:31, Ivan Vecera wrote:
>> +
>> + if (zl3073x_dpll_is_input_pin(pin))
>> + node_name = "input-pins";
>> + else
>> + node_name = "output-pins";
>> +
>> + /* Get node containing input or output pins */
>> + pins_node = device_get_named_child_node(zldpll->mfd->dev, node_name);
>> + if (!pins_node) {
>> + dev_dbg(zldpll->mfd->dev, "'%s' sub-node is missing\n",
>> + node_name);
>> + return NULL;
>> + }
>> +
>> + /* Get pin HW index */
>> + idx = zl3073x_dpll_pin_index_get(pin);
>> +
>> + /* Enumerate 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 (idx == reg)
>> + break;
>
> Where do you drop the reference to pin_node?
The pin_node is a return value of this function. It is stored into
pin_info->fwnode (in zl3073x_dpll_pin_info_get()) and later the
reference is dropped by zl3073x_dpll_pin_info_put().
>> + }
>> +
>> + /* Release pin parent node */
>> + fwnode_handle_put(pins_node);
>> +
>
>
>
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-09 7:19 ` Ivan Vecera
@ 2025-04-10 7:01 ` Krzysztof Kozlowski
2025-04-10 10:28 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-10 7:01 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 09/04/2025 09:19, Ivan Vecera wrote:
>>> +
>>> +maintainers:
>>> + - Ivan Vecera <ivecera@redhat.com>
>>> +
>>> +properties:
>>> + compatible:
>>> + enum:
>>> + - microchip,zl3073x-i2c
>>> + - microchip,zl3073x-spi
>>
>> 1. No, you do not get two compatibles. Only one.
>
> Will split to two files, one for i2c and one for spi.
No. One device, one compatible.
>
>> 2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
>
> Microchip refers to the ZL3073x as a family of compatible DPLL chips
> with the same features. There is no need to introduce separate
> compatible string for each of them.
So a wildcard, thus drop. Use full product names. Google search gives me
no products for ZL3073x but gives me ZL30735.
>
>>> +
>>> + reg:
>>> + maxItems: 1
>>> +
>>> +required:
>>> + - compatible
>>> + - reg
>>> +
>>> +allOf:
>>> + - $ref: /schemas/dpll/dpll-device.yaml
>>> +
>>> +unevaluatedProperties: false
>>> +
>>> +examples:
>>> + - |
>>> + i2c {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + dpll@70 {
>>> + compatible = "microchip,zl3073x-i2c";
>>
>>> + #address-cells = <0>;
>>> + #size-cells = <0>;
>>
>> Again, why do you need them if you are not using these two?
>
> The dpll-device.yaml defines them as required. Shouldn't they be
> specified explicitly?
But you do not use them. Where is any child node?
>
>>> + reg = <0x70>;
>>> + status = "okay";
>>
>> Drop
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-09 6:44 ` Ivan Vecera
@ 2025-04-10 7:11 ` Krzysztof Kozlowski
2025-04-10 10:23 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-10 7:11 UTC (permalink / raw)
To: Ivan Vecera, Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 09/04/2025 08:44, Ivan Vecera wrote:
> On 07. 04. 25 11:09 odp., Andrew Lunn wrote:
>> On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
>>> Add register definitions for components versions and report them
>>> during probe.
>>>
>>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>>> ---
>>> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
>>> 1 file changed, 35 insertions(+)
>>>
>>> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
>>> index 39d4c8608a740..b3091b00cffa8 100644
>>> --- a/drivers/mfd/zl3073x-core.c
>>> +++ b/drivers/mfd/zl3073x-core.c
>>> @@ -1,10 +1,19 @@
>>> // SPDX-License-Identifier: GPL-2.0-only
>>>
>>> +#include <linux/bitfield.h>
>>> #include <linux/module.h>
>>> #include <linux/unaligned.h>
>>> #include <net/devlink.h>
>>> #include "zl3073x.h"
>>>
>>> +/*
>>> + * Register Map Page 0, General
>>> + */
>>> +ZL3073X_REG16_DEF(id, 0x0001);
>>> +ZL3073X_REG16_DEF(revision, 0x0003);
>>> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
>>> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
>>> +
>>> /*
>>> * Regmap ranges
>>> */
>>> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>>>
>>> int zl3073x_dev_init(struct zl3073x_dev *zldev)
>>> {
>>> + u16 id, revision, fw_ver;
>>> struct devlink *devlink;
>>> + u32 cfg_ver;
>>> + int rc;
>>>
>>> devm_mutex_init(zldev->dev, &zldev->lock);
>>>
>>> + scoped_guard(zl3073x, zldev) {
>>
>> Why the scoped_guard? The locking scheme you have seems very opaque.
>
> We are read the HW registers in this block and the access is protected
> by this device lock. Regmap locking will be disabled in v2 as this is
Reading ID must be protected by mutex? Why and how?
What is a "device lock"?
> not sufficient.
>
>>> + rc = zl3073x_read_id(zldev, &id);
>>> + if (rc)
>>> + return rc;
>>> + rc = zl3073x_read_revision(zldev, &revision);
>>> + if (rc)
>>> + return rc;
>>> + rc = zl3073x_read_fw_ver(zldev, &fw_ver);
>>> + if (rc)
>>> + return rc;
>>> + rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
>>> + if (rc)
>>> + return rc;
>>
>> Could a parallel operation change the ID? Upgrade the firmware
>> version?
>>
>> Andrew
>
> No, but register access functions require the device lock to be held.
> See above.
Andrew comments stay valid here. This is weird need of locking and your
explanation is very vague.
BTW, do not send v2 before people respond to your comments in reasonable
time. You just send 28 patchset and expect people to finish review after
one day.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-10 7:11 ` Krzysztof Kozlowski
@ 2025-04-10 10:23 ` Ivan Vecera
2025-04-10 10:42 ` Krzysztof Kozlowski
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-10 10:23 UTC (permalink / raw)
To: Krzysztof Kozlowski, Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 10. 04. 25 9:11 dop., Krzysztof Kozlowski wrote:
> On 09/04/2025 08:44, Ivan Vecera wrote:
>> On 07. 04. 25 11:09 odp., Andrew Lunn wrote:
>>> On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
>>>> Add register definitions for components versions and report them
>>>> during probe.
>>>>
>>>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>>>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>>>> ---
>>>> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
>>>> 1 file changed, 35 insertions(+)
>>>>
>>>> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
>>>> index 39d4c8608a740..b3091b00cffa8 100644
>>>> --- a/drivers/mfd/zl3073x-core.c
>>>> +++ b/drivers/mfd/zl3073x-core.c
>>>> @@ -1,10 +1,19 @@
>>>> // SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> +#include <linux/bitfield.h>
>>>> #include <linux/module.h>
>>>> #include <linux/unaligned.h>
>>>> #include <net/devlink.h>
>>>> #include "zl3073x.h"
>>>>
>>>> +/*
>>>> + * Register Map Page 0, General
>>>> + */
>>>> +ZL3073X_REG16_DEF(id, 0x0001);
>>>> +ZL3073X_REG16_DEF(revision, 0x0003);
>>>> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
>>>> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
>>>> +
>>>> /*
>>>> * Regmap ranges
>>>> */
>>>> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>>>>
>>>> int zl3073x_dev_init(struct zl3073x_dev *zldev)
>>>> {
>>>> + u16 id, revision, fw_ver;
>>>> struct devlink *devlink;
>>>> + u32 cfg_ver;
>>>> + int rc;
>>>>
>>>> devm_mutex_init(zldev->dev, &zldev->lock);
>>>>
>>>> + scoped_guard(zl3073x, zldev) {
>>>
>>> Why the scoped_guard? The locking scheme you have seems very opaque.
>>
>> We are read the HW registers in this block and the access is protected
>> by this device lock. Regmap locking will be disabled in v2 as this is
>
> Reading ID must be protected by mutex? Why and how?
Yes, the ID is read from the hardware register and HW access functions
are protected by zl3073x_dev->lock. The access is not protected by
regmap locking schema. Set of registers are indirect and are accessed by
mailboxes where multiple register accesses need to be done atomically.
This is the reason why regmap locking is not sufficient.
> What is a "device lock"?
zl3073x_dev->lock
Sorry for confusing.
>> not sufficient.
>>
>>>> + rc = zl3073x_read_id(zldev, &id);
>>>> + if (rc)
>>>> + return rc;
>>>> + rc = zl3073x_read_revision(zldev, &revision);
>>>> + if (rc)
>>>> + return rc;
>>>> + rc = zl3073x_read_fw_ver(zldev, &fw_ver);
>>>> + if (rc)
>>>> + return rc;
>>>> + rc = zl3073x_read_custom_config_ver(zldev, &cfg_ver);
>>>> + if (rc)
>>>> + return rc;
>>>
>>> Could a parallel operation change the ID? Upgrade the firmware
>>> version?
>>>
>>> Andrew
>>
>> No, but register access functions require the device lock to be held.
>> See above.
>
> Andrew comments stay valid here. This is weird need of locking and your
> explanation is very vague.
See above why custom locking schema is necessary.
> BTW, do not send v2 before people respond to your comments in reasonable
> time. You just send 28 patchset and expect people to finish review after
> one day.
I'm sorry... will wait.
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-10 7:01 ` Krzysztof Kozlowski
@ 2025-04-10 10:28 ` Ivan Vecera
2025-04-10 12:19 ` Krzysztof Kozlowski
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-10 10:28 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 10. 04. 25 9:01 dop., Krzysztof Kozlowski wrote:
> On 09/04/2025 09:19, Ivan Vecera wrote:
>>>> +
>>>> +maintainers:
>>>> + - Ivan Vecera <ivecera@redhat.com>
>>>> +
>>>> +properties:
>>>> + compatible:
>>>> + enum:
>>>> + - microchip,zl3073x-i2c
>>>> + - microchip,zl3073x-spi
>>>
>>> 1. No, you do not get two compatibles. Only one.
>>
>> Will split to two files, one for i2c and one for spi.
>
> No. One device, one compatible.
OK, get it now. I thought that I need to have separate compatible for
each bus access type.
>>> 2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
>>
>> Microchip refers to the ZL3073x as a family of compatible DPLL chips
>> with the same features. There is no need to introduce separate
>> compatible string for each of them.
>
> So a wildcard, thus drop. Use full product names. Google search gives me
> no products for ZL3073x but gives me ZL30735.
I will use more appropriate microchip,azurite compatible.
>>
>>>> +
>>>> + reg:
>>>> + maxItems: 1
>>>> +
>>>> +required:
>>>> + - compatible
>>>> + - reg
>>>> +
>>>> +allOf:
>>>> + - $ref: /schemas/dpll/dpll-device.yaml
>>>> +
>>>> +unevaluatedProperties: false
>>>> +
>>>> +examples:
>>>> + - |
>>>> + i2c {
>>>> + #address-cells = <1>;
>>>> + #size-cells = <0>;
>>>> +
>>>> + dpll@70 {
>>>> + compatible = "microchip,zl3073x-i2c";
>>>
>>>> + #address-cells = <0>;
>>>> + #size-cells = <0>;
>>>
>>> Again, why do you need them if you are not using these two?
>>
>> The dpll-device.yaml defines them as required. Shouldn't they be
>> specified explicitly?
>
> But you do not use them. Where is any child node?
I though I have to specify this due to existence of 'input-pins' and
'output-pins' in the example.
>>
>>>> + reg = <0x70>;
>>>> + status = "okay";
>>>
>>> Drop
> Best regards,
> Krzysztof
>
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-10 10:23 ` Ivan Vecera
@ 2025-04-10 10:42 ` Krzysztof Kozlowski
2025-04-10 12:01 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-10 10:42 UTC (permalink / raw)
To: Ivan Vecera, Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 10/04/2025 12:23, Ivan Vecera wrote:
>
>
> On 10. 04. 25 9:11 dop., Krzysztof Kozlowski wrote:
>> On 09/04/2025 08:44, Ivan Vecera wrote:
>>> On 07. 04. 25 11:09 odp., Andrew Lunn wrote:
>>>> On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
>>>>> Add register definitions for components versions and report them
>>>>> during probe.
>>>>>
>>>>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>>>>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>>>>> ---
>>>>> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
>>>>> 1 file changed, 35 insertions(+)
>>>>>
>>>>> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
>>>>> index 39d4c8608a740..b3091b00cffa8 100644
>>>>> --- a/drivers/mfd/zl3073x-core.c
>>>>> +++ b/drivers/mfd/zl3073x-core.c
>>>>> @@ -1,10 +1,19 @@
>>>>> // SPDX-License-Identifier: GPL-2.0-only
>>>>>
>>>>> +#include <linux/bitfield.h>
>>>>> #include <linux/module.h>
>>>>> #include <linux/unaligned.h>
>>>>> #include <net/devlink.h>
>>>>> #include "zl3073x.h"
>>>>>
>>>>> +/*
>>>>> + * Register Map Page 0, General
>>>>> + */
>>>>> +ZL3073X_REG16_DEF(id, 0x0001);
>>>>> +ZL3073X_REG16_DEF(revision, 0x0003);
>>>>> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
>>>>> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
>>>>> +
>>>>> /*
>>>>> * Regmap ranges
>>>>> */
>>>>> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>>>>>
>>>>> int zl3073x_dev_init(struct zl3073x_dev *zldev)
>>>>> {
>>>>> + u16 id, revision, fw_ver;
>>>>> struct devlink *devlink;
>>>>> + u32 cfg_ver;
>>>>> + int rc;
>>>>>
>>>>> devm_mutex_init(zldev->dev, &zldev->lock);
>>>>>
>>>>> + scoped_guard(zl3073x, zldev) {
>>>>
>>>> Why the scoped_guard? The locking scheme you have seems very opaque.
>>>
>>> We are read the HW registers in this block and the access is protected
>>> by this device lock. Regmap locking will be disabled in v2 as this is
>>
>> Reading ID must be protected by mutex? Why and how?
>
> Yes, the ID is read from the hardware register and HW access functions
> are protected by zl3073x_dev->lock. The access is not protected by
Please do not keep repeating the same. You again describe the code. We
ask why do you implement that way?
> regmap locking schema. Set of registers are indirect and are accessed by
> mailboxes where multiple register accesses need to be done atomically.
regmap handles that, but anyway, how multiple register access to ID
registers happen? From what module? Which code does it? So they write
here something in the middle and reading would be unsynced?
> This is the reason why regmap locking is not sufficient.
Again, you explained nothing why it is not sufficient. You only keep
saying "access is protected" and therefore regmap locking is not enough.
That's not a valid argument.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 05/28] mfd: zl3073x: Add components versions register defs
2025-04-10 10:42 ` Krzysztof Kozlowski
@ 2025-04-10 12:01 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-10 12:01 UTC (permalink / raw)
To: Krzysztof Kozlowski, Andrew Lunn
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andy Shevchenko,
Andrew Morton, devicetree, linux-kernel, linux-hardening
On 10. 04. 25 12:42 odp., Krzysztof Kozlowski wrote:
> On 10/04/2025 12:23, Ivan Vecera wrote:
>>
>>
>> On 10. 04. 25 9:11 dop., Krzysztof Kozlowski wrote:
>>> On 09/04/2025 08:44, Ivan Vecera wrote:
>>>> On 07. 04. 25 11:09 odp., Andrew Lunn wrote:
>>>>> On Mon, Apr 07, 2025 at 07:28:32PM +0200, Ivan Vecera wrote:
>>>>>> Add register definitions for components versions and report them
>>>>>> during probe.
>>>>>>
>>>>>> Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
>>>>>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>>>>>> ---
>>>>>> drivers/mfd/zl3073x-core.c | 35 +++++++++++++++++++++++++++++++++++
>>>>>> 1 file changed, 35 insertions(+)
>>>>>>
>>>>>> diff --git a/drivers/mfd/zl3073x-core.c b/drivers/mfd/zl3073x-core.c
>>>>>> index 39d4c8608a740..b3091b00cffa8 100644
>>>>>> --- a/drivers/mfd/zl3073x-core.c
>>>>>> +++ b/drivers/mfd/zl3073x-core.c
>>>>>> @@ -1,10 +1,19 @@
>>>>>> // SPDX-License-Identifier: GPL-2.0-only
>>>>>>
>>>>>> +#include <linux/bitfield.h>
>>>>>> #include <linux/module.h>
>>>>>> #include <linux/unaligned.h>
>>>>>> #include <net/devlink.h>
>>>>>> #include "zl3073x.h"
>>>>>>
>>>>>> +/*
>>>>>> + * Register Map Page 0, General
>>>>>> + */
>>>>>> +ZL3073X_REG16_DEF(id, 0x0001);
>>>>>> +ZL3073X_REG16_DEF(revision, 0x0003);
>>>>>> +ZL3073X_REG16_DEF(fw_ver, 0x0005);
>>>>>> +ZL3073X_REG32_DEF(custom_config_ver, 0x0007);
>>>>>> +
>>>>>> /*
>>>>>> * Regmap ranges
>>>>>> */
>>>>>> @@ -159,10 +168,36 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_dev_alloc, "ZL3073X");
>>>>>>
>>>>>> int zl3073x_dev_init(struct zl3073x_dev *zldev)
>>>>>> {
>>>>>> + u16 id, revision, fw_ver;
>>>>>> struct devlink *devlink;
>>>>>> + u32 cfg_ver;
>>>>>> + int rc;
>>>>>>
>>>>>> devm_mutex_init(zldev->dev, &zldev->lock);
>>>>>>
>>>>>> + scoped_guard(zl3073x, zldev) {
>>>>>
>>>>> Why the scoped_guard? The locking scheme you have seems very opaque.
>>>>
>>>> We are read the HW registers in this block and the access is protected
>>>> by this device lock. Regmap locking will be disabled in v2 as this is
>>>
>>> Reading ID must be protected by mutex? Why and how?
>>
>> Yes, the ID is read from the hardware register and HW access functions
>> are protected by zl3073x_dev->lock. The access is not protected by
>
> Please do not keep repeating the same. You again describe the code. We
> ask why do you implement that way?
>
>> regmap locking schema. Set of registers are indirect and are accessed by
>> mailboxes where multiple register accesses need to be done atomically.
>
> regmap handles that, but anyway, how multiple register access to ID
> registers happen? From what module? Which code does it? So they write
> here something in the middle and reading would be unsynced?
OK, I'm going to try to explain in detail...
The device have 16 register pages where each of them has 128 registers
and register 0x7f on each page is a page selector.
Pages 0..9 contain direct registers that can be arbitrary read or
written in any order. For these registers implicit regmap locking is
sufficient.
Pages 10..16 contain indirect registers and these pages are called
mailboxes. Each mailbox cover specific part of hardware (synth, DPLL
channel, input ref, output...) and each of them contain mailbox_mask
register and mailbox_sem register. The rest of registers in the
particular page (mailbox) are latch registers.
Read operation (described in patch 8 in this v1 series):
E.g. driver needs to read frequency of input pin 4:
1) it set value of mailbox_mask (in input mailbox/page) to 4
2) it set mailbox_sem register (--"--) to read operation
3) it polls mailbox_sem to be cleared (firmware fills latch registers)
4) it reads frequency latch register (--"--) filled by FW
Write is similar but opposite:
1) it writes frequency to freq latch register (in input mb)
2) it set value of mailbox_mask
3) it set mailbox_sem to write operation
4) it polls mailbox_sem to be cleared (write was finished)
Steps 1-4 for both cases have to be done atomically - other reader
cannot modify mailbox_sem prior step 4 is finished and other writer
cannot touch latch registers prior step 4 is finished.
The module dpll_zl3073x (later in this series) and ptp_zl3073x (will be
posted later) use this intensively from multiple contexts (DPLL core
callbacks and monitoring threads).
So I have decided to use the custom locking scheme for accessing
registers instead of regmap locking that cannot guarantee this atomicity.
Would it be better to leave implicit regmap locking scheme for direct
registers and to have extra locking for mailboxes? If so, single mutex
for all mailboxes or separate mutex for each mailbox type?
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-10 10:28 ` Ivan Vecera
@ 2025-04-10 12:19 ` Krzysztof Kozlowski
2025-04-10 12:38 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-10 12:19 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 10/04/2025 12:28, Ivan Vecera wrote:
>
>>>> 2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
>>>
>>> Microchip refers to the ZL3073x as a family of compatible DPLL chips
>>> with the same features. There is no need to introduce separate
>>> compatible string for each of them.
>>
>> So a wildcard, thus drop. Use full product names. Google search gives me
>> no products for ZL3073x but gives me ZL30735.
>
> I will use more appropriate microchip,azurite compatible.
Hm? What/who gave such hint? Please read writing bindings or any other
guide/speech about it. If that's a zl30735 then use "zl30735" as device
part. If you have more devices, use fallbacks. See writing bindings.
>
>>>
>>>>> +
>>>>> + reg:
>>>>> + maxItems: 1
>>>>> +
>>>>> +required:
>>>>> + - compatible
>>>>> + - reg
>>>>> +
>>>>> +allOf:
>>>>> + - $ref: /schemas/dpll/dpll-device.yaml
>>>>> +
>>>>> +unevaluatedProperties: false
>>>>> +
>>>>> +examples:
>>>>> + - |
>>>>> + i2c {
>>>>> + #address-cells = <1>;
>>>>> + #size-cells = <0>;
>>>>> +
>>>>> + dpll@70 {
>>>>> + compatible = "microchip,zl3073x-i2c";
>>>>
>>>>> + #address-cells = <0>;
>>>>> + #size-cells = <0>;
>>>>
>>>> Again, why do you need them if you are not using these two?
>>>
>>> The dpll-device.yaml defines them as required. Shouldn't they be
>>> specified explicitly?
>>
>> But you do not use them. Where is any child node?
>
> I though I have to specify this due to existence of 'input-pins' and
> 'output-pins' in the example.
They do not have addressing, so no need for cells.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family
2025-04-10 12:19 ` Krzysztof Kozlowski
@ 2025-04-10 12:38 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-10 12:38 UTC (permalink / raw)
To: Krzysztof Kozlowski, netdev
Cc: Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Prathosh Satish,
Lee Jones, Kees Cook, Andy Shevchenko, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 10. 04. 25 2:19 odp., Krzysztof Kozlowski wrote:
> On 10/04/2025 12:28, Ivan Vecera wrote:
>>
>>>>> 2. What is 'x'? Wildcard? If so, drop and use specific compatibles.
>>>>
>>>> Microchip refers to the ZL3073x as a family of compatible DPLL chips
>>>> with the same features. There is no need to introduce separate
>>>> compatible string for each of them.
>>>
>>> So a wildcard, thus drop. Use full product names. Google search gives me
>>> no products for ZL3073x but gives me ZL30735.
>>
>> I will use more appropriate microchip,azurite compatible.
>
> Hm? What/who gave such hint? Please read writing bindings or any other
> guide/speech about it. If that's a zl30735 then use "zl30735" as device
> part. If you have more devices, use fallbacks. See writing bindings.
>
Something like this:
1)
properties:
compatible:
enum:
- microchip,zl30731
- microchip,zl30732
- microchip,zl30732
- microchip,zl80032
- microchip,zl80732
or
2)
properties:
compatible:
items:
- enum:
- microchip,zl30731
- microchip,zl30732
- microchip,zl30732
- microchip,zl80032
- microchip,zl80732
- const: microchip,azurite
If 1) what should be the filename ?
Thanks for patience.
Ivan
>>
>>>>
>>>>>> +
>>>>>> + reg:
>>>>>> + maxItems: 1
>>>>>> +
>>>>>> +required:
>>>>>> + - compatible
>>>>>> + - reg
>>>>>> +
>>>>>> +allOf:
>>>>>> + - $ref: /schemas/dpll/dpll-device.yaml
>>>>>> +
>>>>>> +unevaluatedProperties: false
>>>>>> +
>>>>>> +examples:
>>>>>> + - |
>>>>>> + i2c {
>>>>>> + #address-cells = <1>;
>>>>>> + #size-cells = <0>;
>>>>>> +
>>>>>> + dpll@70 {
>>>>>> + compatible = "microchip,zl3073x-i2c";
>>>>>
>>>>>> + #address-cells = <0>;
>>>>>> + #size-cells = <0>;
>>>>>
>>>>> Again, why do you need them if you are not using these two?
>>>>
>>>> The dpll-device.yaml defines them as required. Shouldn't they be
>>>> specified explicitly?
>>>
>>> But you do not use them. Where is any child node?
>>
>> I though I have to specify this due to existence of 'input-pins' and
>> 'output-pins' in the example.
>
> They do not have addressing, so no need for cells.
Thanks for explanation.
>
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-09 6:40 ` Ivan Vecera
@ 2025-04-14 6:36 ` Andy Shevchenko
2025-04-14 11:39 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 6:36 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On Wed, Apr 09, 2025 at 08:40:22AM +0200, Ivan Vecera wrote:
> On 07. 04. 25 8:05 odp., Andy Shevchenko wrote:
> > On Mon, Apr 07, 2025 at 07:28:28PM +0200, Ivan Vecera wrote:
...
> > > +/*
> > > + * Regmap ranges
> > > + */
> > > +#define ZL3073x_PAGE_SIZE 128
> > > +#define ZL3073x_NUM_PAGES 16
> > > +#define ZL3073x_PAGE_SEL 0x7F
> > > +
> > > +static const struct regmap_range_cfg zl3073x_regmap_ranges[] = {
> > > + {
> > > + .range_min = 0,
> >
> > Are you sure you get the idea of virtual address pages here?
>
> What is wrong here?
>
> I have a device that uses 7-bit addresses and have 16 register pages.
> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
> where bits 0-3 select the page.
The problem is that you overlap virtual page over the real one (the main one).
The drivers you mentioned in v2 discussions most likely are also buggy.
As I implied in the above question the developers hardly get the regmap ranges
right. It took me quite a while to see the issue, so it's not particularly your
fault.
> > > + .range_max = ZL3073x_NUM_PAGES * ZL3073x_PAGE_SIZE,
> > > + .selector_reg = ZL3073x_PAGE_SEL,
> > > + .selector_mask = GENMASK(3, 0),
> > > + .selector_shift = 0,
> > > + .window_start = 0,
> > > + .window_len = ZL3073x_PAGE_SIZE,
> > > + },
> > > +};
...
> > > + zldev->regmap = devm_regmap_init_i2c(client,
> > > + zl3073x_get_regmap_config());
> >
> > It's perfectly a single line.
>
> I tried to follow strictly 80 chars/line. Will fix.
Yes, but in some cases when it gains in readability it is allowed to use longer
lines even if one sticks with stricter 80 characters limit.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 6:36 ` Andy Shevchenko
@ 2025-04-14 11:39 ` Ivan Vecera
2025-04-14 11:52 ` Ivan Vecera
2025-04-14 13:58 ` Andy Shevchenko
0 siblings, 2 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 11:39 UTC (permalink / raw)
To: Andy Shevchenko
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
>> What is wrong here?
>>
>> I have a device that uses 7-bit addresses and have 16 register pages.
>> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
>> where bits 0-3 select the page.
> The problem is that you overlap virtual page over the real one (the main one).
>
> The drivers you mentioned in v2 discussions most likely are also buggy.
> As I implied in the above question the developers hardly get the regmap ranges
> right. It took me quite a while to see the issue, so it's not particularly your
> fault.
Hi Andy,
thank you I see the point.
Do you mean that the selector register should not be part of the range?
If so, does it mean that I have to specify a range for each page? Like this:
{
/* Page 0 */
.range_min = 0x000,
.range_max = 0x07e,
.selector_reg = ZL3073x_PAGE_SEL,
.selector_mask = GENMASK(3, 0),
.selector_shift = 0,
.window_start = 0,
.window_len = 0x7e,
},
{
/* Page 1 */
.range_min = 0x080,
.range_max = 0x0fe,
.selector_reg = ZL3073x_PAGE_SEL,
.selector_mask = GENMASK(3, 0),
.selector_shift = 0,
.window_start = 0,
.window_len = 0x7e,
},
...
Thank you,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 11:39 ` Ivan Vecera
@ 2025-04-14 11:52 ` Ivan Vecera
2025-04-14 13:55 ` Andy Shevchenko
2025-04-14 14:07 ` Ivan Vecera
2025-04-14 13:58 ` Andy Shevchenko
1 sibling, 2 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 11:52 UTC (permalink / raw)
To: Andy Shevchenko
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 14. 04. 25 1:39 odp., Ivan Vecera wrote:
>
>
> On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
>>> What is wrong here?
>>>
>>> I have a device that uses 7-bit addresses and have 16 register pages.
>>> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
>>> where bits 0-3 select the page.
>> The problem is that you overlap virtual page over the real one (the
>> main one).
>>
>> The drivers you mentioned in v2 discussions most likely are also buggy.
>> As I implied in the above question the developers hardly get the
>> regmap ranges
>> right. It took me quite a while to see the issue, so it's not
>> particularly your
>> fault.
> Hi Andy,
>
> thank you I see the point.
>
> Do you mean that the selector register should not be part of the range?
>
> If so, does it mean that I have to specify a range for each page? Like
> this:
>
> {
> /* Page 0 */
> .range_min = 0x000,
> .range_max = 0x07e,
> .selector_reg = ZL3073x_PAGE_SEL,
> .selector_mask = GENMASK(3, 0),
> .selector_shift = 0,
> .window_start = 0,
> .window_len = 0x7e,
> },
> {
> /* Page 1 */
> .range_min = 0x080,
> .range_max = 0x0fe,
> .selector_reg = ZL3073x_PAGE_SEL,
> .selector_mask = GENMASK(3, 0),
> .selector_shift = 0,
> .window_start = 0,
> .window_len = 0x7e,
> },
> ...
>
>
> Thank you,
> Ivan
Sorry,
.window_len = 0x7f /* Exclude selector reg */
I.
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 11:52 ` Ivan Vecera
@ 2025-04-14 13:55 ` Andy Shevchenko
2025-04-14 14:08 ` Ivan Vecera
2025-04-14 14:07 ` Ivan Vecera
1 sibling, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 13:55 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 2:52 PM Ivan Vecera <ivecera@redhat.com> wrote:
> On 14. 04. 25 1:39 odp., Ivan Vecera wrote:
> > On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
> >>> What is wrong here?
> >>>
> >>> I have a device that uses 7-bit addresses and have 16 register pages.
> >>> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
> >>> where bits 0-3 select the page.
> >> The problem is that you overlap virtual page over the real one (the
> >> main one).
> >>
> >> The drivers you mentioned in v2 discussions most likely are also buggy.
> >> As I implied in the above question the developers hardly get the
> >> regmap ranges
> >> right. It took me quite a while to see the issue, so it's not
> >> particularly your
> >> fault.
> > Hi Andy,
> >
> > thank you I see the point.
> >
> > Do you mean that the selector register should not be part of the range?
> >
> > If so, does it mean that I have to specify a range for each page? Like
> > this:
> >
> > {
> > /* Page 0 */
> > .range_min = 0x000,
> > .range_max = 0x07e,
> > .selector_reg = ZL3073x_PAGE_SEL,
> > .selector_mask = GENMASK(3, 0),
> > .selector_shift = 0,
> > .window_start = 0,
> > .window_len = 0x7e,
> > },
> > {
> > /* Page 1 */
> > .range_min = 0x080,
> > .range_max = 0x0fe,
> > .selector_reg = ZL3073x_PAGE_SEL,
> > .selector_mask = GENMASK(3, 0),
> > .selector_shift = 0,
> > .window_start = 0,
> > .window_len = 0x7e,
> > },
...
> Sorry,
> .window_len = 0x7f /* Exclude selector reg */
It actually will make things worse. If selector register is accessible
to all of the pages, it's better to include it in all pages.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 11:39 ` Ivan Vecera
2025-04-14 11:52 ` Ivan Vecera
@ 2025-04-14 13:58 ` Andy Shevchenko
1 sibling, 0 replies; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 13:58 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 2:40 PM Ivan Vecera <ivecera@redhat.com> wrote:
> On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
...
> >> What is wrong here?
> >>
> >> I have a device that uses 7-bit addresses and have 16 register pages.
> >> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
> >> where bits 0-3 select the page.
> > The problem is that you overlap virtual page over the real one (the main one).
> >
> > The drivers you mentioned in v2 discussions most likely are also buggy.
> > As I implied in the above question the developers hardly get the regmap ranges
> > right. It took me quite a while to see the issue, so it's not particularly your
> > fault.
> thank you I see the point.
>
> Do you mean that the selector register should not be part of the range?
No.
> If so, does it mean that I have to specify a range for each page? Like this:
No.
Yes, tough thingy :-)
> {
> /* Page 0 */
> .range_min = 0x000,
> .range_max = 0x07e,
> .selector_reg = ZL3073x_PAGE_SEL,
> .selector_mask = GENMASK(3, 0),
> .selector_shift = 0,
> .window_start = 0,
> .window_len = 0x7e,
> },
Page 0 shouldn't exist in your case in the *virtual* ranges. And this
struct defines the *virtual* ranges.
> {
> /* Page 1 */
> .range_min = 0x080,
> .range_max = 0x0fe,
> .selector_reg = ZL3073x_PAGE_SEL,
> .selector_mask = GENMASK(3, 0),
> .selector_shift = 0,
> .window_start = 0,
> .window_len = 0x7e,
> },
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 11:52 ` Ivan Vecera
2025-04-14 13:55 ` Andy Shevchenko
@ 2025-04-14 14:07 ` Ivan Vecera
2025-04-14 14:10 ` Andy Shevchenko
1 sibling, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 14:07 UTC (permalink / raw)
To: Andy Shevchenko
Cc: netdev, Michal Schmidt, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Prathosh Satish, Lee Jones, Kees Cook, Andrew Morton, devicetree,
linux-kernel, linux-hardening
On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
>
>
> On 14. 04. 25 1:39 odp., Ivan Vecera wrote:
>>
>>
>> On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
>>>> What is wrong here?
>>>>
>>>> I have a device that uses 7-bit addresses and have 16 register pages.
>>>> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
>>>> where bits 0-3 select the page.
>>> The problem is that you overlap virtual page over the real one (the
>>> main one).
>>>
>>> The drivers you mentioned in v2 discussions most likely are also buggy.
>>> As I implied in the above question the developers hardly get the
>>> regmap ranges
>>> right. It took me quite a while to see the issue, so it's not
>>> particularly your
>>> fault.
>> Hi Andy,
>>
>> thank you I see the point.
>>
>> Do you mean that the selector register should not be part of the range?
>>
>> If so, does it mean that I have to specify a range for each page? Like
>> this:
>>
>> {
>> /* Page 0 */
>> .range_min = 0x000,
>> .range_max = 0x07e,
>> .selector_reg = ZL3073x_PAGE_SEL,
>> .selector_mask = GENMASK(3, 0),
>> .selector_shift = 0,
>> .window_start = 0,
>> .window_len = 0x7e,
>> },
>> {
>> /* Page 1 */
>> .range_min = 0x080,
>> .range_max = 0x0fe,
>> .selector_reg = ZL3073x_PAGE_SEL,
>> .selector_mask = GENMASK(3, 0),
>> .selector_shift = 0,
>> .window_start = 0,
>> .window_len = 0x7e,
>> },
>> ...
No, I will answer by myself... this is non-sense.... window_len has to
be 0x80. But I probably know what do you mean...
regmap range should not overlap... so I should use something like:
{
/* original <0x000-0x77f> with offset of 0x100 to move
the range outside of <0x00-0x7f> used by real one */
.range_min = 0x100,
.range_max = 0x87f,
.selector_reg = 0x7f,
.selector_mask = GENMASK(3, 0),
.selector_shift = 0,
.window_start = 0,
.window_len = 0x80,
},
With this I have to modify the driver to use this 0x100 offset. I mean
the datasheet says that register BLAH is at 0x201-0x202. So in the
driver I have to use 0x301-0x302.
Then the _regmap_select_page maps this 0x301 this way:
window_offset = (0x301 - range_min) % window_len;
window_page = (0x301 - range_min) / window len;
thus
window_offset = (0x301 - 0x100) % 0x80 = 0x001
window_page = (0x301 - 0x100) / 0x80 = 4
Long story short, I have to move virtual range outside real address
range and apply this offset in the driver code.
Is this correct?
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 13:55 ` Andy Shevchenko
@ 2025-04-14 14:08 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 14:08 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On 14. 04. 25 3:55 odp., Andy Shevchenko wrote:
> On Mon, Apr 14, 2025 at 2:52 PM Ivan Vecera <ivecera@redhat.com> wrote:
>> On 14. 04. 25 1:39 odp., Ivan Vecera wrote:
>>> On 14. 04. 25 8:36 dop., Andy Shevchenko wrote:
>>>>> What is wrong here?
>>>>>
>>>>> I have a device that uses 7-bit addresses and have 16 register pages.
>>>>> Each pages is from 0x00-0x7f and register 0x7f is used as page selector
>>>>> where bits 0-3 select the page.
>>>> The problem is that you overlap virtual page over the real one (the
>>>> main one).
>>>>
>>>> The drivers you mentioned in v2 discussions most likely are also buggy.
>>>> As I implied in the above question the developers hardly get the
>>>> regmap ranges
>>>> right. It took me quite a while to see the issue, so it's not
>>>> particularly your
>>>> fault.
>>> Hi Andy,
>>>
>>> thank you I see the point.
>>>
>>> Do you mean that the selector register should not be part of the range?
>>>
>>> If so, does it mean that I have to specify a range for each page? Like
>>> this:
>>>
>>> {
>>> /* Page 0 */
>>> .range_min = 0x000,
>>> .range_max = 0x07e,
>>> .selector_reg = ZL3073x_PAGE_SEL,
>>> .selector_mask = GENMASK(3, 0),
>>> .selector_shift = 0,
>>> .window_start = 0,
>>> .window_len = 0x7e,
>>> },
>>> {
>>> /* Page 1 */
>>> .range_min = 0x080,
>>> .range_max = 0x0fe,
>>> .selector_reg = ZL3073x_PAGE_SEL,
>>> .selector_mask = GENMASK(3, 0),
>>> .selector_shift = 0,
>>> .window_start = 0,
>>> .window_len = 0x7e,
>>> },
>
> ...
>
>> Sorry,
>> .window_len = 0x7f /* Exclude selector reg */
>
> It actually will make things worse. If selector register is accessible
> to all of the pages, it's better to include it in all pages.
Yes :-) I know this is non-sense.
See my recent reply.
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 14:07 ` Ivan Vecera
@ 2025-04-14 14:10 ` Andy Shevchenko
2025-04-14 14:13 ` Andy Shevchenko
0 siblings, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 14:10 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 5:07 PM Ivan Vecera <ivecera@redhat.com> wrote:
> On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
...
> Long story short, I have to move virtual range outside real address
> range and apply this offset in the driver code.
>
> Is this correct?
Bingo!
And for the offsets, you form them as "page number * page offset +
offset inside the page".
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 14:10 ` Andy Shevchenko
@ 2025-04-14 14:13 ` Andy Shevchenko
2025-04-14 14:16 ` Andy Shevchenko
0 siblings, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 14:13 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 5:10 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>
> On Mon, Apr 14, 2025 at 5:07 PM Ivan Vecera <ivecera@redhat.com> wrote:
> > On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
>
> ...
>
> > Long story short, I have to move virtual range outside real address
> > range and apply this offset in the driver code.
> >
> > Is this correct?
>
> Bingo!
>
> And for the offsets, you form them as "page number * page offset +
> offset inside the page".
Note, for easier reference you may still map page 0 to the virtual
space, but make sure that page 0 (or main page) is available outside
of the ranges, or i.o.w. ranges do not overlap the main page, even if
they include page 0.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 14:13 ` Andy Shevchenko
@ 2025-04-14 14:16 ` Andy Shevchenko
2025-04-14 14:53 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 14:16 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 5:13 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Mon, Apr 14, 2025 at 5:10 PM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
> > On Mon, Apr 14, 2025 at 5:07 PM Ivan Vecera <ivecera@redhat.com> wrote:
> > > On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
...
> > > Long story short, I have to move virtual range outside real address
> > > range and apply this offset in the driver code.
> > >
> > > Is this correct?
> >
> > Bingo!
> >
> > And for the offsets, you form them as "page number * page offset +
> > offset inside the page".
>
> Note, for easier reference you may still map page 0 to the virtual
> space, but make sure that page 0 (or main page) is available outside
> of the ranges, or i.o.w. ranges do not overlap the main page, even if
> they include page 0.
So, you will have the following layout
0x00 - 0xnn - real registers of page 0.
0x100 - 0xppp -- pages 0 ... N
Register access either direct for when direct is required, or as
0x100 + PageSize * Index + RegOffset
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 14:16 ` Andy Shevchenko
@ 2025-04-14 14:53 ` Ivan Vecera
2025-04-14 17:09 ` Andy Shevchenko
0 siblings, 1 reply; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 14:53 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On 14. 04. 25 4:16 odp., Andy Shevchenko wrote:
> On Mon, Apr 14, 2025 at 5:13 PM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
>> On Mon, Apr 14, 2025 at 5:10 PM Andy Shevchenko
>> <andy.shevchenko@gmail.com> wrote:
>>> On Mon, Apr 14, 2025 at 5:07 PM Ivan Vecera <ivecera@redhat.com> wrote:
>>>> On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
>
> ...
>
>>>> Long story short, I have to move virtual range outside real address
>>>> range and apply this offset in the driver code.
>>>>
>>>> Is this correct?
>>>
>>> Bingo!
>>>
>>> And for the offsets, you form them as "page number * page offset +
>>> offset inside the page".
>>
>> Note, for easier reference you may still map page 0 to the virtual
>> space, but make sure that page 0 (or main page) is available outside
>> of the ranges, or i.o.w. ranges do not overlap the main page, even if
>> they include page 0.
>
> So, you will have the following layout
>
> 0x00 - 0xnn - real registers of page 0.
>
> 0x100 - 0xppp -- pages 0 ... N
>
> Register access either direct for when direct is required, or as
> 0x100 + PageSize * Index + RegOffset
Now, get it...
I was a little bit confused by code of _regmap_select_page() that takes
care of selector_reg.
Btw, why is this needed? why they cannot overlap?
Let's say I have virtual range <0, 0xfff>, window <0, 0xff> and window
selector 0xff>.
1. I'm calling regmap_read(regmap, 0x8f, ...)
2. The regmap looks for the range and it finds it (0..0xfff)
3. Then it calls _regmap_select_page() that computes:
window_offset = (0x8f - 0x000) % 0x100 = 0x8f
window_page = (0x8f - 0x000) / 0x100 = 0
4. _regmap_select_page() set window selector to 0 and reg is updated to
reg = window_start + window_offset = 0x8f
And for window_selector value: regmap_read(regmap, 0xff, ...) is the
same except _regmap_select_page() checks that the given address is
selector_reg and won't perform page switching.
When I think about it, in my case there is no normal page, there is only
volatile register window <0x00-0x7e> and only single direct register
that is page selector at 0x7f.
Thanks,
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 14:53 ` Ivan Vecera
@ 2025-04-14 17:09 ` Andy Shevchenko
2025-04-14 17:42 ` Ivan Vecera
0 siblings, 1 reply; 70+ messages in thread
From: Andy Shevchenko @ 2025-04-14 17:09 UTC (permalink / raw)
To: Ivan Vecera
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On Mon, Apr 14, 2025 at 5:53 PM Ivan Vecera <ivecera@redhat.com> wrote:
> On 14. 04. 25 4:16 odp., Andy Shevchenko wrote:
> > On Mon, Apr 14, 2025 at 5:13 PM Andy Shevchenko
> > <andy.shevchenko@gmail.com> wrote:
> >> On Mon, Apr 14, 2025 at 5:10 PM Andy Shevchenko
> >> <andy.shevchenko@gmail.com> wrote:
> >>> On Mon, Apr 14, 2025 at 5:07 PM Ivan Vecera <ivecera@redhat.com> wrote:
> >>>> On 14. 04. 25 1:52 odp., Ivan Vecera wrote:
> >
> > ...
> >
> >>>> Long story short, I have to move virtual range outside real address
> >>>> range and apply this offset in the driver code.
> >>>>
> >>>> Is this correct?
> >>>
> >>> Bingo!
> >>>
> >>> And for the offsets, you form them as "page number * page offset +
> >>> offset inside the page".
> >>
> >> Note, for easier reference you may still map page 0 to the virtual
> >> space, but make sure that page 0 (or main page) is available outside
> >> of the ranges, or i.o.w. ranges do not overlap the main page, even if
> >> they include page 0.
> >
> > So, you will have the following layout
> >
> > 0x00 - 0xnn - real registers of page 0.
> >
> > 0x100 - 0xppp -- pages 0 ... N
> >
> > Register access either direct for when direct is required, or as
> > 0x100 + PageSize * Index + RegOffset
>
> Now, get it...
>
> I was a little bit confused by code of _regmap_select_page() that takes
> care of selector_reg.
>
> Btw, why is this needed? why they cannot overlap?
>
> Let's say I have virtual range <0, 0xfff>, window <0, 0xff> and window
> selector 0xff>.
> 1. I'm calling regmap_read(regmap, 0x8f, ...)
> 2. The regmap looks for the range and it finds it (0..0xfff)
> 3. Then it calls _regmap_select_page() that computes:
> window_offset = (0x8f - 0x000) % 0x100 = 0x8f
> window_page = (0x8f - 0x000) / 0x100 = 0
> 4. _regmap_select_page() set window selector to 0 and reg is updated to
> reg = window_start + window_offset = 0x8f
>
> And for window_selector value: regmap_read(regmap, 0xff, ...) is the
> same except _regmap_select_page() checks that the given address is
> selector_reg and won't perform page switching.
>
> When I think about it, in my case there is no normal page, there is only
> volatile register window <0x00-0x7e> and only single direct register
> that is page selector at 0x7f.
Because you are effectively messing up with cache. Also it's not
recommended in general to do such due to some registers that might
need to be accessed directly. And also note, that each time you access
this you will call a selector write _each_ time you want to write the
register in the map (if there is no cache, or if cache is messed up).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 01/28] mfd: Add Microchip ZL3073x support
2025-04-14 17:09 ` Andy Shevchenko
@ 2025-04-14 17:42 ` Ivan Vecera
0 siblings, 0 replies; 70+ messages in thread
From: Ivan Vecera @ 2025-04-14 17:42 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Andy Shevchenko, netdev, Michal Schmidt, Vadim Fedorenko,
Arkadiusz Kubalewski, Jiri Pirko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Prathosh Satish, Lee Jones,
Kees Cook, Andrew Morton, devicetree, linux-kernel,
linux-hardening
On 14. 04. 25 7:09 odp., Andy Shevchenko wrote:
>> Now, get it...
>>
>> I was a little bit confused by code of _regmap_select_page() that takes
>> care of selector_reg.
>>
>> Btw, why is this needed? why they cannot overlap?
>>
>> Let's say I have virtual range <0, 0xfff>, window <0, 0xff> and window
>> selector 0xff>.
>> 1. I'm calling regmap_read(regmap, 0x8f, ...)
>> 2. The regmap looks for the range and it finds it (0..0xfff)
>> 3. Then it calls _regmap_select_page() that computes:
>> window_offset = (0x8f - 0x000) % 0x100 = 0x8f
>> window_page = (0x8f - 0x000) / 0x100 = 0
>> 4. _regmap_select_page() set window selector to 0 and reg is updated to
>> reg = window_start + window_offset = 0x8f
>>
>> And for window_selector value: regmap_read(regmap, 0xff, ...) is the
>> same except _regmap_select_page() checks that the given address is
>> selector_reg and won't perform page switching.
>>
>> When I think about it, in my case there is no normal page, there is only
>> volatile register window <0x00-0x7e> and only single direct register
>> that is page selector at 0x7f.
> Because you are effectively messing up with cache. Also it's not
> recommended in general to do such due to some registers that might
> need to be accessed directly. And also note, that each time you access
> this you will call a selector write_each_ time you want to write the
> register in the map (if there is no cache, or if cache is messed up).
Get it fully now...
Thank you, Andy, for the explanation and for the patience.
Will send v3 soon.
Ivan
^ permalink raw reply [flat|nested] 70+ messages in thread
end of thread, other threads:[~2025-04-14 17:42 UTC | newest]
Thread overview: 70+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-07 17:28 [PATCH 00/28] Add Microchip ZL3073x support Ivan Vecera
2025-04-07 17:28 ` [PATCH 01/28] mfd: " Ivan Vecera
2025-04-07 17:53 ` Krzysztof Kozlowski
2025-04-09 6:31 ` Ivan Vecera
2025-04-07 18:05 ` Andy Shevchenko
2025-04-09 6:40 ` Ivan Vecera
2025-04-14 6:36 ` Andy Shevchenko
2025-04-14 11:39 ` Ivan Vecera
2025-04-14 11:52 ` Ivan Vecera
2025-04-14 13:55 ` Andy Shevchenko
2025-04-14 14:08 ` Ivan Vecera
2025-04-14 14:07 ` Ivan Vecera
2025-04-14 14:10 ` Andy Shevchenko
2025-04-14 14:13 ` Andy Shevchenko
2025-04-14 14:16 ` Andy Shevchenko
2025-04-14 14:53 ` Ivan Vecera
2025-04-14 17:09 ` Andy Shevchenko
2025-04-14 17:42 ` Ivan Vecera
2025-04-14 13:58 ` Andy Shevchenko
2025-04-07 17:28 ` [PATCH 02/28] mfd: zl3073x: Register itself as devlink device Ivan Vecera
2025-04-07 20:57 ` Andrew Lunn
2025-04-09 6:41 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 03/28] mfd: zl3073x: Add register access helpers Ivan Vecera
2025-04-07 21:03 ` Andrew Lunn
2025-04-09 6:42 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 04/28] mfd: zl3073x: Add macros for device registers access Ivan Vecera
2025-04-07 17:28 ` [PATCH 05/28] mfd: zl3073x: Add components versions register defs Ivan Vecera
2025-04-07 21:09 ` Andrew Lunn
2025-04-09 6:44 ` Ivan Vecera
2025-04-10 7:11 ` Krzysztof Kozlowski
2025-04-10 10:23 ` Ivan Vecera
2025-04-10 10:42 ` Krzysztof Kozlowski
2025-04-10 12:01 ` Ivan Vecera
2025-04-07 17:28 ` [PATCH 06/28] mfd: zl3073x: Implement devlink device info Ivan Vecera
2025-04-07 17:28 ` [PATCH 07/28] mfd: zl3073x: Add macro to wait for register value bits to be cleared Ivan Vecera
2025-04-07 17:28 ` [PATCH 08/28] mfd: zl3073x: Add functions to work with register mailboxes Ivan Vecera
2025-04-07 17:28 ` [PATCH 09/28] mfd: zl3073x: Add clock_id field Ivan Vecera
2025-04-07 17:31 ` [PATCH 10/28] lib: Allow modules to use strnchrnul Ivan Vecera
2025-04-07 17:50 ` Kees Cook
2025-04-07 17:31 ` [PATCH 11/28] mfd: zl3073x: Load mfg file into HW if it is present Ivan Vecera
2025-04-07 17:31 ` [PATCH 12/28] mfd: zl3073x: Fetch invariants during probe Ivan Vecera
2025-04-07 17:31 ` [PATCH 13/28] dpll: Add Microchip ZL3073x DPLL driver Ivan Vecera
2025-04-07 17:31 ` [PATCH 14/28] mfd: zl3073x: Register DPLL sub-device during init Ivan Vecera
2025-04-07 17:31 ` [PATCH 15/28] dt-bindings: dpll: Add device tree bindings for DPLL device and pin Ivan Vecera
2025-04-07 18:01 ` Krzysztof Kozlowski
2025-04-07 18:10 ` Krzysztof Kozlowski
2025-04-08 5:19 ` Michal Schmidt
2025-04-09 7:09 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 16/28] dt-bindings: dpll: Add support for Microchip Azurite chip family Ivan Vecera
2025-04-07 18:04 ` Krzysztof Kozlowski
2025-04-09 7:19 ` Ivan Vecera
2025-04-10 7:01 ` Krzysztof Kozlowski
2025-04-10 10:28 ` Ivan Vecera
2025-04-10 12:19 ` Krzysztof Kozlowski
2025-04-10 12:38 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 17/28] dpll: zl3073x: Read DPLL types from firmware Ivan Vecera
2025-04-07 17:31 ` [PATCH 18/28] dpll: zl3073x: Read optional pin properties " Ivan Vecera
2025-04-07 18:06 ` Krzysztof Kozlowski
2025-04-09 7:22 ` Ivan Vecera
2025-04-07 17:31 ` [PATCH 19/28] dpll: zl3073x: Implement input pin selection in manual mode Ivan Vecera
2025-04-07 17:32 ` [PATCH 20/28] dpll: zl3073x: Add support to get/set priority on input pins Ivan Vecera
2025-04-07 17:32 ` [PATCH 21/28] dpll: zl3073x: Implement input pin state setting in automatic mode Ivan Vecera
2025-04-07 17:32 ` [PATCH 22/28] dpll: zl3073x: Add support to get/set frequency on input pins Ivan Vecera
2025-04-07 17:32 ` [PATCH 23/28] dpll: zl3073x: Add support to get/set frequency on output pins Ivan Vecera
2025-04-07 17:32 ` [PATCH 24/28] dpll: zl3073x: Read pin supported frequencies from firmware Ivan Vecera
2025-04-07 17:32 ` [PATCH 25/28] dpll: zl3073x: Add support to get phase offset on input pins Ivan Vecera
2025-04-07 17:32 ` [PATCH 26/28] dpll: zl3073x: Add support to get/set phase adjust on pins Ivan Vecera
2025-04-07 17:33 ` [PATCH 27/28] dpll: zl3073x: Add support to get/set esync " Ivan Vecera
2025-04-07 17:33 ` [PATCH 28/28] dpll: zl3073x: Add support to get fractional frequency offset on input pins Ivan Vecera
2025-04-07 18:06 ` [PATCH 00/28] Add Microchip ZL3073x support Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).