diff --git a/COPYING-5.14.0-611.11.1.el9 b/COPYING-5.14.0-611.13.1.el9 similarity index 100% rename from COPYING-5.14.0-611.11.1.el9 rename to COPYING-5.14.0-611.13.1.el9 diff --git a/Documentation/devicetree/bindings/dpll/dpll-device.yaml b/Documentation/devicetree/bindings/dpll/dpll-device.yaml new file mode 100644 index 0000000000..fb8d7a9a36 --- /dev/null +++ b/Documentation/devicetree/bindings/dpll/dpll-device.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dpll/dpll-device.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Digital Phase-Locked Loop (DPLL) Device + +maintainers: + - Ivan Vecera + +description: + Digital Phase-Locked Loop (DPLL) device is used for precise clock + synchronization in networking and telecom hardware. The device can + have one or more channels (DPLLs) and one or more physical input and + output pins. Each DPLL channel can either produce pulse-per-clock signal + or drive ethernet equipment clock. The type of each channel can be + indicated by dpll-types property. + +properties: + $nodename: + pattern: "^dpll(@.*)?$" + + "#address-cells": + const: 0 + + "#size-cells": + const: 0 + + dpll-types: + description: List of DPLL channel types, one per DPLL instance. + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + items: + enum: [pps, eec] + + input-pins: + type: object + description: DPLL input pins + unevaluatedProperties: false + + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 0 + + patternProperties: + "^pin@[0-9a-f]+$": + $ref: /schemas/dpll/dpll-pin.yaml + unevaluatedProperties: false + + required: + - "#address-cells" + - "#size-cells" + + output-pins: + type: object + description: DPLL output pins + unevaluatedProperties: false + + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 0 + + patternProperties: + "^pin@[0-9]+$": + $ref: /schemas/dpll/dpll-pin.yaml + unevaluatedProperties: false + + required: + - "#address-cells" + - "#size-cells" + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/dpll/dpll-pin.yaml b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml new file mode 100644 index 0000000000..51db93b773 --- /dev/null +++ b/Documentation/devicetree/bindings/dpll/dpll-pin.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dpll/dpll-pin.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: DPLL Pin + +maintainers: + - Ivan Vecera + +description: | + The DPLL pin is either a physical input or output pin that is provided + by a DPLL( Digital Phase-Locked Loop) device. The pin is identified by + its physical order number that is stored in reg property and can have + an additional set of properties like supported (allowed) frequencies, + label, type and may support embedded sync. + + Note that the pin in this context has nothing to do with pinctrl. + +properties: + reg: + description: Hardware index of the DPLL pin. + maxItems: 1 + + connection-type: + description: Connection type of the pin + $ref: /schemas/types.yaml#/definitions/string + enum: [ext, gnss, int, mux, synce] + + esync-control: + description: Indicates whether the pin supports embedded sync functionality. + type: boolean + + label: + description: String exposed as the pin board label + $ref: /schemas/types.yaml#/definitions/string + + supported-frequencies-hz: + description: List of supported frequencies for this pin, expressed in Hz. + +required: + - reg + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml new file mode 100644 index 0000000000..17747f754b --- /dev/null +++ b/Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dpll/microchip,zl30731.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip Azurite DPLL device + +maintainers: + - Ivan Vecera + +description: + Microchip Azurite DPLL (ZL3073x) is a family of DPLL devices that + provides up to 5 independent DPLL channels, up to 10 differential or + single-ended inputs and 10 differential or 20 single-ended outputs. + These devices support both I2C and SPI interfaces. + +properties: + compatible: + enum: + - microchip,zl30731 + - microchip,zl30732 + - microchip,zl30733 + - microchip,zl30734 + - microchip,zl30735 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +allOf: + - $ref: /schemas/dpll/dpll-device.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + dpll@70 { + compatible = "microchip,zl30732"; + reg = <0x70>; + dpll-types = "pps", "eec"; + + input-pins { + #address-cells = <1>; + #size-cells = <0>; + + pin@0 { /* REF0P */ + reg = <0>; + connection-type = "ext"; + label = "Input 0"; + supported-frequencies-hz = /bits/ 64 <1 1000>; + }; + }; + + output-pins { + #address-cells = <1>; + #size-cells = <0>; + + pin@3 { /* OUT1N */ + reg = <3>; + connection-type = "gnss"; + esync-control; + label = "Output 1"; + supported-frequencies-hz = /bits/ 64 <1 10000>; + }; + }; + }; + }; + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + dpll@70 { + compatible = "microchip,zl30731"; + reg = <0x70>; + spi-max-frequency = <12500000>; + + dpll-types = "pps"; + + input-pins { + #address-cells = <1>; + #size-cells = <0>; + + pin@0 { /* REF0P */ + reg = <0>; + connection-type = "ext"; + label = "Input 0"; + supported-frequencies-hz = /bits/ 64 <1 1000>; + }; + }; + + output-pins { + #address-cells = <1>; + #size-cells = <0>; + + pin@3 { /* OUT1N */ + reg = <3>; + connection-type = "gnss"; + esync-control; + label = "Output 1"; + supported-frequencies-hz = /bits/ 64 <1 10000>; + }; + }; + }; + }; +... diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index fc5b9c3f5f..fe8c811f98 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -96,3 +96,4 @@ parameters, info versions, and other features it supports. prestera iosm sfc + zl3073x diff --git a/Documentation/networking/devlink/zl3073x.rst b/Documentation/networking/devlink/zl3073x.rst new file mode 100644 index 0000000000..fc5a8dc272 --- /dev/null +++ b/Documentation/networking/devlink/zl3073x.rst @@ -0,0 +1,65 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================= +zl3073x devlink support +======================= + +This document describes the devlink features implemented by the ``zl3073x`` +device driver. + +Parameters +========== + +.. list-table:: Generic parameters implemented + :widths: 5 5 90 + + * - Name + - Mode + - Notes + * - ``clock_id`` + - driverinit + - Set the clock ID that is used by the driver for registering DPLL devices + and pins. + +Info versions +============= + +The ``zl3073x`` driver reports the following versions + +.. list-table:: devlink info versions implemented + :widths: 5 5 5 90 + + * - Name + - Type + - Example + - Description + * - ``asic.id`` + - fixed + - 1E94 + - Chip identification number + * - ``asic.rev`` + - fixed + - 300 + - Chip revision number + * - ``fw`` + - running + - 7006 + - Firmware version number + * - ``custom_cfg`` + - running + - 1.3.0.1 + - Device configuration version customized by OEM + +Flash Update +============ + +The ``zl3073x`` driver implements support for flash update using the +``devlink-flash`` interface. It supports updating the device flash using a +combined flash image ("bundle") that contains multiple components (firmware +parts and configurations). + +During the flash procedure, the standard firmware interface is not available, +so the driver unregisters all DPLLs and associated pins, and re-registers them +once the flash procedure is complete. + +The driver does not support any overwrite mask flags. diff --git a/MAINTAINERS b/MAINTAINERS index d64b9ae56e..a6c2077845 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5946,6 +5946,8 @@ M: Arkadiusz Kubalewski M: Jiri Pirko 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 @@ -12924,6 +12926,14 @@ L: linux-wireless@vger.kernel.org S: Supported F: drivers/net/wireless/microchip/wilc1000/ +MICROCHIP ZL3073X DRIVER +M: Ivan Vecera +M: Prathosh Satish +L: netdev@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml +F: drivers/dpll/zl3073x/ + MICROSEMI MIPS SOCS M: Alexandre Belloni M: UNGLinuxDriver@microchip.com diff --git a/Makefile.rhelver b/Makefile.rhelver index 16279dd4ea..bf8f719124 100644 --- a/Makefile.rhelver +++ b/Makefile.rhelver @@ -12,7 +12,7 @@ RHEL_MINOR = 7 # # Use this spot to avoid future merge conflicts. # Do not trim this comment. -RHEL_RELEASE = 611.11.1 +RHEL_RELEASE = 611.13.1 # # ZSTREAM diff --git a/arch/x86/hyperv/ivm.c b/arch/x86/hyperv/ivm.c index 10d6ac500a..42f79526af 100644 --- a/arch/x86/hyperv/ivm.c +++ b/arch/x86/hyperv/ivm.c @@ -462,6 +462,195 @@ void hv_ivm_msr_read(u64 msr, u64 *value) hv_ghcb_msr_read(msr, value); } +/* + * Keep track of the PFN regions which were shared with the host. The access + * must be revoked upon kexec/kdump (see hv_ivm_clear_host_access()). + */ +struct hv_enc_pfn_region { + struct list_head list; + u64 pfn; + int count; +}; + +static LIST_HEAD(hv_list_enc); +static DEFINE_RAW_SPINLOCK(hv_list_enc_lock); + +static int hv_list_enc_add(const u64 *pfn_list, int count) +{ + struct hv_enc_pfn_region *ent; + unsigned long flags; + u64 pfn; + int i; + + for (i = 0; i < count; i++) { + pfn = pfn_list[i]; + + raw_spin_lock_irqsave(&hv_list_enc_lock, flags); + /* Check if the PFN already exists in some region first */ + list_for_each_entry(ent, &hv_list_enc, list) { + if ((ent->pfn <= pfn) && (ent->pfn + ent->count - 1 >= pfn)) + /* Nothing to do - pfn is already in the list */ + goto unlock_done; + } + + /* + * Check if the PFN is adjacent to an existing region. Growing + * a region can make it adjacent to another one but merging is + * not (yet) implemented for simplicity. A PFN cannot be added + * to two regions to keep the logic in hv_list_enc_remove() + * correct. + */ + list_for_each_entry(ent, &hv_list_enc, list) { + if (ent->pfn + ent->count == pfn) { + /* Grow existing region up */ + ent->count++; + goto unlock_done; + } else if (pfn + 1 == ent->pfn) { + /* Grow existing region down */ + ent->pfn--; + ent->count++; + goto unlock_done; + } + } + raw_spin_unlock_irqrestore(&hv_list_enc_lock, flags); + + /* No adjacent region found -- create a new one */ + ent = kzalloc(sizeof(struct hv_enc_pfn_region), GFP_KERNEL); + if (!ent) + return -ENOMEM; + + ent->pfn = pfn; + ent->count = 1; + + raw_spin_lock_irqsave(&hv_list_enc_lock, flags); + list_add(&ent->list, &hv_list_enc); + +unlock_done: + raw_spin_unlock_irqrestore(&hv_list_enc_lock, flags); + } + + return 0; +} + +static int hv_list_enc_remove(const u64 *pfn_list, int count) +{ + struct hv_enc_pfn_region *ent, *t; + struct hv_enc_pfn_region new_region; + unsigned long flags; + u64 pfn; + int i; + + for (i = 0; i < count; i++) { + pfn = pfn_list[i]; + + raw_spin_lock_irqsave(&hv_list_enc_lock, flags); + list_for_each_entry_safe(ent, t, &hv_list_enc, list) { + if (pfn == ent->pfn + ent->count - 1) { + /* Removing tail pfn */ + ent->count--; + if (!ent->count) { + list_del(&ent->list); + kfree(ent); + } + goto unlock_done; + } else if (pfn == ent->pfn) { + /* Removing head pfn */ + ent->count--; + ent->pfn++; + if (!ent->count) { + list_del(&ent->list); + kfree(ent); + } + goto unlock_done; + } else if (pfn > ent->pfn && pfn < ent->pfn + ent->count - 1) { + /* + * Removing a pfn in the middle. Cut off the tail + * of the existing region and create a template for + * the new one. + */ + new_region.pfn = pfn + 1; + new_region.count = ent->count - (pfn - ent->pfn + 1); + ent->count = pfn - ent->pfn; + goto unlock_split; + } + + } +unlock_done: + raw_spin_unlock_irqrestore(&hv_list_enc_lock, flags); + continue; + +unlock_split: + raw_spin_unlock_irqrestore(&hv_list_enc_lock, flags); + + ent = kzalloc(sizeof(struct hv_enc_pfn_region), GFP_KERNEL); + if (!ent) + return -ENOMEM; + + ent->pfn = new_region.pfn; + ent->count = new_region.count; + + raw_spin_lock_irqsave(&hv_list_enc_lock, flags); + list_add(&ent->list, &hv_list_enc); + raw_spin_unlock_irqrestore(&hv_list_enc_lock, flags); + } + + return 0; +} + +/* Stop new private<->shared conversions */ +static void hv_vtom_kexec_begin(void) +{ + if (!IS_ENABLED(CONFIG_KEXEC_CORE)) + return; + + /* + * Crash kernel reaches here with interrupts disabled: can't wait for + * conversions to finish. + * + * If race happened, just report and proceed. + */ + if (!set_memory_enc_stop_conversion()) + pr_warn("Failed to stop shared<->private conversions\n"); +} + +static void hv_vtom_kexec_finish(void) +{ + struct hv_gpa_range_for_visibility *input; + struct hv_enc_pfn_region *ent; + unsigned long flags; + u64 hv_status; + int cur, i; + + local_irq_save(flags); + input = *this_cpu_ptr(hyperv_pcpu_input_arg); + + if (unlikely(!input)) + goto out; + + list_for_each_entry(ent, &hv_list_enc, list) { + for (i = 0, cur = 0; i < ent->count; i++) { + input->gpa_page_list[cur] = ent->pfn + i; + cur++; + + if (cur == HV_MAX_MODIFY_GPA_REP_COUNT || i == ent->count - 1) { + input->partition_id = HV_PARTITION_ID_SELF; + input->host_visibility = VMBUS_PAGE_NOT_VISIBLE; + input->reserved0 = 0; + input->reserved1 = 0; + hv_status = hv_do_rep_hypercall( + HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY, + cur, 0, input, NULL); + WARN_ON_ONCE(!hv_result_success(hv_status)); + cur = 0; + } + } + + } + +out: + local_irq_restore(flags); +} + /* * hv_mark_gpa_visibility - Set pages visible to host via hvcall. * @@ -475,6 +664,7 @@ static int hv_mark_gpa_visibility(u16 count, const u64 pfn[], struct hv_gpa_range_for_visibility *input; u64 hv_status; unsigned long flags; + int ret; /* no-op if partition isolation is not enabled */ if (!hv_is_isolation_supported()) @@ -486,6 +676,13 @@ static int hv_mark_gpa_visibility(u16 count, const u64 pfn[], return -EINVAL; } + if (visibility == VMBUS_PAGE_NOT_VISIBLE) + ret = hv_list_enc_remove(pfn, count); + else + ret = hv_list_enc_add(pfn, count); + if (ret) + return ret; + local_irq_save(flags); input = *this_cpu_ptr(hyperv_pcpu_input_arg); @@ -506,8 +703,18 @@ static int hv_mark_gpa_visibility(u16 count, const u64 pfn[], if (hv_result_success(hv_status)) return 0; + + if (visibility == VMBUS_PAGE_NOT_VISIBLE) + ret = hv_list_enc_add(pfn, count); else - return -EFAULT; + ret = hv_list_enc_remove(pfn, count); + /* + * There's no good way to recover from -ENOMEM here, the accounting is + * wrong either way. + */ + WARN_ON_ONCE(ret); + + return -EFAULT; } /* @@ -669,6 +876,8 @@ void __init hv_vtom_init(void) x86_platform.guest.enc_tlb_flush_required = hv_vtom_tlb_flush_required; x86_platform.guest.enc_status_change_prepare = hv_vtom_clear_present; x86_platform.guest.enc_status_change_finish = hv_vtom_set_host_visibility; + x86_platform.guest.enc_kexec_begin = hv_vtom_kexec_begin; + x86_platform.guest.enc_kexec_finish = hv_vtom_kexec_finish; /* Set WB as the default cache mode. */ mtrr_overwrite_state(NULL, 0, MTRR_TYPE_WRBACK); diff --git a/configs/kernel-5.14.0-aarch64-64k-debug.config b/configs/kernel-5.14.0-aarch64-64k-debug.config index 291c869a86..52f5d69d51 100644 --- a/configs/kernel-5.14.0-aarch64-64k-debug.config +++ b/configs/kernel-5.14.0-aarch64-64k-debug.config @@ -4093,6 +4093,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7269,7 +7278,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m CONFIG_HTE_TEGRA194_TEST=m -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-64k.config b/configs/kernel-5.14.0-aarch64-64k.config index d87d05ca87..e63d622200 100644 --- a/configs/kernel-5.14.0-aarch64-64k.config +++ b/configs/kernel-5.14.0-aarch64-64k.config @@ -4076,6 +4076,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7246,7 +7255,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m # CONFIG_HTE_TEGRA194_TEST is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-debug.config b/configs/kernel-5.14.0-aarch64-debug.config index 70f44f22a5..17a585b9ee 100644 --- a/configs/kernel-5.14.0-aarch64-debug.config +++ b/configs/kernel-5.14.0-aarch64-debug.config @@ -4097,6 +4097,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7274,7 +7283,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m CONFIG_HTE_TEGRA194_TEST=m -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-rt-64k-debug.config b/configs/kernel-5.14.0-aarch64-rt-64k-debug.config index 1c4238580a..5183e819c8 100644 --- a/configs/kernel-5.14.0-aarch64-rt-64k-debug.config +++ b/configs/kernel-5.14.0-aarch64-rt-64k-debug.config @@ -4096,6 +4096,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7273,7 +7282,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m CONFIG_HTE_TEGRA194_TEST=m -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-rt-64k.config b/configs/kernel-5.14.0-aarch64-rt-64k.config index 4ecb2824e6..79b9989822 100644 --- a/configs/kernel-5.14.0-aarch64-rt-64k.config +++ b/configs/kernel-5.14.0-aarch64-rt-64k.config @@ -4080,6 +4080,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7251,7 +7260,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m # CONFIG_HTE_TEGRA194_TEST is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-rt-debug.config b/configs/kernel-5.14.0-aarch64-rt-debug.config index 0ffad34c6f..a1a5d35e3f 100644 --- a/configs/kernel-5.14.0-aarch64-rt-debug.config +++ b/configs/kernel-5.14.0-aarch64-rt-debug.config @@ -4098,6 +4098,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7276,7 +7285,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m CONFIG_HTE_TEGRA194_TEST=m -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64-rt.config b/configs/kernel-5.14.0-aarch64-rt.config index a7a966ccf3..c28d51a200 100644 --- a/configs/kernel-5.14.0-aarch64-rt.config +++ b/configs/kernel-5.14.0-aarch64-rt.config @@ -4082,6 +4082,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7254,7 +7263,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m # CONFIG_HTE_TEGRA194_TEST is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-aarch64.config b/configs/kernel-5.14.0-aarch64.config index d06ab94814..f7344d4b06 100644 --- a/configs/kernel-5.14.0-aarch64.config +++ b/configs/kernel-5.14.0-aarch64.config @@ -4080,6 +4080,15 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_GENERIC_PINCTRL_GROUPS=y CONFIG_PINMUX=y @@ -7251,7 +7260,6 @@ CONFIG_INTERCONNECT_IMX8MP=m CONFIG_HTE=y CONFIG_HTE_TEGRA194=m # CONFIG_HTE_TEGRA194_TEST is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-ppc64le-debug.config b/configs/kernel-5.14.0-ppc64le-debug.config index c2aa190e32..e005dfc246 100644 --- a/configs/kernel-5.14.0-ppc64le-debug.config +++ b/configs/kernel-5.14.0-ppc64le-debug.config @@ -3325,6 +3325,14 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +# end of DPLL device support + # CONFIG_PINCTRL is not set CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 @@ -5679,7 +5687,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-ppc64le-kvm-debug.config b/configs/kernel-5.14.0-ppc64le-kvm-debug.config index 368258fd13..8656475046 100644 --- a/configs/kernel-5.14.0-ppc64le-kvm-debug.config +++ b/configs/kernel-5.14.0-ppc64le-kvm-debug.config @@ -3342,6 +3342,14 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +# end of DPLL device support + # CONFIG_PINCTRL is not set CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 @@ -5696,7 +5704,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-ppc64le-kvm.config b/configs/kernel-5.14.0-ppc64le-kvm.config index aea07bda17..7ef02fbc0b 100644 --- a/configs/kernel-5.14.0-ppc64le-kvm.config +++ b/configs/kernel-5.14.0-ppc64le-kvm.config @@ -3345,6 +3345,14 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +# end of DPLL device support + # CONFIG_PINCTRL is not set CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 @@ -5693,7 +5701,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-ppc64le.config b/configs/kernel-5.14.0-ppc64le.config index 5b5816188c..5380b7d70c 100644 --- a/configs/kernel-5.14.0-ppc64le.config +++ b/configs/kernel-5.14.0-ppc64le.config @@ -3328,6 +3328,14 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +# end of DPLL device support + # CONFIG_PINCTRL is not set CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 @@ -5676,7 +5684,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-s390x-debug.config b/configs/kernel-5.14.0-s390x-debug.config index 2e94ca8588..b19dc05c98 100644 --- a/configs/kernel-5.14.0-s390x-debug.config +++ b/configs/kernel-5.14.0-s390x-debug.config @@ -2400,6 +2400,13 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +# CONFIG_ZL3073X_I2C is not set +# end of DPLL device support + # CONFIG_PINCTRL is not set # CONFIG_GPIOLIB is not set # CONFIG_W1 is not set @@ -2979,7 +2986,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-s390x-zfcpdump.config b/configs/kernel-5.14.0-s390x-zfcpdump.config index 1c7678c4d8..05f2089c54 100644 --- a/configs/kernel-5.14.0-s390x-zfcpdump.config +++ b/configs/kernel-5.14.0-s390x-zfcpdump.config @@ -1002,6 +1002,11 @@ CONFIG_PTP_1588_CLOCK_OPTIONAL=y # CONFIG_PTP_1588_CLOCK_MOCK is not set # end of PTP clock support +# +# DPLL device support +# +# end of DPLL device support + # CONFIG_PINCTRL is not set # CONFIG_GPIOLIB is not set CONFIG_POWER_RESET=y diff --git a/configs/kernel-5.14.0-s390x.config b/configs/kernel-5.14.0-s390x.config index e34b7bd55d..608578d59c 100644 --- a/configs/kernel-5.14.0-s390x.config +++ b/configs/kernel-5.14.0-s390x.config @@ -2423,6 +2423,13 @@ CONFIG_PTP_1588_CLOCK_MOCK=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +# CONFIG_ZL3073X_I2C is not set +# end of DPLL device support + # CONFIG_PINCTRL is not set # CONFIG_GPIOLIB is not set # CONFIG_W1 is not set @@ -3002,7 +3009,6 @@ CONFIG_NVMEM_SYSFS=y # CONFIG_COUNTER is not set # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-x86_64-debug.config b/configs/kernel-5.14.0-x86_64-debug.config index 24451e2383..8b8b467c4e 100644 --- a/configs/kernel-5.14.0-x86_64-debug.config +++ b/configs/kernel-5.14.0-x86_64-debug.config @@ -3934,7 +3934,7 @@ CONFIG_I2C_MUX=m # CONFIG_I2C_MUX_GPIO is not set # CONFIG_I2C_MUX_LTC4306 is not set # CONFIG_I2C_MUX_PCA9541 is not set -# CONFIG_I2C_MUX_PCA954x is not set +CONFIG_I2C_MUX_PCA954x=m # CONFIG_I2C_MUX_REG is not set CONFIG_I2C_MUX_MLXCPLD=m # end of Multiplexer I2C Chip support @@ -4092,6 +4092,15 @@ CONFIG_PTP_1588_CLOCK_VMW=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_PINMUX=y CONFIG_PINCONF=y @@ -7970,7 +7979,6 @@ CONFIG_COUNTER=m CONFIG_INTEL_QEP=m # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-x86_64-rt-debug.config b/configs/kernel-5.14.0-x86_64-rt-debug.config index c64f9ab66e..f46fb50770 100644 --- a/configs/kernel-5.14.0-x86_64-rt-debug.config +++ b/configs/kernel-5.14.0-x86_64-rt-debug.config @@ -3940,7 +3940,7 @@ CONFIG_I2C_MUX=m # CONFIG_I2C_MUX_GPIO is not set # CONFIG_I2C_MUX_LTC4306 is not set # CONFIG_I2C_MUX_PCA9541 is not set -# CONFIG_I2C_MUX_PCA954x is not set +CONFIG_I2C_MUX_PCA954x=m # CONFIG_I2C_MUX_REG is not set CONFIG_I2C_MUX_MLXCPLD=m # end of Multiplexer I2C Chip support @@ -4098,6 +4098,15 @@ CONFIG_PTP_1588_CLOCK_VMW=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_PINMUX=y CONFIG_PINCONF=y @@ -8022,7 +8031,6 @@ CONFIG_COUNTER=m CONFIG_INTEL_QEP=m # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-x86_64-rt.config b/configs/kernel-5.14.0-x86_64-rt.config index 99170c3c9d..30cd0492e4 100644 --- a/configs/kernel-5.14.0-x86_64-rt.config +++ b/configs/kernel-5.14.0-x86_64-rt.config @@ -3924,7 +3924,7 @@ CONFIG_I2C_MUX=m # CONFIG_I2C_MUX_GPIO is not set # CONFIG_I2C_MUX_LTC4306 is not set # CONFIG_I2C_MUX_PCA9541 is not set -# CONFIG_I2C_MUX_PCA954x is not set +CONFIG_I2C_MUX_PCA954x=m # CONFIG_I2C_MUX_REG is not set CONFIG_I2C_MUX_MLXCPLD=m # end of Multiplexer I2C Chip support @@ -4082,6 +4082,15 @@ CONFIG_PTP_1588_CLOCK_VMW=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_PINMUX=y CONFIG_PINCONF=y @@ -7995,7 +8004,6 @@ CONFIG_COUNTER=m CONFIG_INTEL_QEP=m # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/configs/kernel-5.14.0-x86_64.config b/configs/kernel-5.14.0-x86_64.config index 14540bf494..293259c3d8 100644 --- a/configs/kernel-5.14.0-x86_64.config +++ b/configs/kernel-5.14.0-x86_64.config @@ -3917,7 +3917,7 @@ CONFIG_I2C_MUX=m # CONFIG_I2C_MUX_GPIO is not set # CONFIG_I2C_MUX_LTC4306 is not set # CONFIG_I2C_MUX_PCA9541 is not set -# CONFIG_I2C_MUX_PCA954x is not set +CONFIG_I2C_MUX_PCA954x=m # CONFIG_I2C_MUX_REG is not set CONFIG_I2C_MUX_MLXCPLD=m # end of Multiplexer I2C Chip support @@ -4075,6 +4075,15 @@ CONFIG_PTP_1588_CLOCK_VMW=m # CONFIG_PTP_1588_CLOCK_OCP is not set # end of PTP clock support +# +# DPLL device support +# +CONFIG_DPLL=y +CONFIG_ZL3073X=m +CONFIG_ZL3073X_I2C=m +CONFIG_ZL3073X_SPI=m +# end of DPLL device support + CONFIG_PINCTRL=y CONFIG_PINMUX=y CONFIG_PINCONF=y @@ -7943,7 +7952,6 @@ CONFIG_COUNTER=m CONFIG_INTEL_QEP=m # CONFIG_MOST is not set # CONFIG_HTE is not set -CONFIG_DPLL=y # end of Device Drivers # diff --git a/drivers/Kconfig b/drivers/Kconfig index 44a09f1a67..f1880ba50e 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -75,6 +75,8 @@ source "drivers/pps/Kconfig" source "drivers/ptp/Kconfig" +source "drivers/dpll/Kconfig" + source "drivers/pinctrl/Kconfig" source "drivers/gpio/Kconfig" @@ -245,6 +247,4 @@ source "drivers/most/Kconfig" source "drivers/hte/Kconfig" -source "drivers/dpll/Kconfig" - endmenu diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig index 20607ed542..ade872c915 100644 --- a/drivers/dpll/Kconfig +++ b/drivers/dpll/Kconfig @@ -3,5 +3,11 @@ # Generic DPLL drivers configuration # +menu "DPLL device support" + config DPLL bool + +source "drivers/dpll/zl3073x/Kconfig" + +endmenu diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile index 2e5b278501..9e7a3a3e59 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_ZL3073X) += zl3073x/ diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig new file mode 100644 index 0000000000..5bbca14005 --- /dev/null +++ b/drivers/dpll/zl3073x/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config ZL3073X + tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST + depends on NET + select DPLL + select NET_DEVLINK + select REGMAP + help + This driver supports Microchip Azurite family DPLL/PTP/SyncE + devices that support up to 5 independent DPLL channels, + 10 input pins and up to 20 output pins. + + To compile this driver as a module, choose M here. The module + will be called zl3073x. + +config ZL3073X_I2C + tristate "I2C bus implementation for Microchip Azurite devices" + depends on I2C && NET + select REGMAP_I2C + select ZL3073X + help + This is I2C bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_i2c. + +config ZL3073X_SPI + tristate "SPI bus implementation for Microchip Azurite devices" + depends on NET && SPI + select REGMAP_SPI + select ZL3073X + help + This is SPI bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_spi. diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile new file mode 100644 index 0000000000..84e22aae57 --- /dev/null +++ b/drivers/dpll/zl3073x/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_ZL3073X) += zl3073x.o +zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o prop.o + +obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o +zl3073x_i2c-objs := i2c.o + +obj-$(CONFIG_ZL3073X_SPI) += zl3073x_spi.o +zl3073x_spi-objs := spi.o diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c new file mode 100644 index 0000000000..e4bd6119ae --- /dev/null +++ b/drivers/dpll/zl3073x/core.c @@ -0,0 +1,1259 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "dpll.h" +#include "regs.h" + +/* Chip IDs for zl30731 */ +static const u16 zl30731_ids[] = { + 0x0E93, + 0x1E93, + 0x2E93, +}; + +const struct zl3073x_chip_info zl30731_chip_info = { + .ids = zl30731_ids, + .num_ids = ARRAY_SIZE(zl30731_ids), + .num_channels = 1, +}; +EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, ZL3073X); + +/* Chip IDs for zl30732 */ +static const u16 zl30732_ids[] = { + 0x0E30, + 0x0E94, + 0x1E94, + 0x1F60, + 0x2E94, + 0x3FC4, +}; + +const struct zl3073x_chip_info zl30732_chip_info = { + .ids = zl30732_ids, + .num_ids = ARRAY_SIZE(zl30732_ids), + .num_channels = 2, +}; +EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, ZL3073X); + +/* Chip IDs for zl30733 */ +static const u16 zl30733_ids[] = { + 0x0E95, + 0x1E95, + 0x2E95, +}; + +const struct zl3073x_chip_info zl30733_chip_info = { + .ids = zl30733_ids, + .num_ids = ARRAY_SIZE(zl30733_ids), + .num_channels = 3, +}; +EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, ZL3073X); + +/* Chip IDs for zl30734 */ +static const u16 zl30734_ids[] = { + 0x0E96, + 0x1E96, + 0x2E96, +}; + +const struct zl3073x_chip_info zl30734_chip_info = { + .ids = zl30734_ids, + .num_ids = ARRAY_SIZE(zl30734_ids), + .num_channels = 4, +}; +EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, ZL3073X); + +/* Chip IDs for zl30735 */ +static const u16 zl30735_ids[] = { + 0x0E97, + 0x1E97, + 0x2E97, +}; + +const struct zl3073x_chip_info zl30735_chip_info = { + .ids = zl30735_ids, + .num_ids = ARRAY_SIZE(zl30735_ids), + .num_channels = 5, +}; +EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, ZL3073X); + +#define ZL_RANGE_OFFSET 0x80 +#define ZL_PAGE_SIZE 0x80 +#define ZL_NUM_PAGES 256 +#define ZL_PAGE_SEL 0x7F +#define ZL_PAGE_SEL_MASK GENMASK(7, 0) +#define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE) + +/* Regmap range configuration */ +static const struct regmap_range_cfg zl3073x_regmap_range = { + .range_min = ZL_RANGE_OFFSET, + .range_max = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1, + .selector_reg = ZL_PAGE_SEL, + .selector_mask = ZL_PAGE_SEL_MASK, + .selector_shift = 0, + .window_start = 0, + .window_len = ZL_PAGE_SIZE, +}; + +static bool +zl3073x_is_volatile_reg(struct device *dev __maybe_unused, unsigned int reg) +{ + /* Only page selector is non-volatile */ + return reg != ZL_PAGE_SEL; +} + +const struct regmap_config zl3073x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1, + .ranges = &zl3073x_regmap_range, + .num_ranges = 1, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = zl3073x_is_volatile_reg, +}; +EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, ZL3073X); + +/** + * zl3073x_ref_freq_factorize - factorize given frequency + * @freq: input frequency + * @base: base frequency + * @mult: multiplier + * + * Checks if the given frequency can be factorized using one of the + * supported base frequencies. If so the base frequency and multiplier + * are stored into appropriate parameters if they are not NULL. + * + * Return: 0 on success, -EINVAL if the frequency cannot be factorized + */ +int +zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) +{ + static const u16 base_freqs[] = { + 1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125, + 128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000, + 1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250, + 6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250, + 32000, 40000, 50000, 62500, + }; + u32 div; + int i; + + for (i = 0; i < ARRAY_SIZE(base_freqs); i++) { + div = freq / base_freqs[i]; + + if (div <= U16_MAX && (freq % base_freqs[i]) == 0) { + if (base) + *base = base_freqs[i]; + if (mult) + *mult = div; + + return 0; + } + } + + return -EINVAL; +} + +static bool +zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size) +{ + /* Check that multiop lock is held when accessing registers + * from page 10 and above except the page 255 that does not + * need this protection. + */ + if (ZL_REG_PAGE(reg) >= 10 && ZL_REG_PAGE(reg) < 255) + lockdep_assert_held(&zldev->multiop_lock); + + /* Check the index is in valid range for indexed register */ + if (ZL_REG_OFFSET(reg) > ZL_REG_MAX_OFFSET(reg)) { + dev_err(zldev->dev, "Index out of range for reg 0x%04lx\n", + ZL_REG_ADDR(reg)); + return false; + } + /* Check the requested size corresponds to register size */ + if (ZL_REG_SIZE(reg) != size) { + dev_err(zldev->dev, "Invalid size %zu for reg 0x%04lx\n", + size, ZL_REG_ADDR(reg)); + return false; + } + + return true; +} + +static int +zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val, + size_t size) +{ + int rc; + + if (!zl3073x_check_reg(zldev, reg, size)) + return -EINVAL; + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + rc = regmap_bulk_read(zldev->regmap, reg, val, size); + if (rc) { + dev_err(zldev->dev, "Failed to read reg 0x%04x: %pe\n", reg, + ERR_PTR(rc)); + return rc; + } + + return 0; +} + +static int +zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val, + size_t size) +{ + int rc; + + if (!zl3073x_check_reg(zldev, reg, size)) + return -EINVAL; + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + rc = regmap_bulk_write(zldev->regmap, reg, val, size); + if (rc) { + dev_err(zldev->dev, "Failed to write reg 0x%04x: %pe\n", reg, + ERR_PTR(rc)); + return rc; + } + + return 0; +} + +/** + * zl3073x_read_u8 - read value from 8bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 8bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val) +{ + return zl3073x_read_reg(zldev, reg, val, sizeof(*val)); +} + +/** + * zl3073x_write_u8 - write value to 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 8bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val) +{ + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u16 - read value from 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 16bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val) +{ + int rc; + + rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val)); + if (!rc) + be16_to_cpus(val); + + return rc; +} + +/** + * zl3073x_write_u16 - write value to 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 16bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val) +{ + cpu_to_be16s(&val); + + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u32 - read value from 32bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 32bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val) +{ + int rc; + + rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val)); + if (!rc) + be32_to_cpus(val); + + return rc; +} + +/** + * zl3073x_write_u32 - write value to 32bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 32bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val) +{ + cpu_to_be32s(&val); + + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u48 - read value from 48bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 48bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val) +{ + u8 buf[6]; + int rc; + + rc = zl3073x_read_reg(zldev, reg, buf, sizeof(buf)); + if (!rc) + *val = get_unaligned_be48(buf); + + return rc; +} + +/** + * zl3073x_write_u48 - write value to 48bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 48bit register. + * The value must be from the interval -S48_MIN to U48_MAX. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) +{ + u8 buf[6]; + + /* Check the value belongs to + * Any value >= S48_MIN has bits 47..63 set. + */ + if (val > GENMASK_ULL(47, 0) && val < GENMASK_ULL(63, 47)) { + dev_err(zldev->dev, "Value 0x%0llx out of range\n", val); + return -EINVAL; + } + + put_unaligned_be48(val, buf); + + return zl3073x_write_reg(zldev, reg, buf, sizeof(buf)); +} + +/** + * zl3073x_poll_zero_u8 - wait for register to be cleared by device + * @zldev: zl3073x device pointer + * @reg: register to poll (has to be 8bit register) + * @mask: bit mask for polling + * + * Waits for bits specified by @mask in register @reg value to be cleared + * by the device. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) +{ + /* Register polling sleep & timeout */ +#define ZL_POLL_SLEEP_US 10 +#define ZL_POLL_TIMEOUT_US 2000000 + unsigned int val; + + /* Check the register is 8bit */ + if (ZL_REG_SIZE(reg) != 1) { + dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n", + ZL_REG_ADDR(reg)); + return -EINVAL; + } + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), + ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US); +} + +int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, + unsigned int mask_reg, u16 mask_val) +{ + int rc; + + /* Set mask for the operation */ + rc = zl3073x_write_u16(zldev, mask_reg, mask_val); + if (rc) + return rc; + + /* Trigger the operation */ + rc = zl3073x_write_u8(zldev, op_reg, op_val); + if (rc) + return rc; + + /* Wait for the operation to actually finish */ + return zl3073x_poll_zero_u8(zldev, op_reg, op_val); +} + +/** + * zl3073x_do_hwreg_op - Perform HW register read/write operation + * @zldev: zl3073x device pointer + * @op: operation to perform + * + * Performs requested operation and waits for its completion. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) +{ + int rc; + + /* Set requested operation and set pending bit */ + rc = zl3073x_write_u8(zldev, ZL_REG_HWREG_OP, op | ZL_HWREG_OP_PENDING); + if (rc) + return rc; + + /* Poll for completion - pending bit cleared */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, + ZL_HWREG_OP_PENDING); +} + +/** + * zl3073x_read_hwreg - Read HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value of the HW register + * + * Reads HW register value and stores it into @value. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value) +{ + int rc; + + /* Set address to read data from */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Perform the read operation */ + rc = zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_READ); + if (rc) + return rc; + + /* Read the received data */ + return zl3073x_read_u32(zldev, ZL_REG_HWREG_READ_DATA, value); +} + +/** + * zl3073x_write_hwreg - Write value to HW register + * @zldev: zl3073x device pointer + * @addr: HW registers address + * @value: Value to be written to HW register + * + * Stores the requested value into HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value) +{ + int rc; + + /* Set address to write data to */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Set data to be written */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_WRITE_DATA, value); + if (rc) + return rc; + + /* Perform the write operation */ + return zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_WRITE); +} + +/** + * zl3073x_update_hwreg - Update certain bits in HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value to be written into HW register + * @mask: Bitmask indicating bits to be updated + * + * Reads given HW register, updates requested bits specified by value and + * mask and writes result back to HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask) +{ + u32 tmp; + int rc; + + rc = zl3073x_read_hwreg(zldev, addr, &tmp); + if (rc) + return rc; + + tmp &= ~mask; + tmp |= value & mask; + + return zl3073x_write_hwreg(zldev, addr, tmp); +} + +/** + * zl3073x_write_hwreg_seq - Write HW registers sequence + * @zldev: pointer to device structure + * @seq: pointer to first sequence item + * @num_items: number of items in sequence + * + * Writes given HW registers sequence. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items) +{ + int i, rc = 0; + + for (i = 0; i < num_items; i++) { + dev_dbg(zldev->dev, "Write 0x%0x [0x%0x] to 0x%0x", + seq[i].value, seq[i].mask, seq[i].addr); + + if (seq[i].mask == U32_MAX) + /* Write value directly */ + rc = zl3073x_write_hwreg(zldev, seq[i].addr, + seq[i].value); + else + /* Update only bits specified by the mask */ + rc = zl3073x_update_hwreg(zldev, seq[i].addr, + seq[i].value, seq[i].mask); + if (rc) + return rc; + + if (seq->wait) + msleep(seq->wait); + } + + return rc; +} + +/** + * zl3073x_ref_state_fetch - get input reference state + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index to fetch state for + * + * Function fetches information for the given input reference that are + * invariant and stores them for later use. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_ref *input = &zldev->ref[index]; + u8 ref_config; + int rc; + + /* If the input is differential then the configuration for N-pin + * reference is ignored and P-pin config is used for both. + */ + if (zl3073x_is_n_pin(index) && + zl3073x_ref_is_diff(zldev, index - 1)) { + input->enabled = zl3073x_ref_is_enabled(zldev, index - 1); + input->diff = true; + + return 0; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read ref_config register */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref_config); + if (rc) + return rc; + + input->enabled = FIELD_GET(ZL_REF_CONFIG_ENABLE, ref_config); + input->diff = FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref_config); + + dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, + str_enabled_disabled(input->enabled), + input->diff ? "differential" : "single-ended"); + + return rc; +} + +/** + * zl3073x_out_state_fetch - get output state + * @zldev: pointer to zl3073x_dev structure + * @index: output index to fetch state for + * + * Function fetches information for the given output (not output pin) + * that are invariant and stores them for later use. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_out *out = &zldev->out[index]; + u8 output_ctrl, output_mode; + int rc; + + /* Read output configuration */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &output_ctrl); + if (rc) + return rc; + + /* Store info about output enablement and synthesizer the output + * is connected to. + */ + out->enabled = FIELD_GET(ZL_OUTPUT_CTRL_EN, output_ctrl); + out->synth = FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, output_ctrl); + + dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index, + str_enabled_disabled(out->enabled), out->synth); + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read output_mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + if (rc) + return rc; + + /* Extract and store output signal format */ + out->signal_format = FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, + output_mode); + + dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, + out->signal_format); + + return rc; +} + +/** + * zl3073x_synth_state_fetch - get synth state + * @zldev: pointer to zl3073x_dev structure + * @index: synth index to fetch state for + * + * Function fetches information for the given synthesizer that are + * invariant and stores them for later use. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_synth *synth = &zldev->synth[index]; + u16 base, m, n; + u8 synth_ctrl; + u32 mult; + int rc; + + /* Read synth control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth_ctrl); + if (rc) + return rc; + + /* Store info about synth enablement and DPLL channel the synth is + * driven by. + */ + synth->enabled = FIELD_GET(ZL_SYNTH_CTRL_EN, synth_ctrl); + synth->dpll = FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth_ctrl); + + dev_dbg(zldev->dev, "SYNTH%u is %s and driven by DPLL%u\n", index, + str_enabled_disabled(synth->enabled), synth->dpll); + + guard(mutex)(&zldev->multiop_lock); + + /* Read synth configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD, + ZL_REG_SYNTH_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* The output frequency is determined by the following formula: + * base * multiplier * numerator / denominator + * + * Read registers with these values + */ + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &base); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &mult); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &m); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &n); + if (rc) + return rc; + + /* Check denominator for zero to avoid div by 0 */ + if (!n) { + dev_err(zldev->dev, + "Zero divisor for SYNTH%u retrieved from device\n", + index); + return -EINVAL; + } + + /* Compute and store synth frequency */ + zldev->synth[index].freq = div_u64(mul_u32_u32(base * m, mult), n); + + dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index, + zldev->synth[index].freq); + + return rc; +} + +static int +zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) +{ + int rc; + u8 i; + + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + rc = zl3073x_ref_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch input state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + rc = zl3073x_synth_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch synth state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + for (i = 0; i < ZL3073X_NUM_OUTS; i++) { + rc = zl3073x_out_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch output state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + return rc; +} + +/** + * zl3073x_ref_phase_offsets_update - update reference phase offsets + * @zldev: pointer to zl3073x_dev structure + * @channel: DPLL channel number or -1 + * + * The function asks device to update phase offsets latch registers with + * the latest measured values. There are 2 sets of latch registers: + * + * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset + * values between particular DPLL channel and its *connected* input + * reference. + * + * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values + * between selected DPLL channel and all input references. + * + * If the caller is interested in 2) then it has to pass DPLL channel number + * in @channel parameter. If it is interested only in 1) then it should pass + * @channel parameter with value of -1. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) +{ + int rc; + + /* Per datasheet we have to wait for 'dpll_ref_phase_err_rqst_rd' + * to be zero to ensure that the measured data are coherent. + */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD); + if (rc) + return rc; + + /* Select DPLL channel if it is specified */ + if (channel != -1) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel); + if (rc) + return rc; + } + + /* Request to update phase offsets measurement values */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD); + if (rc) + return rc; + + /* Wait for finish */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD); +} + +/** + * zl3073x_ref_ffo_update - update reference fractional frequency offsets + * @zldev: pointer to zl3073x_dev structure + * + * The function asks device to update fractional frequency offsets latch + * registers the latest measured values, reads and stores them into + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_ffo_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + /* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero + * to ensure that the measured data are coherent. + */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL); + if (rc) + return rc; + + /* Select all references for measurement */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_3_0, + GENMASK(7, 0)); /* REF0P..REF3N */ + if (rc) + return rc; + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_4, + GENMASK(1, 0)); /* REF4P..REF4N */ + if (rc) + return rc; + + /* Request frequency offset measurement */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF); + if (rc) + return rc; + + /* Wait for finish */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL); + if (rc) + return rc; + + /* Read DPLL-to-REFx frequency offset measurements */ + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + s32 value; + + /* Read value stored in units of 2^-32 signed */ + rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value); + if (rc) + return rc; + + /* Convert to ppm -> ffo = (10^6 * value) / 2^32 */ + zldev->ref[i].ffo = mul_s64_u64_shr(value, 1000000, 32); + } + + return 0; +} + +static void +zl3073x_dev_periodic_work(struct kthread_work *work) +{ + struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev, + work.work); + struct zl3073x_dpll *zldpll; + int rc; + + /* Update DPLL-to-connected-ref phase offsets registers */ + rc = zl3073x_ref_phase_offsets_update(zldev, -1); + if (rc) + dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n", + ERR_PTR(rc)); + + /* Update references' fractional frequency offsets */ + rc = zl3073x_ref_ffo_update(zldev); + if (rc) + dev_warn(zldev->dev, + "Failed to update fractional frequency offsets: %pe\n", + ERR_PTR(rc)); + + list_for_each_entry(zldpll, &zldev->dplls, list) + zl3073x_dpll_changes_check(zldpll); + + /* Run twice a second */ + kthread_queue_delayed_work(zldev->kworker, &zldev->work, + msecs_to_jiffies(500)); +} + +/** + * zl3073x_dev_phase_meas_setup - setup phase offset measurement + * @zldev: pointer to zl3073x_dev structure + * + * Enable phase offset measurement block, set measurement averaging factor + * and enable DPLL-to-its-ref phase measurement for all DPLLs. + * + * Returns: 0 on success, <0 on error + */ +static int +zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + u8 dpll_meas_ctrl, mask = 0; + int rc; + + /* Read DPLL phase measurement control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); + if (rc) + return rc; + + /* Setup phase measurement averaging factor */ + dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR; + dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3); + + /* Enable DPLL measurement block */ + dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN; + + /* Update phase measurement control register */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); + if (rc) + return rc; + + /* Enable DPLL-to-connected-ref measurement for each channel */ + list_for_each_entry(zldpll, &zldev->dplls, list) + mask |= BIT(zldpll->id); + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask); +} + +/** + * zl3073x_dev_start - Start normal operation + * @zldev: zl3073x device pointer + * @full: perform full initialization + * + * The function starts normal operation, which means registering all DPLLs and + * their pins, and starting monitoring. If full initialization is requested, + * the function additionally initializes the phase offset measurement block and + * fetches hardware-invariant parameters. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full) +{ + struct zl3073x_dpll *zldpll; + u8 info; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_INFO, &info); + if (rc) { + dev_err(zldev->dev, "Failed to read device status info\n"); + return rc; + } + + if (!FIELD_GET(ZL_INFO_READY, info)) { + /* The ready bit indicates that the firmware was successfully + * configured and is ready for normal operation. If it is + * cleared then the configuration stored in flash is wrong + * or missing. In this situation the driver will expose + * only devlink interface to give an opportunity to flash + * the correct config. + */ + dev_info(zldev->dev, + "FW not fully ready - missing or corrupted config\n"); + + return 0; + } + + if (full) { + /* Fetch device state */ + rc = zl3073x_dev_state_fetch(zldev); + if (rc) + return rc; + + /* Setup phase offset measurement block */ + rc = zl3073x_dev_phase_meas_setup(zldev); + if (rc) { + dev_err(zldev->dev, + "Failed to setup phase measurement\n"); + return rc; + } + } + + /* Register all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + rc = zl3073x_dpll_register(zldpll); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to register DPLL%u\n", + zldpll->id); + return rc; + } + } + + /* Perform initial firmware fine phase correction */ + rc = zl3073x_dpll_init_fine_phase_adjust(zldev); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to init fine phase correction\n"); + return rc; + } + + /* Start monitoring */ + kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0); + + return 0; +} + +/** + * zl3073x_dev_stop - Stop normal operation + * @zldev: zl3073x device pointer + * + * The function stops the normal operation that mean deregistration of all + * DPLLs and their pins and stop monitoring. + * + * Return: 0 on success, <0 on error + */ +void zl3073x_dev_stop(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + + /* Stop monitoring */ + kthread_cancel_delayed_work_sync(&zldev->work); + + /* Unregister all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + if (zldpll->dpll_dev) + zl3073x_dpll_unregister(zldpll); + } +} + +static void zl3073x_dev_dpll_fini(void *ptr) +{ + struct zl3073x_dpll *zldpll, *next; + struct zl3073x_dev *zldev = ptr; + + /* Stop monitoring and unregister DPLLs */ + zl3073x_dev_stop(zldev); + + /* Destroy monitoring thread */ + if (zldev->kworker) { + kthread_destroy_worker(zldev->kworker); + zldev->kworker = NULL; + } + + /* Free all DPLLs */ + list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) { + list_del(&zldpll->list); + zl3073x_dpll_free(zldpll); + } +} + +static int +zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) +{ + struct kthread_worker *kworker; + struct zl3073x_dpll *zldpll; + unsigned int i; + int rc; + + INIT_LIST_HEAD(&zldev->dplls); + + /* Allocate all DPLLs */ + for (i = 0; i < num_dplls; i++) { + zldpll = zl3073x_dpll_alloc(zldev, i); + if (IS_ERR(zldpll)) { + dev_err_probe(zldev->dev, PTR_ERR(zldpll), + "Failed to alloc DPLL%u\n", i); + rc = PTR_ERR(zldpll); + goto error; + } + + list_add_tail(&zldpll->list, &zldev->dplls); + } + + /* Initialize monitoring thread */ + kthread_init_delayed_work(&zldev->work, zl3073x_dev_periodic_work); + kworker = kthread_create_worker(0, "zl3073x-%s", dev_name(zldev->dev)); + if (IS_ERR(kworker)) { + rc = PTR_ERR(kworker); + goto error; + } + wake_up_process(kworker->task); + + zldev->kworker = kworker; + + /* Start normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) { + dev_err_probe(zldev->dev, rc, "Failed to start device\n"); + goto error; + } + + /* Add devres action to release DPLL related resources */ + rc = devm_add_action_or_reset(zldev->dev, zl3073x_dev_dpll_fini, zldev); + if (rc) + goto error; + + return 0; + +error: + zl3073x_dev_dpll_fini(zldev); + + return rc; +} + +/** + * zl3073x_dev_probe - initialize zl3073x device + * @zldev: pointer to zl3073x device + * @chip_info: chip info based on compatible + * + * Common initialization of zl3073x device structure. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_dev_probe(struct zl3073x_dev *zldev, + const struct zl3073x_chip_info *chip_info) +{ + u16 id, revision, fw_ver; + unsigned int i; + u32 cfg_ver; + int rc; + + /* Read chip ID */ + rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id); + if (rc) + return rc; + + /* Check it matches */ + for (i = 0; i < chip_info->num_ids; i++) { + if (id == chip_info->ids[i]) + break; + } + + if (i == chip_info->num_ids) { + return dev_err_probe(zldev->dev, -ENODEV, + "Unknown or non-match chip ID: 0x%0x\n", + id); + } + + /* Read revision, firmware version and custom config version */ + rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver); + if (rc) + return rc; + rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver); + if (rc) + return rc; + + dev_dbg(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n", id, + revision, fw_ver); + dev_dbg(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n", + FIELD_GET(GENMASK(31, 24), cfg_ver), + FIELD_GET(GENMASK(23, 16), cfg_ver), + FIELD_GET(GENMASK(15, 8), cfg_ver), + FIELD_GET(GENMASK(7, 0), cfg_ver)); + + /* Generate random clock ID as the device has not such property that + * could be used for this purpose. A user can later change this value + * using devlink. + */ + zldev->clock_id = get_random_u64(); + + /* Initialize mutex for operations where multiple reads, writes + * and/or polls are required to be done atomically. + */ + rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + + /* Register DPLL channels */ + rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels); + if (rc) + return rc; + + /* Register the devlink instance and parameters */ + rc = zl3073x_devlink_register(zldev); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to register devlink instance\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, ZL3073X); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h new file mode 100644 index 0000000000..128fb899ca --- /dev/null +++ b/drivers/dpll/zl3073x/core.h @@ -0,0 +1,416 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_CORE_H +#define _ZL3073X_CORE_H + +#include +#include +#include +#include +#include + +#include "regs.h" + +struct device; +struct regmap; +struct zl3073x_dpll; + +/* + * Hardware limits for ZL3073x chip family + */ +#define ZL3073X_MAX_CHANNELS 5 +#define ZL3073X_NUM_REFS 10 +#define ZL3073X_NUM_OUTS 10 +#define ZL3073X_NUM_SYNTHS 5 +#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS +#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) +#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ + ZL3073X_NUM_OUTPUT_PINS) + +/** + * struct zl3073x_ref - input reference invariant info + * @enabled: input reference is enabled or disabled + * @diff: true if input reference is differential + * @ffo: current fractional frequency offset + */ +struct zl3073x_ref { + bool enabled; + bool diff; + s64 ffo; +}; + +/** + * struct zl3073x_out - output invariant info + * @enabled: out is enabled or disabled + * @synth: synthesizer the out is connected to + * @signal_format: out signal format + */ +struct zl3073x_out { + bool enabled; + u8 synth; + u8 signal_format; +}; + +/** + * struct zl3073x_synth - synthesizer invariant info + * @freq: synthesizer frequency + * @dpll: ID of DPLL the synthesizer is driven by + * @enabled: synth is enabled or disabled + */ +struct zl3073x_synth { + u32 freq; + u8 dpll; + bool enabled; +}; + +/** + * struct zl3073x_dev - zl3073x device + * @dev: pointer to device + * @regmap: regmap to access device registers + * @multiop_lock: to serialize multiple register operations + * @clock_id: clock id of the device + * @ref: array of input references' invariants + * @out: array of outs' invariants + * @synth: array of synths' invariants + * @dplls: list of DPLLs + * @kworker: thread for periodic work + * @work: periodic work + */ +struct zl3073x_dev { + struct device *dev; + struct regmap *regmap; + struct mutex multiop_lock; + u64 clock_id; + + /* Invariants */ + struct zl3073x_ref ref[ZL3073X_NUM_REFS]; + struct zl3073x_out out[ZL3073X_NUM_OUTS]; + struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS]; + + /* DPLL channels */ + struct list_head dplls; + + /* Monitor */ + struct kthread_worker *kworker; + struct kthread_delayed_work work; +}; + +struct zl3073x_chip_info { + const u16 *ids; + size_t num_ids; + int num_channels; +}; + +extern const struct zl3073x_chip_info zl30731_chip_info; +extern const struct zl3073x_chip_info zl30732_chip_info; +extern const struct zl3073x_chip_info zl30733_chip_info; +extern const struct zl3073x_chip_info zl30734_chip_info; +extern const struct zl3073x_chip_info zl30735_chip_info; +extern const struct regmap_config zl3073x_regmap_config; + +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); +int zl3073x_dev_probe(struct zl3073x_dev *zldev, + const struct zl3073x_chip_info *chip_info); + +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full); +void zl3073x_dev_stop(struct zl3073x_dev *zldev); + +/********************** + * Registers operations + **********************/ + +/** + * struct zl3073x_hwreg_seq_item - HW register write sequence item + * @addr: HW register to be written + * @value: value to be written to HW register + * @mask: bitmask indicating bits to be updated + * @wait: number of ms to wait after register write + */ +struct zl3073x_hwreg_seq_item { + u32 addr; + u32 value; + u32 mask; + u32 wait; +}; + +#define HWREG_SEQ_ITEM(_addr, _value, _mask, _wait) \ +{ \ + .addr = _addr, \ + .value = FIELD_PREP_CONST(_mask, _value), \ + .mask = _mask, \ + .wait = _wait, \ +} + +int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, + unsigned int mask_reg, u16 mask_val); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); +int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); +int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); +int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val); +int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val); +int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val); +int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val); +int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val); +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value); +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value); +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask); +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items); + +/***************** + * Misc operations + *****************/ + +int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult); +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel); + +static inline bool +zl3073x_is_n_pin(u8 id) +{ + /* P-pins ids are even while N-pins are odd */ + return id & 1; +} + +static inline bool +zl3073x_is_p_pin(u8 id) +{ + return !zl3073x_is_n_pin(id); +} + +/** + * zl3073x_input_pin_ref_get - get reference for given input pin + * @id: input pin id + * + * Return: reference id for the given input pin + */ +static inline u8 +zl3073x_input_pin_ref_get(u8 id) +{ + return id; +} + +/** + * zl3073x_output_pin_out_get - get output for the given output pin + * @id: output pin id + * + * Return: output id for the given output pin + */ +static inline u8 +zl3073x_output_pin_out_get(u8 id) +{ + /* Output pin pair shares the single output */ + return id / 2; +} + +/** + * zl3073x_ref_ffo_get - get current fractional frequency offset + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: the latest measured fractional frequency offset + */ +static inline s64 +zl3073x_ref_ffo_get(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->ref[index].ffo; +} + +/** + * zl3073x_ref_is_diff - check if the given input reference is differential + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: true if reference is differential, false if reference is single-ended + */ +static inline bool +zl3073x_ref_is_diff(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->ref[index].diff; +} + +/** + * zl3073x_ref_is_enabled - check if the given input reference is enabled + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: true if input refernce is enabled, false otherwise + */ +static inline bool +zl3073x_ref_is_enabled(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->ref[index].enabled; +} + +/** + * zl3073x_synth_dpll_get - get DPLL ID the synth is driven by + * @zldev: pointer to zl3073x device + * @index: synth index + * + * Return: ID of DPLL the given synthetizer is driven by + */ +static inline u8 +zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->synth[index].dpll; +} + +/** + * zl3073x_synth_freq_get - get synth current freq + * @zldev: pointer to zl3073x device + * @index: synth index + * + * Return: frequency of given synthetizer + */ +static inline u32 +zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->synth[index].freq; +} + +/** + * zl3073x_synth_is_enabled - check if the given synth is enabled + * @zldev: pointer to zl3073x device + * @index: synth index + * + * Return: true if synth is enabled, false otherwise + */ +static inline bool +zl3073x_synth_is_enabled(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->synth[index].enabled; +} + +/** + * zl3073x_out_synth_get - get synth connected to given output + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: index of synth connected to given output. + */ +static inline u8 +zl3073x_out_synth_get(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->out[index].synth; +} + +/** + * zl3073x_out_is_enabled - check if the given output is enabled + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if the output is enabled, false otherwise + */ +static inline bool +zl3073x_out_is_enabled(struct zl3073x_dev *zldev, u8 index) +{ + u8 synth; + + /* Output is enabled only if associated synth is enabled */ + synth = zl3073x_out_synth_get(zldev, index); + if (zl3073x_synth_is_enabled(zldev, synth)) + return zldev->out[index].enabled; + + return false; +} + +/** + * zl3073x_out_signal_format_get - get output signal format + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: signal format of given output + */ +static inline u8 +zl3073x_out_signal_format_get(struct zl3073x_dev *zldev, u8 index) +{ + return zldev->out[index].signal_format; +} + +/** + * zl3073x_out_dpll_get - get DPLL ID the output is driven by + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: ID of DPLL the given output is driven by + */ +static inline +u8 zl3073x_out_dpll_get(struct zl3073x_dev *zldev, u8 index) +{ + u8 synth; + + /* Get synthesizer connected to given output */ + synth = zl3073x_out_synth_get(zldev, index); + + /* Return DPLL that drives the synth */ + return zl3073x_synth_dpll_get(zldev, synth); +} + +/** + * zl3073x_out_is_diff - check if the given output is differential + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if output is differential, false if output is single-ended + */ +static inline bool +zl3073x_out_is_diff(struct zl3073x_dev *zldev, u8 index) +{ + switch (zl3073x_out_signal_format_get(zldev, index)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM: + return true; + default: + break; + } + + return false; +} + +/** + * zl3073x_output_pin_is_enabled - check if the given output pin is enabled + * @zldev: pointer to zl3073x device + * @id: output pin id + * + * Checks if the output of the given output pin is enabled and also that + * its signal format also enables the given pin. + * + * Return: true if output pin is enabled, false if output pin is disabled + */ +static inline bool +zl3073x_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id) +{ + u8 output = zl3073x_output_pin_out_get(id); + + /* Check if the whole output is enabled */ + if (!zl3073x_out_is_enabled(zldev, output)) + return false; + + /* Check signal format */ + switch (zl3073x_out_signal_format_get(zldev, output)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED: + /* Both output pins are disabled by signal format */ + return false; + + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P: + /* Output is one single ended P-pin output */ + if (zl3073x_is_n_pin(id)) + return false; + break; + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N: + /* Output is one single ended N-pin output */ + if (zl3073x_is_p_pin(id)) + return false; + break; + default: + /* For other format both pins are enabled */ + break; + } + + return true; +} + +#endif /* _ZL3073X_CORE_H */ diff --git a/drivers/dpll/zl3073x/devlink.c b/drivers/dpll/zl3073x/devlink.c new file mode 100644 index 0000000000..f04eab8d2c --- /dev/null +++ b/drivers/dpll/zl3073x/devlink.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "dpll.h" +#include "flash.h" +#include "fw.h" +#include "regs.h" + +/** + * zl3073x_devlink_info_get - Devlink device info callback + * @devlink: devlink structure pointer + * @req: devlink request pointer to store information + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + u16 id, revision, fw_ver; + char buf[16]; + u32 cfg_ver; + int rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", id); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", revision); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_REV, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%u", fw_ver); + rc = devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver); + if (rc) + return rc; + + /* No custom config version */ + if (cfg_ver == U32_MAX) + return 0; + + snprintf(buf, sizeof(buf), "%lu.%lu.%lu.%lu", + FIELD_GET(GENMASK(31, 24), cfg_ver), + FIELD_GET(GENMASK(23, 16), cfg_ver), + FIELD_GET(GENMASK(15, 8), cfg_ver), + FIELD_GET(GENMASK(7, 0), cfg_ver)); + + return devlink_info_version_running_put(req, "custom_cfg", buf); +} + +static int +zl3073x_devlink_reload_down(struct devlink *devlink, bool netns_change, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + /* Stop normal operation */ + zl3073x_dev_stop(zldev); + + return 0; +} + +static int +zl3073x_devlink_reload_up(struct devlink *devlink, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + u32 *actions_performed, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + union devlink_param_value val; + int rc; + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + rc = devl_param_driverinit_value_get(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + &val); + if (rc) + return rc; + + if (zldev->clock_id != val.vu64) { + dev_dbg(zldev->dev, + "'clock_id' changed to %016llx\n", val.vu64); + zldev->clock_id = val.vu64; + } + + /* Restart normal operation */ + rc = zl3073x_dev_start(zldev, false); + if (rc) + dev_warn(zldev->dev, "Failed to re-start normal operation\n"); + + *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT); + + return 0; +} + +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total) +{ + struct devlink *devlink = priv_to_devlink(zldev); + + devlink_flash_update_status_notify(devlink, msg, component, done, + total); +} + +/** + * zl3073x_devlink_flash_prepare - Prepare and enter flash mode + * @zldev: zl3073x device pointer + * @zlfw: pointer to loaded firmware + * @extack: netlink extack pointer to report errors + * + * The function stops normal operation and switches the device to flash mode. + * If an error occurs the normal operation is resumed. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_prepare(struct zl3073x_dev *zldev, + struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *util; + int rc; + + util = zlfw->component[ZL_FW_COMPONENT_UTIL]; + if (!util) { + zl3073x_devlink_flash_notify(zldev, + "Utility is missing in firmware", + NULL, 0, 0); + return -ENOEXEC; + } + + /* Stop normal operation prior entering flash mode */ + zl3073x_dev_stop(zldev); + + rc = zl3073x_flash_mode_enter(zldev, util->data, util->size, extack); + if (rc) { + zl3073x_devlink_flash_notify(zldev, + "Failed to enter flash mode", + NULL, 0, 0); + + /* Resume normal operation */ + zl3073x_dev_start(zldev, true); + + return rc; + } + + return 0; +} + +/** + * zl3073x_devlink_flash_finish - Leave flash mode and resume normal operation + * @zldev: zl3073x device pointer + * @extack: netlink extack pointer to report errors + * + * The function switches the device back to standard mode and resumes normal + * operation. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Reset device CPU to normal mode */ + zl3073x_flash_mode_leave(zldev, extack); + + /* Resume normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) + zl3073x_devlink_flash_notify(zldev, + "Failed to start normal operation", + NULL, 0, 0); + + return rc; +} + +/** + * zl3073x_devlink_flash_update - Devlink flash update callback + * @devlink: devlink structure pointer + * @params: flashing parameters pointer + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_update(struct devlink *devlink, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + struct zl3073x_fw *zlfw; + int rc = 0; + + zlfw = zl3073x_fw_load(zldev, params->fw->data, params->fw->size, + extack); + if (IS_ERR(zlfw)) { + zl3073x_devlink_flash_notify(zldev, "Failed to load firmware", + NULL, 0, 0); + rc = PTR_ERR(zlfw); + goto finish; + } + + /* Stop normal operation and enter flash mode */ + rc = zl3073x_devlink_flash_prepare(zldev, zlfw, extack); + if (rc) + goto finish; + + rc = zl3073x_fw_flash(zldev, zlfw, extack); + if (rc) { + zl3073x_devlink_flash_finish(zldev, extack); + goto finish; + } + + /* Resume normal mode */ + rc = zl3073x_devlink_flash_finish(zldev, extack); + +finish: + if (!IS_ERR(zlfw)) + zl3073x_fw_free(zlfw); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + NULL, 0, 0); + + return rc; +} + +static const struct devlink_ops zl3073x_devlink_ops = { + .info_get = zl3073x_devlink_info_get, + .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT), + .reload_down = zl3073x_devlink_reload_down, + .reload_up = zl3073x_devlink_reload_up, + .flash_update = zl3073x_devlink_flash_update, +}; + +static void +zl3073x_devlink_free(void *ptr) +{ + devlink_free(ptr); +} + +/** + * zl3073x_devm_alloc - allocates zl3073x device structure + * @dev: pointer to device structure + * + * Allocates zl3073x device structure as device resource. + * + * Return: pointer to zl3073x device on success, error pointer on error + */ +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev) +{ + struct zl3073x_dev *zldev; + struct devlink *devlink; + int rc; + + devlink = devlink_alloc(&zl3073x_devlink_ops, sizeof(*zldev), dev); + if (!devlink) + return ERR_PTR(-ENOMEM); + + /* Add devres action to free devlink device */ + rc = devm_add_action_or_reset(dev, zl3073x_devlink_free, devlink); + if (rc) + return ERR_PTR(rc); + + zldev = devlink_priv(devlink); + zldev->dev = dev; + dev_set_drvdata(zldev->dev, zldev); + + return zldev; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, ZL3073X); + +static int +zl3073x_devlink_param_clock_id_validate(struct devlink *devlink, u32 id, + union devlink_param_value val, + struct netlink_ext_ack *extack) +{ + if (!val.vu64) { + NL_SET_ERR_MSG_MOD(extack, "'clock_id' must be non-zero"); + return -EINVAL; + } + + return 0; +} + +static const struct devlink_param zl3073x_devlink_params[] = { + DEVLINK_PARAM_GENERIC(CLOCK_ID, BIT(DEVLINK_PARAM_CMODE_DRIVERINIT), + NULL, NULL, + zl3073x_devlink_param_clock_id_validate), +}; + +static void +zl3073x_devlink_unregister(void *ptr) +{ + struct devlink *devlink = priv_to_devlink(ptr); + + devl_lock(devlink); + + /* Unregister devlink params */ + devl_params_unregister(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + + /* Unregister devlink instance */ + devl_unregister(devlink); + + devl_unlock(devlink); +} + +/** + * zl3073x_devlink_register - register devlink instance and params + * @zldev: zl3073x device to register the devlink for + * + * Register the devlink instance and parameters associated with the device. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_devlink_register(struct zl3073x_dev *zldev) +{ + struct devlink *devlink = priv_to_devlink(zldev); + union devlink_param_value value; + int rc; + + devl_lock(devlink); + + /* Register devlink params */ + rc = devl_params_register(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + if (rc) { + devl_unlock(devlink); + + return rc; + } + + value.vu64 = zldev->clock_id; + devl_param_driverinit_value_set(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + value); + + /* Register devlink instance */ + devl_register(devlink); + + devl_unlock(devlink); + + /* Add devres action to unregister devlink device */ + return devm_add_action_or_reset(zldev->dev, zl3073x_devlink_unregister, + zldev); +} diff --git a/drivers/dpll/zl3073x/devlink.h b/drivers/dpll/zl3073x/devlink.h new file mode 100644 index 0000000000..63dfd6fa1c --- /dev/null +++ b/drivers/dpll/zl3073x/devlink.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_DEVLINK_H +#define _ZL3073X_DEVLINK_H + +struct zl3073x_dev; + +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); + +int zl3073x_devlink_register(struct zl3073x_dev *zldev); + +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total); + +#endif /* _ZL3073X_DEVLINK_H */ diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c new file mode 100644 index 0000000000..8d20cc5266 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.c @@ -0,0 +1,2318 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "dpll.h" +#include "prop.h" +#include "regs.h" + +#define ZL3073X_DPLL_REF_NONE ZL3073X_NUM_REFS +#define ZL3073X_DPLL_REF_IS_VALID(_ref) ((_ref) != ZL3073X_DPLL_REF_NONE) + +/** + * struct zl3073x_dpll_pin - DPLL pin + * @list: this DPLL pin list entry + * @dpll: DPLL the pin is registered to + * @dpll_pin: pointer to registered dpll_pin + * @label: package label + * @dir: pin direction + * @id: pin id + * @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 + * @freq_offset: last saved fractional frequency offset + */ +struct zl3073x_dpll_pin { + struct list_head list; + struct zl3073x_dpll *dpll; + struct dpll_pin *dpll_pin; + char label[8]; + enum dpll_pin_direction dir; + u8 id; + u8 prio; + bool selectable; + bool esync_control; + enum dpll_pin_state pin_state; + s64 phase_offset; + s64 freq_offset; +}; + +/* + * 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_input_pin - check if the pin is input one + * @pin: pin to check + * + * Return: true if pin is input, false if pin is output. + */ +static bool +zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->dir == DPLL_PIN_DIRECTION_INPUT; +} + +/** + * zl3073x_dpll_is_p_pin - check if the pin is P-pin + * @pin: pin to check + * + * Return: true if the pin is P-pin, false if it is N-pin + */ +static bool +zl3073x_dpll_is_p_pin(struct zl3073x_dpll_pin *pin) +{ + return zl3073x_is_p_pin(pin->id); +} + +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; + + *direction = pin->dir; + + return 0; +} + +/** + * zl3073x_dpll_input_ref_frequency_get - get input reference frequency + * @zldpll: pointer to zl3073x_dpll + * @ref_id: reference id + * @frequency: pointer to variable to store frequency + * + * Reads frequency of given input reference. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dpll *zldpll, u8 ref_id, + u32 *frequency) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u16 base, mult, num, denom; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref_id)); + if (rc) + return rc; + + /* Read registers to compute resulting frequency */ + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &base); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &mult); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &num); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &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; + } + + /* Compute the frequency */ + *frequency = mul_u64_u32_div(base * mult, num, denom); + + 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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 ref, ref_sync_ctrl, sync_mode; + u32 esync_div, ref_freq; + int rc; + + /* Get reference frequency */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, pin->id, &ref_freq); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Get ref sync mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); + if (rc) + return rc; + + /* Get esync divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &esync_div); + if (rc) + return rc; + + sync_mode = FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref_sync_ctrl); + + switch (sync_mode) { + case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: + esync->freq = (esync_div == ZL_REF_ESYNC_DIV_1HZ) ? 1 : 0; + esync->pulse = 25; + break; + default: + esync->freq = 0; + esync->pulse = 0; + 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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 ref, ref_sync_ctrl, sync_mode; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration into mailbox */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Get ref sync mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); + if (rc) + return rc; + + /* Use freq == 0 to disable esync */ + if (!freq) + sync_mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + else + sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75; + + ref_sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE; + ref_sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode); + + /* Update ref sync control register */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, ref_sync_ctrl); + if (rc) + return rc; + + if (freq) { + /* 1 Hz is only supported frequnecy currently */ + rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV, + ZL_REF_ESYNC_DIV_1HZ); + if (rc) + return rc; + } + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +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_pin *pin = pin_priv; + + *ffo = pin->freq_offset; + + return 0; +} + +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_dpll_pin *pin = pin_priv; + u32 ref_freq; + u8 ref; + int rc; + + /* Read and return ref frequency */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, &ref_freq); + if (!rc) + *frequency = ref_freq; + + return rc; +} + +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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u16 base, mult; + u8 ref; + int rc; + + /* Get base frequency and multiplier for the requested frequency */ + rc = zl3073x_ref_freq_factorize(frequency, &base, &mult); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Load reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + + /* Update base frequency, multiplier, numerator & denominator */ + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE, base); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT, mult); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M, 1); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N, 1); + if (rc) + return rc; + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +/** + * 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 on success, <0 on error + */ +static int +zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u8 state, value; + int rc; + + switch (zldpll->refsel_mode) { + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + /* For automatic mode read refsel_status register */ + rc = zl3073x_read_u8(zldev, + ZL_REG_DPLL_REFSEL_STATUS(zldpll->id), + &value); + if (rc) + return rc; + + /* Extract reference state */ + state = FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, value); + + /* Return the reference only if the DPLL is locked to it */ + if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) + *ref = FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, value); + else + *ref = ZL3073X_DPLL_REF_NONE; + break; + case ZL_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_DPLL_REF_NONE; + break; + } + + return 0; +} + +/** + * zl3073x_dpll_selected_ref_set - select reference in manual mode + * @zldpll: pointer to zl3073x_dpll + * @ref: input reference to be selected + * + * Selects the given reference for the DPLL channel it should be + * locked to. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u8 mode, mode_refsel; + int rc; + + mode = zldpll->refsel_mode; + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* Manual mode with ref selected */ + if (ref == ZL3073X_DPLL_REF_NONE) { + switch (zldpll->lock_status) { + case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: + case DPLL_LOCK_STATUS_HOLDOVER: + /* Switch to forced holdover */ + mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; + break; + default: + /* Switch to freerun */ + mode = ZL_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 ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + /* Manual mode without no ref */ + if (ref == ZL3073X_DPLL_REF_NONE) + /* No register update - keep current mode */ + return 0; + + /* Switch to reflock mode and update ref selection */ + mode = ZL_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(ZL_DPLL_MODE_REFSEL_MODE, mode) | + FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); + + /* Update dpll_mode_refsel register */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), + mode_refsel); + if (rc) + return rc; + + /* Store new mode and forced reference */ + zldpll->refsel_mode = mode; + zldpll->forced_ref = ref; + + return rc; +} + +/** + * 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 on success, <0 on error + */ +static int +zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + int rc; + + /* Get currently selected input reference */ + rc = zl3073x_dpll_selected_ref_get(zldpll, ref); + if (rc) + return rc; + + if (ZL3073X_DPLL_REF_IS_VALID(*ref)) { + u8 ref_status; + + /* Read the reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(*ref), + &ref_status); + if (rc) + return rc; + + /* If the monitor indicates an error nothing is connected */ + if (ref_status != ZL_REF_MON_STATUS_OK) + *ref = ZL3073X_DPLL_REF_NONE; + } + + return 0; +} + +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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 conn_ref, ref, ref_status; + s64 ref_phase; + int rc; + + /* Get currently connected reference */ + rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref); + if (rc) + return rc; + + /* Report phase offset only for currently connected pin if the phase + * monitor feature is disabled. + */ + ref = zl3073x_input_pin_ref_get(pin->id); + if (!zldpll->phase_monitor && ref != conn_ref) { + *phase_offset = 0; + + return 0; + } + + /* Get this pin monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &ref_status); + if (rc) + return rc; + + /* Report phase offset only if the input pin signal is present */ + if (ref_status != ZL_REF_MON_STATUS_OK) { + *phase_offset = 0; + + return 0; + } + + ref_phase = pin->phase_offset; + + /* 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_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) { + u32 conn_freq, ref_freq; + + /* Get frequency of connected ref */ + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref, + &conn_freq); + if (rc) + return rc; + + /* Get frequency of given ref */ + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, + &ref_freq); + if (rc) + return rc; + + if (conn_freq > ref_freq) { + s64 conn_period, div_factor; + + conn_period = div_s64(PSEC_PER_SEC, conn_freq); + div_factor = div64_s64(ref_phase, conn_period); + ref_phase -= conn_period * div_factor; + } + } + + *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER; + + 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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + s64 phase_comp; + u8 ref; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Read current phase offset compensation */ + rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, &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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + s64 phase_comp; + u8 ref; + int rc; + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + phase_comp = -phase_adjust; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Write the requested value into the compensation register */ + rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, phase_comp); + if (rc) + return rc; + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +/** + * 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. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_prio; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); + if (rc) + return rc; + + /* Read reference priority - one value for P&N pins (4 bits/pin) */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), + &ref_prio); + if (rc) + return rc; + + /* Select nibble according pin type */ + if (zl3073x_dpll_is_p_pin(pin)) + *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, ref_prio); + else + *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, ref_prio); + + return rc; +} + +/** + * zl3073x_dpll_ref_prio_set - set priority for given input pin + * @pin: pointer to pin + * @prio: place to store priority + * + * Sets priority for the given input pin. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_prio; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); + if (rc) + return rc; + + /* Read reference priority - one value shared between P&N pins */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio); + if (rc) + return rc; + + /* Update nibble according pin type */ + if (zl3073x_dpll_is_p_pin(pin)) { + ref_prio &= ~ZL_DPLL_REF_PRIO_REF_P; + ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio); + } else { + ref_prio &= ~ZL_DPLL_REF_PRIO_REF_N; + ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio); + } + + /* Update reference priority */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), ref_prio); + if (rc) + return rc; + + /* Commit configuration */ + return zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); +} + +/** + * zl3073x_dpll_ref_state_get - get status for given input pin + * @pin: pointer to pin + * @state: place to store status + * + * Checks current status for the given input pin and stores the value + * to @state. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin, + enum dpll_pin_state *state) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_conn, status; + int rc; + + ref = zl3073x_input_pin_ref_get(pin->id); + + /* Get currently connected reference */ + rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn); + if (rc) + return rc; + + if (ref == 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 == ZL_DPLL_MODE_REFSEL_MODE_AUTO && + pin->selectable) { + /* Read reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), + &status); + if (rc) + return rc; + + /* If the monitor indicates errors report the reference + * as disconnected + */ + if (status == ZL_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_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_pin *pin = pin_priv; + + return zl3073x_dpll_ref_state_get(pin, state); +} + +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 ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + if (state == DPLL_PIN_STATE_CONNECTED) { + /* Choose the pin as new selected reference */ + new_ref = zl3073x_input_pin_ref_get(pin->id); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + /* No reference */ + new_ref = ZL3073X_DPLL_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; + + case ZL_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, + ZL_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, + "Pin state cannot be changed in current mode"); + rc = -EOPNOTSUPP; + break; + } + + 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_pin *pin = pin_priv; + int rc; + + if (prio > ZL_DPLL_REF_PRIO_MAX) + return -EINVAL; + + /* If the pin is selectable then update HW registers */ + if (pin->selectable) { + 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_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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct device *dev = zldev->dev; + u32 esync_period, esync_width; + u8 clock_type, synth; + u8 out, output_mode; + u32 output_div; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + + /* 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_out_signal_format_get(zldev, out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + return -EOPNOTSUPP; + default: + break; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + if (rc) + return rc; + + /* Read output divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!output_div) { + dev_err(dev, "Zero divisor for OUTPUT%u got from device\n", + out); + return -EINVAL; + } + + /* Get synth attached to output pin */ + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode); + if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + /* No need to read esync data if it is not enabled */ + esync->freq = 0; + esync->pulse = 0; + + goto finish; + } + + /* Read esync period */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period); + if (rc) + return rc; + + /* Check esync divisor for zero */ + if (!esync_period) { + dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n", + out); + return -EINVAL; + } + + /* Get esync pulse width in units of half synth cycles */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width); + if (rc) + return rc; + + /* Compute esync frequency */ + esync->freq = synth_freq / 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 && (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) +{ + u32 esync_period, esync_width, output_div; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 clock_type, out, output_mode, synth; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + + /* 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_out_signal_format_get(zldev, out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + return -EOPNOTSUPP; + default: + break; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + if (rc) + return rc; + + /* Select clock type */ + if (freq) + clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; + else + clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL; + + /* Update clock type in output mode */ + output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; + output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode); + if (rc) + return rc; + + /* If esync is being disabled just write mailbox and finish */ + if (!freq) + goto write_mailbox; + + /* Get synth attached to output pin */ + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &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", out); + return -EINVAL; + } + + /* Compute and update esync period */ + esync_period = synth_freq / (u32)freq / output_div; + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, 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_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width); + if (rc) + return rc; + +write_mailbox: + /* Commit output configuration */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); +} + +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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct device *dev = zldev->dev; + u8 out, signal_format, synth; + u32 output_div, synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!output_div) { + dev_err(dev, "Zero divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Read used signal format for the given output */ + signal_format = zl3073x_out_signal_format_get(zldev, out); + + switch (signal_format) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_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 = synth_freq / output_div; + } else { + /* For N-pin we have to divide additionally by + * divisor stored in esync_period output mailbox + * register that is used as N-pin divisor for these + * modes. + */ + u32 ndiv; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + &ndiv); + if (rc) + return rc; + + /* Check N-pin divisor for zero */ + if (!ndiv) { + dev_err(dev, + "Zero N-pin divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Compute final divisor for N-pin */ + *frequency = synth_freq / output_div / ndiv; + } + break; + default: + /* In other modes the resulting frequency is computed as + * division of synth frequency and output divisor. + */ + *frequency = 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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct device *dev = zldev->dev; + u32 output_n_freq, output_p_freq; + u8 out, signal_format, synth; + u32 cur_div, new_div, ndiv; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + new_div = synth_freq / (u32)frequency; + + /* Get used signal format for the given output */ + signal_format = zl3073x_out_signal_format_get(zldev, out); + + guard(mutex)(&zldev->multiop_lock); + + /* Load output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Check signal format */ + if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV && + signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV) { + /* For non N-divided signal formats the frequency is computed + * as division of synth frequency and output divisor. + */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); + if (rc) + return rc; + + /* Commit output configuration */ + return zl3073x_mb_op(zldev, + ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + } + + /* For N-divided signal format get current divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &cur_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!cur_div) { + dev_err(dev, "Zero divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Get N-pin divisor (shares the same register with esync */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &ndiv); + if (rc) + return rc; + + /* Check N-pin divisor for zero */ + if (!ndiv) { + dev_err(dev, + "Zero N-pin divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Compute current output frequency for P-pin */ + output_p_freq = synth_freq / cur_div; + + /* Compute current N-pin frequency */ + output_n_freq = output_p_freq / ndiv; + + 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 output divisor */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); + if (rc) + return rc; + + /* Compute new divisor for N-pin */ + ndiv = (u32)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 */ + ndiv = output_p_freq / (u32)frequency; + } + + /* Update divisor for the N-pin */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, ndiv); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, ndiv); + if (rc) + return rc; + + /* Commit output configuration */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); +} + +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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u32 synth_freq; + s32 phase_comp; + u8 out, synth; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + /* Check synth freq for zero */ + if (!synth_freq) { + dev_err(zldev->dev, "Got zero synth frequency for output %u\n", + out); + return -EINVAL; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read current output phase compensation */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &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->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + int half_synth_cycle; + u32 synth_freq; + u8 out, synth; + int rc; + + /* Get attached synth */ + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + + /* 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; + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + phase_adjust = -phase_adjust; + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Write the requested value into the compensation register */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust); + if (rc) + return rc; + + /* Update output configuration from mailbox */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); +} + +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->dev; + u8 mon_status, state; + int rc; + + switch (zldpll->refsel_mode) { + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + /* In FREERUN and NCO modes the DPLL is always unlocked */ + *status = DPLL_LOCK_STATUS_UNLOCKED; + + return 0; + default: + break; + } + + /* Read DPLL monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(zldpll->id), + &mon_status); + if (rc) + return rc; + state = FIELD_GET(ZL_DPLL_MON_STATUS_STATE, mon_status); + + switch (state) { + case ZL_DPLL_MON_STATUS_STATE_LOCK: + if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status)) + *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; + else + *status = DPLL_LOCK_STATUS_LOCKED; + break; + case ZL_DPLL_MON_STATUS_STATE_HOLDOVER: + case ZL_DPLL_MON_STATUS_STATE_ACQUIRING: + *status = DPLL_LOCK_STATUS_HOLDOVER; + break; + default: + dev_warn(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n", + mon_status); + *status = DPLL_LOCK_STATUS_UNLOCKED; + break; + } + + return 0; +} + +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 ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* Use MANUAL for device FREERUN, HOLDOVER, NCO and + * REFLOCK modes + */ + *mode = DPLL_MODE_MANUAL; + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + /* Use AUTO for device AUTO mode */ + *mode = DPLL_MODE_AUTOMATIC; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + if (zldpll->phase_monitor) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + + return 0; +} + +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, + .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, + .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set, +}; + +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, + .phase_adjust_set = zl3073x_dpll_output_pin_phase_adjust_set, + .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, + .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, + .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, +}; + +/** + * zl3073x_dpll_pin_alloc - allocate DPLL pin + * @zldpll: pointer to zl3073x_dpll + * @dir: pin direction + * @id: pin id + * + * Allocates and initializes zl3073x_dpll_pin structure for given + * pin id and direction. + * + * Return: pointer to allocated structure on success, error pointer on error + */ +static struct zl3073x_dpll_pin * +zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, + u8 id) +{ + struct zl3073x_dpll_pin *pin; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return ERR_PTR(-ENOMEM); + + pin->dpll = zldpll; + pin->dir = dir; + pin->id = id; + + return pin; +} + +/** + * zl3073x_dpll_pin_free - deallocate DPLL pin + * @pin: pin to free + * + * Deallocates DPLL pin previously allocated by @zl3073x_dpll_pin_alloc. + */ +static void +zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin) +{ + WARN(pin->dpll_pin, "DPLL pin is still registered\n"); + + kfree(pin); +} + +/** + * zl3073x_dpll_pin_register - register DPLL pin + * @pin: pointer to DPLL pin + * @index: absolute pin index for registration + * + * Registers given DPLL pin into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_pin_props *props; + const struct dpll_pin_ops *ops; + int rc; + + /* Get pin properties */ + props = zl3073x_pin_props_get(zldpll->dev, pin->dir, pin->id); + if (IS_ERR(props)) + return PTR_ERR(props); + + /* Save package label & esync capability */ + strscpy(pin->label, props->package_label); + pin->esync_control = props->esync_control; + + if (zl3073x_dpll_is_input_pin(pin)) { + rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio); + if (rc) + goto err_prio_get; + + if (pin->prio == ZL_DPLL_REF_PRIO_NONE) { + /* Clamp prio to max value & mark pin non-selectable */ + pin->prio = ZL_DPLL_REF_PRIO_MAX; + pin->selectable = false; + } else { + /* Mark pin as selectable */ + pin->selectable = true; + } + } + + /* Create or get existing DPLL pin */ + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, index, THIS_MODULE, + &props->dpll_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 properties */ + zl3073x_pin_props_put(props); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin); +err_prio_get: + pin->dpll_pin = NULL; +err_pin_get: + zl3073x_pin_props_put(props); + + return rc; +} + +/** + * zl3073x_dpll_pin_unregister - unregister DPLL pin + * @pin: pointer to DPLL pin + * + * Unregisters pin previously registered by @zl3073x_dpll_pin_register. + */ +static void +zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + const struct dpll_pin_ops *ops; + + WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); + + 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; +} + +/** + * zl3073x_dpll_pins_unregister - unregister all registered DPLL pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Enumerates all DPLL pins registered to given DPLL device and + * unregisters them. + */ +static void +zl3073x_dpll_pins_unregister(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin, *next; + + list_for_each_entry_safe(pin, next, &zldpll->pins, list) { + zl3073x_dpll_pin_unregister(pin); + list_del(&pin->list); + zl3073x_dpll_pin_free(pin); + } +} + +/** + * zl3073x_dpll_pin_is_registrable - check if the pin is registrable + * @zldpll: pointer to zl3073x_dpll structure + * @dir: pin direction + * @index: pin index + * + * Checks if the given pin can be registered to given DPLL. For both + * directions the pin can be registered if it is enabled. In case of + * differential signal type only P-pin is reported as registrable. + * And additionally for the output pin, the pin can be registered only + * if it is connected to synthesizer that is driven by given DPLL. + * + * Return: true if the pin is registrable, false if not + */ +static bool +zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, + enum dpll_pin_direction dir, u8 index) +{ + struct zl3073x_dev *zldev = zldpll->dev; + bool is_diff, is_enabled; + const char *name; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + u8 ref = zl3073x_input_pin_ref_get(index); + + name = "REF"; + + /* Skip the pin if the DPLL is running in NCO mode */ + if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO) + return false; + + is_diff = zl3073x_ref_is_diff(zldev, ref); + is_enabled = zl3073x_ref_is_enabled(zldev, ref); + } else { + /* Output P&N pair shares single HW output */ + u8 out = zl3073x_output_pin_out_get(index); + + name = "OUT"; + + /* Skip the pin if it is connected to different DPLL channel */ + if (zl3073x_out_dpll_get(zldev, out) != zldpll->id) { + dev_dbg(zldev->dev, + "%s%u is driven by different DPLL\n", name, + out); + + return false; + } + + is_diff = zl3073x_out_is_diff(zldev, out); + is_enabled = zl3073x_output_pin_is_enabled(zldev, index); + } + + /* Skip N-pin if the corresponding input/output is differential */ + if (is_diff && zl3073x_is_n_pin(index)) { + dev_dbg(zldev->dev, "%s%u is differential, skipping N-pin\n", + name, index / 2); + + return false; + } + + /* Skip the pin if it is disabled */ + if (!is_enabled) { + dev_dbg(zldev->dev, "%s%u%c is disabled\n", name, index / 2, + zl3073x_is_p_pin(index) ? 'P' : 'N'); + + return false; + } + + return true; +} + +/** + * zl3073x_dpll_pins_register - register all registerable DPLL pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Enumerates all possible input/output pins and registers all of them + * that are registrable. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + enum dpll_pin_direction dir; + u8 id, index; + int rc; + + /* Process input pins */ + for (index = 0; index < ZL3073X_NUM_PINS; index++) { + /* First input pins and then output pins */ + if (index < ZL3073X_NUM_INPUT_PINS) { + id = index; + dir = DPLL_PIN_DIRECTION_INPUT; + } else { + id = index - ZL3073X_NUM_INPUT_PINS; + dir = DPLL_PIN_DIRECTION_OUTPUT; + } + + /* Check if the pin registrable to this DPLL */ + if (!zl3073x_dpll_pin_is_registrable(zldpll, dir, id)) + continue; + + pin = zl3073x_dpll_pin_alloc(zldpll, dir, id); + if (IS_ERR(pin)) { + rc = PTR_ERR(pin); + goto error; + } + + rc = zl3073x_dpll_pin_register(pin, index); + if (rc) + goto error; + + list_add(&pin->list, &zldpll->pins); + } + + return 0; + +error: + zl3073x_dpll_pins_unregister(zldpll); + + return rc; +} + +/** + * zl3073x_dpll_device_register - register DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u8 dpll_mode_refsel; + int rc; + + /* Read DPLL mode and forcibly selected reference */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), + &dpll_mode_refsel); + if (rc) + return rc; + + /* Extract mode and selected input reference */ + zldpll->refsel_mode = FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, + dpll_mode_refsel); + zldpll->forced_ref = FIELD_GET(ZL_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)) { + rc = PTR_ERR(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; + + return rc; + } + + rc = dpll_device_register(zldpll->dpll_dev, + zl3073x_prop_dpll_type_get(zldev, zldpll->id), + &zl3073x_dpll_device_ops, zldpll); + if (rc) { + dpll_device_put(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; + } + + return rc; +} + +/** + * zl3073x_dpll_device_unregister - unregister DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device from DPLL sub-system previously registered + * by @zl3073x_dpll_device_register. + */ +static void +zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll) +{ + WARN(!zldpll->dpll_dev, "DPLL device is not registered\n"); + + dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops, + zldpll); + dpll_device_put(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; +} + +/** + * zl3073x_dpll_pin_phase_offset_check - check for pin phase offset change + * @pin: pin to check + * + * Check for the change of DPLL to connected pin phase offset change. + * + * Return: true on phase offset change, false otherwise + */ +static bool +zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + unsigned int reg; + s64 phase_offset; + u8 ref; + int rc; + + ref = zl3073x_input_pin_ref_get(pin->id); + + /* Select register to read phase offset value depending on pin and + * phase monitor state: + * 1) For connected pin use dpll_phase_err_data register + * 2) For other pins use appropriate ref_phase register if the phase + * monitor feature is enabled and reference monitor does not + * report signal errors for given input pin + */ + if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) { + reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id); + } else if (zldpll->phase_monitor) { + u8 status; + + /* Get reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), + &status); + if (rc) { + dev_err(zldev->dev, + "Failed to read %s refmon status: %pe\n", + pin->label, ERR_PTR(rc)); + + return false; + } + + if (status != ZL_REF_MON_STATUS_OK) + return false; + + reg = ZL_REG_REF_PHASE(ref); + } else { + /* The pin is not connected or phase monitor disabled */ + return false; + } + + /* Read measured phase offset value */ + rc = zl3073x_read_u48(zldev, reg, &phase_offset); + if (rc) { + dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n", + ERR_PTR(rc)); + + return false; + } + + /* Convert to ps */ + phase_offset = div_s64(sign_extend64(phase_offset, 47), 100); + + /* Compare with previous value */ + if (phase_offset != pin->phase_offset) { + dev_dbg(zldev->dev, "%s phase offset changed: %lld -> %lld\n", + pin->label, pin->phase_offset, phase_offset); + pin->phase_offset = phase_offset; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change + * @pin: pin to check + * + * Check for the given pin's fractional frequency change. + * + * Return: true on fractional frequency offset change, false otherwise + */ +static bool +zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, status; + s64 ffo; + int rc; + + /* Get reference monitor status */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status); + if (rc) { + dev_err(zldev->dev, "Failed to read %s refmon status: %pe\n", + pin->label, ERR_PTR(rc)); + + return false; + } + + /* Do not report ffo changes if the reference monitor report errors */ + if (status != ZL_REF_MON_STATUS_OK) + return false; + + /* Get the latest measured ref's ffo */ + ffo = zl3073x_ref_ffo_get(zldev, ref); + + /* Compare with previous value */ + if (pin->freq_offset != ffo) { + dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n", + pin->label, pin->freq_offset, ffo); + pin->freq_offset = ffo; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_changes_check - check for changes and send notifications + * @zldpll: pointer to zl3073x_dpll structure + * + * Checks for changes on given DPLL device and its registered DPLL pins + * and sends notifications about them. + * + * This function is periodically called from @zl3073x_dev_periodic_work. + */ +void +zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + enum dpll_lock_status lock_status; + struct device *dev = zldev->dev; + struct zl3073x_dpll_pin *pin; + int rc; + + zldpll->check_count++; + + /* 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(dev, "Failed to get DPLL%u lock status: %pe\n", + zldpll->id, ERR_PTR(rc)); + return; + } + + /* 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); + } + + /* Input pin monitoring does make sense only in automatic + * or forced reference modes. + */ + if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO && + zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK) + return; + + /* Update phase offset latch registers for this DPLL if the phase + * offset monitor feature is enabled. + */ + if (zldpll->phase_monitor) { + rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id); + if (rc) { + dev_err(zldev->dev, + "Failed to update phase offsets: %pe\n", + ERR_PTR(rc)); + return; + } + } + + list_for_each_entry(pin, &zldpll->pins, list) { + enum dpll_pin_state state; + bool pin_changed = false; + + /* Output pins change checks are not necessary because output + * states are constant. + */ + if (!zl3073x_dpll_is_input_pin(pin)) + continue; + + rc = zl3073x_dpll_ref_state_get(pin, &state); + if (rc) { + dev_err(dev, + "Failed to get %s on DPLL%u state: %pe\n", + pin->label, zldpll->id, ERR_PTR(rc)); + return; + } + + if (state != pin->pin_state) { + dev_dbg(dev, "%s state changed: %u->%u\n", pin->label, + pin->pin_state, state); + pin->pin_state = state; + pin_changed = true; + } + + /* Check for phase offset and ffo change once per second */ + if (zldpll->check_count % 2 == 0) { + if (zl3073x_dpll_pin_phase_offset_check(pin)) + pin_changed = true; + + if (zl3073x_dpll_pin_ffo_check(pin)) + pin_changed = true; + } + + if (pin_changed) + dpll_pin_change_ntf(pin->dpll_pin); + } +} + +/** + * zl3073x_dpll_init_fine_phase_adjust - do initial fine phase adjustments + * @zldev: pointer to zl3073x device + * + * Performs initial fine phase adjustments needed per datasheet. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) +{ + int rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_MASK, 0x1f); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_INTVL, 0x01); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_SYNTH_PHASE_SHIFT_DATA, 0xffff); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); + if (rc) + return rc; + + return rc; +} + +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc(sizeof(*zldpll), GFP_KERNEL); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + kfree(zldpll); +} + +/** + * zl3073x_dpll_register - register DPLL device and all its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device and all its pins into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_register(struct zl3073x_dpll *zldpll) +{ + int rc; + + rc = zl3073x_dpll_device_register(zldpll); + if (rc) + return rc; + + rc = zl3073x_dpll_pins_register(zldpll); + if (rc) { + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + return 0; +} + +/** + * zl3073x_dpll_unregister - unregister DPLL device and its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device and all its pins from DPLL sub-system + * previously registered by @zl3073x_dpll_register. + */ +void +zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll) +{ + /* Unregister all pins and dpll */ + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); +} diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h new file mode 100644 index 0000000000..304910ffc9 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_DPLL_H +#define _ZL3073X_DPLL_H + +#include +#include + +#include "core.h" + +/** + * struct zl3073x_dpll - ZL3073x DPLL sub-device structure + * @list: this DPLL list entry + * @dev: pointer to multi-function parent device + * @id: DPLL index + * @refsel_mode: reference selection mode + * @forced_ref: selected reference in forced reference lock mode + * @check_count: periodic check counter + * @phase_monitor: is phase offset monitor enabled + * @dpll_dev: pointer to registered DPLL device + * @lock_status: last saved DPLL lock status + * @pins: list of pins + */ +struct zl3073x_dpll { + struct list_head list; + struct zl3073x_dev *dev; + u8 id; + u8 refsel_mode; + u8 forced_ref; + u8 check_count; + bool phase_monitor; + struct dpll_device *dpll_dev; + enum dpll_lock_status lock_status; + struct list_head pins; +}; + +struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch); +void zl3073x_dpll_free(struct zl3073x_dpll *zldpll); + +int zl3073x_dpll_register(struct zl3073x_dpll *zldpll); +void zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll); + +int zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev); +void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll); + +#endif /* _ZL3073X_DPLL_H */ diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c new file mode 100644 index 0000000000..e2771dd7f5 --- /dev/null +++ b/drivers/dpll/zl3073x/flash.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "flash.h" + +#define ZL_FLASH_ERR_PFX "FW update failed: " +#define ZL_FLASH_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL_FLASH_ERR_PFX _msg, \ + ## __VA_ARGS__) + +/** + * zl3073x_flash_download - Download image block to device memory + * @zldev: zl3073x device structure + * @component: name of the component to be downloaded + * @addr: device memory target address + * @data: pointer to data to download + * @size: size of data to download + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_download(struct zl3073x_dev *zldev, const char *component, + u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_CHECK_DELAY 5000 /* Check for interrupt each 5 seconds */ + unsigned long check_time; + const void *ptr, *end; + int rc = 0; + + dev_dbg(zldev->dev, "Downloading %zu bytes to device memory at 0x%0x\n", + size, addr); + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + + for (ptr = data, end = data + size; ptr < end; ptr += 4, addr += 4) { + /* Write current word to HW memory */ + rc = zl3073x_write_hwreg(zldev, addr, + get_unaligned((u32 *)ptr)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, + "failed to write to memory at 0x%0x", + addr); + return rc; + } + + if (time_is_before_jiffies(check_time)) { + if (signal_pending(current)) { + ZL_FLASH_ERR_MSG(extack, + "Flashing interrupted"); + return -EINTR; + } + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + } + + /* Report status each 1 kB block */ + if ((ptr - data) % 1024 == 0) + zl3073x_devlink_flash_notify(zldev, "Downloading image", + component, ptr - data, + size); + } + + zl3073x_devlink_flash_notify(zldev, "Downloading image", component, + ptr - data, size); + + dev_dbg(zldev->dev, "%zu bytes downloaded to device memory\n", size); + + return rc; +} + +/** + * zl3073x_flash_error_check - Check for flash utility errors + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function checks for errors detected by the flash utility and + * reports them if any were found. + * + * Return: 0 on success, -EIO when errors are detected + */ +static int +zl3073x_flash_error_check(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + u32 count, cause; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_COUNT, &count); + if (rc) + return rc; + else if (!count) + return 0; /* No error */ + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause); + if (rc) + return rc; + + /* Report errors */ + ZL_FLASH_ERR_MSG(extack, + "utility error occurred: count=%u cause=0x%x", count, + cause); + + return -EIO; +} + +/** + * zl3073x_flash_wait_ready - Check or wait for utility to be ready to flash + * @zldev: zl3073x device structure + * @timeout_ms: timeout for the waiting + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_wait_ready(struct zl3073x_dev *zldev, unsigned int timeout_ms) +{ +#define ZL_FLASH_POLL_DELAY_MS 100 + unsigned long timeout; + int rc, i; + + dev_dbg(zldev->dev, "Waiting for flashing to be ready\n"); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + + for (i = 0; time_is_after_jiffies(timeout); i++) { + u8 value; + + /* Check for interrupt each 1s */ + if (i > 9) { + if (signal_pending(current)) + return -EINTR; + i = 0; + } + + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + value = FIELD_GET(ZL_WRITE_FLASH_OP, value); + + if (value == ZL_WRITE_FLASH_OP_DONE) + return 0; /* Successfully done */ + + msleep(ZL_FLASH_POLL_DELAY_MS); + } + + return -ETIMEDOUT; +} + +/** + * zl3073x_flash_cmd_wait - Perform flash operation and wait for finish + * @zldev: zl3073x device structure + * @operation: operation to perform + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000 /* up to 1 minute */ +#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000 /* up to 2 minutes */ + u8 value; + int rc; + + dev_dbg(zldev->dev, "Sending flash command: 0x%x\n", operation); + + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE1_TIMEOUT_MS); + if (rc) + return rc; + + /* Issue the requested operation */ + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + value &= ~ZL_WRITE_FLASH_OP; + value |= FIELD_PREP(ZL_WRITE_FLASH_OP, operation); + + rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value); + if (rc) + return rc; + + /* Wait for command completion */ + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS); + if (rc) + return rc; + + return zl3073x_flash_error_check(zldev, extack); +} + +/** + * zl3073x_flash_get_sector_size - Get flash sector size + * @zldev: zl3073x device structure + * @sector_size: sector size returned by the function + * + * The function reads the flash sector size detected by flash utility and + * stores it into @sector_size. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_get_sector_size(struct zl3073x_dev *zldev, size_t *sector_size) +{ + u8 flash_info; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_INFO, &flash_info); + if (rc) + return rc; + + switch (FIELD_GET(ZL_FLASH_INFO_SECTOR_SIZE, flash_info)) { + case ZL_FLASH_INFO_SECTOR_4K: + *sector_size = SZ_4K; + break; + case ZL_FLASH_INFO_SECTOR_64K: + *sector_size = SZ_64K; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/** + * zl3073x_flash_block - Download and flash memory block + * @zldev: zl3073x device structure + * @component: component name + * @operation: flash operation to perform + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function downloads the memory block given by the @data pointer and + * the size @size and flashes it into internal memory on flash page @page. + * The internal flash operation performed by the firmware is specified by + * the @operation parameter. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_block(struct zl3073x_dev *zldev, const char *component, + u32 operation, u32 page, u32 addr, const void *data, + size_t size, struct netlink_ext_ack *extack) +{ + int rc; + + /* Download block to device memory */ + rc = zl3073x_flash_download(zldev, component, addr, data, size, extack); + if (rc) + return rc; + + /* Set address to flash from */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr); + if (rc) + return rc; + + /* Set size of block to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size); + if (rc) + return rc; + + /* Set destination page to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page); + if (rc) + return rc; + + /* Set filling pattern */ + rc = zl3073x_write_u32(zldev, ZL_REG_FILL_PATTERN, U32_MAX); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, 0, + size); + + dev_dbg(zldev->dev, "Flashing %zu bytes to page %u\n", size, page); + + /* Execute sectors flash operation */ + rc = zl3073x_flash_cmd_wait(zldev, operation, extack); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size, + size); + + return 0; +} + +/** + * zl3073x_flash_sectors - Flash sectors + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting from page @page. The function uses sector flash + * method and has to take into account the flash sector size reported by + * flashing utility. Input data are spliced into blocks according this + * sector size and each block is flashed separately. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_MAX_BLOCK_SIZE 0x0001E000 +#define ZL_FLASH_PAGE_SIZE 256 + size_t max_block_size, block_size, sector_size; + const void *ptr, *end; + int rc; + + /* Get flash sector size */ + rc = zl3073x_flash_get_sector_size(zldev, §or_size); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size"); + return rc; + } + + /* Determine max block size depending on sector size */ + max_block_size = ALIGN_DOWN(ZL_FLASH_MAX_BLOCK_SIZE, sector_size); + + for (ptr = data, end = data + size; ptr < end; ptr += block_size) { + char comp_str[32]; + + block_size = min_t(size_t, max_block_size, end - ptr); + + /* Add suffix '-partN' if the requested component size is + * greater than max_block_size. + */ + if (max_block_size < size) + snprintf(comp_str, sizeof(comp_str), "%s-part%zu", + component, (ptr - data) / max_block_size + 1); + else + strscpy(comp_str, component); + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, comp_str, + ZL_WRITE_FLASH_OP_SECTORS, page, addr, + ptr, block_size, extack); + if (rc) + goto finish; + + /* Move to next page */ + page += block_size / ZL_FLASH_PAGE_SIZE; + } + +finish: + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page - Flash page + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting with page @page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, component, ZL_WRITE_FLASH_OP_PAGE, page, + addr, data, size, extack); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page_copy - Copy flash page + * @zldev: zl3073x device structure + * @component: component name + * @src_page: source page to copy + * @dst_page: destination page + * @extack: netlink extack pointer to report errors + * + * The function copies one flash page specified by @src_page into the flash + * page specified by @dst_page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Set source page to be copied */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page); + if (rc) + return rc; + + /* Set destination page for the copy */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page); + if (rc) + return rc; + + /* Perform copy operation */ + rc = zl3073x_flash_cmd_wait(zldev, ZL_WRITE_FLASH_OP_COPY_PAGE, extack); + if (rc) + ZL_FLASH_ERR_MSG(extack, "Failed to copy page %u to page %u", + src_page, dst_page); + + return rc; +} + +/** + * zl3073x_flash_mode_verify - Check flash utility + * @zldev: zl3073x device structure + * + * Return: 0 if the flash utility is ready, <0 on error + */ +static int +zl3073x_flash_mode_verify(struct zl3073x_dev *zldev) +{ + u8 family, release; + u32 hash; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_FLASH_HASH, &hash); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_FAMILY, &family); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_RELEASE, &release); + if (rc) + return rc; + + dev_dbg(zldev->dev, + "Flash utility check: hash 0x%08x, fam 0x%02x, rel 0x%02x\n", + hash, family, release); + + /* Return success for correct family */ + return (family == 0x21) ? 0 : -ENODEV; +} + +static int +zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev) +{ + u8 host_ctrl; + int rc; + + /* Enable host control */ + rc = zl3073x_read_u8(zldev, ZL_REG_HOST_CONTROL, &host_ctrl); + if (rc) + return rc; + + host_ctrl |= ZL_HOST_CONTROL_ENABLE; + + return zl3073x_write_u8(zldev, ZL_REG_HOST_CONTROL, host_ctrl); +} + +/** + * zl3073x_flash_mode_enter - Switch the device to flash mode + * @zldev: zl3073x device structure + * @util_ptr: buffer with flash utility + * @util_size: size of buffer with flash utility + * @extack: netlink extack pointer to report errors + * + * The function prepares and switches the device into flash mode. + * + * The procedure: + * 1) Stop device CPU by specific HW register sequence + * 2) Download flash utility to device memory + * 3) Resume device CPU by specific HW register sequence + * 4) Check communication with flash utility + * 5) Enable host control necessary to access flash API + * 6) Check for potential error detected by the utility + * + * The API provided by normal firmware is not available in flash mode + * so the caller has to ensure that this API is not used in this mode. + * + * After performing flash operation the caller should call + * @zl3073x_flash_mode_leave to return back to normal operation. + * + * Return: 0 on success, <0 on error. + */ +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack) +{ + /* Sequence to be written prior utility download */ + static const struct zl3073x_hwreg_seq_item pre_seq[] = { + HWREG_SEQ_ITEM(0x80000400, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80206340, 1, BIT(4), 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(2), 0), + HWREG_SEQ_ITEM(0x10000024, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(10), 1000), + }; + /* Sequence to be written after utility download */ + static const struct zl3073x_hwreg_seq_item post_seq[] = { + HWREG_SEQ_ITEM(0x10400004, 0x000000C0, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400008, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400010, 0x20000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400014, 0x20000004, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, GENMASK(10, 9), 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 0, BIT(0), 1000), + }; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Prepare flash mode", "utility", + 0, 0); + + /* Execure pre-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, pre_seq, ARRAY_SIZE(pre_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute pre-load sequence"); + goto error; + } + + /* Download utility image to device memory */ + rc = zl3073x_flash_download(zldev, "utility", 0x20000000, util_ptr, + util_size, extack); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot download flash utility"); + goto error; + } + + /* Execute post-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, post_seq, ARRAY_SIZE(post_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute post-load sequence"); + goto error; + } + + /* Check that utility identifies itself correctly */ + rc = zl3073x_flash_mode_verify(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "flash utility check failed"); + goto error; + } + + /* Enable host control */ + rc = zl3073x_flash_host_ctrl_enable(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot enable host control"); + goto error; + } + + zl3073x_devlink_flash_notify(zldev, "Flash mode enabled", "utility", + 0, 0); + + return 0; + +error: + zl3073x_flash_mode_leave(zldev, extack); + + return rc; +} + +/** + * zl3073x_flash_mode_leave - Leave flash mode + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function instructs the device to leave the flash mode and + * to return back to normal operation. + * + * The procedure: + * 1) Set reset flag + * 2) Reset the device CPU by specific HW register sequence + * 3) Wait for the device to be ready + * 4) Check the reset flag was cleared + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + /* Sequence to be written after flash */ + static const struct zl3073x_hwreg_seq_item fw_reset_seq[] = { + HWREG_SEQ_ITEM(0x80000404, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80000410, 1, BIT(0), 0), + }; + u8 reset_status; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Leaving flash mode", "utility", + 0, 0); + + /* Read reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Set reset bit */ + reset_status |= ZL_REG_RESET_STATUS_RESET; + + /* Update reset status register */ + rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status); + if (rc) + return rc; + + /* We do not check the return value here as the sequence resets + * the device CPU and the last write always return an error. + */ + zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq)); + + /* Wait for the device to be ready */ + msleep(500); + + /* Read again the reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Check the reset bit was cleared */ + if (reset_status & ZL_REG_RESET_STATUS_RESET) { + dev_err(zldev->dev, + "Reset not confirmed after switch to normal mode\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/dpll/zl3073x/flash.h b/drivers/dpll/zl3073x/flash.h new file mode 100644 index 0000000000..effe1b16b3 --- /dev/null +++ b/drivers/dpll/zl3073x/flash.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __ZL3073X_FLASH_H +#define __ZL3073X_FLASH_H + +#include + +struct netlink_ext_ack; +struct zl3073x_dev; + +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack); + +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack); + +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +#endif /* __ZL3073X_FLASH_H */ diff --git a/drivers/dpll/zl3073x/fw.c b/drivers/dpll/zl3073x/fw.c new file mode 100644 index 0000000000..55b638247f --- /dev/null +++ b/drivers/dpll/zl3073x/fw.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "flash.h" +#include "fw.h" + +#define ZL3073X_FW_ERR_PFX "FW load failed: " +#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg, \ + ## __VA_ARGS__) + +enum zl3073x_flash_type { + ZL3073X_FLASH_TYPE_NONE = 0, + ZL3073X_FLASH_TYPE_SECTORS, + ZL3073X_FLASH_TYPE_PAGE, + ZL3073X_FLASH_TYPE_PAGE_AND_COPY, +}; + +struct zl3073x_fw_component_info { + const char *name; + size_t max_size; + enum zl3073x_flash_type flash_type; + u32 load_addr; + u32 dest_page; + u32 copy_page; +}; + +static const struct zl3073x_fw_component_info component_info[] = { + [ZL_FW_COMPONENT_UTIL] = { + .name = "utility", + .max_size = 0x4000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_NONE, + }, + [ZL_FW_COMPONENT_FW1] = { + .name = "firmware1", + .max_size = 0x35000, + .load_addr = 0x20002000, + .flash_type = ZL3073X_FLASH_TYPE_SECTORS, + .dest_page = 0x020, + }, + [ZL_FW_COMPONENT_FW2] = { + .name = "firmware2", + .max_size = 0x0040, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e0, + .copy_page = 0x000, + }, + [ZL_FW_COMPONENT_FW3] = { + .name = "firmware3", + .max_size = 0x0248, + .load_addr = 0x20000400, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e4, + .copy_page = 0x004, + }, + [ZL_FW_COMPONENT_CFG0] = { + .name = "config0", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3d0, + }, + [ZL_FW_COMPONENT_CFG1] = { + .name = "config1", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3c0, + }, + [ZL_FW_COMPONENT_CFG2] = { + .name = "config2", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3b0, + }, + [ZL_FW_COMPONENT_CFG3] = { + .name = "config3", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3a0, + }, + [ZL_FW_COMPONENT_CFG4] = { + .name = "config4", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x390, + }, + [ZL_FW_COMPONENT_CFG5] = { + .name = "config5", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x380, + }, + [ZL_FW_COMPONENT_CFG6] = { + .name = "config6", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x370, + }, +}; + +/* Sanity check */ +static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS); + +/** + * zl3073x_fw_component_alloc - Alloc structure to hold firmware component + * @size: size of buffer to store data + * + * Return: pointer to allocated component structure or NULL on error. + */ +static struct zl3073x_fw_component * +zl3073x_fw_component_alloc(size_t size) +{ + struct zl3073x_fw_component *comp; + + comp = kzalloc(sizeof(*comp), GFP_KERNEL); + if (!comp) + return NULL; + + comp->size = size; + comp->data = kzalloc(size, GFP_KERNEL); + if (!comp->data) { + kfree(comp); + return NULL; + } + + return comp; +} + +/** + * zl3073x_fw_component_free - Free allocated component structure + * @comp: pointer to allocated component + */ +static void +zl3073x_fw_component_free(struct zl3073x_fw_component *comp) +{ + if (comp) + kfree(comp->data); + + kfree(comp); +} + +/** + * zl3073x_fw_component_id_get - Get ID for firmware component name + * @name: input firmware component name + * + * Return: + * - ZL3073X_FW_COMPONENT_* ID for known component name + * - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown + */ +static enum zl3073x_fw_component_id +zl3073x_fw_component_id_get(const char *name) +{ + enum zl3073x_fw_component_id id; + + for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++) + if (!strcasecmp(name, component_info[id].name)) + return id; + + return ZL_FW_COMPONENT_INVALID; +} + +/** + * zl3073x_fw_component_load - Load component from firmware source + * @zldev: zl3073x device structure + * @pcomp: pointer to loaded component + * @psrc: data pointer to load component from + * @psize: remaining bytes in buffer + * @extack: netlink extack pointer to report errors + * + * The function allocates single firmware component and loads the data from + * the buffer specified by @psrc and @psize. Pointer to allocated component + * is stored in output @pcomp. Source data pointer @psrc and remaining bytes + * @psize are updated accordingly. + * + * Return: + * * 1 when component was allocated and loaded + * * 0 when there is no component to load + * * <0 on error + */ +static ssize_t +zl3073x_fw_component_load(struct zl3073x_dev *zldev, + struct zl3073x_fw_component **pcomp, + const char **psrc, size_t *psize, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + struct zl3073x_fw_component *comp = NULL; + struct device *dev = zldev->dev; + enum zl3073x_fw_component_id id; + char buf[32], name[16]; + u32 count, size, *dest; + int pos, rc; + + /* Fetch image name and size from input */ + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%15s %u %n", name, &count, &pos); + if (!rc) { + /* No more data */ + return 0; + } else if (rc == 1 || count > U32_MAX / sizeof(u32)) { + ZL3073X_FW_ERR_MSG(extack, "invalid component size"); + return -EINVAL; + } + *psrc += pos; + *psize -= pos; + + dev_dbg(dev, "Firmware component '%s' found\n", name); + + id = zl3073x_fw_component_id_get(name); + if (id == ZL_FW_COMPONENT_INVALID) { + ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name); + return -EINVAL; + } + + info = &component_info[id]; + size = count * sizeof(u32); /* get size in bytes */ + + /* Check image size validity */ + if (size > component_info[id].max_size) { + ZL3073X_FW_ERR_MSG(extack, + "[%s] component is too big (%u bytes)\n", + info->name, size); + return -EINVAL; + } + + dev_dbg(dev, "Indicated component image size: %u bytes\n", size); + + /* Alloc component */ + comp = zl3073x_fw_component_alloc(size); + if (!comp) { + ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory"); + return -ENOMEM; + } + comp->id = id; + + /* Load component data from firmware source */ + for (dest = comp->data; count; count--, dest++) { + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%x %n", dest, &pos); + if (!rc) + goto err_data; + + *psrc += pos; + *psize -= pos; + } + + *pcomp = comp; + + return 1; + +err_data: + ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name); + + zl3073x_fw_component_free(comp); + + return -ENODATA; +} + +/** + * zl3073x_fw_free - Free allocated firmware + * @fw: firmware pointer + * + * The function frees existing firmware allocated by @zl3073x_fw_load. + */ +void zl3073x_fw_free(struct zl3073x_fw *fw) +{ + size_t i; + + if (!fw) + return; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) + zl3073x_fw_component_free(fw->component[i]); + + kfree(fw); +} + +/** + * zl3073x_fw_load - Load all components from source + * @zldev: zl3073x device structure + * @data: source buffer pointer + * @size: size of source buffer + * @extack: netlink extack pointer to report errors + * + * The functions allocate firmware structure and loads all components from + * the given buffer specified by @data and @size. + * + * Return: pointer to firmware on success, error pointer on error + */ +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *comp; + enum zl3073x_fw_component_id id; + struct zl3073x_fw *fw; + ssize_t rc; + + /* Allocate firmware structure */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); + if (!fw) + return ERR_PTR(-ENOMEM); + + do { + /* Load single component */ + rc = zl3073x_fw_component_load(zldev, &comp, &data, &size, + extack); + if (rc <= 0) + /* Everything was read or error occurred */ + break; + + id = comp->id; + + /* Report error if the given component is present twice + * or more. + */ + if (fw->component[id]) { + ZL3073X_FW_ERR_MSG(extack, + "duplicate component '%s' detected", + component_info[id].name); + zl3073x_fw_component_free(comp); + rc = -EINVAL; + break; + } + + fw->component[id] = comp; + } while (true); + + if (rc) { + /* Free allocated firmware in case of error */ + zl3073x_fw_free(fw); + return ERR_PTR(rc); + } + + return fw; +} + +/** + * zl3073x_fw_component_flash - Flash all components + * @zldev: zl3073x device structure + * @comp: pointer to components array + * @extack: netlink extack pointer to report errors + * + * Return: 0 in case of success or negative number otherwise. + */ +static int +zl3073x_fw_component_flash(struct zl3073x_dev *zldev, + struct zl3073x_fw_component *comp, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + int rc; + + info = &component_info[comp->id]; + + switch (info->flash_type) { + case ZL3073X_FLASH_TYPE_NONE: + /* Non-flashable component - used for utility */ + return 0; + case ZL3073X_FLASH_TYPE_SECTORS: + rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page, + info->load_addr, comp->data, + comp->size, extack); + break; + case ZL3073X_FLASH_TYPE_PAGE: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + break; + case ZL3073X_FLASH_TYPE_PAGE_AND_COPY: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + if (!rc) + rc = zl3073x_flash_page_copy(zldev, info->name, + info->dest_page, + info->copy_page, extack); + break; + } + if (rc) + ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'", + info->name); + + return rc; +} + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + int i, rc = 0; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) { + if (!zlfw->component[i]) + continue; /* Component is not present */ + + rc = zl3073x_fw_component_flash(zldev, zlfw->component[i], + extack); + if (rc) + break; + } + + return rc; +} diff --git a/drivers/dpll/zl3073x/fw.h b/drivers/dpll/zl3073x/fw.h new file mode 100644 index 0000000000..fcaa89ab07 --- /dev/null +++ b/drivers/dpll/zl3073x/fw.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_FW_H +#define _ZL3073X_FW_H + +/* + * enum zl3073x_fw_component_id - Identifiers for possible flash components + */ +enum zl3073x_fw_component_id { + ZL_FW_COMPONENT_INVALID = -1, + ZL_FW_COMPONENT_UTIL = 0, + ZL_FW_COMPONENT_FW1, + ZL_FW_COMPONENT_FW2, + ZL_FW_COMPONENT_FW3, + ZL_FW_COMPONENT_CFG0, + ZL_FW_COMPONENT_CFG1, + ZL_FW_COMPONENT_CFG2, + ZL_FW_COMPONENT_CFG3, + ZL_FW_COMPONENT_CFG4, + ZL_FW_COMPONENT_CFG5, + ZL_FW_COMPONENT_CFG6, + ZL_FW_NUM_COMPONENTS +}; + +/** + * struct zl3073x_fw_component - Firmware component + * @id: Flash component ID + * @size: Size of the buffer + * @data: Pointer to buffer with component data + */ +struct zl3073x_fw_component { + enum zl3073x_fw_component_id id; + size_t size; + void *data; +}; + +/** + * struct zl3073x_fw - Firmware bundle + * @component: firmware components array + */ +struct zl3073x_fw { + struct zl3073x_fw_component *component[ZL_FW_NUM_COMPONENTS]; +}; + +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack); +void zl3073x_fw_free(struct zl3073x_fw *fw); + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack); + +#endif /* _ZL3073X_FW_H */ diff --git a/drivers/dpll/zl3073x/i2c.c b/drivers/dpll/zl3073x/i2c.c new file mode 100644 index 0000000000..a7ac128008 --- /dev/null +++ b/drivers/dpll/zl3073x/i2c.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_i2c(client, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev, i2c_get_match_data(client)); +} + +static const struct i2c_device_id zl3073x_i2c_id[] = { + { + .name = "zl30731", + .driver_data = (kernel_ulong_t)&zl30731_chip_info, + }, + { + .name = "zl30732", + .driver_data = (kernel_ulong_t)&zl30732_chip_info, + }, + { + .name = "zl30733", + .driver_data = (kernel_ulong_t)&zl30733_chip_info, + }, + { + .name = "zl30734", + .driver_data = (kernel_ulong_t)&zl30734_chip_info, + }, + { + .name = "zl30735", + .driver_data = (kernel_ulong_t)&zl30735_chip_info, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id); + +static const struct of_device_id zl3073x_i2c_of_match[] = { + { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, + { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, + { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, + { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, + { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match); + +static struct i2c_driver zl3073x_i2c_driver = { + .driver = { + .name = "zl3073x-i2c", + .of_match_table = zl3073x_i2c_of_match, + }, + .probe_new = zl3073x_i2c_probe, + .id_table = zl3073x_i2c_id, +}; +module_i2c_driver(zl3073x_i2c_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x I2C driver"); +MODULE_IMPORT_NS(ZL3073X); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c new file mode 100644 index 0000000000..4cf7e8aefc --- /dev/null +++ b/drivers/dpll/zl3073x/prop.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "prop.h" + +/** + * zl3073x_pin_check_freq - verify frequency for given pin + * @zldev: pointer to zl3073x device + * @dir: pin direction + * @id: pin index + * @freq: frequency to check + * + * The function checks the given frequency is valid for the device. For input + * pins it checks that the frequency can be factorized using supported base + * frequencies. For output pins it checks that the frequency divides connected + * synth frequency without remainder. + * + * Return: true if the frequency is valid, false if not. + */ +static bool +zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir, + u8 id, u64 freq) +{ + if (freq > U32_MAX) + goto err_inv_freq; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + int rc; + + /* Check if the frequency can be factorized */ + rc = zl3073x_ref_freq_factorize(freq, NULL, NULL); + if (rc) + goto err_inv_freq; + } else { + u32 synth_freq; + u8 out, synth; + + /* Get output pin synthesizer */ + out = zl3073x_output_pin_out_get(id); + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + /* Check the frequency divides synth frequency */ + if (synth_freq % (u32)freq) + goto err_inv_freq; + } + + return true; + +err_inv_freq: + dev_warn(zldev->dev, + "Unsupported frequency %llu Hz in firmware node\n", freq); + + return false; +} + +/** + * zl3073x_prop_pin_package_label_set - get package label for the pin + * @zldev: pointer to zl3073x device + * @props: pointer to pin properties + * @dir: pin direction + * @id: pin index + * + * Generates package label string and stores it into pin properties structure. + * + * Possible formats: + * REF - differential input reference + * REFP & REFN - single-ended input reference (P or N pin) + * OUT - differential output + * OUTP & OUTN - single-ended output (P or N pin) + */ +static void +zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev, + struct zl3073x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + const char *prefix, *suffix; + bool is_diff; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + u8 ref; + + prefix = "REF"; + ref = zl3073x_input_pin_ref_get(id); + is_diff = zl3073x_ref_is_diff(zldev, ref); + } else { + u8 out; + + prefix = "OUT"; + out = zl3073x_output_pin_out_get(id); + is_diff = zl3073x_out_is_diff(zldev, out); + } + + if (!is_diff) + suffix = zl3073x_is_p_pin(id) ? "P" : "N"; + else + suffix = ""; /* No suffix for differential one */ + + snprintf(props->package_label, sizeof(props->package_label), "%s%u%s", + prefix, id / 2, suffix); + + /* Set package_label pointer in DPLL core properties to generated + * string. + */ + props->dpll_props.package_label = props->package_label; +} + +/** + * zl3073x_prop_pin_fwnode_get - get fwnode for given pin + * @zldev: pointer to zl3073x device + * @props: pointer to pin properties + * @dir: pin direction + * @id: pin index + * + * Return: 0 on success, -ENOENT if the firmware node does not exist + */ +static int +zl3073x_prop_pin_fwnode_get(struct zl3073x_dev *zldev, + struct zl3073x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + struct fwnode_handle *pins_node, *pin_node; + const char *node_name; + + if (dir == DPLL_PIN_DIRECTION_INPUT) + node_name = "input-pins"; + else + node_name = "output-pins"; + + /* Get node containing input or output pins */ + pins_node = device_get_named_child_node(zldev->dev, node_name); + if (!pins_node) { + dev_dbg(zldev->dev, "'%s' sub-node is missing\n", node_name); + return -ENOENT; + } + + /* Enumerate child pin nodes and find the requested one */ + fwnode_for_each_child_node(pins_node, pin_node) { + u32 reg; + + if (fwnode_property_read_u32(pin_node, "reg", ®)) + continue; + + if (id == reg) + break; + } + + /* Release pin parent node */ + fwnode_handle_put(pins_node); + + /* Save found node */ + props->fwnode = pin_node; + + dev_dbg(zldev->dev, "Firmware node for %s %sfound\n", + props->package_label, pin_node ? "" : "NOT "); + + return pin_node ? 0 : -ENOENT; +} + +/** + * zl3073x_pin_props_get - get pin properties + * @zldev: pointer to zl3073x device + * @dir: pin direction + * @index: pin index + * + * The function looks for firmware node for the given pin if it is provided + * by the system firmware (DT or ACPI), allocates pin properties structure, + * generates package label string according pin type and optionally fetches + * board label, connection type, supported frequencies and esync capability + * from the firmware node if it does exist. + * + * Pointer that is returned by this function should be freed using + * @zl3073x_pin_props_put(). + * + * Return: + * * pointer to allocated pin properties structure on success + * * error pointer in case of error + */ +struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, + enum dpll_pin_direction dir, + u8 index) +{ + struct dpll_pin_frequency *ranges; + struct zl3073x_pin_props *props; + int i, j, num_freqs, rc; + const char *type; + u64 *freqs; + + props = kzalloc(sizeof(*props), GFP_KERNEL); + if (!props) + return ERR_PTR(-ENOMEM); + + /* Set default pin type and capabilities */ + if (dir == DPLL_PIN_DIRECTION_INPUT) { + props->dpll_props.type = DPLL_PIN_TYPE_EXT; + props->dpll_props.capabilities = + DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + } else { + props->dpll_props.type = DPLL_PIN_TYPE_GNSS; + } + + props->dpll_props.phase_range.min = S32_MIN; + props->dpll_props.phase_range.max = S32_MAX; + + zl3073x_prop_pin_package_label_set(zldev, props, dir, index); + + /* Get firmware node for the given pin */ + rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index); + if (rc) + return props; /* Return if it does not exist */ + + /* Look for label property and store the value as board label */ + fwnode_property_read_string(props->fwnode, "label", + &props->dpll_props.board_label); + + /* Look for pin type property and translate its value to DPLL + * pin type enum if it is present. + */ + if (!fwnode_property_read_string(props->fwnode, "connection-type", + &type)) { + if (!strcmp(type, "ext")) + props->dpll_props.type = DPLL_PIN_TYPE_EXT; + else if (!strcmp(type, "gnss")) + props->dpll_props.type = DPLL_PIN_TYPE_GNSS; + else if (!strcmp(type, "int")) + props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR; + else if (!strcmp(type, "synce")) + props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT; + else + dev_warn(zldev->dev, + "Unknown or unsupported pin type '%s'\n", + type); + } + + /* Check if the pin supports embedded sync control */ + props->esync_control = fwnode_property_read_bool(props->fwnode, + "esync-control"); + + /* Read supported frequencies property if it is specified */ + num_freqs = fwnode_property_count_u64(props->fwnode, + "supported-frequencies-hz"); + if (num_freqs <= 0) + /* Return if the property does not exist or number is 0 */ + return props; + + /* The firmware node specifies list of supported frequencies while + * DPLL core pin properties requires list of frequency ranges. + * So read the frequency list into temporary array. + */ + freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL); + if (!freqs) { + rc = -ENOMEM; + goto err_alloc_freqs; + } + + /* Read frequencies list from firmware node */ + fwnode_property_read_u64_array(props->fwnode, + "supported-frequencies-hz", freqs, + num_freqs); + + /* Allocate frequency ranges list and fill it */ + ranges = kcalloc(num_freqs, sizeof(*ranges), GFP_KERNEL); + if (!ranges) { + rc = -ENOMEM; + goto err_alloc_ranges; + } + + /* Convert list of frequencies to list of frequency ranges but + * filter-out frequencies that are not representable by device + */ + for (i = 0, j = 0; i < num_freqs; i++) { + struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]); + + if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) { + ranges[j] = freq; + j++; + } + } + + /* Save number of freq ranges and pointer to them into pin properties */ + props->dpll_props.freq_supported = ranges; + props->dpll_props.freq_supported_num = j; + + /* Free temporary array */ + kfree(freqs); + + return props; + +err_alloc_ranges: + kfree(freqs); +err_alloc_freqs: + fwnode_handle_put(props->fwnode); + kfree(props); + + return ERR_PTR(rc); +} + +/** + * zl3073x_pin_props_put - release pin properties + * @props: pin properties to free + * + * The function deallocates given pin properties structure. + */ +void zl3073x_pin_props_put(struct zl3073x_pin_props *props) +{ + /* Free supported frequency ranges list if it is present */ + kfree(props->dpll_props.freq_supported); + + /* Put firmware handle if it is present */ + if (props->fwnode) + fwnode_handle_put(props->fwnode); + + kfree(props); +} + +/** + * zl3073x_prop_dpll_type_get - get DPLL channel type + * @zldev: pointer to zl3073x device + * @index: DPLL channel index + * + * Return: DPLL type for given DPLL channel + */ +enum dpll_type +zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index) +{ + const char *types[ZL3073X_MAX_CHANNELS]; + int count; + + /* Read dpll types property from firmware */ + count = device_property_read_string_array(zldev->dev, "dpll-types", + types, ARRAY_SIZE(types)); + + /* Return default if property or entry for given channel is missing */ + if (index >= count) + return DPLL_TYPE_PPS; + + if (!strcmp(types[index], "pps")) + return DPLL_TYPE_PPS; + else if (!strcmp(types[index], "eec")) + return DPLL_TYPE_EEC; + + dev_info(zldev->dev, "Unknown DPLL type '%s', using default\n", + types[index]); + + return DPLL_TYPE_PPS; /* Default */ +} diff --git a/drivers/dpll/zl3073x/prop.h b/drivers/dpll/zl3073x/prop.h new file mode 100644 index 0000000000..721a18f059 --- /dev/null +++ b/drivers/dpll/zl3073x/prop.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_PROP_H +#define _ZL3073X_PROP_H + +#include + +#include "core.h" + +struct fwnode_handle; + +/** + * struct zl3073x_pin_props - pin properties + * @fwnode: pin firmware node + * @dpll_props: DPLL core pin properties + * @package_label: pin package label + * @esync_control: embedded sync support + */ +struct zl3073x_pin_props { + struct fwnode_handle *fwnode; + struct dpll_pin_properties dpll_props; + char package_label[8]; + bool esync_control; +}; + +enum dpll_type zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index); + +struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, + enum dpll_pin_direction dir, + u8 index); + +void zl3073x_pin_props_put(struct zl3073x_pin_props *props); + +#endif /* _ZL3073X_PROP_H */ diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h new file mode 100644 index 0000000000..d837bee72b --- /dev/null +++ b/drivers/dpll/zl3073x/regs.h @@ -0,0 +1,317 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_REGS_H +#define _ZL3073X_REGS_H + +#include +#include + +/* + * Register address structure: + * =========================== + * 25 19 18 16 15 7 6 0 + * +------------------------------------------+ + * | max_offset | size | page | page_offset | + * +------------------------------------------+ + * + * page_offset ... <0x00..0x7F> + * page .......... HW page number + * size .......... register byte size (1, 2, 4 or 6) + * max_offset .... maximal offset for indexed registers + * (for non-indexed regs max_offset == page_offset) + */ + +#define ZL_REG_OFFSET_MASK GENMASK(6, 0) +#define ZL_REG_PAGE_MASK GENMASK(15, 7) +#define ZL_REG_SIZE_MASK GENMASK(18, 16) +#define ZL_REG_MAX_OFFSET_MASK GENMASK(25, 19) +#define ZL_REG_ADDR_MASK GENMASK(15, 0) + +#define ZL_REG_OFFSET(_reg) FIELD_GET(ZL_REG_OFFSET_MASK, _reg) +#define ZL_REG_PAGE(_reg) FIELD_GET(ZL_REG_PAGE_MASK, _reg) +#define ZL_REG_MAX_OFFSET(_reg) FIELD_GET(ZL_REG_MAX_OFFSET_MASK, _reg) +#define ZL_REG_SIZE(_reg) FIELD_GET(ZL_REG_SIZE_MASK, _reg) +#define ZL_REG_ADDR(_reg) FIELD_GET(ZL_REG_ADDR_MASK, _reg) + +/** + * ZL_REG_IDX - define indexed register + * @_idx: index of register to access + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * @_items: number of register indices + * @_stride: stride between items in bytes + * + * All parameters except @_idx should be constant. + */ +#define ZL_REG_IDX(_idx, _page, _offset, _size, _items, _stride) \ + (FIELD_PREP(ZL_REG_OFFSET_MASK, \ + (_offset) + (_idx) * (_stride)) | \ + FIELD_PREP_CONST(ZL_REG_PAGE_MASK, _page) | \ + FIELD_PREP_CONST(ZL_REG_SIZE_MASK, _size) | \ + FIELD_PREP_CONST(ZL_REG_MAX_OFFSET_MASK, \ + (_offset) + ((_items) - 1) * (_stride))) + +/** + * ZL_REG - define simple (non-indexed) register + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * + * All parameters should be constant. + */ +#define ZL_REG(_page, _offset, _size) \ + ZL_REG_IDX(0, _page, _offset, _size, 1, 0) + +/************************** + * Register Page 0, General + **************************/ + +#define ZL_REG_INFO ZL_REG(0, 0x00, 1) +#define ZL_INFO_READY BIT(7) + +#define ZL_REG_ID ZL_REG(0, 0x01, 2) +#define ZL_REG_REVISION ZL_REG(0, 0x03, 2) +#define ZL_REG_FW_VER ZL_REG(0, 0x05, 2) +#define ZL_REG_CUSTOM_CONFIG_VER ZL_REG(0, 0x07, 4) + +#define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1) +#define ZL_REG_RESET_STATUS_RESET BIT(0) + +/************************* + * Register Page 2, Status + *************************/ + +#define ZL_REG_REF_MON_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x02, 1, ZL3073X_NUM_REFS, 1) +#define ZL_REF_MON_STATUS_OK 0 /* all bits zeroed */ + +#define ZL_REG_DPLL_MON_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x10, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_MON_STATUS_STATE GENMASK(1, 0) +#define ZL_DPLL_MON_STATUS_STATE_ACQUIRING 0 +#define ZL_DPLL_MON_STATUS_STATE_LOCK 1 +#define ZL_DPLL_MON_STATUS_STATE_HOLDOVER 2 +#define ZL_DPLL_MON_STATUS_HO_READY BIT(2) + +#define ZL_REG_DPLL_REFSEL_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x30, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_REFSEL_STATUS_REFSEL GENMASK(3, 0) +#define ZL_DPLL_REFSEL_STATUS_STATE GENMASK(6, 4) +#define ZL_DPLL_REFSEL_STATUS_STATE_LOCK 4 + +#define ZL_REG_REF_FREQ(_idx) \ + ZL_REG_IDX(_idx, 2, 0x44, 4, ZL3073X_NUM_REFS, 4) + +/********************** + * Register Page 4, Ref + **********************/ + +#define ZL_REG_REF_PHASE_ERR_READ_RQST ZL_REG(4, 0x0f, 1) +#define ZL_REF_PHASE_ERR_READ_RQST_RD BIT(0) + +#define ZL_REG_REF_FREQ_MEAS_CTRL ZL_REG(4, 0x1c, 1) +#define ZL_REF_FREQ_MEAS_CTRL GENMASK(1, 0) +#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ 1 +#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF 2 +#define ZL_REF_FREQ_MEAS_CTRL_DPLL_FREQ_OFF 3 + +#define ZL_REG_REF_FREQ_MEAS_MASK_3_0 ZL_REG(4, 0x1d, 1) +#define ZL_REF_FREQ_MEAS_MASK_3_0(_ref) BIT(_ref) + +#define ZL_REG_REF_FREQ_MEAS_MASK_4 ZL_REG(4, 0x1e, 1) +#define ZL_REF_FREQ_MEAS_MASK_4(_ref) BIT((_ref) - 8) + +#define ZL_REG_DPLL_MEAS_REF_FREQ_CTRL ZL_REG(4, 0x1f, 1) +#define ZL_DPLL_MEAS_REF_FREQ_CTRL_EN BIT(0) +#define ZL_DPLL_MEAS_REF_FREQ_CTRL_IDX GENMASK(6, 4) + +#define ZL_REG_REF_PHASE(_idx) \ + ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6) + +/*********************** + * Register Page 5, DPLL + ***********************/ + +#define ZL_REG_DPLL_MODE_REFSEL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x04, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_MODE_REFSEL_MODE GENMASK(2, 0) +#define ZL_DPLL_MODE_REFSEL_MODE_FREERUN 0 +#define ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER 1 +#define ZL_DPLL_MODE_REFSEL_MODE_REFLOCK 2 +#define ZL_DPLL_MODE_REFSEL_MODE_AUTO 3 +#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 +#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) + +#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) +#define ZL_DPLL_MEAS_CTRL_EN BIT(0) +#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4) + +#define ZL_REG_DPLL_MEAS_IDX ZL_REG(5, 0x51, 1) +#define ZL_DPLL_MEAS_IDX GENMASK(2, 0) + +#define ZL_REG_DPLL_PHASE_ERR_READ_MASK ZL_REG(5, 0x54, 1) + +#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \ + ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6) + +/*********************************** + * Register Page 9, Synth and Output + ***********************************/ + +#define ZL_REG_SYNTH_CTRL(_idx) \ + ZL_REG_IDX(_idx, 9, 0x00, 1, ZL3073X_NUM_SYNTHS, 1) +#define ZL_SYNTH_CTRL_EN BIT(0) +#define ZL_SYNTH_CTRL_DPLL_SEL GENMASK(6, 4) + +#define ZL_REG_SYNTH_PHASE_SHIFT_CTRL ZL_REG(9, 0x1e, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_MASK ZL_REG(9, 0x1f, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_INTVL ZL_REG(9, 0x20, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_DATA ZL_REG(9, 0x21, 2) + +#define ZL_REG_OUTPUT_CTRL(_idx) \ + ZL_REG_IDX(_idx, 9, 0x28, 1, ZL3073X_NUM_OUTS, 1) +#define ZL_OUTPUT_CTRL_EN BIT(0) +#define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4) + +/******************************* + * Register Page 10, Ref Mailbox + *******************************/ + +#define ZL_REG_REF_MB_MASK ZL_REG(10, 0x02, 2) + +#define ZL_REG_REF_MB_SEM ZL_REG(10, 0x04, 1) +#define ZL_REF_MB_SEM_WR BIT(0) +#define ZL_REF_MB_SEM_RD BIT(1) + +#define ZL_REG_REF_FREQ_BASE ZL_REG(10, 0x05, 2) +#define ZL_REG_REF_FREQ_MULT ZL_REG(10, 0x07, 2) +#define ZL_REG_REF_RATIO_M ZL_REG(10, 0x09, 2) +#define ZL_REG_REF_RATIO_N ZL_REG(10, 0x0b, 2) + +#define ZL_REG_REF_CONFIG ZL_REG(10, 0x0d, 1) +#define ZL_REF_CONFIG_ENABLE BIT(0) +#define ZL_REF_CONFIG_DIFF_EN BIT(2) + +#define ZL_REG_REF_PHASE_OFFSET_COMP ZL_REG(10, 0x28, 6) + +#define ZL_REG_REF_SYNC_CTRL ZL_REG(10, 0x2e, 1) +#define ZL_REF_SYNC_CTRL_MODE GENMASK(2, 0) +#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0 +#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2 + +#define ZL_REG_REF_ESYNC_DIV ZL_REG(10, 0x30, 4) +#define ZL_REF_ESYNC_DIV_1HZ 0 + +/******************************** + * Register Page 12, DPLL Mailbox + ********************************/ + +#define ZL_REG_DPLL_MB_MASK ZL_REG(12, 0x02, 2) + +#define ZL_REG_DPLL_MB_SEM ZL_REG(12, 0x04, 1) +#define ZL_DPLL_MB_SEM_WR BIT(0) +#define ZL_DPLL_MB_SEM_RD BIT(1) + +#define ZL_REG_DPLL_REF_PRIO(_idx) \ + ZL_REG_IDX(_idx, 12, 0x52, 1, ZL3073X_NUM_REFS / 2, 1) +#define ZL_DPLL_REF_PRIO_REF_P GENMASK(3, 0) +#define ZL_DPLL_REF_PRIO_REF_N GENMASK(7, 4) +#define ZL_DPLL_REF_PRIO_MAX 14 +#define ZL_DPLL_REF_PRIO_NONE 15 + +/********************************* + * Register Page 13, Synth Mailbox + *********************************/ + +#define ZL_REG_SYNTH_MB_MASK ZL_REG(13, 0x02, 2) + +#define ZL_REG_SYNTH_MB_SEM ZL_REG(13, 0x04, 1) +#define ZL_SYNTH_MB_SEM_WR BIT(0) +#define ZL_SYNTH_MB_SEM_RD BIT(1) + +#define ZL_REG_SYNTH_FREQ_BASE ZL_REG(13, 0x06, 2) +#define ZL_REG_SYNTH_FREQ_MULT ZL_REG(13, 0x08, 4) +#define ZL_REG_SYNTH_FREQ_M ZL_REG(13, 0x0c, 2) +#define ZL_REG_SYNTH_FREQ_N ZL_REG(13, 0x0e, 2) + +/********************************** + * Register Page 14, Output Mailbox + **********************************/ +#define ZL_REG_OUTPUT_MB_MASK ZL_REG(14, 0x02, 2) + +#define ZL_REG_OUTPUT_MB_SEM ZL_REG(14, 0x04, 1) +#define ZL_OUTPUT_MB_SEM_WR BIT(0) +#define ZL_OUTPUT_MB_SEM_RD BIT(1) + +#define ZL_REG_OUTPUT_MODE ZL_REG(14, 0x05, 1) +#define ZL_OUTPUT_MODE_CLOCK_TYPE GENMASK(2, 0) +#define ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL 0 +#define ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC 1 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT GENMASK(7, 4) +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED 0 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS 1 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF 2 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM 3 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2 4 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P 5 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N 6 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_INV 7 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV 12 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV 15 + +#define ZL_REG_OUTPUT_DIV ZL_REG(14, 0x0c, 4) +#define ZL_REG_OUTPUT_WIDTH ZL_REG(14, 0x10, 4) +#define ZL_REG_OUTPUT_ESYNC_PERIOD ZL_REG(14, 0x14, 4) +#define ZL_REG_OUTPUT_ESYNC_WIDTH ZL_REG(14, 0x18, 4) +#define ZL_REG_OUTPUT_PHASE_COMP ZL_REG(14, 0x20, 4) + +/* + * Register Page 255 - HW registers access + */ +#define ZL_REG_HWREG_OP ZL_REG(0xff, 0x00, 1) +#define ZL_HWREG_OP_WRITE 0x28 +#define ZL_HWREG_OP_READ 0x29 +#define ZL_HWREG_OP_PENDING BIT(1) + +#define ZL_REG_HWREG_ADDR ZL_REG(0xff, 0x04, 4) +#define ZL_REG_HWREG_WRITE_DATA ZL_REG(0xff, 0x08, 4) +#define ZL_REG_HWREG_READ_DATA ZL_REG(0xff, 0x0c, 4) + +/* + * Registers available in flash mode + */ +#define ZL_REG_FLASH_HASH ZL_REG(0, 0x78, 4) +#define ZL_REG_FLASH_FAMILY ZL_REG(0, 0x7c, 1) +#define ZL_REG_FLASH_RELEASE ZL_REG(0, 0x7d, 1) + +#define ZL_REG_HOST_CONTROL ZL_REG(1, 0x02, 1) +#define ZL_HOST_CONTROL_ENABLE BIT(0) + +#define ZL_REG_IMAGE_START_ADDR ZL_REG(1, 0x04, 4) +#define ZL_REG_IMAGE_SIZE ZL_REG(1, 0x08, 4) +#define ZL_REG_FLASH_INDEX_READ ZL_REG(1, 0x0c, 4) +#define ZL_REG_FLASH_INDEX_WRITE ZL_REG(1, 0x10, 4) +#define ZL_REG_FILL_PATTERN ZL_REG(1, 0x14, 4) + +#define ZL_REG_WRITE_FLASH ZL_REG(1, 0x18, 1) +#define ZL_WRITE_FLASH_OP GENMASK(2, 0) +#define ZL_WRITE_FLASH_OP_DONE 0x0 +#define ZL_WRITE_FLASH_OP_SECTORS 0x2 +#define ZL_WRITE_FLASH_OP_PAGE 0x3 +#define ZL_WRITE_FLASH_OP_COPY_PAGE 0x4 + +#define ZL_REG_FLASH_INFO ZL_REG(2, 0x00, 1) +#define ZL_FLASH_INFO_SECTOR_SIZE GENMASK(3, 0) +#define ZL_FLASH_INFO_SECTOR_4K 0 +#define ZL_FLASH_INFO_SECTOR_64K 1 + +#define ZL_REG_ERROR_COUNT ZL_REG(2, 0x04, 4) +#define ZL_REG_ERROR_CAUSE ZL_REG(2, 0x08, 4) + +#define ZL_REG_OP_STATE ZL_REG(2, 0x14, 1) +#define ZL_OP_STATE_NO_COMMAND 0 +#define ZL_OP_STATE_PENDING 1 +#define ZL_OP_STATE_DONE 2 + +#endif /* _ZL3073X_REGS_H */ diff --git a/drivers/dpll/zl3073x/spi.c b/drivers/dpll/zl3073x/spi.c new file mode 100644 index 0000000000..00c5e3804c --- /dev/null +++ b/drivers/dpll/zl3073x/spi.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_spi(spi, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev, spi_get_device_match_data(spi)); +} + +static const struct spi_device_id zl3073x_spi_id[] = { + { + .name = "zl30731", + .driver_data = (kernel_ulong_t)&zl30731_chip_info + }, + { + .name = "zl30732", + .driver_data = (kernel_ulong_t)&zl30732_chip_info, + }, + { + .name = "zl30733", + .driver_data = (kernel_ulong_t)&zl30733_chip_info, + }, + { + .name = "zl30734", + .driver_data = (kernel_ulong_t)&zl30734_chip_info, + }, + { + .name = "zl30735", + .driver_data = (kernel_ulong_t)&zl30735_chip_info, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id); + +static const struct of_device_id zl3073x_spi_of_match[] = { + { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, + { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, + { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, + { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, + { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match); + +static struct spi_driver zl3073x_spi_driver = { + .driver = { + .name = "zl3073x-spi", + .of_match_table = zl3073x_spi_of_match, + }, + .probe = zl3073x_spi_probe, + .id_table = zl3073x_spi_id, +}; +module_spi_driver(zl3073x_spi_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x SPI driver"); +MODULE_IMPORT_NS(ZL3073X); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_main.c b/drivers/net/ethernet/marvell/octeon_ep/octep_main.c index 24499bb36c..bcea3fc26a 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_main.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_main.c @@ -1124,11 +1124,24 @@ static int octep_set_features(struct net_device *dev, netdev_features_t features return err; } +static bool octep_is_vf_valid(struct octep_device *oct, int vf) +{ + if (vf >= CFG_GET_ACTIVE_VFS(oct->conf)) { + netdev_err(oct->netdev, "Invalid VF ID %d\n", vf); + return false; + } + + return true; +} + static int octep_get_vf_config(struct net_device *dev, int vf, struct ifla_vf_info *ivi) { struct octep_device *oct = netdev_priv(dev); + if (!octep_is_vf_valid(oct, vf)) + return -EINVAL; + ivi->vf = vf; ether_addr_copy(ivi->mac, oct->vf_info[vf].mac_addr); ivi->spoofchk = true; @@ -1143,6 +1156,9 @@ static int octep_set_vf_mac(struct net_device *dev, int vf, u8 *mac) struct octep_device *oct = netdev_priv(dev); int err; + if (!octep_is_vf_valid(oct, vf)) + return -EINVAL; + if (!is_valid_ether_addr(mac)) { dev_err(&oct->pdev->dev, "Invalid MAC Address %pM\n", mac); return -EADDRNOTAVAIL; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c index 6163bc98d9..a22ecf1415 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c @@ -658,7 +658,7 @@ static void del_sw_hw_rule(struct fs_node *node) BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_ACTION) | BIT(MLX5_SET_FTE_MODIFY_ENABLE_MASK_FLOW_COUNTERS); fte->act_dests.action.action &= ~MLX5_FLOW_CONTEXT_ACTION_COUNT; - mlx5_fc_local_destroy(rule->dest_attr.counter); + mlx5_fc_local_put(rule->dest_attr.counter); goto out; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h index 0767239f65..d8b474cd9b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.h @@ -345,6 +345,7 @@ struct mlx5_fc { /* last{packets,bytes} are used for calculating deltas since last reading. */ u64 lastpackets; u64 lastbytes; + RH_KABI_EXTEND(refcount_t fc_local_refcount) }; struct mlx5_fc_bulk { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_counters.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_counters.c index 492775d3d1..83001eda38 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_counters.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_counters.c @@ -562,17 +562,36 @@ mlx5_fc_local_create(u32 counter_id, u32 offset, u32 bulk_size) counter->id = counter_id; fc_bulk->base_id = counter_id - offset; fc_bulk->fs_bulk.bulk_len = bulk_size; + refcount_set(&fc_bulk->hws_data.hws_action_refcount, 0); + mutex_init(&fc_bulk->hws_data.lock); counter->bulk = fc_bulk; + refcount_set(&counter->fc_local_refcount, 1); return counter; } EXPORT_SYMBOL(mlx5_fc_local_create); void mlx5_fc_local_destroy(struct mlx5_fc *counter) { - if (!counter || counter->type != MLX5_FC_TYPE_LOCAL) - return; - kfree(counter->bulk); kfree(counter); } EXPORT_SYMBOL(mlx5_fc_local_destroy); + +void mlx5_fc_local_get(struct mlx5_fc *counter) +{ + if (!counter || counter->type != MLX5_FC_TYPE_LOCAL) + return; + + refcount_inc(&counter->fc_local_refcount); +} + +void mlx5_fc_local_put(struct mlx5_fc *counter) +{ + if (!counter || counter->type != MLX5_FC_TYPE_LOCAL) + return; + + if (!refcount_dec_and_test(&counter->fc_local_refcount)) + return; + + mlx5_fc_local_destroy(counter); +} diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c index f1ecdba74e..839d71bd42 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/fs_hws_pools.c @@ -407,15 +407,21 @@ struct mlx5hws_action *mlx5_fc_get_hws_action(struct mlx5hws_context *ctx, { struct mlx5_fs_hws_create_action_ctx create_ctx; struct mlx5_fc_bulk *fc_bulk = counter->bulk; + struct mlx5hws_action *hws_action; create_ctx.hws_ctx = ctx; create_ctx.id = fc_bulk->base_id; create_ctx.actions_type = MLX5HWS_ACTION_TYP_CTR; - return mlx5_fs_get_hws_action(&fc_bulk->hws_data, &create_ctx); + mlx5_fc_local_get(counter); + hws_action = mlx5_fs_get_hws_action(&fc_bulk->hws_data, &create_ctx); + if (!hws_action) + mlx5_fc_local_put(counter); + return hws_action; } void mlx5_fc_put_hws_action(struct mlx5_fc *counter) { mlx5_fs_put_hws_action(&counter->bulk->hws_data); + mlx5_fc_local_put(counter); } diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 1847391da2..983a7f259f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -360,6 +360,18 @@ const struct spi_device_id *spi_get_device_id(const struct spi_device *sdev) } EXPORT_SYMBOL_GPL(spi_get_device_id); +const void *spi_get_device_match_data(const struct spi_device *sdev) +{ + const void *match; + + match = device_get_match_data(&sdev->dev); + if (match) + return match; + + return (const void *)spi_get_device_id(sdev)->driver_data; +} +EXPORT_SYMBOL_GPL(spi_get_device_match_data); + static int spi_match_device(struct device *dev, struct device_driver *drv) { const struct spi_device *spi = to_spi_device(dev); diff --git a/include/config/ZL3073X b/include/config/ZL3073X new file mode 100644 index 0000000000..e69de29bb2 diff --git a/include/config/ZL3073X_I2C b/include/config/ZL3073X_I2C new file mode 100644 index 0000000000..e69de29bb2 diff --git a/include/config/ZL3073X_SPI b/include/config/ZL3073X_SPI new file mode 100644 index 0000000000..e69de29bb2 diff --git a/include/config/auto.conf b/include/config/auto.conf index 2a2e81de50..a04a2987a6 100644 --- a/include/config/auto.conf +++ b/include/config/auto.conf @@ -27,6 +27,7 @@ CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y CONFIG_RTLBTCOEXIST=m CONFIG_NFT_NAT=m CONFIG_POLYNOMIAL=m +CONFIG_ZL3073X_SPI=m CONFIG_ZRAM_WRITEBACK=y CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y CONFIG_INPUT_KEYBOARD=y @@ -925,6 +926,7 @@ CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y CONFIG_CB710_DEBUG_ASSUMPTIONS=y CONFIG_SPLIT_PTLOCK_CPUS=4 CONFIG_SBITMAP=y +CONFIG_ZL3073X=m CONFIG_IMX_INTMUX=y CONFIG_MSPRO_BLOCK=m CONFIG_GPIO_XLP=m @@ -2949,6 +2951,7 @@ CONFIG_IPV6_OPTIMISTIC_DAD=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y CONFIG_NETFS_SUPPORT=m CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y +CONFIG_ZL3073X_I2C=m CONFIG_MD_AUTODETECT=y CONFIG_PANIC_ON_OOPS_VALUE=1 CONFIG_PLDMFW=y diff --git a/include/config/auto.conf.cmd b/include/config/auto.conf.cmd index 9149258258..5373ca957b 100644 --- a/include/config/auto.conf.cmd +++ b/include/config/auto.conf.cmd @@ -146,7 +146,6 @@ deps_config := \ fs/ext4/Kconfig \ fs/ext2/Kconfig \ fs/Kconfig \ - drivers/dpll/Kconfig \ drivers/hte/Kconfig \ drivers/most/Kconfig \ drivers/counter/Kconfig \ @@ -934,6 +933,8 @@ deps_config := \ drivers/pinctrl/aspeed/Kconfig \ drivers/pinctrl/actions/Kconfig \ drivers/pinctrl/Kconfig \ + drivers/dpll/zl3073x/Kconfig \ + drivers/dpll/Kconfig \ drivers/ptp/Kconfig \ drivers/pps/generators/Kconfig \ drivers/pps/clients/Kconfig \ diff --git a/include/generated/autoconf.h b/include/generated/autoconf.h index 84890473f2..2951ef62db 100644 --- a/include/generated/autoconf.h +++ b/include/generated/autoconf.h @@ -29,6 +29,7 @@ #define CONFIG_RTLBTCOEXIST_MODULE 1 #define CONFIG_NFT_NAT_MODULE 1 #define CONFIG_POLYNOMIAL_MODULE 1 +#define CONFIG_ZL3073X_SPI_MODULE 1 #define CONFIG_ZRAM_WRITEBACK 1 #define CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK 1 #define CONFIG_INPUT_KEYBOARD 1 @@ -927,6 +928,7 @@ #define CONFIG_CB710_DEBUG_ASSUMPTIONS 1 #define CONFIG_SPLIT_PTLOCK_CPUS 4 #define CONFIG_SBITMAP 1 +#define CONFIG_ZL3073X_MODULE 1 #define CONFIG_IMX_INTMUX 1 #define CONFIG_MSPRO_BLOCK_MODULE 1 #define CONFIG_GPIO_XLP_MODULE 1 @@ -2951,6 +2953,7 @@ #define CONFIG_ARCH_HAS_ELF_RANDOMIZE 1 #define CONFIG_NETFS_SUPPORT_MODULE 1 #define CONFIG_HAVE_FUNCTION_ARG_ACCESS_API 1 +#define CONFIG_ZL3073X_I2C_MODULE 1 #define CONFIG_MD_AUTODETECT 1 #define CONFIG_PANIC_ON_OOPS_VALUE 1 #define CONFIG_PLDMFW 1 diff --git a/include/linux/mlx5/fs.h b/include/linux/mlx5/fs.h index 939e58c2f3..fb5f98fcc7 100644 --- a/include/linux/mlx5/fs.h +++ b/include/linux/mlx5/fs.h @@ -308,6 +308,8 @@ struct mlx5_fc *mlx5_fc_create(struct mlx5_core_dev *dev, bool aging); void mlx5_fc_destroy(struct mlx5_core_dev *dev, struct mlx5_fc *counter); struct mlx5_fc *mlx5_fc_local_create(u32 counter_id, u32 offset, u32 bulk_size); void mlx5_fc_local_destroy(struct mlx5_fc *counter); +void mlx5_fc_local_get(struct mlx5_fc *counter); +void mlx5_fc_local_put(struct mlx5_fc *counter); u64 mlx5_fc_query_lastuse(struct mlx5_fc *counter); void mlx5_fc_query_cached(struct mlx5_fc *counter, u64 *bytes, u64 *packets, u64 *lastuse); diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 190f56fd3d..e5c0eb4947 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -1590,6 +1590,9 @@ extern void spi_unregister_device(struct spi_device *spi); extern const struct spi_device_id * spi_get_device_id(const struct spi_device *sdev); +extern const void * +spi_get_device_match_data(const struct spi_device *sdev); + static inline bool spi_transfer_is_last(struct spi_controller *ctlr, struct spi_transfer *xfer) { diff --git a/net/can/j1939/j1939-priv.h b/net/can/j1939/j1939-priv.h index 31a93cae51..81f58924b4 100644 --- a/net/can/j1939/j1939-priv.h +++ b/net/can/j1939/j1939-priv.h @@ -212,6 +212,7 @@ void j1939_priv_get(struct j1939_priv *priv); /* notify/alert all j1939 sockets bound to ifindex */ void j1939_sk_netdev_event_netdown(struct j1939_priv *priv); +void j1939_sk_netdev_event_unregister(struct j1939_priv *priv); int j1939_cancel_active_session(struct j1939_priv *priv, struct sock *sk); void j1939_tp_init(struct j1939_priv *priv); diff --git a/net/can/j1939/main.c b/net/can/j1939/main.c index a6fb89fa62..f235b5862d 100644 --- a/net/can/j1939/main.c +++ b/net/can/j1939/main.c @@ -381,6 +381,11 @@ static int j1939_netdev_notify(struct notifier_block *nb, j1939_sk_netdev_event_netdown(priv); j1939_ecu_unmap_all(priv); break; + case NETDEV_UNREGISTER: + j1939_cancel_active_session(priv, NULL); + j1939_sk_netdev_event_netdown(priv); + j1939_sk_netdev_event_unregister(priv); + break; } j1939_priv_put(priv); diff --git a/net/can/j1939/socket.c b/net/can/j1939/socket.c index c7cc36fde0..d49f5b1d6d 100644 --- a/net/can/j1939/socket.c +++ b/net/can/j1939/socket.c @@ -1296,6 +1296,55 @@ void j1939_sk_netdev_event_netdown(struct j1939_priv *priv) read_unlock_bh(&priv->j1939_socks_lock); } +void j1939_sk_netdev_event_unregister(struct j1939_priv *priv) +{ + struct sock *sk; + struct j1939_sock *jsk; + bool wait_rcu = false; + +rescan: /* The caller is holding a ref on this "priv" via j1939_priv_get_by_ndev(). */ + read_lock_bh(&priv->j1939_socks_lock); + list_for_each_entry(jsk, &priv->j1939_socks, list) { + /* Skip if j1939_jsk_add() is not called on this socket. */ + if (!(jsk->state & J1939_SOCK_BOUND)) + continue; + sk = &jsk->sk; + sock_hold(sk); + read_unlock_bh(&priv->j1939_socks_lock); + /* Check if j1939_jsk_del() is not yet called on this socket after holding + * socket's lock, for both j1939_sk_bind() and j1939_sk_release() call + * j1939_jsk_del() with socket's lock held. + */ + lock_sock(sk); + if (jsk->state & J1939_SOCK_BOUND) { + /* Neither j1939_sk_bind() nor j1939_sk_release() called j1939_jsk_del(). + * Make this socket no longer bound, by pretending as if j1939_sk_bind() + * dropped old references but did not get new references. + */ + j1939_jsk_del(priv, jsk); + j1939_local_ecu_put(priv, jsk->addr.src_name, jsk->addr.sa); + j1939_netdev_stop(priv); + /* Call j1939_priv_put() now and prevent j1939_sk_sock_destruct() from + * calling the corresponding j1939_priv_put(). + * + * j1939_sk_sock_destruct() is supposed to call j1939_priv_put() after + * an RCU grace period. But since the caller is holding a ref on this + * "priv", we can defer synchronize_rcu() until immediately before + * the caller calls j1939_priv_put(). + */ + j1939_priv_put(priv); + jsk->priv = NULL; + wait_rcu = true; + } + release_sock(sk); + sock_put(sk); + goto rescan; + } + read_unlock_bh(&priv->j1939_socks_lock); + if (wait_rcu) + synchronize_rcu(); +} + static int j1939_sk_no_ioctlcmd(struct socket *sock, unsigned int cmd, unsigned long arg) { diff --git a/redhat/configs/common/generic/CONFIG_ZL3073X b/redhat/configs/common/generic/CONFIG_ZL3073X new file mode 100644 index 0000000000..5db9d954a7 --- /dev/null +++ b/redhat/configs/common/generic/CONFIG_ZL3073X @@ -0,0 +1 @@ +CONFIG_ZL3073X=m diff --git a/redhat/configs/common/generic/CONFIG_ZL3073X_I2C b/redhat/configs/common/generic/CONFIG_ZL3073X_I2C new file mode 100644 index 0000000000..d0f1ec708f --- /dev/null +++ b/redhat/configs/common/generic/CONFIG_ZL3073X_I2C @@ -0,0 +1 @@ +CONFIG_ZL3073X_I2C=m diff --git a/redhat/configs/common/generic/CONFIG_ZL3073X_SPI b/redhat/configs/common/generic/CONFIG_ZL3073X_SPI new file mode 100644 index 0000000000..0ee00074e1 --- /dev/null +++ b/redhat/configs/common/generic/CONFIG_ZL3073X_SPI @@ -0,0 +1 @@ +CONFIG_ZL3073X_SPI=m diff --git a/redhat/configs/common/generic/s390x/CONFIG_ZL3073X b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X new file mode 100644 index 0000000000..2cea51a6a1 --- /dev/null +++ b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X @@ -0,0 +1 @@ +# CONFIG_ZL3073X is not set diff --git a/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_I2C b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_I2C new file mode 100644 index 0000000000..68671bd8d1 --- /dev/null +++ b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_I2C @@ -0,0 +1 @@ +# CONFIG_ZL3073X_I2C is not set diff --git a/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_SPI b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_SPI new file mode 100644 index 0000000000..97254f72dc --- /dev/null +++ b/redhat/configs/common/generic/s390x/CONFIG_ZL3073X_SPI @@ -0,0 +1 @@ +# CONFIG_ZL3073X_SPI is not set diff --git a/redhat/configs/common/generic/x86/CONFIG_I2C_MUX_PCA954x b/redhat/configs/common/generic/x86/CONFIG_I2C_MUX_PCA954x new file mode 100644 index 0000000000..24c8bfd0fc --- /dev/null +++ b/redhat/configs/common/generic/x86/CONFIG_I2C_MUX_PCA954x @@ -0,0 +1 @@ +CONFIG_I2C_MUX_PCA954x=m diff --git a/redhat/kernel.changelog-9.7 b/redhat/kernel.changelog-9.7 index 57df71857e..fe66e01435 100644 --- a/redhat/kernel.changelog-9.7 +++ b/redhat/kernel.changelog-9.7 @@ -1,3 +1,45 @@ +* Sat Nov 29 2025 CKI KWF Bot [5.14.0-611.13.1.el9_7] +- can: j1939: add missing calls in NETDEV_UNREGISTER notification handler (CKI Backport Bot) [RHEL-124105] {CVE-2025-39925} +- can: j1939: implement NETDEV_UNREGISTER notification handler (CKI Backport Bot) [RHEL-124105] {CVE-2025-39925} +Resolves: RHEL-124105 + +* Thu Nov 27 2025 CKI KWF Bot [5.14.0-611.12.1.el9_7] +- x86/hyperv: Fix kdump on Azure CVMs (Li Tian) [RHEL-129776] +- net/mlx5: fs, fix UAF in flow counter release (Michal Schmidt) [RHEL-124428] {CVE-2025-39979} +- octeon_ep: Validate the VF ID (Kamal Heib) [RHEL-117604] +- dpll: zl3073x: fix kernel-doc name and missing parameter in fw.c (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Fix output pin registration (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Increase maximum size of flash utility (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Fix double free in zl3073x_devlink_flash_update() (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Implement devlink flash callback (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Add firmware loading functionality (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Add low-level flash functions (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Add functions to access hardware registers (Ivan Vecera) [RHEL-116162] +- dpll: zl3073x: Handle missing or corrupted flash configuration (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Refactor DPLL initialization (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: ZL3073X_I2C and ZL3073X_SPI should depend on NET (Ivan Vecera) [RHEL-113083] +- dpll: Make ZL3073X invisible (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Fix build failure (Ivan Vecera) [RHEL-113083] +- redhat/configs: enable CONFIG_ZL3073X* (Ivan Vecera) [RHEL-113083] +- redhat/configs: enable CONFIG_I2C_MUX_PCA954x on x86 (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to get fractional frequency offset (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to adjust phase (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Implement phase offset monitor feature (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to get phase offset on connected input pin (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to get/set esync on pins (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to get/set frequency on pins (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Implement input pin state setting in automatic mode (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Add support to get/set priority on input pins (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Implement input pin selection in manual mode (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Register DPLL devices and pins (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Read DPLL types and pin properties from system firmware (Ivan Vecera) [RHEL-113083] +- dpll: zl3073x: Fetch invariants during probe (Ivan Vecera) [RHEL-113083] +- dpll: Add basic Microchip ZL3073x support (Ivan Vecera) [RHEL-113083] +- spi: Introduce spi_get_device_match_data() helper (Ivan Vecera) [RHEL-113083] +- dt-bindings: dpll: Add support for Microchip Azurite chip family (Ivan Vecera) [RHEL-113083] +- dt-bindings: dpll: Add DPLL device and pin (Ivan Vecera) [RHEL-113083] +Resolves: RHEL-113083, RHEL-116162, RHEL-117604, RHEL-124428, RHEL-129776 + * Tue Nov 25 2025 CKI KWF Bot [5.14.0-611.11.1.el9_7] - tcp: Don't call reqsk_fastopen_remove() in tcp_conn_request(). (Antoine Tenart) [RHEL-120668] - tcp: Clear tcp_sk(sk)->fastopen_rsk in tcp_disconnect(). (Antoine Tenart) [RHEL-120668] {CVE-2025-39955}