From patchwork Sun Mar 12 02:28:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171098 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 96FB1C74A44 for ; Sun, 12 Mar 2023 02:28:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229768AbjCLC2w (ORCPT ); Sat, 11 Mar 2023 21:28:52 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35040 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229996AbjCLC2s (ORCPT ); Sat, 11 Mar 2023 21:28:48 -0500 Received: from mx0a-00082601.pphosted.com (mx0a-00082601.pphosted.com [67.231.145.42]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7DA8A3402E; Sat, 11 Mar 2023 18:28:45 -0800 (PST) Received: from pps.filterd (m0109333.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32C2HZfX000640; Sat, 11 Mar 2023 18:28:24 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=+y1+/RoW91etQaOVEUUeBRGbdv9GXi5UUEQ1ZDZIGbE=; b=HNw0Q7MAqIWon5VTY5iUYst9V4GZdzo0FTZPRpsldVl68qOwOS50Uxn4vEFtUY2M6I9E gPuCAH8zfEM7ivbuS7yvbrCXb+YP37NF4ylhoI37myKbZEl0ZYmw+6JFxbi12SY1ACka ra0Bk4gn9VSDHH5fQ+6ZM2xmLtqPqsfbPnSdOehu7r3Kn9RYxbIJIsKhMMybSHgjutKy fRWUiZgwGkVlC7G+0eXauDg2dUDp9AE2hvDORZeFjZ5cAw4L6iz6kOGEIWZk1pieroAa NdIWOqUgSflL9jpMOHvyIr/MGnjmPw3BX7UmApD6eZd8UAW+mbMyci8BnwJRZl2R8++/ gA== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8pjst0vv-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:24 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub103.TheFacebook.com (2620:10d:c0a8:82::c) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:23 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:21 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , "Arkadiusz Kubalewski" , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , , , , , , "Michal Michalik" Subject: [PATCH RFC v6 1/6] dpll: spec: Add Netlink spec in YAML Date: Sat, 11 Mar 2023 18:28:02 -0800 Message-ID: <20230312022807.278528-2-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-ORIG-GUID: hfdvK4U4ARlPIW2UDiBgREACjjguKeOz X-Proofpoint-GUID: hfdvK4U4ARlPIW2UDiBgREACjjguKeOz X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC From: Arkadiusz Kubalewski Add a protocol spec for DPLL. Add code generated from the spec. Signed-off-by: Jakub Kicinski Signed-off-by: Michal Michalik Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Vadim Fedorenko --- Documentation/netlink/specs/dpll.yaml | 514 ++++++++++++++++++++++++++ drivers/dpll/dpll_nl.c | 126 +++++++ drivers/dpll/dpll_nl.h | 42 +++ include/uapi/linux/dpll.h | 196 ++++++++++ 4 files changed, 878 insertions(+) create mode 100644 Documentation/netlink/specs/dpll.yaml create mode 100644 drivers/dpll/dpll_nl.c create mode 100644 drivers/dpll/dpll_nl.h create mode 100644 include/uapi/linux/dpll.h diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml new file mode 100644 index 000000000000..03e9f0e2d3d2 --- /dev/null +++ b/Documentation/netlink/specs/dpll.yaml @@ -0,0 +1,514 @@ +name: dpll + +doc: DPLL subsystem. + +definitions: + - + type: const + name: temp-divider + value: 10 + - + type: const + name: pin-freq-1-hz + value: 1 + - + type: const + name: pin-freq-10-mhz + value: 10000000 + - + type: enum + name: lock-status + doc: | + Provides information of dpll device lock status, valid values for + DPLL_A_LOCK_STATUS attribute + entries: + - + name: unspec + doc: unspecified value + - + name: unlocked + doc: | + dpll was not yet locked to any valid (or is in one of modes: + DPLL_MODE_FREERUN, DPLL_MODE_NCO) + - + name: calibrating + doc: dpll is trying to lock to a valid signal + - + name: locked + doc: dpll is locked + - + name: holdover + doc: | + dpll is in holdover state - lost a valid lock or was forced by + selecting DPLL_MODE_HOLDOVER mode + render-max: true + - + type: enum + name: pin-type + doc: Enumerates types of a pin, valid values for DPLL_A_PIN_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: mux + doc: aggregates another layer of selectable pins + - + name: ext + doc: external source + - + name: synce-eth-port + doc: ethernet port PHY's recovered clock + - + name: int-oscillator + doc: device internal oscillator + - + name: gnss + doc: GNSS recovered clock + render-max: true + - + type: enum + name: pin-state + doc: available pin modes + entries: + - + name: unspec + doc: unspecified value + - + name: connected + doc: pin connected + - + name: disconnected + doc: pin disconnected + render-max: true + - + type: enum + name: pin-direction + doc: available pin direction + entries: + - + name: unspec + doc: unspecified value + - + name: source + doc: pin used as a source of a signal + - + name: output + doc: pin used to output the signal + render-max: true + - + type: enum + name: mode + doc: | + working-modes a dpll can support, differentiate if and how dpll selects + one of its sources to syntonize with it + entries: + - + name: unspec + doc: unspecified value + - + name: manual + doc: source can be only selected by sending a request to dpll + - + name: automatic + doc: highest prio, valid source, auto selected by dpll + - + name: holdover + doc: dpll forced into holdover mode + - + name: freerun + doc: dpll driven on system clk, no holdover available + - + name: nco + doc: dpll driven by Numerically Controlled Oscillator + render-max: true + - + type: enum + name: type + doc: type of dpll, valid values for DPLL_A_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: pps + doc: dpll produces Pulse-Per-Second signal + - + name: eec + doc: dpll drives the Ethernet Equipment Clock + render-max: true + - + type: enum + name: event + doc: events of dpll generic netlink family + entries: + - + name: unspec + doc: invalid event type + - + name: device-create + doc: dpll device created + - + name: device-delete + doc: dpll device deleted + - + name: device-change + doc: | + attribute of dpll device or pin changed, reason is to be found with + an attribute type (DPLL_A_*) received with the event + - + type: flags + name: pin-caps + doc: define capabilities of a pin + entries: + - + name: direction-can-change + - + name: priority-can-change + - + name: state-can-change + + +attribute-sets: + - + name: dpll + enum-name: dplla + attributes: + - + name: device + type: nest + value: 1 + multi-attr: true + nested-attributes: device + - + name: id + type: u32 + - + name: dev-name + type: string + - + name: bus-name + type: string + - + name: mode + type: u8 + enum: mode + - + name: mode-supported + type: u8 + enum: mode + multi-attr: true + - + name: source-pin-idx + type: u32 + - + name: lock-status + type: u8 + enum: lock-status + - + name: temp + type: s32 + - + name: clock-id + type: u64 + - + name: type + type: u8 + enum: type + - + name: pin + type: nest + multi-attr: true + nested-attributes: pin + value: 12 + - + name: pin-idx + type: u32 + - + name: pin-description + type: string + - + name: pin-type + type: u8 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + multi-attr: true + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u8 + enum: pin-state + - + name: pin-parent + type: nest + multi-attr: true + nested-attributes: pin + value: 23 + - + name: pin-parent-idx + type: u32 + - + name: pin-rclk-device + type: string + - + name: pin-dpll-caps + type: u32 + - + name: device + subset-of: dpll + attributes: + - + name: id + type: u32 + value: 2 + - + name: dev-name + type: string + - + name: bus-name + type: string + - + name: mode + type: u8 + enum: mode + - + name: mode-supported + type: u8 + enum: mode + multi-attr: true + - + name: source-pin-idx + type: u32 + - + name: lock-status + type: u8 + enum: lock-status + - + name: temp + type: s32 + - + name: clock-id + type: u64 + - + name: type + type: u8 + enum: type + - + name: pin + type: nest + value: 12 + multi-attr: true + nested-attributes: pin + - + name: pin-prio + type: u32 + value: 21 + - + name: pin-state + type: u8 + enum: pin-state + - + name: pin-dpll-caps + type: u32 + value: 26 + - + name: pin + subset-of: dpll + attributes: + - + name: device + type: nest + value: 1 + multi-attr: true + nested-attributes: device + - + name: pin-idx + type: u32 + value: 13 + - + name: pin-description + type: string + - + name: pin-type + type: u8 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + multi-attr: true + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u8 + enum: pin-state + - + name: pin-parent + type: nest + multi-attr: true + nested-attributes: pin-parent + value: 23 + - + name: pin-rclk-device + type: string + value: 25 + - + name: pin-dpll-caps + type: u32 + - + name: pin-parent + subset-of: dpll + attributes: + - + name: pin-state + type: u8 + value: 22 + enum: pin-state + - + name: pin-parent-idx + type: u32 + value: 24 + - + name: pin-rclk-device + type: string + + +operations: + list: + - + name: unspec + doc: unused + + - + name: device-get + doc: | + Get list of DPLL devices (dump) or attributes of a single dpll device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + post: dpll-post-doit + request: + attributes: + - id + - bus-name + - dev-name + reply: + attributes: + - device + + dump: + pre: dpll-pre-dumpit + post: dpll-post-dumpit + reply: + attributes: + - device + + - + name: device-set + doc: Set attributes for a DPLL device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + post: dpll-post-doit + request: + attributes: + - id + - bus-name + - dev-name + - mode + + - + name: pin-get + doc: | + Get list of pins and its attributes. + - dump request without any attributes given - list all the pins in the system + - dump request with target dpll - list all the pins registered with a given dpll device + - do request with target dpll and target pin - single pin attributes + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + post: dpll-pin-post-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + reply: + attributes: + - pin + + dump: + pre: dpll-pin-pre-dumpit + post: dpll-pin-post-dumpit + request: + attributes: + - id + - bus-name + - dev-name + reply: + attributes: + - pin + + - + name: pin-set + doc: Set attributes of a target pin + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + post: dpll-pin-post-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + - pin-frequency + - pin-direction + - pin-prio + - pin-parent-idx + - pin-state + +mcast-groups: + list: + - + name: monitor diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c new file mode 100644 index 000000000000..099d1e30ca7c --- /dev/null +++ b/drivers/dpll/dpll_nl.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "dpll_nl.h" + +#include + +/* DPLL_CMD_DEVICE_GET - do */ +static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_DEVICE_SET - do */ +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_MODE + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_MODE] = NLA_POLICY_MAX(NLA_U8, 5), +}; + +/* DPLL_CMD_PIN_GET - do */ +static const struct nla_policy dpll_pin_get_do_nl_policy[DPLL_A_PIN_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, +}; + +/* DPLL_CMD_PIN_GET - dump */ +static const struct nla_policy dpll_pin_get_dump_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_PIN_SET - do */ +static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_PARENT_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_FREQUENCY] = { .type = NLA_U32, }, + [DPLL_A_PIN_DIRECTION] = NLA_POLICY_MAX(NLA_U8, 2), + [DPLL_A_PIN_PRIO] = { .type = NLA_U32, }, + [DPLL_A_PIN_PARENT_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_STATE] = NLA_POLICY_MAX(NLA_U8, 2), +}; + +/* Ops table for dpll */ +static const struct genl_split_ops dpll_nl_ops[6] = { + { + .cmd = DPLL_CMD_DEVICE_GET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_get_doit, + .post_doit = dpll_post_doit, + .policy = dpll_device_get_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_DEVICE_GET, + .start = dpll_pre_dumpit, + .dumpit = dpll_nl_device_get_dumpit, + .done = dpll_post_dumpit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_DEVICE_SET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_set_doit, + .post_doit = dpll_post_doit, + .policy = dpll_device_set_nl_policy, + .maxattr = DPLL_A_MODE, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_get_doit, + .post_doit = dpll_pin_post_doit, + .policy = dpll_pin_get_do_nl_policy, + .maxattr = DPLL_A_PIN_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .start = dpll_pin_pre_dumpit, + .dumpit = dpll_nl_pin_get_dumpit, + .done = dpll_pin_post_dumpit, + .policy = dpll_pin_get_dump_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_PIN_SET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_set_doit, + .post_doit = dpll_pin_post_doit, + .policy = dpll_pin_set_nl_policy, + .maxattr = DPLL_A_PIN_PARENT_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group dpll_nl_mcgrps[] = { + [DPLL_NLGRP_MONITOR] = { "monitor", }, +}; + +struct genl_family dpll_nl_family __ro_after_init = { + .name = DPLL_FAMILY_NAME, + .version = DPLL_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = dpll_nl_ops, + .n_split_ops = ARRAY_SIZE(dpll_nl_ops), + .mcgrps = dpll_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(dpll_nl_mcgrps), +}; diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h new file mode 100644 index 000000000000..3a354dfb9a31 --- /dev/null +++ b/drivers/dpll/dpll_nl.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_DPLL_GEN_H +#define _LINUX_DPLL_GEN_H + +#include +#include + +#include + +int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +void +dpll_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +void +dpll_pin_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_pre_dumpit(struct netlink_callback *cb); +int dpll_pin_pre_dumpit(struct netlink_callback *cb); +int dpll_post_dumpit(struct netlink_callback *cb); +int dpll_pin_post_dumpit(struct netlink_callback *cb); + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + DPLL_NLGRP_MONITOR, +}; + +extern struct genl_family dpll_nl_family; + +#endif /* _LINUX_DPLL_GEN_H */ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h new file mode 100644 index 000000000000..ece6fe685d08 --- /dev/null +++ b/include/uapi/linux/dpll.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_DPLL_H +#define _UAPI_LINUX_DPLL_H + +#define DPLL_FAMILY_NAME "dpll" +#define DPLL_FAMILY_VERSION 1 + +#define DPLL_TEMP_DIVIDER 10 +#define DPLL_PIN_FREQ_1_HZ 1 +#define DPLL_PIN_FREQ_10_MHZ 10000000 + +/** + * enum dpll_lock_status - Provides information of dpll device lock status, + * valid values for DPLL_A_LOCK_STATUS attribute + * @DPLL_LOCK_STATUS_UNSPEC: unspecified value + * @DPLL_LOCK_STATUS_UNLOCKED: dpll was not yet locked to any valid (or is in + * one of modes: DPLL_MODE_FREERUN, DPLL_MODE_NCO) + * @DPLL_LOCK_STATUS_CALIBRATING: dpll is trying to lock to a valid signal + * @DPLL_LOCK_STATUS_LOCKED: dpll is locked + * @DPLL_LOCK_STATUS_HOLDOVER: dpll is in holdover state - lost a valid lock or + * was forced by selecting DPLL_MODE_HOLDOVER mode + */ +enum dpll_lock_status { + DPLL_LOCK_STATUS_UNSPEC, + DPLL_LOCK_STATUS_UNLOCKED, + DPLL_LOCK_STATUS_CALIBRATING, + DPLL_LOCK_STATUS_LOCKED, + DPLL_LOCK_STATUS_HOLDOVER, + + __DPLL_LOCK_STATUS_MAX, + DPLL_LOCK_STATUS_MAX = (__DPLL_LOCK_STATUS_MAX - 1) +}; + +/** + * enum dpll_pin_type - Enumerates types of a pin, valid values for + * DPLL_A_PIN_TYPE attribute + * @DPLL_PIN_TYPE_UNSPEC: unspecified value + * @DPLL_PIN_TYPE_MUX: aggregates another layer of selectable pins + * @DPLL_PIN_TYPE_EXT: external source + * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock + * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator + * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + */ +enum dpll_pin_type { + DPLL_PIN_TYPE_UNSPEC, + DPLL_PIN_TYPE_MUX, + DPLL_PIN_TYPE_EXT, + DPLL_PIN_TYPE_SYNCE_ETH_PORT, + DPLL_PIN_TYPE_INT_OSCILLATOR, + DPLL_PIN_TYPE_GNSS, + + __DPLL_PIN_TYPE_MAX, + DPLL_PIN_TYPE_MAX = (__DPLL_PIN_TYPE_MAX - 1) +}; + +/** + * enum dpll_pin_state - available pin modes + * @DPLL_PIN_STATE_UNSPEC: unspecified value + * @DPLL_PIN_STATE_CONNECTED: pin connected + * @DPLL_PIN_STATE_DISCONNECTED: pin disconnected + */ +enum dpll_pin_state { + DPLL_PIN_STATE_UNSPEC, + DPLL_PIN_STATE_CONNECTED, + DPLL_PIN_STATE_DISCONNECTED, + + __DPLL_PIN_STATE_MAX, + DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1) +}; + +/** + * enum dpll_pin_direction - available pin direction + * @DPLL_PIN_DIRECTION_UNSPEC: unspecified value + * @DPLL_PIN_DIRECTION_SOURCE: pin used as a source of a signal + * @DPLL_PIN_DIRECTION_OUTPUT: pin used to output the signal + */ +enum dpll_pin_direction { + DPLL_PIN_DIRECTION_UNSPEC, + DPLL_PIN_DIRECTION_SOURCE, + DPLL_PIN_DIRECTION_OUTPUT, + + __DPLL_PIN_DIRECTION_MAX, + DPLL_PIN_DIRECTION_MAX = (__DPLL_PIN_DIRECTION_MAX - 1) +}; + +/** + * enum dpll_mode - working-modes a dpll can support, differentiate if and how + * dpll selects one of its sources to syntonize with it + * @DPLL_MODE_UNSPEC: unspecified value + * @DPLL_MODE_MANUAL: source can be only selected by sending a request to dpll + * @DPLL_MODE_AUTOMATIC: highest prio, valid source, auto selected by dpll + * @DPLL_MODE_HOLDOVER: dpll forced into holdover mode + * @DPLL_MODE_FREERUN: dpll driven on system clk, no holdover available + * @DPLL_MODE_NCO: dpll driven by Numerically Controlled Oscillator + */ +enum dpll_mode { + DPLL_MODE_UNSPEC, + DPLL_MODE_MANUAL, + DPLL_MODE_AUTOMATIC, + DPLL_MODE_HOLDOVER, + DPLL_MODE_FREERUN, + DPLL_MODE_NCO, + + __DPLL_MODE_MAX, + DPLL_MODE_MAX = (__DPLL_MODE_MAX - 1) +}; + +/** + * enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute + * @DPLL_TYPE_UNSPEC: unspecified value + * @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal + * @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock + */ +enum dpll_type { + DPLL_TYPE_UNSPEC, + DPLL_TYPE_PPS, + DPLL_TYPE_EEC, + + __DPLL_TYPE_MAX, + DPLL_TYPE_MAX = (__DPLL_TYPE_MAX - 1) +}; + +/** + * enum dpll_event - events of dpll generic netlink family + * @DPLL_EVENT_UNSPEC: invalid event type + * @DPLL_EVENT_DEVICE_CREATE: dpll device created + * @DPLL_EVENT_DEVICE_DELETE: dpll device deleted + * @DPLL_EVENT_DEVICE_CHANGE: attribute of dpll device or pin changed, reason + * is to be found with an attribute type (DPLL_A_*) received with the event + */ +enum dpll_event { + DPLL_EVENT_UNSPEC, + DPLL_EVENT_DEVICE_CREATE, + DPLL_EVENT_DEVICE_DELETE, + DPLL_EVENT_DEVICE_CHANGE, +}; + +/** + * enum dpll_pin_caps - define capabilities of a pin + */ +enum dpll_pin_caps { + DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE = 1, + DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE = 2, + DPLL_PIN_CAPS_STATE_CAN_CHANGE = 4, +}; + +enum dplla { + DPLL_A_DEVICE = 1, + DPLL_A_ID, + DPLL_A_DEV_NAME, + DPLL_A_BUS_NAME, + DPLL_A_MODE, + DPLL_A_MODE_SUPPORTED, + DPLL_A_SOURCE_PIN_IDX, + DPLL_A_LOCK_STATUS, + DPLL_A_TEMP, + DPLL_A_CLOCK_ID, + DPLL_A_TYPE, + DPLL_A_PIN, + DPLL_A_PIN_IDX, + DPLL_A_PIN_DESCRIPTION, + DPLL_A_PIN_TYPE, + DPLL_A_PIN_DIRECTION, + DPLL_A_PIN_FREQUENCY, + DPLL_A_PIN_FREQUENCY_SUPPORTED, + DPLL_A_PIN_ANY_FREQUENCY_MIN, + DPLL_A_PIN_ANY_FREQUENCY_MAX, + DPLL_A_PIN_PRIO, + DPLL_A_PIN_STATE, + DPLL_A_PIN_PARENT, + DPLL_A_PIN_PARENT_IDX, + DPLL_A_PIN_RCLK_DEVICE, + DPLL_A_PIN_DPLL_CAPS, + + __DPLL_A_MAX, + DPLL_A_MAX = (__DPLL_A_MAX - 1) +}; + +enum { + DPLL_CMD_UNSPEC, + DPLL_CMD_DEVICE_GET, + DPLL_CMD_DEVICE_SET, + DPLL_CMD_PIN_GET, + DPLL_CMD_PIN_SET, + + __DPLL_CMD_MAX, + DPLL_CMD_MAX = (__DPLL_CMD_MAX - 1) +}; + +#define DPLL_MCGRP_MONITOR "monitor" + +#endif /* _UAPI_LINUX_DPLL_H */ From patchwork Sun Mar 12 02:28:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171100 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 592F1C74A4B for ; Sun, 12 Mar 2023 02:29:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230092AbjCLC3E (ORCPT ); Sat, 11 Mar 2023 21:29:04 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35770 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230048AbjCLC24 (ORCPT ); Sat, 11 Mar 2023 21:28:56 -0500 Received: from mx0a-00082601.pphosted.com (mx0a-00082601.pphosted.com [67.231.145.42]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2A52337B59; Sat, 11 Mar 2023 18:28:50 -0800 (PST) Received: from pps.filterd (m0109334.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32C1t84P007044; Sat, 11 Mar 2023 18:28:26 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=WPsDgeAtCvjGtX9crbAP7ull+4GUVUzgCssN9hhWiDY=; b=S8Yq3JQhQldtc3R8f0pVlbE0YCDrm38Za1IFYUlnKhtO1jRe75irqvA2HxT2ZwpUNsDw HRRn1lauG30mM5YhVaZTS6pH0JRdaJGEwAnyPno0FKzCn+ckWeQjQefTQpxemM1zMRkf Nteb4GmIHkhMfQspimezeHOus1HRRIXtcHZTR5D0ztm/MMpWWF6jsoU6W+6bRqf9oO95 obWRJBt+UpycBAm8JmiRLYWN0C/8R+ryW1sK2NQqGmRSe2C00l6xUDL4+gpNj/VS+hap NMHJCn/s5UnwFhN5kniEKMzK0DSx20tl7OWMfO9uvHDwQOnr21u58QThS9FqAgcyLC8e 8A== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8suap7p8-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:26 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub104.TheFacebook.com (2620:10d:c0a8:82::d) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:24 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:23 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , Vadim Fedorenko , , , , , , Milena Olech , "Michal Michalik" Subject: [PATCH RFC v6 2/6] dpll: Add DPLL framework base functions Date: Sat, 11 Mar 2023 18:28:03 -0800 Message-ID: <20230312022807.278528-3-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-GUID: 7q7wqjAWW9mDF567ob5R297I7ccpAawX X-Proofpoint-ORIG-GUID: 7q7wqjAWW9mDF567ob5R297I7ccpAawX X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC DPLL framework is used to represent and configure DPLL devices in systems. Each device that has DPLL and can configure sources and outputs can use this framework. Netlink interface is used to provide configuration data and to receive notification messages about changes in the configuration or status of DPLL device. Inputs and outputs of the DPLL device are represented as special objects which could be dynamically added to and removed from DPLL device. Changes: dpll: adapt changes after introduction of dpll yaml spec dpll: redesign after review comments, fix minor issues dpll: add get pin command dpll: _get/_put approach for creating and realesing pin or dpll objects dpll: lock access to dplls with global lock dpll: lock access to pins with global lock dpll: replace cookie with clock id dpll: add clock class Provide userspace with clock class value of DPLL with dpll device dump netlink request. Clock class is assigned by driver allocating a dpll device. Clock class values are defined as specified in: ITU-T G.8273.2/Y.1368.2 recommendation. dpll: follow one naming schema in dpll subsys dpll: fix dpll device naming scheme Fix dpll device naming scheme by use of new pattern. "dpll_%s_%d_%d", where: - %s - dev_name(parent) of parent device, - %d (1) - enum value of dpll type, - %d (2) - device index provided by parent device. dpll: remove description length parameter dpll: fix muxed/shared pin registration Let the kernel module to register a shared or muxed pin without finding it or its parent. Instead use a parent/shared pin description to find correct pin internally in dpll_core, simplifing a dpll API. dpll: move function comments to dpll_core.c, fix exports dpll: remove single-use helper functions dpll: merge device register with alloc dpll: lock and unlock mutex on dpll device release dpll: move dpll_type to uapi header dpll: rename DPLLA_DUMP_FILTER to DPLLA_FILTER dpll: rename dpll_pin_state to dpll_pin_mode dpll: rename DPLL_MODE_FORCED to DPLL_MODE_MANUAL dpll: remove DPLL_CHANGE_PIN_TYPE enum value Co-developed-by: Milena Olech Signed-off-by: Milena Olech Co-developed-by: Michal Michalik Signed-off-by: Michal Michalik Co-developed-by: Arkadiusz Kubalewski Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Vadim Fedorenko --- MAINTAINERS | 9 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/dpll/Kconfig | 7 + drivers/dpll/Makefile | 10 + drivers/dpll/dpll_core.c | 835 +++++++++++++++++++++++++++ drivers/dpll/dpll_core.h | 99 ++++ drivers/dpll/dpll_netlink.c | 1065 +++++++++++++++++++++++++++++++++++ drivers/dpll/dpll_netlink.h | 30 + include/linux/dpll.h | 284 ++++++++++ 10 files changed, 2342 insertions(+) create mode 100644 drivers/dpll/Kconfig create mode 100644 drivers/dpll/Makefile create mode 100644 drivers/dpll/dpll_core.c create mode 100644 drivers/dpll/dpll_core.h create mode 100644 drivers/dpll/dpll_netlink.c create mode 100644 drivers/dpll/dpll_netlink.h create mode 100644 include/linux/dpll.h diff --git a/MAINTAINERS b/MAINTAINERS index edd3d562beee..0222b19af545 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6289,6 +6289,15 @@ F: Documentation/networking/device_drivers/ethernet/freescale/dpaa2/switch-drive F: drivers/net/ethernet/freescale/dpaa2/dpaa2-switch* F: drivers/net/ethernet/freescale/dpaa2/dpsw* +DPLL CLOCK SUBSYSTEM +M: Vadim Fedorenko +M: Arkadiusz Kubalewski +L: netdev@vger.kernel.org +S: Maintained +F: drivers/dpll/* +F: include/net/dpll.h +F: include/uapi/linux/dpll.h + DRBD DRIVER M: Philipp Reisner M: Lars Ellenberg diff --git a/drivers/Kconfig b/drivers/Kconfig index 968bd0a6fd78..453df9e1210d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -241,4 +241,6 @@ source "drivers/peci/Kconfig" source "drivers/hte/Kconfig" +source "drivers/dpll/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 20b118dca999..9ffb554507ef 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -194,3 +194,4 @@ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_PECI) += peci/ obj-$(CONFIG_HTE) += hte/ obj-$(CONFIG_DRM_ACCEL) += accel/ +obj-$(CONFIG_DPLL) += dpll/ diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig new file mode 100644 index 000000000000..a4cae73f20d3 --- /dev/null +++ b/drivers/dpll/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Generic DPLL drivers configuration +# + +config DPLL + bool diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile new file mode 100644 index 000000000000..d3926f2a733d --- /dev/null +++ b/drivers/dpll/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for DPLL drivers. +# + +obj-$(CONFIG_DPLL) += dpll_sys.o +dpll_sys-y += dpll_core.o +dpll_sys-y += dpll_netlink.o +dpll_sys-y += dpll_nl.o + diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c new file mode 100644 index 000000000000..3fc151e16751 --- /dev/null +++ b/drivers/dpll/dpll_core.c @@ -0,0 +1,835 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dpll_core.c - Generic DPLL Management class support. + * + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "dpll_core.h" + +DEFINE_MUTEX(dpll_device_xa_lock); +DEFINE_MUTEX(dpll_pin_xa_lock); + +DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC); +DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC); + +#define ASSERT_DPLL_REGISTERED(d) \ + WARN_ON_ONCE(!xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) +#define ASSERT_DPLL_NOT_REGISTERED(d) \ + WARN_ON_ONCE(xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) + +static struct class dpll_class = { + .name = "dpll", +}; + +/** + * dpll_device_get_by_id - find dpll device by it's id + * @id: id of searched dpll + * + * Return: + * * dpll_device struct if found + * * NULL otherwise + */ +struct dpll_device *dpll_device_get_by_id(int id) +{ + struct dpll_device *dpll = NULL; + + if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED)) + dpll = xa_load(&dpll_device_xa, id); + + return dpll; +} + +/** + * dpll_device_get_by_name - find dpll device by it's id + * @bus_name: bus name of searched dpll + * @dev_name: dev name of searched dpll + * + * Return: + * * dpll_device struct if found + * * NULL otherwise + */ +struct dpll_device * +dpll_device_get_by_name(const char *bus_name, const char *device_name) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + xa_for_each_marked(&dpll_device_xa, index, dpll, DPLL_REGISTERED) { + if (!strcmp(dev_bus_name(&dpll->dev), bus_name) && + !strcmp(dev_name(&dpll->dev), device_name)) { + ret = dpll; + break; + } + } + + return ret; +} + +/** + * dpll_xa_ref_pin_add - add pin reference to a given xarray + * @xa_pins: dpll_pin_ref xarray holding pins + * @pin: pin being added + * @ops: ops for a pin + * @priv: pointer to private data of owner + * + * Allocate and create reference of a pin or increase refcount on existing pin + * reference on given xarray. + * + * Return: + * * 0 on success + * * -ENOMEM on failed allocation + */ +static int +dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref; + unsigned long i; + u32 idx; + int ret; + + xa_for_each(xa_pins, i, ref) { + if (ref->pin == pin) { + refcount_inc(&ref->refcount); + return 0; + } + } + + ref = kzalloc(sizeof(*ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->pin = pin; + ref->ops = ops; + ref->priv = priv; + ret = xa_alloc(xa_pins, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_set(&ref->refcount, 1); + else + kfree(ref); + + return ret; +} + +/** + * dpll_xa_ref_pin_del - remove reference of a pin from xarray + * @xa_pins: dpll_pin_ref xarray holding pins + * @pin: pointer to a pin + * + * Decrement refcount of existing pin reference on given xarray. + * If all references are dropped, delete the reference and free its memory. + * + * Return: + * * 0 on success + * * -EINVAL if reference to a pin was not found + */ +static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(xa_pins, i, ref) { + if (ref->pin == pin) { + if (refcount_dec_and_test(&ref->refcount)) { + xa_erase(xa_pins, i); + kfree(ref); + } + return 0; + } + } + + return -EINVAL; +} + +/** + * dpll_xa_ref_pin_find - find pin reference on xarray + * @xa_pins: dpll_pin_ref xarray holding pins + * @pin: pointer to a pin + * + * Search for pin reference struct of a given pin on given xarray. + * + * Return: + * * pin reference struct pointer on success + * * NULL - reference to a pin was not found + */ +struct dpll_pin_ref * +dpll_xa_ref_pin_find(struct xarray *xa_pins, const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(xa_pins, i, ref) { + if (ref->pin == pin) + return ref; + } + + return NULL; +} + +/** + * dpll_xa_ref_dpll_add - add dpll reference to a given xarray + * @xa_dplls: dpll_pin_ref xarray holding dplls + * @dpll: dpll being added + * @ops: pin-reference ops for a dpll + * @priv: pointer to private data of owner + * + * Allocate and create reference of a dpll-pin ops or increase refcount + * on existing dpll reference on given xarray. + * + * Return: + * * 0 on success + * * -ENOMEM on failed allocation + */ +static int +dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref; + unsigned long i; + u32 idx; + int ret; + + xa_for_each(xa_dplls, i, ref) { + if (ref->dpll == dpll) { + refcount_inc(&ref->refcount); + return 0; + } + } + ref = kzalloc(sizeof(*ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->dpll = dpll; + ref->ops = ops; + ref->priv = priv; + ret = xa_alloc(xa_dplls, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_set(&ref->refcount, 1); + else + kfree(ref); + + return ret; +} + +/** + * dpll_xa_ref_dpll_del - remove reference of a dpll from xarray + * @xa_dplls: dpll_pin_ref xarray holding dplls + * @dpll: pointer to a dpll to remove + * + * Decrement refcount of existing dpll reference on given xarray. + * If all references are dropped, delete the reference and free its memory. + */ +static void +dpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(xa_dplls, i, ref) { + if (ref->dpll == dpll) { + if (refcount_dec_and_test(&ref->refcount)) { + xa_erase(xa_dplls, i); + kfree(ref); + } + break; + } + } +} + +/** + * dpll_xa_ref_dpll_find - find dpll reference on xarray + * @xa_dplls: dpll_pin_ref xarray holding dplls + * @dpll: pointer to a dpll + * + * Search for dpll-pin ops reference struct of a given dpll on given xarray. + * + * Return: + * * pin reference struct pointer on success + * * NULL - reference to a pin was not found + */ +struct dpll_pin_ref * +dpll_xa_ref_dpll_find(struct xarray *xa_refs, const struct dpll_device *dpll) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(xa_refs, i, ref) { + if (ref->dpll == dpll) + return ref; + } + + return NULL; +} + + +/** + * dpll_device_alloc - allocate the memory for dpll device + * @clock_id: clock_id of creator + * @dev_driver_id: id given by dev driver + * @module: reference to registering module + * + * Allocates memory and initialize dpll device, hold its reference on global + * xarray. + * + * Return: + * * dpll_device struct pointer if succeeded + * * ERR_PTR(X) - failed allocation + */ +struct dpll_device * +dpll_device_alloc(const u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll; + int ret; + + dpll = kzalloc(sizeof(*dpll), GFP_KERNEL); + if (!dpll) + return ERR_PTR(-ENOMEM); + refcount_set(&dpll->refcount, 1); + dpll->dev.class = &dpll_class; + dpll->dev_driver_id = dev_driver_id; + dpll->clock_id = clock_id; + dpll->module = module; + ret = xa_alloc(&dpll_device_xa, &dpll->id, dpll, + xa_limit_16b, GFP_KERNEL); + if (ret) { + kfree(dpll); + return ERR_PTR(ret); + } + xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC); + + return dpll; +} + +/** + * dpll_device_get - find existing or create new dpll device + * @clock_id: clock_id of creator + * @dev_driver_id: id given by dev driver + * @module: reference to registering module + * + * Get existing object of a dpll device, unique for given arguments. + * Create new if doesn't exist yet. + * + * Return: + * * valid dpll_device struct pointer if succeeded + * * ERR_PTR of an error + */ +struct dpll_device * +dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each(&dpll_device_xa, index, dpll) { + if (dpll->clock_id == clock_id && + dpll->dev_driver_id == dev_driver_id && + dpll->module == module) { + ret = dpll; + refcount_inc(&ret->refcount); + break; + } + } + if (!ret) + ret = dpll_device_alloc(clock_id, dev_driver_id, module); + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_device_get); + +/** + * dpll_device_put - decrease the refcount and free memory if possible + * @dpll: dpll_device struct pointer + * + * Drop reference for a dpll device, if all references are gone, delete + * dpll device object. + */ +void dpll_device_put(struct dpll_device *dpll) +{ + if (!dpll) + return; + mutex_lock(&dpll_device_xa_lock); + if (refcount_dec_and_test(&dpll->refcount)) { + WARN_ON_ONCE(!xa_empty(&dpll->pin_refs)); + xa_destroy(&dpll->pin_refs); + xa_erase(&dpll_device_xa, dpll->id); + kfree(dpll); + } + mutex_unlock(&dpll_device_xa_lock); +} +EXPORT_SYMBOL_GPL(dpll_device_put); + +/** + * dpll_device_register - register the dpll device in the subsystem + * @dpll: pointer to a dpll + * @type: type of a dpll + * @ops: ops for a dpll device + * @priv: pointer to private information of owner + * @owner: pointer to owner device + * + * Make dpll device available for user space. + * + * Return: + * * 0 on success + * * -EINVAL on failure + */ +int dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + struct dpll_device_ops *ops, void *priv, + struct device *owner) +{ + if (WARN_ON(!ops || !owner)) + return -EINVAL; + if (WARN_ON(type <= DPLL_TYPE_UNSPEC || type > DPLL_TYPE_MAX)) + return -EINVAL; + mutex_lock(&dpll_device_xa_lock); + if (ASSERT_DPLL_NOT_REGISTERED(dpll)) { + mutex_unlock(&dpll_device_xa_lock); + return -EEXIST; + } + dpll->dev.bus = owner->bus; + dpll->parent = owner; + dpll->type = type; + dpll->ops = ops; + dev_set_name(&dpll->dev, "%s_%d", dev_name(owner), + dpll->dev_driver_id); + dpll->priv = priv; + xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); + mutex_unlock(&dpll_device_xa_lock); + dpll_notify_device_create(dpll); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_device_register); + +/** + * dpll_device_unregister - deregister dpll device + * @dpll: registered dpll pointer + * + * Deregister device, make it unavailable for userspace. + * Note: It does not free the memory + */ +void dpll_device_unregister(struct dpll_device *dpll) +{ + mutex_lock(&dpll_device_xa_lock); + ASSERT_DPLL_REGISTERED(dpll); + xa_clear_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); + mutex_unlock(&dpll_device_xa_lock); + dpll_notify_device_delete(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_unregister); + +/** + * dpll_pin_alloc - allocate the memory for dpll pin + * @clock_id: clock_id of creator + * @dev_driver_id: id given by dev driver + * @module: reference to registering module + * @prop: dpll pin properties + * + * Return: + * * valid allocated dpll_pin struct pointer if succeeded + * * ERR_PTR of an error + */ +struct dpll_pin * +dpll_pin_alloc(u64 clock_id, u8 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pin; + int ret; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return ERR_PTR(-ENOMEM); + pin->dev_driver_id = device_drv_id; + pin->clock_id = clock_id; + pin->module = module; + refcount_set(&pin->refcount, 1); + if (WARN_ON(!prop->description)) { + ret = -EINVAL; + goto release; + } + pin->prop.description = kstrdup(prop->description, GFP_KERNEL); + if (!pin->prop.description) { + ret = -ENOMEM; + goto release; + } + if (WARN_ON(prop->type <= DPLL_PIN_TYPE_UNSPEC || + prop->type > DPLL_PIN_TYPE_MAX)) { + ret = -EINVAL; + goto release; + } + pin->prop.type = prop->type; + pin->prop.capabilities = prop->capabilities; + pin->prop.freq_supported = prop->freq_supported; + pin->prop.any_freq_min = prop->any_freq_min; + pin->prop.any_freq_max = prop->any_freq_max; + xa_init_flags(&pin->dpll_refs, XA_FLAGS_ALLOC); + xa_init_flags(&pin->parent_refs, XA_FLAGS_ALLOC); + ret = xa_alloc(&dpll_pin_xa, &pin->idx, pin, + xa_limit_16b, GFP_KERNEL); +release: + if (ret) { + xa_destroy(&pin->dpll_refs); + xa_destroy(&pin->parent_refs); + kfree(pin->prop.description); + kfree(pin->rclk_dev_name); + kfree(pin); + return ERR_PTR(ret); + } + + return pin; +} + +/** + * dpll_pin_get - find existing or create new dpll pin + * @clock_id: clock_id of creator + * @dev_driver_id: id given by dev driver + * @module: reference to registering module + * @prop: dpll pin properties + * + * Get existing object of a pin (unique for given arguments) or create new + * if doesn't exist yet. + * + * Return: + * * valid allocated dpll_pin struct pointer if succeeded + * * ERR_PTR of an error + */ +struct dpll_pin * +dpll_pin_get(u64 clock_id, u32 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pos, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_pin_xa_lock); + xa_for_each(&dpll_pin_xa, index, pos) { + if (pos->clock_id == clock_id && + pos->dev_driver_id == device_drv_id && + pos->module == module) { + ret = pos; + refcount_inc(&ret->refcount); + break; + } + } + if (!ret) + ret = dpll_pin_alloc(clock_id, device_drv_id, module, prop); + mutex_unlock(&dpll_pin_xa_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_get); + +/** + * dpll_pin_put - decrease the refcount and free memory if possible + * @dpll: dpll_device struct pointer + * + * Drop reference for a pin, if all references are gone, delete pin object. + */ +void dpll_pin_put(struct dpll_pin *pin) +{ + if (!pin) + return; + mutex_lock(&dpll_pin_xa_lock); + if (refcount_dec_and_test(&pin->refcount)) { + xa_destroy(&pin->dpll_refs); + xa_destroy(&pin->parent_refs); + xa_erase(&dpll_pin_xa, pin->idx); + kfree(pin->prop.description); + kfree(pin->rclk_dev_name); + kfree(pin); + } + mutex_unlock(&dpll_pin_xa_lock); +} +EXPORT_SYMBOL_GPL(dpll_pin_put); + +static int +__dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + const char *rclk_device_name) +{ + int ret; + + if (rclk_device_name && !pin->rclk_dev_name) { + pin->rclk_dev_name = kstrdup(rclk_device_name, GFP_KERNEL); + if (!pin->rclk_dev_name) + return -ENOMEM; + } + ret = dpll_xa_ref_pin_add(&dpll->pin_refs, pin, ops, priv); + if (ret) + goto rclk_free; + ret = dpll_xa_ref_dpll_add(&pin->dpll_refs, dpll, ops, priv); + if (ret) + goto ref_pin_del; + else + dpll_pin_notify(dpll, pin, DPLL_A_PIN_IDX); + + return ret; + +ref_pin_del: + dpll_xa_ref_pin_del(&dpll->pin_refs, pin); +rclk_free: + kfree(pin->rclk_dev_name); + return ret; +} + +/** + * dpll_pin_register - register the dpll pin in the subsystem + * @dpll: pointer to a dpll + * @pin: pointer to a dpll pin + * @ops: ops for a dpll pin ops + * @priv: pointer to private information of owner + * @rclk_device: pointer to recovered clock device + * + * Return: + * * 0 on success + * * -EINVAL - missing dpll or pin + * * -ENOMEM - failed to allocate memory + */ +int +dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + const char *rclk_name = rclk_device ? dev_name(rclk_device) : NULL; + int ret; + + if (WARN_ON(!dpll)) + return -EINVAL; + if (WARN_ON(!pin)) + return -EINVAL; + + mutex_lock(&dpll_device_xa_lock); + mutex_lock(&dpll_pin_xa_lock); + ret = __dpll_pin_register(dpll, pin, ops, priv, rclk_name); + mutex_unlock(&dpll_pin_xa_lock); + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_register); + +static void +__dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin) +{ + dpll_xa_ref_pin_del(&dpll->pin_refs, pin); + dpll_xa_ref_dpll_del(&pin->dpll_refs, dpll); +} + +/** + * dpll_pin_unregister - deregister dpll pin from dpll device + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * + * Note: It does not free the memory + */ +int dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin) +{ + if (WARN_ON(xa_empty(&dpll->pin_refs))) + return -ENOENT; + + mutex_lock(&dpll_device_xa_lock); + mutex_lock(&dpll_pin_xa_lock); + __dpll_pin_unregister(dpll, pin); + mutex_unlock(&dpll_pin_xa_lock); + mutex_unlock(&dpll_device_xa_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_unregister); + +/** + * dpll_pin_on_pin_register - register a pin with a parent pin + * @parent: pointer to a parent pin + * @pin: pointer to a pin + * @ops: ops for a dpll pin + * @priv: pointer to private information of owner + * @rclk_device: pointer to recovered clock device + * + * Register a pin with a parent pin, create references between them and + * between newly registered pin and dplls connected with a parent pin. + * + * Return: + * * 0 on success + * * -EINVAL missing pin or parent + * * -ENOMEM failed allocation + * * -EPERM if parent is not allowed + */ +int +dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + struct dpll_pin_ref *ref; + unsigned long i, stop; + int ret; + + if (WARN_ON(!pin || !parent)) + return -EINVAL; + if (WARN_ON(parent->prop.type != DPLL_PIN_TYPE_MUX)) + return -EPERM; + mutex_lock(&dpll_pin_xa_lock); + ret = dpll_xa_ref_pin_add(&pin->parent_refs, parent, ops, priv); + if (ret) + goto unlock; + refcount_inc(&pin->refcount); + xa_for_each(&parent->dpll_refs, i, ref) { + mutex_lock(&dpll_device_xa_lock); + ret = __dpll_pin_register(ref->dpll, pin, ops, priv, + rclk_device ? + dev_name(rclk_device) : NULL); + mutex_unlock(&dpll_device_xa_lock); + if (ret) { + stop = i; + goto dpll_unregister; + } + dpll_pin_parent_notify(ref->dpll, pin, parent, DPLL_A_PIN_IDX); + } + mutex_unlock(&dpll_pin_xa_lock); + + return ret; + +dpll_unregister: + xa_for_each(&parent->dpll_refs, i, ref) { + if (i < stop) { + mutex_lock(&dpll_device_xa_lock); + __dpll_pin_unregister(ref->dpll, pin); + mutex_unlock(&dpll_device_xa_lock); + } + } + refcount_dec(&pin->refcount); + dpll_xa_ref_pin_del(&pin->parent_refs, parent); +unlock: + mutex_unlock(&dpll_pin_xa_lock); + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register); + +/** + * dpll_pin_on_pin_unregister - deregister dpll pin from a parent pin + * @parent: pointer to a parent pin + * @pin: pointer to a pin + * + * Note: It does not free the memory + */ +void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + mutex_lock(&dpll_device_xa_lock); + mutex_lock(&dpll_pin_xa_lock); + dpll_xa_ref_pin_del(&pin->parent_refs, parent); + refcount_dec(&pin->refcount); + xa_for_each(&pin->dpll_refs, i, ref) { + __dpll_pin_unregister(ref->dpll, pin); + dpll_pin_parent_notify(ref->dpll, pin, parent, + DPLL_A_PIN_IDX); + } + mutex_unlock(&dpll_pin_xa_lock); + mutex_unlock(&dpll_device_xa_lock); +} +EXPORT_SYMBOL_GPL(dpll_pin_on_pin_unregister); + +/** + * dpll_pin_get_by_idx - find a pin ref on dpll by pin index + * @dpll: dpll device pointer + * @idx: index of pin + * + * Find a reference to a pin registered with given dpll and return its pointer. + * + * Return: + * * valid pointer if pin was found + * * NULL if not found + */ +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, u32 idx) +{ + struct dpll_pin_ref *pos; + unsigned long i; + + xa_for_each(&dpll->pin_refs, i, pos) { + if (pos && pos->pin && pos->pin->dev_driver_id == idx) + return pos->pin; + } + + return NULL; +} + +/** + * dpll_priv - get the dpll device private owner data + * @dpll: registered dpll pointer + * + * Return: pointer to the data + */ +void *dpll_priv(const struct dpll_device *dpll) +{ + return dpll->priv; +} +EXPORT_SYMBOL_GPL(dpll_priv); + +/** + * dpll_pin_on_dpll_priv - get the dpll device private owner data + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * + * Return: pointer to the data + */ +void *dpll_pin_on_dpll_priv(const struct dpll_device *dpll, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + + ref = dpll_xa_ref_pin_find((struct xarray *)&dpll->pin_refs, pin); + if (!ref) + return NULL; + + return ref->priv; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_dpll_priv); + +/** + * dpll_pin_on_pin_priv - get the dpll pin private owner data + * @parent: pointer to a parent pin + * @pin: pointer to a pin + * + * Return: pointer to the data + */ +void *dpll_pin_on_pin_priv(const struct dpll_pin *parent, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + + ref = dpll_xa_ref_pin_find((struct xarray *)&pin->parent_refs, parent); + if (!ref) + return NULL; + + return ref->priv; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_pin_priv); + +static int __init dpll_init(void) +{ + int ret; + + ret = dpll_netlink_init(); + if (ret) + goto error; + + ret = class_register(&dpll_class); + if (ret) + goto unregister_netlink; + + return 0; + +unregister_netlink: + dpll_netlink_finish(); +error: + mutex_destroy(&dpll_device_xa_lock); + mutex_destroy(&dpll_pin_xa_lock); + return ret; +} +subsys_initcall(dpll_init); diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h new file mode 100644 index 000000000000..876b6ac6f3a0 --- /dev/null +++ b/drivers/dpll/dpll_core.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_CORE_H__ +#define __DPLL_CORE_H__ + +#include +#include +#include "dpll_netlink.h" + +#define DPLL_REGISTERED XA_MARK_1 + +/** + * struct dpll_device - structure for a DPLL device + * @id: unique id number for each device + * @dev_driver_id: id given by dev driver + * @dev: struct device for this dpll device + * @parent: parent device + * @module: module of creator + * @ops: operations this &dpll_device supports + * @lock: mutex to serialize operations + * @type: type of a dpll + * @priv: pointer to private information of owner + * @pins: list of pointers to pins registered with this dpll + * @clock_id: unique identifier (clock_id) of a dpll + * @mode_supported_mask: mask of supported modes + * @refcount: refcount + **/ +struct dpll_device { + u32 id; + u32 dev_driver_id; + struct device dev; + struct device *parent; + struct module *module; + struct dpll_device_ops *ops; + enum dpll_type type; + void *priv; + struct xarray pin_refs; + u64 clock_id; + unsigned long mode_supported_mask; + refcount_t refcount; +}; + +/** + * struct dpll_pin - structure for a dpll pin + * @idx: unique idx given by alloc on global pin's XA + * @dev_driver_id: id given by dev driver + * @clock_id: clock_id of creator + * @module: module of creator + * @dpll_refs: hold referencees to dplls that pin is registered with + * @pin_refs: hold references to pins that pin is registered with + * @prop: properties given by registerer + * @rclk_dev_name: holds name of device when pin can recover clock from it + * @refcount: refcount + **/ +struct dpll_pin { + u32 idx; + u32 dev_driver_id; + u64 clock_id; + struct module *module; + struct xarray dpll_refs; + struct xarray parent_refs; + struct dpll_pin_properties prop; + char *rclk_dev_name; + refcount_t refcount; +}; + +/** + * struct dpll_pin_ref - structure for referencing either dpll or pins + * @dpll: pointer to a dpll + * @pin: pointer to a pin + * @ops: ops for a dpll pin + * @priv: pointer to private information of owner + **/ +struct dpll_pin_ref { + union { + struct dpll_device *dpll; + struct dpll_pin *pin; + }; + struct dpll_pin_ops *ops; + void *priv; + refcount_t refcount; +}; + +struct dpll_device *dpll_device_get_by_id(int id); +struct dpll_device *dpll_device_get_by_name(const char *bus_name, + const char *dev_name); +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, u32 idx); +struct dpll_pin_ref * +dpll_xa_ref_pin_find(struct xarray *xa_refs, const struct dpll_pin *pin); +struct dpll_pin_ref * +dpll_xa_ref_dpll_find(struct xarray *xa_refs, const struct dpll_device *dpll); +extern struct xarray dpll_device_xa; +extern struct xarray dpll_pin_xa; +extern struct mutex dpll_device_xa_lock; +extern struct mutex dpll_pin_xa_lock; +#endif diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c new file mode 100644 index 000000000000..46aefeb1ac93 --- /dev/null +++ b/drivers/dpll/dpll_netlink.c @@ -0,0 +1,1065 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic netlink for DPLL management framework + * + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + * + */ +#include +#include +#include +#include "dpll_core.h" +#include "dpll_nl.h" +#include + +static u32 dpll_pin_freq_value[] = { + [DPLL_PIN_FREQ_SUPP_1_HZ] = DPLL_PIN_FREQ_1_HZ, + [DPLL_PIN_FREQ_SUPP_10_MHZ] = DPLL_PIN_FREQ_10_MHZ, +}; + +static int +dpll_msg_add_dev_handle(struct sk_buff *msg, const struct dpll_device *dpll) +{ + if (nla_put_u32(msg, DPLL_A_ID, dpll->id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_BUS_NAME, dev_bus_name(&dpll->dev))) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_DEV_NAME, dev_name(&dpll->dev))) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_mode(struct sk_buff *msg, const struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_mode mode; + + if (WARN_ON(!dpll->ops->mode_get)) + return -EOPNOTSUPP; + if (dpll->ops->mode_get(dpll, &mode, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_MODE, mode)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_source_pin_idx(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + u32 source_pin_idx; + + if (WARN_ON(!dpll->ops->source_pin_idx_get)) + return -EOPNOTSUPP; + if (dpll->ops->source_pin_idx_get(dpll, &source_pin_idx, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_SOURCE_PIN_IDX, source_pin_idx)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_lock_status status; + + if (WARN_ON(!dpll->ops->lock_status_get)) + return -EOPNOTSUPP; + if (dpll->ops->lock_status_get(dpll, &status, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_LOCK_STATUS, status)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_temp(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + s32 temp; + + if (!dpll->ops->temp_get) + return -EOPNOTSUPP; + if (dpll->ops->temp_get(dpll, &temp, extack)) + return -EFAULT; + if (nla_put_s32(msg, DPLL_A_TEMP, temp)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_prio(struct sk_buff *msg, const struct dpll_pin *pin, + struct dpll_pin_ref *ref, + struct netlink_ext_ack *extack) +{ + u32 prio; + + if (!ref->ops->prio_get) + return -EOPNOTSUPP; + if (ref->ops->prio_get(pin, ref->dpll, &prio, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_PIN_PRIO, prio)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_on_dpll_state(struct sk_buff *msg, const struct dpll_pin *pin, + struct dpll_pin_ref *ref, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_state state; + + if (!ref->ops->state_on_dpll_get) + return -EOPNOTSUPP; + if (ref->ops->state_on_dpll_get(pin, ref->dpll, &state, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_direction(struct sk_buff *msg, const struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_direction direction; + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each((struct xarray *)&pin->dpll_refs, i, ref) { + if (ref && ref->ops && ref->dpll) + break; + } + if (!ref || !ref->ops || !ref->dpll) + return -ENODEV; + if (!ref->ops->direction_get) + return -EOPNOTSUPP; + if (ref->ops->direction_get(pin, ref->dpll, &direction, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_PIN_DIRECTION, direction)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_freq(struct sk_buff *msg, const struct dpll_pin *pin, + struct netlink_ext_ack *extack, bool dump_any_freq) +{ + enum dpll_pin_freq_supp fs; + struct dpll_pin_ref *ref; + unsigned long i; + u32 freq; + + xa_for_each((struct xarray *)&pin->dpll_refs, i, ref) { + if (ref && ref->ops && ref->dpll) + break; + } + if (!ref || !ref->ops || !ref->dpll) + return -ENODEV; + if (!ref->ops->frequency_get) + return -EOPNOTSUPP; + if (ref->ops->frequency_get(pin, ref->dpll, &freq, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY, freq)) + return -EMSGSIZE; + if (!dump_any_freq) + return 0; + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC + 1; + fs <= DPLL_PIN_FREQ_SUPP_MAX; fs++) { + if (test_bit(fs, &pin->prop.freq_supported)) { + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY_SUPPORTED, + dpll_pin_freq_value[fs])) + return -EMSGSIZE; + } + } + if (pin->prop.any_freq_min && pin->prop.any_freq_max) { + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MIN, + pin->prop.any_freq_min)) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MAX, + pin->prop.any_freq_max)) + return -EMSGSIZE; + } + + return 0; +} + +static int +dpll_msg_add_pin_parents(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref_parent; + enum dpll_pin_state state; + struct nlattr *nest; + unsigned long index; + int ret; + + xa_for_each(&pin->parent_refs, index, ref_parent) { + if (WARN_ON(!ref_parent->ops->state_on_pin_get)) + return -EFAULT; + ret = ref_parent->ops->state_on_pin_get(pin, ref_parent->pin, + &state, extack); + if (ret) + return -EFAULT; + nest = nla_nest_start(msg, DPLL_A_PIN_PARENT); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_PARENT_IDX, + ref_parent->pin->dev_driver_id)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + nla_nest_end(msg, nest); + } + + return 0; + +nest_cancel: + nla_nest_cancel(msg, nest); + return ret; +} + +static int +dpll_msg_add_pins_on_pin(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref = NULL; + enum dpll_pin_state state; + struct nlattr *nest; + unsigned long index; + int ret; + + xa_for_each(&pin->parent_refs, index, ref) { + if (WARN_ON(!ref->ops->state_on_pin_get)) + return -EFAULT; + ret = ref->ops->state_on_pin_get(pin, ref->pin, &state, + extack); + if (ret) + return -EFAULT; + nest = nla_nest_start(msg, DPLL_A_PIN_PARENT); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_PARENT_IDX, + ref->pin->dev_driver_id)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + nla_nest_end(msg, nest); + } + + return 0; + +nest_cancel: + nla_nest_cancel(msg, nest); + return ret; +} + +static int +dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + struct nlattr *attr; + unsigned long index; + int ret; + + xa_for_each(&pin->dpll_refs, index, ref) { + attr = nla_nest_start(msg, DPLL_A_DEVICE); + if (!attr) + return -EMSGSIZE; + ret = dpll_msg_add_dev_handle(msg, ref->dpll); + if (ret) + goto nest_cancel; + ret = dpll_msg_add_pin_on_dpll_state(msg, pin, ref, extack); + if (ret && ret != -EOPNOTSUPP) + goto nest_cancel; + ret = dpll_msg_add_pin_prio(msg, pin, ref, extack); + if (ret && ret != -EOPNOTSUPP) + goto nest_cancel; + nla_nest_end(msg, attr); + } + + return 0; + +nest_cancel: + nla_nest_end(msg, attr); + return ret; +} + +static int +dpll_cmd_pin_on_dpll_get(struct sk_buff *msg, struct dpll_pin *pin, + struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + int ret; + + if (nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_PIN_DESCRIPTION, pin->prop.description)) + return -EMSGSIZE; + if (nla_put_u8(msg, DPLL_A_PIN_TYPE, pin->prop.type)) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_DPLL_CAPS, pin->prop.capabilities)) + return -EMSGSIZE; + ret = dpll_msg_add_pin_direction(msg, pin, extack); + if (ret) + return ret; + ret = dpll_msg_add_pin_freq(msg, pin, extack, true); + if (ret && ret != -EOPNOTSUPP) + return ret; + ref = dpll_xa_ref_dpll_find(&pin->dpll_refs, dpll); + if (!ref) + return -EFAULT; + ret = dpll_msg_add_pin_prio(msg, pin, ref, extack); + if (ret && ret != -EOPNOTSUPP) + return ret; + ret = dpll_msg_add_pin_on_dpll_state(msg, pin, ref, extack); + if (ret && ret != -EOPNOTSUPP) + return ret; + ret = dpll_msg_add_pin_parents(msg, pin, extack); + if (ret) + return ret; + if (pin->rclk_dev_name) + if (nla_put_string(msg, DPLL_A_PIN_RCLK_DEVICE, + pin->rclk_dev_name)) + return -EMSGSIZE; + + return 0; +} + +static int +__dpll_cmd_pin_dump_one(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack, bool dump_dpll) +{ + int ret; + + if (nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_PIN_DESCRIPTION, pin->prop.description)) + return -EMSGSIZE; + if (nla_put_u8(msg, DPLL_A_PIN_TYPE, pin->prop.type)) + return -EMSGSIZE; + ret = dpll_msg_add_pin_direction(msg, pin, extack); + if (ret) + return ret; + ret = dpll_msg_add_pin_freq(msg, pin, extack, true); + if (ret && ret != -EOPNOTSUPP) + return ret; + ret = dpll_msg_add_pins_on_pin(msg, pin, extack); + if (ret) + return ret; + if (!xa_empty(&pin->dpll_refs) && dump_dpll) { + ret = dpll_msg_add_pin_dplls(msg, pin, extack); + if (ret) + return ret; + } + if (pin->rclk_dev_name) + if (nla_put_string(msg, DPLL_A_PIN_RCLK_DEVICE, + pin->rclk_dev_name)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + enum dpll_mode mode; + unsigned long i; + int ret; + + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + return ret; + ret = dpll_msg_add_source_pin_idx(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_temp(msg, dpll, extack); + if (ret && ret != -EOPNOTSUPP) + return ret; + ret = dpll_msg_add_lock_status(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_mode(msg, dpll, extack); + if (ret) + return ret; + for (mode = DPLL_MODE_UNSPEC + 1; mode <= DPLL_MODE_MAX; mode++) + if (test_bit(mode, &dpll->mode_supported_mask)) + if (nla_put_s32(msg, DPLL_A_MODE_SUPPORTED, mode)) + return -EMSGSIZE; + if (nla_put_64bit(msg, DPLL_A_CLOCK_ID, sizeof(dpll->clock_id), + &dpll->clock_id, 0)) + return -EMSGSIZE; + if (nla_put_u8(msg, DPLL_A_TYPE, dpll->type)) + return -EMSGSIZE; + xa_for_each(&dpll->pin_refs, i, ref) { + struct nlattr *nest = nla_nest_start(msg, DPLL_A_PIN); + + if (!nest) { + ret = -EMSGSIZE; + break; + } + ret = dpll_cmd_pin_on_dpll_get(msg, ref->pin, dpll, extack); + if (ret) { + nla_nest_cancel(msg, nest); + break; + } + nla_nest_end(msg, nest); + } + + return ret; +} + +static bool dpll_pin_is_freq_supported(struct dpll_pin *pin, u32 freq) +{ + enum dpll_pin_freq_supp fs; + + if (freq >= pin->prop.any_freq_min && freq <= pin->prop.any_freq_max) + return true; + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC + 1; + fs <= DPLL_PIN_FREQ_SUPP_MAX; fs++) + if (test_bit(fs, &pin->prop.freq_supported)) + if (freq == dpll_pin_freq_value[fs]) + return true; + return false; +} + +static int +dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + u32 freq = nla_get_u32(a); + struct dpll_pin_ref *ref; + unsigned long i; + int ret; + + if (!dpll_pin_is_freq_supported(pin, freq)) + return -EINVAL; + + xa_for_each(&pin->dpll_refs, i, ref) { + ret = ref->ops->frequency_set(pin, ref->dpll, freq, extack); + if (ret) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_FREQUENCY); + } + + return 0; +} + +static int +dpll_pin_on_pin_state_set(struct dpll_device *dpll, struct dpll_pin *pin, + u32 parent_idx, enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + struct dpll_pin *parent; + + if (!(DPLL_PIN_CAPS_STATE_CAN_CHANGE & pin->prop.capabilities)) + return -EOPNOTSUPP; + parent = dpll_pin_get_by_idx(dpll, parent_idx); + if (!parent) + return -EINVAL; + ref = dpll_xa_ref_pin_find(&pin->parent_refs, parent); + if (!ref) + return -EINVAL; + if (!ref->ops || !ref->ops->state_on_pin_set) + return -EOPNOTSUPP; + if (ref->ops->state_on_pin_set(pin, parent, state, extack)) + return -EFAULT; + dpll_pin_parent_notify(dpll, pin, parent, DPLL_A_PIN_STATE); + + return 0; +} + +static int +dpll_pin_state_set(struct dpll_device *dpll, struct dpll_pin *pin, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + + if (!(DPLL_PIN_CAPS_STATE_CAN_CHANGE & pin->prop.capabilities)) + return -EOPNOTSUPP; + ref = dpll_xa_ref_dpll_find(&pin->dpll_refs, dpll); + if (!ref) + return -EFAULT; + if (!ref->ops || !ref->ops->state_on_dpll_set) + return -EOPNOTSUPP; + if (ref->ops->state_on_dpll_set(pin, ref->dpll, state, extack)) + return -EINVAL; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_STATE); + + return 0; +} + +static int +dpll_pin_prio_set(struct dpll_device *dpll, struct dpll_pin *pin, + struct nlattr *prio_attr, struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref; + u32 prio = nla_get_u8(prio_attr); + + if (!(DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE & pin->prop.capabilities)) + return -EOPNOTSUPP; + ref = dpll_xa_ref_dpll_find(&pin->dpll_refs, dpll); + if (!ref) + return -EFAULT; + if (!ref->ops || !ref->ops->prio_set) + return -EOPNOTSUPP; + if (ref->ops->prio_set(pin, dpll, prio, extack)) + return -EINVAL; + dpll_pin_notify(dpll, pin, DPLL_A_PIN_PRIO); + + return 0; +} + +static int +dpll_pin_direction_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_direction direction = nla_get_u8(a); + struct dpll_pin_ref *ref; + unsigned long i; + + if (!(DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE & pin->prop.capabilities)) + return -EOPNOTSUPP; + + xa_for_each(&pin->dpll_refs, i, ref) { + if (ref->ops->direction_set(pin, ref->dpll, direction, extack)) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_DIRECTION); + } + + return 0; +} + +static int +dpll_pin_set_from_nlattr(struct dpll_device *dpll, + struct dpll_pin *pin, struct genl_info *info) +{ + enum dpll_pin_state state = DPLL_PIN_STATE_UNSPEC; + u32 parent_idx = PIN_IDX_INVALID; + int rem, ret = -EINVAL; + struct nlattr *a; + + nla_for_each_attr(a, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(a)) { + case DPLL_A_PIN_FREQUENCY: + ret = dpll_pin_freq_set(pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_DIRECTION: + ret = dpll_pin_direction_set(pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_PRIO: + ret = dpll_pin_prio_set(dpll, pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_PARENT_IDX: + parent_idx = nla_get_u32(a); + break; + case DPLL_A_PIN_STATE: + state = nla_get_u8(a); + break; + default: + break; + } + } + if (state != DPLL_PIN_STATE_UNSPEC) { + if (parent_idx == PIN_IDX_INVALID) { + ret = dpll_pin_state_set(dpll, pin, state, + info->extack); + if (ret) + return ret; + } else { + ret = dpll_pin_on_pin_state_set(dpll, pin, parent_idx, + state, info->extack); + if (ret) + return ret; + } + } + + return ret; +} + +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct dpll_pin *pin = info->user_ptr[1]; + + return dpll_pin_set_from_nlattr(dpll, pin, info); +} + +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_pin *pin = info->user_ptr[1]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + int ret; + + if (!pin) + return -ENODEV; + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + hdr = genlmsg_put_reply(msg, info, &dpll_nl_family, 0, + DPLL_CMD_PIN_GET); + if (!hdr) + return -EMSGSIZE; + nest = nla_nest_start(msg, DPLL_A_PIN); + if (!nest) + return -EMSGSIZE; + ret = __dpll_cmd_pin_dump_one(msg, pin, info->extack, true); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_pin *pin; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_PIN_GET); + if (!hdr) + return -EMSGSIZE; + + xa_for_each(&dpll_pin_xa, i, pin) { + if (xa_empty(&pin->dpll_refs)) + continue; + nest = nla_nest_start(skb, DPLL_A_PIN); + if (!nest) { + ret = -EMSGSIZE; + break; + } + ret = __dpll_cmd_pin_dump_one(skb, pin, cb->extack, true); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +static int +dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info) +{ + struct nlattr *attr; + enum dpll_mode mode; + int rem, ret = 0; + + nla_for_each_attr(attr, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(attr)) { + case DPLL_A_MODE: + mode = nla_get_u8(attr); + + if (!dpll->ops || !dpll->ops->mode_set) + return -EOPNOTSUPP; + ret = dpll->ops->mode_set(dpll, mode, info->extack); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + + return dpll_set_from_nlattr(dpll, info); +} + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + int ret; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + hdr = genlmsg_put_reply(msg, info, &dpll_nl_family, 0, + DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + nest = nla_nest_start(msg, DPLL_A_DEVICE); + ret = dpll_device_get_one(dpll, msg, info->extack); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_device *dpll; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + xa_for_each_marked(&dpll_device_xa, i, dpll, DPLL_REGISTERED) { + nest = nla_nest_start(skb, DPLL_A_DEVICE); + ret = dpll_msg_add_dev_handle(skb, dpll); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + struct dpll_device *dpll_id = NULL, *dpll_name = NULL; + int ret = -ENODEV; + + if (!info->attrs[DPLL_A_ID] && + !(info->attrs[DPLL_A_BUS_NAME] && info->attrs[DPLL_A_DEV_NAME])) + return -EINVAL; + + mutex_lock(&dpll_device_xa_lock); + if (info->attrs[DPLL_A_ID]) { + u32 id = nla_get_u32(info->attrs[DPLL_A_ID]); + + dpll_id = dpll_device_get_by_id(id); + if (!dpll_id) + goto unlock; + info->user_ptr[0] = dpll_id; + } + if (info->attrs[DPLL_A_BUS_NAME] && + info->attrs[DPLL_A_DEV_NAME]) { + const char *bus_name = nla_data(info->attrs[DPLL_A_BUS_NAME]); + const char *dev_name = nla_data(info->attrs[DPLL_A_DEV_NAME]); + + dpll_name = dpll_device_get_by_name(bus_name, dev_name); + if (!dpll_name) { + ret = -ENODEV; + goto unlock; + } + + if (dpll_id && dpll_name != dpll_id) + goto unlock; + info->user_ptr[0] = dpll_name; + } + + return 0; +unlock: + mutex_unlock(&dpll_device_xa_lock); + return ret; +} + +void dpll_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + mutex_unlock(&dpll_device_xa_lock); +} + +int dpll_pre_dumpit(struct netlink_callback *cb) +{ + mutex_lock(&dpll_device_xa_lock); + + return 0; +} + +int dpll_post_dumpit(struct netlink_callback *cb) +{ + mutex_unlock(&dpll_device_xa_lock); + + return 0; +} + +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + int ret = dpll_pre_doit(ops, skb, info); + struct dpll_device *dpll; + struct dpll_pin *pin; + + if (ret) + return ret; + dpll = info->user_ptr[0]; + if (!info->attrs[DPLL_A_PIN_IDX]) { + ret = -EINVAL; + goto unlock_dev; + } + mutex_lock(&dpll_pin_xa_lock); + pin = dpll_pin_get_by_idx(dpll, + nla_get_u32(info->attrs[DPLL_A_PIN_IDX])); + if (!pin) { + ret = -ENODEV; + goto unlock_pin; + } + info->user_ptr[1] = pin; + + return 0; + +unlock_pin: + mutex_unlock(&dpll_pin_xa_lock); +unlock_dev: + mutex_unlock(&dpll_device_xa_lock); + return ret; +} + +void dpll_pin_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + mutex_unlock(&dpll_pin_xa_lock); + dpll_post_doit(ops, skb, info); +} + +int dpll_pin_pre_dumpit(struct netlink_callback *cb) +{ + mutex_lock(&dpll_pin_xa_lock); + + return dpll_pre_dumpit(cb); +} + +int dpll_pin_post_dumpit(struct netlink_callback *cb) +{ + mutex_unlock(&dpll_pin_xa_lock); + + return dpll_post_dumpit(cb); +} + +static int +dpll_event_device_change(struct sk_buff *msg, struct dpll_device *dpll, + struct dpll_pin *pin, struct dpll_pin *parent, + enum dplla attr) +{ + int ret = dpll_msg_add_dev_handle(msg, dpll); + struct dpll_pin_ref *ref = NULL; + enum dpll_pin_state state; + + if (ret) + return ret; + if (pin && nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + + switch (attr) { + case DPLL_A_MODE: + ret = dpll_msg_add_mode(msg, dpll, NULL); + break; + case DPLL_A_SOURCE_PIN_IDX: + ret = dpll_msg_add_source_pin_idx(msg, dpll, NULL); + break; + case DPLL_A_LOCK_STATUS: + ret = dpll_msg_add_lock_status(msg, dpll, NULL); + break; + case DPLL_A_TEMP: + ret = dpll_msg_add_temp(msg, dpll, NULL); + break; + case DPLL_A_PIN_FREQUENCY: + ret = dpll_msg_add_pin_freq(msg, pin, NULL, false); + break; + case DPLL_A_PIN_PRIO: + ref = dpll_xa_ref_dpll_find(&pin->dpll_refs, dpll); + if (!ref) + return -EFAULT; + ret = dpll_msg_add_pin_prio(msg, pin, ref, NULL); + break; + case DPLL_A_PIN_STATE: + if (parent) { + ref = dpll_xa_ref_pin_find(&pin->parent_refs, parent); + if (!ref) + return -EFAULT; + if (!ref->ops || !ref->ops->state_on_pin_get) + return -EOPNOTSUPP; + ret = ref->ops->state_on_pin_get(pin, parent, &state, + NULL); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_PIN_PARENT_IDX, + parent->dev_driver_id)) + return -EMSGSIZE; + } else { + ref = dpll_xa_ref_dpll_find(&pin->dpll_refs, dpll); + if (!ref) + return -EFAULT; + ret = dpll_msg_add_pin_on_dpll_state(msg, pin, ref, + NULL); + if (ret) + return ret; + } + break; + default: + break; + } + + return ret; +} + +static int +dpll_send_event_create(enum dpll_event event, struct dpll_device *dpll) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &dpll_nl_family, 0, event); + if (!hdr) + goto out_free_msg; + + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +static int +dpll_send_event_change(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &dpll_nl_family, 0, + DPLL_EVENT_DEVICE_CHANGE); + if (!hdr) + goto out_free_msg; + + ret = dpll_event_device_change(msg, dpll, pin, parent, attr); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +int dpll_notify_device_create(struct dpll_device *dpll) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_CREATE, dpll); +} + +int dpll_notify_device_delete(struct dpll_device *dpll) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_DELETE, dpll); +} + +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr) +{ + if (WARN_ON(!dpll)) + return -EINVAL; + + return dpll_send_event_change(dpll, NULL, NULL, attr); +} +EXPORT_SYMBOL_GPL(dpll_device_notify); + +int dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, NULL, attr); +} + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, parent, attr); +} + +int __init dpll_netlink_init(void) +{ + return genl_register_family(&dpll_nl_family); +} + +void dpll_netlink_finish(void) +{ + genl_unregister_family(&dpll_nl_family); +} + +void __exit dpll_netlink_fini(void) +{ + dpll_netlink_finish(); +} diff --git a/drivers/dpll/dpll_netlink.h b/drivers/dpll/dpll_netlink.h new file mode 100644 index 000000000000..072efa10f0e6 --- /dev/null +++ b/drivers/dpll/dpll_netlink.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +/** + * dpll_notify_device_create - notify that the device has been created + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_create(struct dpll_device *dpll); + + +/** + * dpll_notify_device_delete - notify that the device has been deleted + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_delete(struct dpll_device *dpll); + +int dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr); + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr); + +int __init dpll_netlink_init(void); +void dpll_netlink_finish(void); diff --git a/include/linux/dpll.h b/include/linux/dpll.h new file mode 100644 index 000000000000..db98b6d4bb73 --- /dev/null +++ b/include/linux/dpll.h @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_H__ +#define __DPLL_H__ + +#include +#include +#include + +struct dpll_device; +struct dpll_pin; + +#define PIN_IDX_INVALID ((u32)ULONG_MAX) + +struct dpll_device_ops { + int (*mode_get)(const struct dpll_device *dpll, enum dpll_mode *mode, + struct netlink_ext_ack *extack); + int (*mode_set)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + bool (*mode_supported)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + int (*source_pin_idx_get)(const struct dpll_device *dpll, + u32 *pin_idx, + struct netlink_ext_ack *extack); + int (*lock_status_get)(const struct dpll_device *dpll, + enum dpll_lock_status *status, + struct netlink_ext_ack *extack); + int (*temp_get)(const struct dpll_device *dpll, s32 *temp, + struct netlink_ext_ack *extack); +}; + +struct dpll_pin_ops { + int (*frequency_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack); + int (*frequency_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, struct netlink_ext_ack *extack); + int (*direction_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_direction direction, + struct netlink_ext_ack *extack); + int (*direction_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack); + int (*state_on_pin_get)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_pin_set)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*prio_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *prio, struct netlink_ext_ack *extack); + int (*prio_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 prio, struct netlink_ext_ack *extack); +}; + +struct dpll_pin_properties { + const char *description; + enum dpll_pin_type type; + unsigned long freq_supported; + u32 any_freq_min; + u32 any_freq_max; + unsigned long capabilities; +}; + +enum dpll_pin_freq_supp { + DPLL_PIN_FREQ_SUPP_UNSPEC = 0, + DPLL_PIN_FREQ_SUPP_1_HZ, + DPLL_PIN_FREQ_SUPP_10_MHZ, + + __DPLL_PIN_FREQ_SUPP_MAX, + DPLL_PIN_FREQ_SUPP_MAX = (__DPLL_PIN_FREQ_SUPP_MAX - 1) +}; + +/** + * dpll_device_get - find or create dpll_device object + * @clock_id: a system unique number for a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * + * Returns: + * * pointer to initialized dpll - success + * * NULL - memory allocation fail + */ +struct dpll_device +*dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module); + +/** + * dpll_device_put - caller drops reference to the device, free resources + * @dpll: dpll device pointer + * + * If all dpll_device_get callers drops their reference, the dpll device + * resources are freed. + */ +void dpll_device_put(struct dpll_device *dpll); + +/** + * dpll_device_register - register device, make it visible in the subsystem. + * @dpll: reference previously allocated with dpll_device_get + * @type: type of dpll + * @ops: callbacks + * @priv: private data of registerer + * @owner: device struct of the owner + * + */ +int dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + struct dpll_device_ops *ops, void *priv, + struct device *owner); + +/** + * dpll_device_unregister - deregister registered dpll + * @dpll: pointer to dpll + * + * Unregister the dpll from the subsystem, make it unavailable for netlink + * API users. + */ +void dpll_device_unregister(struct dpll_device *dpll); + +/** + * dpll_priv - get dpll private data + * @dpll: pointer to dpll + * + * Obtain private data pointer passed to dpll subsystem when allocating + * device with ``dpll_device_alloc(..)`` + */ +void *dpll_priv(const struct dpll_device *dpll); + +/** + * dpll_pin_on_pin_priv - get pin on pin pair private data + * @parent: pointer to a parent pin + * @pin: pointer to a dpll_pin + * + * Obtain private pin data pointer passed to dpll subsystem when pin + * was registered with parent pin. + */ +void *dpll_pin_on_pin_priv(const struct dpll_pin *parent, const struct dpll_pin *pin); + +/** + * dpll_pin_on_dpll_priv - get pin on dpll pair private data + * @dpll: pointer to dpll + * @pin: pointer to a dpll_pin + * + * Obtain private pin-dpll pair data pointer passed to dpll subsystem when pin + * was registered with a dpll. + */ +void *dpll_pin_on_dpll_priv(const struct dpll_device *dpll, const struct dpll_pin *pin); + +/** + * dpll_pin_get - get reference or create new pin object + * @clock_id: a system unique number of a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * @pin_prop: constant properities of a pin + * + * find existing pin with given clock_id, dev_driver_idx and module, or create new + * and returen its reference. + * + * Returns: + * * pointer to initialized pin - success + * * NULL - memory allocation fail + */ +struct dpll_pin +*dpll_pin_get(u64 clock_id, u32 dev_driver_id, struct module *module, + const struct dpll_pin_properties *pin_prop); + +/** + * dpll_pin_register - register pin with a dpll device + * @dpll: pointer to dpll object to register pin with + * @pin: pointer to allocated pin object being registered with dpll + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * Register previously allocated pin object with a dpll device. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this dpll, + * * -EBUSY - couldn't allocate id for a pin. + */ +int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * dpll_pin_unregister - deregister pin from a dpll device + * @dpll: pointer to dpll object to deregister pin from + * @pin: pointer to allocated pin object being deregistered from dpll + * + * Deregister previously registered pin object from a dpll device. + * + * Return: + * * 0 - pin was successfully deregistered from this dpll device, + * * -ENXIO - given pin was not registered with this dpll device, + * * -EINVAL - pin pointer is not valid. + */ +int dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin); + +/** + * dpll_pin_put - drop reference to a pin acquired with dpll_pin_get + * @pin: pointer to allocated pin + * + * Pins shall be deregistered from all dpll devices before putting them, + * otherwise the memory won't be freed. + */ +void dpll_pin_put(struct dpll_pin *pin); + +/** + * dpll_pin_on_pin_register - register a pin to a muxed-type pin + * @parent: parent pin pointer + * @pin: pointer to allocated pin object being registered with a parent pin + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * In case of multiplexed pins, allows registring them under a single + * parent pin. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this parent pin, + */ +int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * dpll_pin_on_pin_register - register a pin to a muxed-type pin + * @parent: parent pin pointer + * @pin: pointer to allocated pin object being registered with a parent pin + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * In case of multiplexed pins, allows registring them under a single + * parent pin. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this parent pin, + */ +void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin); + +/** + * dpll_device_notify - notify on dpll device change + * @dpll: dpll device pointer + * @attr: changed attribute + * + * Broadcast event to the netlink multicast registered listeners. + * + * Return: + * * 0 - success + * * negative - error + */ +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr); + + +#endif From patchwork Sun Mar 12 02:28:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171096 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 14C4EC74A44 for ; Sun, 12 Mar 2023 02:28:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230045AbjCLC2u (ORCPT ); Sat, 11 Mar 2023 21:28:50 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34954 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229685AbjCLC2r (ORCPT ); Sat, 11 Mar 2023 21:28:47 -0500 Received: from mx0a-00082601.pphosted.com (mx0a-00082601.pphosted.com [67.231.145.42]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7CCFF32CF3; Sat, 11 Mar 2023 18:28:45 -0800 (PST) Received: from pps.filterd (m0044010.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32C1iTBj005195; Sat, 11 Mar 2023 18:28:27 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=40EG+05Ne4+wO9JHpvJkSlvSYxtLLL5N/Vyh2Tdm8SU=; b=KqHVZQFo9urhBksEeGwQOiZRAry8HAuLx/tESNOBrzYyWgnY3KzqYn3mdTIvAyVYboQY lQ19nnaQKSQb8ggPTiaySDxdBFXeus5kfkvjrPpbZst119Meuc66yCgjg9DwyI7M0QDJ /jCE0v1nnVNV4rEYesHYegIHG8DjBmhixEJcrn+EaDdRTL0GotHQPXNyQnT5Dkd1vBpY JCH9EAtVOcG6LOqrEcGXzVcktrLzgnA2GiNnfiMEIqz3XMoPMmztZwkPtxqOjkXHBuHe EHMtamBnUNQH3rZbEAR6oiBfjfpkMYoBhoqq4QiBs2MssJfto+/xyHE89vqz94KWVMyY AQ== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8nv0jjhu-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:27 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub203.TheFacebook.com (2620:10d:c0a8:83::5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:26 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:24 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , Vadim Fedorenko , , , , , Subject: [PATCH RFC v6 3/6] dpll: documentation on DPLL subsystem interface Date: Sat, 11 Mar 2023 18:28:04 -0800 Message-ID: <20230312022807.278528-4-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-ORIG-GUID: CIhpIdyg_CL0rA2tEAY11SEF0nO36kHv X-Proofpoint-GUID: CIhpIdyg_CL0rA2tEAY11SEF0nO36kHv X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC Add documentation explaining common netlink interface to configure DPLL devices and monitoring events. Common way to implement DPLL device in a driver is also covered. Signed-off-by: Vadim Fedorenko Signed-off-by: Arkadiusz Kubalewski --- Documentation/networking/dpll.rst | 347 +++++++++++++++++++++++++++++ Documentation/networking/index.rst | 1 + 2 files changed, 348 insertions(+) create mode 100644 Documentation/networking/dpll.rst diff --git a/Documentation/networking/dpll.rst b/Documentation/networking/dpll.rst new file mode 100644 index 000000000000..25cd81edc73c --- /dev/null +++ b/Documentation/networking/dpll.rst @@ -0,0 +1,347 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============================== +The Linux kernel DPLL subsystem +=============================== + + +The main purpose of DPLL subsystem is to provide general interface +to configure devices that use any kind of Digital PLL and could use +different sources of signal to synchronize to as well as different +types of outputs. +The main interface is NETLINK_GENERIC based protocol with an event +monitoring multicast group defined. + + +Dpll object +=========== +Single dpll device object means single Digital PLL circuit and bunch of +pins connected with it. +It provides its capablities and current status to the user in response +to the `do` request of netlink command ``DPLL_CMD_DEVICE_GET`` and list +of dplls registered in the subsystem with `dump` netlink request of same +command. +Requesting configuration of dpll device is done with `do` request of +netlink ``DPLL_CMD_DEVICE_SET`` command. + + +Pin object +========== +A pin is amorphic object which represents either input or output, it +could be internal component of the device, as well as externaly +connected. +The number of pins per dpll vary, but usually multiple pins shall be +provided for a single dpll device. +Pin's properities and capabilities are provided to the user in response +to `do` request of netlink ``DPLL_CMD_PIN_GET`` command. +It is also possible to list all the pins that were registered either +with dpll or different pin with `dump` request of ``DPLL_CMD_PIN_GET`` +command. +Configuration of a pin can be changed by `do` request of netlink +``DPLL_CMD_PIN_SET`` command. + + +Shared pins +=========== +Pin can be shared by multiple dpll devices. Where configuration on one +pin can alter multiple dplls (i.e. PIN_FREQUENCY, PIN_DIRECTION), +or configure just one pin-dpll pair (i.e. PIN_PRIO, PIN_STATE). + + +MUX-type pins +============= +A pin can be MUX-type, which aggregates child pins and serves as pin +multiplexer. One or more pins are attached to MUX-type instead of being +directly connected to a dpll device. +Pins registered with a MUX-type provides user with additional nested +attribute ``DPLL_A_PIN_PARENT`` for each parrent they were registered +with. +Only one child pin can provide it's signal to the parent MUX-type pin at +a time, the selection is done with requesting change of child pin state +to ``DPLL_PIN_STATE_CONNECTED`` and providing a target MUX-type pin +index value in ``DPLL_A_PARENT_PIN_IDX`` + + +Pin priority +============ +Some devices might offer a capability of automatic pin selection mode. +Usually such automatic selection is offloaded to the hardware, +which means only pins directly connected to the dpll are capable of +automatic source pin selection. +In automatic selection mode, the user cannot manually select a source +pin for the device, instead the user shall provide all directly +connected pins with a priority ``DPLL_A_PIN_PRIO``, the device would +pick a highest priority valid signal and connect with it. +Child pin of MUX-type are not capable of automatic source pin selection, +in order to configure a source of a MUX-type pin the user still needs +to request desired pin state. + + +Configuration commands group +============================ + +Configuration commands are used to get or dump information about +registered DPLL devices (and pins), as well as set configuration of +device or pins. As DPLL device could not be abstract and reflects real +hardware, there is no way to add new DPLL device via netlink from user +space and each device should be registered by it's driver. + +All netlink commands require ``GENL_ADMIN_PERM``. This is to prevent +any spamming/D.o.S. from unauthorized userspace applications. + +List of command with possible attributes +======================================== + +All constants identifying command types use ``DPLL_CMD_`` prefix and +suffix according to command purpose. All attributes use ``DPLL_A_`` +prefix and suffix according to attribute purpose: + + ============================ ======================================= + ``DEVICE_GET`` command to get device info or dump list + of available devices + ``ID`` attr internal dpll device ID + ``DEV_NAME`` attr dpll device name + ``BUS_NAME`` attr dpll device bus name + ``MODE`` attr selection mode + ``MODE_SUPPORTED`` attr available selection modes + ``SOURCE_PIN_IDX`` attr index of currently selected source + ``LOCK_STATUS`` attr internal frequency-lock status + ``TEMP`` attr device temperature information + ``CLOCK_ID`` attr Unique Clock Identifier (EUI-64), + as defined by the IEEE 1588 standard + ``TYPE`` attr type or purpose of dpll device + ``DEVICE_SET`` command to set dpll device configuration + ``ID`` attr internal dpll device index + ``NAME`` attr dpll device name (not required if + dpll device index was provided) + ``MODE`` attr selection mode to configure + ``PIN_GET`` command to get pin info or dump list of + available pins + ``DEVICE`` nest attr for each dpll device pin is + connected with + ``ID`` attr internal dpll device ID + ``DEV_NAME`` attr dpll device name + ``BUS_NAME`` attr dpll device bus name + ``PIN_PRIO`` attr priority of pin on the dpll device + ``PIN_STATE`` attr state of pin on the dpll device + ``PIN_IDX`` attr index of a pin on the dpll device + ``PIN_DESCRIPTION`` attr description provided by driver + ``PIN_TYPE`` attr type of a pin + ``PIN_DIRECTION`` attr direction of a pin + ``PIN_FREQUENCY`` attr current frequency of a pin + ``PIN_FREQUENCY_SUPPORTED`` attr provides supported frequencies + ``PIN_ANY_FREQUENCY_MIN`` attr minimum value of frequency in case + pin/dpll supports any frequency + ``PIN_ANY_FREQUENCY_MAX`` attr maximum value of frequency in case + pin/dpll supports any frequency + ``PIN_PARENT`` nest attr for each MUX-type parent, that + pin is connected with + ``PIN_PARENT_IDX`` attr index of a parent pin on the dpll + device + ``PIN_STATE`` attr state of a pin on parent pin + ``PIN_RCLK_DEVICE`` attr name of a device, where pin + recovers clock signal from + ``PIN_DPLL_CAPS`` attr bitmask of pin-dpll capabilities + + ``PIN_SET`` command to set pins configuration + ``ID`` attr internal dpll device index + ``BUS_NAME`` attr dpll device name (not required if + dpll device ID was provided) + ``DEV_NAME`` attr dpll device name (not required if + dpll device ID was provided) + ``PIN_IDX`` attr index of a pin on the dpll device + ``PIN_DIRECTION`` attr direction to be set + ``PIN_FREQUENCY`` attr frequency to be set + ``PIN_PRIO`` attr pin priority to be set + ``PIN_STATE`` attr pin state to be set + ``PIN_PRIO`` attr pin priority to be set + ``PIN_PARENT_IDX`` attr if provided state is to be set with + parent pin instead of with dpll device + +Netlink dump requests +===================== + +The ``DEVICE_GET`` and ``PIN_GET`` commands are capable of dump type +netlink requests. Possible response message attributes for netlink dump +requests: + + ============================== ======================================= + ``PIN_GET`` command to dump pins + ``PIN`` attr nested type contains single pin + ``DEVICE`` nest attr for each dpll device pin is + connected with + ``ID`` attr internal dpll device ID + ``DEV_NAME`` attr dpll device name + ``BUS_NAME`` attr dpll device bus name + ``PIN_IDX`` attr index of dumped pin (on dplls) + ``PIN_DESCRIPTION`` description of a pin provided by driver + ``PIN_TYPE`` attr value of pin type + ``PIN_FREQUENCY`` attr current frequency of a pin + ``PIN_FREQUENCY_SUPPORTED`` attr provides supported frequencies + ``PIN_RCLK_DEVICE`` attr name of a device, where pin + recovers clock signal from + ``PIN_DIRECTION`` attr direction of a pin + ``PIN_PARENT`` nest attr for each MUX-type parent, + that pin is connected with + ``PIN_PARENT_IDX`` attr index of a parent pin on the dpll + device + ``PIN_STATE`` attr state of a pin on parent pin + + ``DEVICE_GET`` command to dump dplls + ``DEVICE`` attr nested type contatin a single + dpll device + ``ID`` attr internal dpll device ID + ``DEV_NAME`` attr dpll device name + ``BUS_NAME`` attr dpll device bus name + + +Dpll device level configuration pre-defined enums +================================================= + +For all below enum names used for configuration of dpll device use +the ``DPLL_`` prefix. + +Values for ``DPLL_A_LOCK_STATUS`` attribute: + + ============================= ====================================== + ``LOCK_STATUS_UNLOCKED`` DPLL is in freerun, not locked to any + source pin + ``LOCK_STATUS_CALIBRATING`` DPLL device calibrates to lock to the + source pin signal + ``LOCK_STATUS_LOCKED`` DPLL device is locked to the source + pin frequency + ``LOCK_STATUS_HOLDOVER`` DPLL device lost a lock, using its + frequency holdover capabilities + +Values for ``DPLL_A_MODE`` attribute: + + =================== ================================================ + ``MODE_FORCED`` source pin is force-selected by setting pin + state to ``DPLL_PIN_STATE_CONNECTED`` on a dpll + ``MODE_AUTOMATIC`` source pin is auto selected according to + configured pin priorities and source signal + validity + ``MODE_HOLDOVER`` force holdover mode of DPLL + ``MODE_FREERUN`` DPLL is driven by supplied system clock without + holdover capabilities + ``MODE_NCO`` similar to FREERUN, with possibility to + numerically control frequency offset + +Values for ``DPLL_A_TYPE`` attribute: + + ============= ================================================ + ``TYPE_PPS`` DPLL used to provide pulse-per-second output + ``TYPE_EEC`` DPLL used to drive ethernet equipment clock + + + +Pin level configuration pre-defined enums +========================================= + +For all below enum names used for configuration of pin use the +``DPLL_PIN`` prefix. + +Values for ``DPLL_A_PIN_STATE`` attribute: + + ======================= ======================================== + ``STATE_CONNECTED`` Pin connected to a dpll or parent pin + ``STATE_DISCONNECTED`` Pin disconnected from dpll or parent pin + +Values for ``DPLL_A_PIN_DIRECTION`` attribute: + + ======================= ============================== + ``DIRECTION_SOURCE`` Pin used as a source of signal + ``DIRECTION_OUTPUT`` Pin used to output signal + +Values for ``DPLL_A_PIN_TYPE`` attributes: + + ======================== ======================================== + ``TYPE_MUX`` MUX type pin, connected pins shall have + their own types + ``TYPE_EXT`` External pin + ``TYPE_SYNCE_ETH_PORT`` SyncE on Ethernet port + ``TYPE_INT_OSCILLATOR`` Internal Oscillator (i.e. Holdover with + Atomic Clock as a Source) + ``TYPE_GNSS`` GNSS 1PPS source + +Values for ``DPLL_A_PIN_DPLL_CAPS`` attributes: + + ============================= ================================ + ``CAPS_DIRECTION_CAN_CHANGE`` Bit present if direction can change + ``CAPS_PRIORITY_CAN_CHANGE`` Bit present if priority can change + ``CAPS_STATE_CAN_CHANGE`` Bit present if state can change + + +Notifications +============= + +DPLL device can provide notifications regarding status changes of the +device, i.e. lock status changes, source/output type changes or alarms. +This is the multicast group that is used to notify user-space apps via +netlink socket: ``DPLL_MCGRP_MONITOR`` + +Notifications messages (attrbiutes use ``DPLL_A`` prefix): + + ========================= ========================================== + ``EVENT_DEVICE_CREATE`` event value new DPLL device was created + ``ID`` attr internal dpll device ID + ``DEV_NAME`` attr dpll device name + ``BUS_NAME`` attr dpll device bus name + ``EVENT_DEVICE_DELETE`` event value DPLL device was deleted + ``ID`` attr dpll device index + ``EVENT_DEVICE_CHANGE`` event value DPLL device attribute has + changed + ``ID`` attr modified dpll device ID + ``PIN_IDX`` attr the modified pin index + +Device change event shall consiste of the attribute and the value that +has changed. + + +Device driver implementation +============================ + +Device is allocated by ``dpll_device_get`` call. Second call with the +same arguments doesn't create new object but provides pointer to +previously created device for given arguments, it also increase refcount +of that object. +Device is deallocated by ``dpll_device_put`` call, which first decreases +the refcount, once refcount is cleared the object is destroyed. + +Device should implement set of operations and register device via +``dpll_device_register`` at which point it becomes available to the +users. Only one driver can register a dpll device within dpll subsytem. +Multiple driver instances can obtain reference to it with +``dpll_device_get``. + +The pins are allocated separately with ``dpll_pin_get``, it works +similarly to ``dpll_device_get``. Creates object and the for each call +with the same arguments the object refcount increases. + +Once DPLL device is created, allocated pin can be registered with it +with 2 different methods, always providing implemented pin callbacks, +and private data pointer for calling them: +``dpll_pin_register`` - simple registration with a dpll device. +``dpll_pin_on_pin_register`` - register pin with another MUX type pin. + +For different instances of a device driver requiring to find already +registered DPLL (i.e. to connect its pins to id) use ``dpll_device_get`` +to obtain proper dpll device pointer. + +The name od DPLL device is generated based on registerer provided device +struct pointer and dev_driver_id value. +Name is in format: ``%s_%u`` witch arguments: +``dev_name(struct device *)`` - syscall on parent device struct +``dev_driver_idx`` - registerer given id + +Notifications of adding or removing DPLL devices are created within +subsystem itself. +Notifications about registering/deregistering pins are also invoked by +the subsystem. +Notifications about dpll status changes shall be requested by device +driver with ``dpll_device_notify`` corresponding attribute as a reason. + +There is no strict requirement to implement all the operations for +each device, every operation handler is checked for existence and +ENOTSUPP is returned in case of absence of specific handler. + diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 4ddcae33c336..6eb83a47cc2d 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -17,6 +17,7 @@ Contents: dsa/index devlink/index caif/index + dpll ethtool-netlink ieee802154 j1939 From patchwork Sun Mar 12 02:28:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171102 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 72308C61DA4 for ; Sun, 12 Mar 2023 02:30:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229637AbjCLCaV (ORCPT ); Sat, 11 Mar 2023 21:30:21 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37448 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229437AbjCLCaU (ORCPT ); Sat, 11 Mar 2023 21:30:20 -0500 Received: from mx0b-00082601.pphosted.com (mx0b-00082601.pphosted.com [67.231.153.30]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 953B73401A; Sat, 11 Mar 2023 18:29:47 -0800 (PST) Received: from pps.filterd (m0109331.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32C0UZ0Q032296; Sat, 11 Mar 2023 18:28:52 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=5PancgE+a6kr5aekJ4VytOKaVSxrmgtkUJ2cxzZAL2k=; b=H9x3sg/UBJ0RpYJzEgwfMjf+tJC2kwQ5nI1YFcGb7vkZPUsgPzoCd9eWBhEJNttcie4r 6fPXi22wo+2Umpt2iVI0jTOds/dikJ0vaDNtul5t+ISyw8Oniq0xeTdKShMNAtPLDvnU aZPmGufbObq30mRWXiUXuacRbFFPfo5nvgLoqIgSDCLNNAHZe28C+mSMxYkY/PDQXTlX TvHiU9rykTKM0hyGoBNAUrE9fBu4/Y2Jj79p3zyhJQyiFyABRWCBOOMpX0oiD2nLVrB3 W2WMU+Kp3//UuBdQA85MyTGIDhTKz0HlckEDpaYaDsIQQ3E93zxGZd867FTs75KGzV4d vQ== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8rc1sces-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:51 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub201.TheFacebook.com (2620:10d:c0a8:83::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:27 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:26 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , "Arkadiusz Kubalewski" , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , , , , , Subject: [PATCH RFC v6 4/6] ice: add admin commands to access cgu configuration Date: Sat, 11 Mar 2023 18:28:05 -0800 Message-ID: <20230312022807.278528-5-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-ORIG-GUID: 5zjNu06u74VKa2k7LiEf52IvO_wjlYaP X-Proofpoint-GUID: 5zjNu06u74VKa2k7LiEf52IvO_wjlYaP X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC From: Arkadiusz Kubalewski Add firmware admin command to access clock generation unit configuration, it is required to enable Extended PTP and SyncE features in the driver. Add definitions of possible hardware variations of input and output pins related to clock generation unit and functions to access the data. Signed-off-by: Arkadiusz Kubalewski --- drivers/net/ethernet/intel/ice/ice.h | 1 + .../net/ethernet/intel/ice/ice_adminq_cmd.h | 240 ++++++++- drivers/net/ethernet/intel/ice/ice_common.c | 467 ++++++++++++++++++ drivers/net/ethernet/intel/ice/ice_common.h | 43 ++ drivers/net/ethernet/intel/ice/ice_lib.c | 17 +- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 411 +++++++++++++++ drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 240 +++++++++ drivers/net/ethernet/intel/ice/ice_type.h | 1 + 8 files changed, 1415 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index b0e29e342401..116eb64db969 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -203,6 +203,7 @@ enum ice_feature { ICE_F_DSCP, ICE_F_PTP_EXTTS, ICE_F_SMA_CTRL, + ICE_F_CGU, ICE_F_GNSS, ICE_F_MAX }; diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h index 838d9b274d68..e6edc1a90f44 100644 --- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h +++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h @@ -1339,6 +1339,32 @@ struct ice_aqc_set_mac_lb { u8 reserved[15]; }; +/* Set PHY recovered clock output (direct 0x0630) */ +struct ice_aqc_set_phy_rec_clk_out { + u8 phy_output; + u8 port_num; +#define ICE_AQC_SET_PHY_REC_CLK_OUT_CURR_PORT 0xFF + u8 flags; +#define ICE_AQC_SET_PHY_REC_CLK_OUT_OUT_EN BIT(0) + u8 rsvd; + __le32 freq; + u8 rsvd2[6]; + __le16 node_handle; +}; + +/* Get PHY recovered clock output (direct 0x0631) */ +struct ice_aqc_get_phy_rec_clk_out { + u8 phy_output; + u8 port_num; +#define ICE_AQC_GET_PHY_REC_CLK_OUT_CURR_PORT 0xFF + u8 flags; +#define ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN BIT(0) + u8 rsvd; + __le32 freq; + u8 rsvd2[6]; + __le16 node_handle; +}; + struct ice_aqc_link_topo_params { u8 lport_num; u8 lport_num_valid; @@ -1355,6 +1381,8 @@ struct ice_aqc_link_topo_params { #define ICE_AQC_LINK_TOPO_NODE_TYPE_CAGE 6 #define ICE_AQC_LINK_TOPO_NODE_TYPE_MEZZ 7 #define ICE_AQC_LINK_TOPO_NODE_TYPE_ID_EEPROM 8 +#define ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_CTRL 9 +#define ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_MUX 10 #define ICE_AQC_LINK_TOPO_NODE_CTX_S 4 #define ICE_AQC_LINK_TOPO_NODE_CTX_M \ (0xF << ICE_AQC_LINK_TOPO_NODE_CTX_S) @@ -1391,7 +1419,12 @@ struct ice_aqc_link_topo_addr { struct ice_aqc_get_link_topo { struct ice_aqc_link_topo_addr addr; u8 node_part_num; -#define ICE_AQC_GET_LINK_TOPO_NODE_NR_PCA9575 0x21 +#define ICE_AQC_GET_LINK_TOPO_NODE_NR_PCA9575 0x21 +#define ICE_ACQ_GET_LINK_TOPO_NODE_NR_ZL30632_80032 0x24 +#define ICE_ACQ_GET_LINK_TOPO_NODE_NR_SI5383_5384 0x25 +#define ICE_ACQ_GET_LINK_TOPO_NODE_NR_E822_PHY 0x30 +#define ICE_ACQ_GET_LINK_TOPO_NODE_NR_C827 0x31 +#define ICE_ACQ_GET_LINK_TOPO_NODE_NR_GEN_CLK_MUX 0x47 u8 rsvd[9]; }; @@ -2079,6 +2112,186 @@ struct ice_aqc_get_pkg_info_resp { struct ice_aqc_get_pkg_info pkg_info[]; }; +/* Get CGU abilities command response data structure (indirect 0x0C61) */ +struct ice_aqc_get_cgu_abilities { + u8 num_inputs; + u8 num_outputs; + u8 pps_dpll_idx; + u8 eec_dpll_idx; + __le32 max_in_freq; + __le32 max_in_phase_adj; + __le32 max_out_freq; + __le32 max_out_phase_adj; + u8 cgu_part_num; + u8 rsvd[3]; +}; + +/* Set CGU input config (direct 0x0C62) */ +struct ice_aqc_set_cgu_input_config { + u8 input_idx; + u8 flags1; +#define ICE_AQC_SET_CGU_IN_CFG_FLG1_UPDATE_FREQ BIT(6) +#define ICE_AQC_SET_CGU_IN_CFG_FLG1_UPDATE_DELAY BIT(7) + u8 flags2; +#define ICE_AQC_SET_CGU_IN_CFG_FLG2_INPUT_EN BIT(5) +#define ICE_AQC_SET_CGU_IN_CFG_FLG2_ESYNC_EN BIT(6) + u8 rsvd; + __le32 freq; + __le32 phase_delay; + u8 rsvd2[2]; + __le16 node_handle; +}; + +/* Get CGU input config response descriptor structure (direct 0x0C63) */ +struct ice_aqc_get_cgu_input_config { + u8 input_idx; + u8 status; +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_LOS BIT(0) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_SCM_FAIL BIT(1) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_CFM_FAIL BIT(2) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_GST_FAIL BIT(3) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_PFM_FAIL BIT(4) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_ESYNC_FAIL BIT(6) +#define ICE_AQC_GET_CGU_IN_CFG_STATUS_ESYNC_CAP BIT(7) + u8 type; +#define ICE_AQC_GET_CGU_IN_CFG_TYPE_READ_ONLY BIT(0) +#define ICE_AQC_GET_CGU_IN_CFG_TYPE_GPS BIT(4) +#define ICE_AQC_GET_CGU_IN_CFG_TYPE_EXTERNAL BIT(5) +#define ICE_AQC_GET_CGU_IN_CFG_TYPE_PHY BIT(6) + u8 flags1; +#define ICE_AQC_GET_CGU_IN_CFG_FLG1_PHASE_DELAY_SUPP BIT(0) +#define ICE_AQC_GET_CGU_IN_CFG_FLG1_1PPS_SUPP BIT(2) +#define ICE_AQC_GET_CGU_IN_CFG_FLG1_10MHZ_SUPP BIT(3) +#define ICE_AQC_GET_CGU_IN_CFG_FLG1_ANYFREQ BIT(7) + __le32 freq; + __le32 phase_delay; + u8 flags2; +#define ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN BIT(5) +#define ICE_AQC_GET_CGU_IN_CFG_FLG2_ESYNC_EN BIT(6) + u8 rsvd[1]; + __le16 node_handle; +}; + +/* Set CGU output config (direct 0x0C64) */ +struct ice_aqc_set_cgu_output_config { + u8 output_idx; + u8 flags; +#define ICE_AQC_SET_CGU_OUT_CFG_OUT_EN BIT(0) +#define ICE_AQC_SET_CGU_OUT_CFG_ESYNC_EN BIT(1) +#define ICE_AQC_SET_CGU_OUT_CFG_UPDATE_FREQ BIT(2) +#define ICE_AQC_SET_CGU_OUT_CFG_UPDATE_PHASE BIT(3) +#define ICE_AQC_SET_CGU_OUT_CFG_UPDATE_SRC_SEL BIT(4) + u8 src_sel; +#define ICE_AQC_SET_CGU_OUT_CFG_DPLL_SRC_SEL ICE_M(0x1F, 0) + u8 rsvd; + __le32 freq; + __le32 phase_delay; + u8 rsvd2[2]; + __le16 node_handle; +}; + +/* Get CGU output config (direct 0x0C65) */ +struct ice_aqc_get_cgu_output_config { + u8 output_idx; + u8 flags; +#define ICE_AQC_GET_CGU_OUT_CFG_OUT_EN BIT(0) +#define ICE_AQC_GET_CGU_OUT_CFG_ESYNC_EN BIT(1) +#define ICE_AQC_GET_CGU_OUT_CFG_ESYNC_ABILITY BIT(2) + u8 src_sel; +#define ICE_AQC_GET_CGU_OUT_CFG_DPLL_SRC_SEL_SHIFT 0 +#define ICE_AQC_GET_CGU_OUT_CFG_DPLL_SRC_SEL \ + ICE_M(0x1F, ICE_AQC_GET_CGU_OUT_CFG_DPLL_SRC_SEL_SHIFT) +#define ICE_AQC_GET_CGU_OUT_CFG_DPLL_MODE_SHIFT 5 +#define ICE_AQC_GET_CGU_OUT_CFG_DPLL_MODE \ + ICE_M(0x7, ICE_AQC_GET_CGU_OUT_CFG_DPLL_MODE_SHIFT) + u8 rsvd; + __le32 freq; + __le32 src_freq; + u8 rsvd2[2]; + __le16 node_handle; +}; + +/* Get CGU DPLL status (direct 0x0C66) */ +struct ice_aqc_get_cgu_dpll_status { + u8 dpll_num; + u8 ref_state; +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_LOS BIT(0) +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_SCM BIT(1) +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_CFM BIT(2) +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_GST BIT(3) +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_PFM BIT(4) +#define ICE_AQC_GET_CGU_DPLL_STATUS_FAST_LOCK_EN BIT(5) +#define ICE_AQC_GET_CGU_DPLL_STATUS_REF_SW_ESYNC BIT(6) + __le16 dpll_state; +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_LOCK BIT(0) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_HO BIT(1) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_HO_READY BIT(2) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_FLHIT BIT(5) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_PSLHIT BIT(7) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_CLK_REF_SHIFT 8 +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_CLK_REF_SEL \ + ICE_M(0x1F, ICE_AQC_GET_CGU_DPLL_STATUS_STATE_CLK_REF_SHIFT) +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_MODE_SHIFT 13 +#define ICE_AQC_GET_CGU_DPLL_STATUS_STATE_MODE \ + ICE_M(0x7, ICE_AQC_GET_CGU_DPLL_STATUS_STATE_MODE_SHIFT) + __le32 phase_offset_h; + __le32 phase_offset_l; + u8 eec_mode; +#define ICE_AQC_GET_CGU_DPLL_STATUS_EEC_MODE_1 0xA +#define ICE_AQC_GET_CGU_DPLL_STATUS_EEC_MODE_2 0xB +#define ICE_AQC_GET_CGU_DPLL_STATUS_EEC_MODE_UNKNOWN 0xF + u8 rsvd[1]; + __le16 node_handle; +}; + +/* Set CGU DPLL config (direct 0x0C67) */ +struct ice_aqc_set_cgu_dpll_config { + u8 dpll_num; + u8 ref_state; +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_LOS BIT(0) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_SCM BIT(1) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_CFM BIT(2) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_GST BIT(3) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_PFM BIT(4) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_FLOCK_EN BIT(5) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_REF_SW_ESYNC BIT(6) + u8 rsvd; + u8 config; +#define ICE_AQC_SET_CGU_DPLL_CONFIG_CLK_REF_SEL ICE_M(0x1F, 0) +#define ICE_AQC_SET_CGU_DPLL_CONFIG_MODE ICE_M(0x7, 5) + u8 rsvd2[8]; + u8 eec_mode; + u8 rsvd3[1]; + __le16 node_handle; +}; + +/* Set CGU reference priority (direct 0x0C68) */ +struct ice_aqc_set_cgu_ref_prio { + u8 dpll_num; + u8 ref_idx; + u8 ref_priority; + u8 rsvd[11]; + __le16 node_handle; +}; + +/* Get CGU reference priority (direct 0x0C69) */ +struct ice_aqc_get_cgu_ref_prio { + u8 dpll_num; + u8 ref_idx; + u8 ref_priority; /* Valid only in response */ + u8 rsvd[13]; +}; + +/* Get CGU info (direct 0x0C6A) */ +struct ice_aqc_get_cgu_info { + __le32 cgu_id; + __le32 cgu_cfg_ver; + __le32 cgu_fw_ver; + u8 node_part_num; + u8 dev_rev; + __le16 node_handle; +}; + /* Driver Shared Parameters (direct, 0x0C90) */ struct ice_aqc_driver_shared_params { u8 set_or_get_op; @@ -2148,6 +2361,8 @@ struct ice_aq_desc { struct ice_aqc_get_phy_caps get_phy; struct ice_aqc_set_phy_cfg set_phy; struct ice_aqc_restart_an restart_an; + struct ice_aqc_set_phy_rec_clk_out set_phy_rec_clk_out; + struct ice_aqc_get_phy_rec_clk_out get_phy_rec_clk_out; struct ice_aqc_gpio read_write_gpio; struct ice_aqc_sff_eeprom read_write_sff_param; struct ice_aqc_set_port_id_led set_port_id_led; @@ -2187,6 +2402,15 @@ struct ice_aq_desc { struct ice_aqc_fw_logging fw_logging; struct ice_aqc_get_clear_fw_log get_clear_fw_log; struct ice_aqc_download_pkg download_pkg; + struct ice_aqc_set_cgu_input_config set_cgu_input_config; + struct ice_aqc_get_cgu_input_config get_cgu_input_config; + struct ice_aqc_set_cgu_output_config set_cgu_output_config; + struct ice_aqc_get_cgu_output_config get_cgu_output_config; + struct ice_aqc_get_cgu_dpll_status get_cgu_dpll_status; + struct ice_aqc_set_cgu_dpll_config set_cgu_dpll_config; + struct ice_aqc_set_cgu_ref_prio set_cgu_ref_prio; + struct ice_aqc_get_cgu_ref_prio get_cgu_ref_prio; + struct ice_aqc_get_cgu_info get_cgu_info; struct ice_aqc_driver_shared_params drv_shared_params; struct ice_aqc_set_mac_lb set_mac_lb; struct ice_aqc_alloc_free_res_cmd sw_res_ctrl; @@ -2310,6 +2534,8 @@ enum ice_adminq_opc { ice_aqc_opc_get_link_status = 0x0607, ice_aqc_opc_set_event_mask = 0x0613, ice_aqc_opc_set_mac_lb = 0x0620, + ice_aqc_opc_set_phy_rec_clk_out = 0x0630, + ice_aqc_opc_get_phy_rec_clk_out = 0x0631, ice_aqc_opc_get_link_topo = 0x06E0, ice_aqc_opc_read_i2c = 0x06E2, ice_aqc_opc_write_i2c = 0x06E3, @@ -2364,6 +2590,18 @@ enum ice_adminq_opc { ice_aqc_opc_update_pkg = 0x0C42, ice_aqc_opc_get_pkg_info_list = 0x0C43, + /* 1588/SyncE commands/events */ + ice_aqc_opc_get_cgu_abilities = 0x0C61, + ice_aqc_opc_set_cgu_input_config = 0x0C62, + ice_aqc_opc_get_cgu_input_config = 0x0C63, + ice_aqc_opc_set_cgu_output_config = 0x0C64, + ice_aqc_opc_get_cgu_output_config = 0x0C65, + ice_aqc_opc_get_cgu_dpll_status = 0x0C66, + ice_aqc_opc_set_cgu_dpll_config = 0x0C67, + ice_aqc_opc_set_cgu_ref_prio = 0x0C68, + ice_aqc_opc_get_cgu_ref_prio = 0x0C69, + ice_aqc_opc_get_cgu_info = 0x0C6A, + ice_aqc_opc_driver_shared_params = 0x0C90, /* Standalone Commands/Events */ diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index c2fda4fa4188..9c2ccb2072fd 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -434,6 +434,83 @@ ice_aq_get_link_topo_handle(struct ice_port_info *pi, u8 node_type, return ice_aq_send_cmd(pi->hw, &desc, NULL, 0, cd); } +/** + * ice_aq_get_netlist_node + * @hw: pointer to the hw struct + * @cmd: get_link_topo AQ structure + * @node_part_number: output node part number if node found + * @node_handle: output node handle parameter if node found + * + * Get netlist node handle. + */ +int +ice_aq_get_netlist_node(struct ice_hw *hw, struct ice_aqc_get_link_topo *cmd, + u8 *node_part_number, u16 *node_handle) +{ + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_link_topo); + desc.params.get_link_topo = *cmd; + + if (ice_aq_send_cmd(hw, &desc, NULL, 0, NULL)) + return -EINTR; + + if (node_handle) + *node_handle = + le16_to_cpu(desc.params.get_link_topo.addr.handle); + if (node_part_number) + *node_part_number = desc.params.get_link_topo.node_part_num; + + return 0; +} + +#define MAX_NETLIST_SIZE 10 + +/** + * ice_find_netlist_node + * @hw: pointer to the hw struct + * @node_type_ctx: type of netlist node to look for + * @node_part_number: node part number to look for + * @node_handle: output parameter if node found - optional + * + * Find and return the node handle for a given node type and part number in the + * netlist. When found ICE_SUCCESS is returned, ICE_ERR_DOES_NOT_EXIST + * otherwise. If node_handle provided, it would be set to found node handle. + */ +int +ice_find_netlist_node(struct ice_hw *hw, u8 node_type_ctx, u8 node_part_number, + u16 *node_handle) +{ + struct ice_aqc_get_link_topo cmd; + u8 rec_node_part_number; + u16 rec_node_handle; + u8 idx; + + for (idx = 0; idx < MAX_NETLIST_SIZE; idx++) { + int status; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.addr.topo_params.node_type_ctx = + (node_type_ctx << ICE_AQC_LINK_TOPO_NODE_TYPE_S); + cmd.addr.topo_params.index = idx; + + status = ice_aq_get_netlist_node(hw, &cmd, + &rec_node_part_number, + &rec_node_handle); + if (status) + return status; + + if (rec_node_part_number == node_part_number) { + if (node_handle) + *node_handle = rec_node_handle; + return 0; + } + } + + return -ENOTBLK; +} + /** * ice_is_media_cage_present * @pi: port information structure @@ -4926,6 +5003,396 @@ ice_dis_vsi_rdma_qset(struct ice_port_info *pi, u16 count, u32 *qset_teid, return status; } +/** + * ice_aq_get_cgu_abilities + * @hw: pointer to the HW struct + * @abilities: CGU abilities + * + * Get CGU abilities (0x0C61) + */ +int +ice_aq_get_cgu_abilities(struct ice_hw *hw, + struct ice_aqc_get_cgu_abilities *abilities) +{ + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_abilities); + return ice_aq_send_cmd(hw, &desc, abilities, sizeof(*abilities), NULL); +} + +/** + * ice_aq_set_input_pin_cfg + * @hw: pointer to the HW struct + * @input_idx: Input index + * @flags1: Input flags + * @flags2: Input flags + * @freq: Frequency in Hz + * @phase_delay: Delay in ps + * + * Set CGU input config (0x0C62) + */ +int +ice_aq_set_input_pin_cfg(struct ice_hw *hw, u8 input_idx, u8 flags1, u8 flags2, + u32 freq, s32 phase_delay) +{ + struct ice_aqc_set_cgu_input_config *cmd; + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_set_cgu_input_config); + cmd = &desc.params.set_cgu_input_config; + cmd->input_idx = input_idx; + cmd->flags1 = flags1; + cmd->flags2 = flags2; + cmd->freq = cpu_to_le32(freq); + cmd->phase_delay = cpu_to_le32(phase_delay); + + return ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); +} + +/** + * ice_aq_get_input_pin_cfg + * @hw: pointer to the HW struct + * @input_idx: Input index + * @status: Pin status + * @type: Pin type + * @flags1: Input flags + * @flags2: Input flags + * @freq: Frequency in Hz + * @phase_delay: Delay in ps + * + * Get CGU input config (0x0C63) + */ +int +ice_aq_get_input_pin_cfg(struct ice_hw *hw, u8 input_idx, u8 *status, u8 *type, + u8 *flags1, u8 *flags2, u32 *freq, s32 *phase_delay) +{ + struct ice_aqc_get_cgu_input_config *cmd; + struct ice_aq_desc desc; + int ret; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_input_config); + cmd = &desc.params.get_cgu_input_config; + cmd->input_idx = input_idx; + + ret = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!ret) { + if (status) + *status = cmd->status; + if (type) + *type = cmd->type; + if (flags1) + *flags1 = cmd->flags1; + if (flags2) + *flags2 = cmd->flags2; + if (freq) + *freq = le32_to_cpu(cmd->freq); + if (phase_delay) + *phase_delay = le32_to_cpu(cmd->phase_delay); + } + + return ret; +} + +/** + * ice_aq_set_output_pin_cfg + * @hw: pointer to the HW struct + * @output_idx: Output index + * @flags: Output flags + * @src_sel: Index of DPLL block + * @freq: Output frequency + * @phase_delay: Output phase compensation + * + * Set CGU output config (0x0C64) + */ +int +ice_aq_set_output_pin_cfg(struct ice_hw *hw, u8 output_idx, u8 flags, + u8 src_sel, u32 freq, s32 phase_delay) +{ + struct ice_aqc_set_cgu_output_config *cmd; + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_set_cgu_output_config); + cmd = &desc.params.set_cgu_output_config; + cmd->output_idx = output_idx; + cmd->flags = flags; + cmd->src_sel = src_sel; + cmd->freq = cpu_to_le32(freq); + cmd->phase_delay = cpu_to_le32(phase_delay); + + return ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); +} + +/** + * ice_aq_get_output_pin_cfg + * @hw: pointer to the HW struct + * @output_idx: Output index + * @flags: Output flags + * @src_sel: Internal DPLL source + * @freq: Output frequency + * @src_freq: Source frequency + * + * Get CGU output config (0x0C65) + */ +int +ice_aq_get_output_pin_cfg(struct ice_hw *hw, u8 output_idx, u8 *flags, + u8 *src_sel, u32 *freq, u32 *src_freq) +{ + struct ice_aqc_get_cgu_output_config *cmd; + struct ice_aq_desc desc; + int ret; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_output_config); + cmd = &desc.params.get_cgu_output_config; + cmd->output_idx = output_idx; + + ret = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!ret) { + if (flags) + *flags = cmd->flags; + if (src_sel) + *src_sel = cmd->src_sel; + if (freq) + *freq = le32_to_cpu(cmd->freq); + if (src_freq) + *src_freq = le32_to_cpu(cmd->src_freq); + } + + return ret; +} + +/** + * convert_s48_to_s64 - convert 48 bit value to 64 bit value + * @signed_48: signed 64 bit variable storing signed 48 bit value + * + * Convert signed 48 bit value to its 64 bit representation. + * + * Return: signed 64 bit representation of signed 48 bit value. + */ +static inline +s64 convert_s48_to_s64(s64 signed_48) +{ + const s64 MASK_SIGN_BITS = GENMASK_ULL(63, 48); + const s64 SIGN_BIT_47 = BIT_ULL(47); + + return ((signed_48 & SIGN_BIT_47) ? (s64)(MASK_SIGN_BITS | signed_48) + : signed_48); +} + +/** + * ice_aq_get_cgu_dpll_status + * @hw: pointer to the HW struct + * @dpll_num: DPLL index + * @ref_state: Reference clock state + * @dpll_state: DPLL state + * @phase_offset: Phase offset in ns + * @eec_mode: EEC_mode + * + * Get CGU DPLL status (0x0C66) + */ +int +ice_aq_get_cgu_dpll_status(struct ice_hw *hw, u8 dpll_num, u8 *ref_state, + u16 *dpll_state, s64 *phase_offset, u8 *eec_mode) +{ + struct ice_aqc_get_cgu_dpll_status *cmd; + const s64 NSEC_PER_PSEC = 1000LL; + struct ice_aq_desc desc; + int status; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_dpll_status); + cmd = &desc.params.get_cgu_dpll_status; + cmd->dpll_num = dpll_num; + + status = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!status) { + *ref_state = cmd->ref_state; + *dpll_state = le16_to_cpu(cmd->dpll_state); + *phase_offset = le32_to_cpu(cmd->phase_offset_h); + *phase_offset <<= 32; + *phase_offset += le32_to_cpu(cmd->phase_offset_l); + *phase_offset = convert_s48_to_s64(*phase_offset) + / NSEC_PER_PSEC; + *eec_mode = cmd->eec_mode; + } + + return status; +} + +/** + * ice_aq_set_cgu_dpll_config + * @hw: pointer to the HW struct + * @dpll_num: DPLL index + * @ref_state: Reference clock state + * @config: DPLL config + * @eec_mode: EEC mode + * + * Set CGU DPLL config (0x0C67) + */ +int +ice_aq_set_cgu_dpll_config(struct ice_hw *hw, u8 dpll_num, u8 ref_state, + u8 config, u8 eec_mode) +{ + struct ice_aqc_set_cgu_dpll_config *cmd; + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_set_cgu_dpll_config); + cmd = &desc.params.set_cgu_dpll_config; + cmd->dpll_num = dpll_num; + cmd->ref_state = ref_state; + cmd->config = config; + cmd->eec_mode = eec_mode; + + return ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); +} + +/** + * ice_aq_set_cgu_ref_prio + * @hw: pointer to the HW struct + * @dpll_num: DPLL index + * @ref_idx: Reference pin index + * @ref_priority: Reference input priority + * + * Set CGU reference priority (0x0C68) + */ +int +ice_aq_set_cgu_ref_prio(struct ice_hw *hw, u8 dpll_num, u8 ref_idx, + u8 ref_priority) +{ + struct ice_aqc_set_cgu_ref_prio *cmd; + struct ice_aq_desc desc; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_set_cgu_ref_prio); + cmd = &desc.params.set_cgu_ref_prio; + cmd->dpll_num = dpll_num; + cmd->ref_idx = ref_idx; + cmd->ref_priority = ref_priority; + + return ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); +} + +/** + * ice_aq_get_cgu_ref_prio + * @hw: pointer to the HW struct + * @dpll_num: DPLL index + * @ref_idx: Reference pin index + * @ref_prio: Reference input priority + * + * Get CGU reference priority (0x0C69) + */ +int +ice_aq_get_cgu_ref_prio(struct ice_hw *hw, u8 dpll_num, u8 ref_idx, + u8 *ref_prio) +{ + struct ice_aqc_get_cgu_ref_prio *cmd; + struct ice_aq_desc desc; + int status; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_ref_prio); + cmd = &desc.params.get_cgu_ref_prio; + cmd->dpll_num = dpll_num; + cmd->ref_idx = ref_idx; + + status = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!status) + *ref_prio = cmd->ref_priority; + + return status; +} + +/** + * ice_aq_get_cgu_info + * @hw: pointer to the HW struct + * @cgu_id: CGU ID + * @cgu_cfg_ver: CGU config version + * @cgu_fw_ver: CGU firmware version + * + * Get CGU info (0x0C6A) + */ +int +ice_aq_get_cgu_info(struct ice_hw *hw, u32 *cgu_id, u32 *cgu_cfg_ver, + u32 *cgu_fw_ver) +{ + struct ice_aqc_get_cgu_info *cmd; + struct ice_aq_desc desc; + int status; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_cgu_info); + cmd = &desc.params.get_cgu_info; + + status = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!status) { + *cgu_id = le32_to_cpu(cmd->cgu_id); + *cgu_cfg_ver = le32_to_cpu(cmd->cgu_cfg_ver); + *cgu_fw_ver = le32_to_cpu(cmd->cgu_fw_ver); + } + + return status; +} + +/** + * ice_aq_set_phy_rec_clk_out - set RCLK phy out + * @hw: pointer to the HW struct + * @phy_output: PHY reference clock output pin + * @enable: GPIO state to be applied + * @freq: PHY output frequency + * + * Set CGU reference priority (0x0630) + * Return 0 on success or negative value on failure. + */ +int +ice_aq_set_phy_rec_clk_out(struct ice_hw *hw, u8 phy_output, bool enable, + u32 *freq) +{ + struct ice_aqc_set_phy_rec_clk_out *cmd; + struct ice_aq_desc desc; + int status; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_set_phy_rec_clk_out); + cmd = &desc.params.set_phy_rec_clk_out; + cmd->phy_output = phy_output; + cmd->port_num = ICE_AQC_SET_PHY_REC_CLK_OUT_CURR_PORT; + cmd->flags = enable & ICE_AQC_SET_PHY_REC_CLK_OUT_OUT_EN; + cmd->freq = cpu_to_le32(*freq); + + status = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!status) + *freq = le32_to_cpu(cmd->freq); + + return status; +} + +/** + * ice_aq_get_phy_rec_clk_out + * @hw: pointer to the HW struct + * @phy_output: PHY reference clock output pin + * @port_num: Port number + * @flags: PHY flags + * @freq: PHY output frequency + * + * Get PHY recovered clock output (0x0631) + */ +int +ice_aq_get_phy_rec_clk_out(struct ice_hw *hw, u8 phy_output, u8 *port_num, + u8 *flags, u32 *freq) +{ + struct ice_aqc_get_phy_rec_clk_out *cmd; + struct ice_aq_desc desc; + int status; + + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_get_phy_rec_clk_out); + cmd = &desc.params.get_phy_rec_clk_out; + cmd->phy_output = phy_output; + cmd->port_num = *port_num; + + status = ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); + if (!status) { + *port_num = cmd->port_num; + *flags = cmd->flags; + *freq = le32_to_cpu(cmd->freq); + } + + return status; +} + /** * ice_replay_pre_init - replay pre initialization * @hw: pointer to the HW struct diff --git a/drivers/net/ethernet/intel/ice/ice_common.h b/drivers/net/ethernet/intel/ice/ice_common.h index 8ba5f935a092..99c933552cc2 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.h +++ b/drivers/net/ethernet/intel/ice/ice_common.h @@ -94,6 +94,12 @@ ice_aq_get_phy_caps(struct ice_port_info *pi, bool qual_mods, u8 report_mode, struct ice_aqc_get_phy_caps_data *caps, struct ice_sq_cd *cd); int +ice_find_netlist_node(struct ice_hw *hw, u8 node_type_ctx, u8 node_part_number, + u16 *node_handle); +int +ice_aq_get_netlist_node(struct ice_hw *hw, struct ice_aqc_get_link_topo *cmd, + u8 *node_part_number, u16 *node_handle); +int ice_aq_list_caps(struct ice_hw *hw, void *buf, u16 buf_size, u32 *cap_count, enum ice_adminq_opc opc, struct ice_sq_cd *cd); int @@ -192,6 +198,43 @@ void ice_output_fw_log(struct ice_hw *hw, struct ice_aq_desc *desc, void *buf); struct ice_q_ctx * ice_get_lan_q_ctx(struct ice_hw *hw, u16 vsi_handle, u8 tc, u16 q_handle); int ice_sbq_rw_reg(struct ice_hw *hw, struct ice_sbq_msg_input *in); +int +ice_aq_get_cgu_abilities(struct ice_hw *hw, + struct ice_aqc_get_cgu_abilities *abilities); +int +ice_aq_set_input_pin_cfg(struct ice_hw *hw, u8 input_idx, u8 flags1, u8 flags2, + u32 freq, s32 phase_delay); +int +ice_aq_get_input_pin_cfg(struct ice_hw *hw, u8 input_idx, u8 *status, u8 *type, + u8 *flags1, u8 *flags2, u32 *freq, s32 *phase_delay); +int +ice_aq_set_output_pin_cfg(struct ice_hw *hw, u8 output_idx, u8 flags, + u8 src_sel, u32 freq, s32 phase_delay); +int +ice_aq_get_output_pin_cfg(struct ice_hw *hw, u8 output_idx, u8 *flags, + u8 *src_sel, u32 *freq, u32 *src_freq); +int +ice_aq_get_cgu_dpll_status(struct ice_hw *hw, u8 dpll_num, u8 *ref_state, + u16 *dpll_state, s64 *phase_offset, u8 *eec_mode); +int +ice_aq_set_cgu_dpll_config(struct ice_hw *hw, u8 dpll_num, u8 ref_state, + u8 config, u8 eec_mode); +int +ice_aq_set_cgu_ref_prio(struct ice_hw *hw, u8 dpll_num, u8 ref_idx, + u8 ref_priority); +int +ice_aq_get_cgu_ref_prio(struct ice_hw *hw, u8 dpll_num, u8 ref_idx, + u8 *ref_prio); +int +ice_aq_get_cgu_info(struct ice_hw *hw, u32 *cgu_id, u32 *cgu_cfg_ver, + u32 *cgu_fw_ver); + +int +ice_aq_set_phy_rec_clk_out(struct ice_hw *hw, u8 phy_output, bool enable, + u32 *freq); +int +ice_aq_get_phy_rec_clk_out(struct ice_hw *hw, u8 phy_output, u8 *port_num, + u8 *flags, u32 *freq); void ice_stat_update40(struct ice_hw *hw, u32 reg, bool prev_stat_loaded, u64 *prev_stat, u64 *cur_stat); diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 781475480ff2..8738eb627fd1 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -4322,13 +4322,22 @@ void ice_init_feature_support(struct ice_pf *pf) case ICE_DEV_ID_E810C_BACKPLANE: case ICE_DEV_ID_E810C_QSFP: case ICE_DEV_ID_E810C_SFP: + case ICE_DEV_ID_E810_XXV_BACKPLANE: + case ICE_DEV_ID_E810_XXV_QSFP: + case ICE_DEV_ID_E810_XXV_SFP: ice_set_feature_support(pf, ICE_F_DSCP); ice_set_feature_support(pf, ICE_F_PTP_EXTTS); - if (ice_is_e810t(&pf->hw)) { + if (ice_is_phy_rclk_present(&pf->hw)) + ice_set_feature_support(pf, ICE_F_PHY_RCLK); + /* If we don't own the timer - don't enable other caps */ + if (!pf->hw.func_caps.ts_func_info.src_tmr_owned) + break; + if (ice_is_cgu_present(&pf->hw)) + ice_set_feature_support(pf, ICE_F_CGU); + if (ice_is_clock_mux_present_e810t(&pf->hw)) ice_set_feature_support(pf, ICE_F_SMA_CTRL); - if (ice_gnss_is_gps_present(&pf->hw)) - ice_set_feature_support(pf, ICE_F_GNSS); - } + if (ice_gnss_is_gps_present(&pf->hw)) + ice_set_feature_support(pf, ICE_F_GNSS); break; default: break; diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index a38614d21ea8..e9a371fa038b 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -3213,6 +3213,91 @@ ice_get_pca9575_handle(struct ice_hw *hw, u16 *pca9575_handle) return 0; } +/** + * ice_is_phy_rclk_present + * @hw: pointer to the hw struct + * + * Check if the PHY Recovered Clock device is present in the netlist + * Return: + * * true - device found in netlist + * * false - device not found + */ +bool ice_is_phy_rclk_present(struct ice_hw *hw) +{ + if (ice_find_netlist_node(hw, ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_CTRL, + ICE_ACQ_GET_LINK_TOPO_NODE_NR_C827, NULL) && + ice_find_netlist_node(hw, ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_CTRL, + ICE_ACQ_GET_LINK_TOPO_NODE_NR_E822_PHY, NULL)) + return false; + + return true; +} + +/** + * ice_is_clock_mux_present_e810t + * @hw: pointer to the hw struct + * + * Check if the Clock Multiplexer device is present in the netlist + * Return: + * * true - device found in netlist + * * false - device not found + */ +bool ice_is_clock_mux_present_e810t(struct ice_hw *hw) +{ + if (ice_find_netlist_node(hw, ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_MUX, + ICE_ACQ_GET_LINK_TOPO_NODE_NR_GEN_CLK_MUX, + NULL)) + return false; + + return true; +} + +/** + * ice_get_pf_c827_idx - find and return the C827 index for the current pf + * @hw: pointer to the hw struct + * @idx: index of the found C827 PHY + * Return: + * * 0 - success + * * negative - failure + */ +int ice_get_pf_c827_idx(struct ice_hw *hw, u8 *idx) +{ + struct ice_aqc_get_link_topo cmd; + u8 node_part_number; + u16 node_handle; + int status; + u8 ctx; + + if (hw->mac_type != ICE_MAC_E810) + return -ENODEV; + + if (hw->device_id != ICE_DEV_ID_E810C_QSFP) { + *idx = C827_0; + return 0; + } + + memset(&cmd, 0, sizeof(cmd)); + + ctx = ICE_AQC_LINK_TOPO_NODE_TYPE_PHY << ICE_AQC_LINK_TOPO_NODE_TYPE_S; + ctx |= ICE_AQC_LINK_TOPO_NODE_CTX_PORT << ICE_AQC_LINK_TOPO_NODE_CTX_S; + cmd.addr.topo_params.node_type_ctx = ctx; + cmd.addr.topo_params.index = 0; + + status = ice_aq_get_netlist_node(hw, &cmd, &node_part_number, + &node_handle); + if (status || node_part_number != ICE_ACQ_GET_LINK_TOPO_NODE_NR_C827) + return -ENOENT; + + if (node_handle == E810C_QSFP_C827_0_HANDLE) + *idx = C827_0; + else if (node_handle == E810C_QSFP_C827_1_HANDLE) + *idx = C827_1; + else + return -EIO; + + return 0; +} + /** * ice_read_sma_ctrl_e810t * @hw: pointer to the hw struct @@ -3381,3 +3466,329 @@ int ice_get_phy_tx_tstamp_ready(struct ice_hw *hw, u8 block, u64 *tstamp_ready) return ice_get_phy_tx_tstamp_ready_e822(hw, block, tstamp_ready); } + +/** + * ice_is_cgu_present + * @hw: pointer to the hw struct + * + * Check if the Clock Generation Unit (CGU) device is present in the netlist + * Return: + * * true - cgu is present + * * false - cgu is not present + */ +bool ice_is_cgu_present(struct ice_hw *hw) +{ + if (!ice_find_netlist_node(hw, ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_CTRL, + ICE_ACQ_GET_LINK_TOPO_NODE_NR_ZL30632_80032, + NULL)) { + hw->cgu_part_number = ICE_ACQ_GET_LINK_TOPO_NODE_NR_ZL30632_80032; + return true; + } else if (!ice_find_netlist_node(hw, + ICE_AQC_LINK_TOPO_NODE_TYPE_CLK_CTRL, + ICE_ACQ_GET_LINK_TOPO_NODE_NR_SI5383_5384, + NULL)) { + hw->cgu_part_number = ICE_ACQ_GET_LINK_TOPO_NODE_NR_SI5383_5384; + return true; + } + + return false; +} + +/** + * ice_cgu_get_pin_desc_e823 + * @hw: pointer to the hw struct + * @input: if request is done against input or output pin + * @size: number of inputs/outputs + * + * Return: pointer to pin description array associated to given hw. + */ +static const struct ice_cgu_pin_desc * +ice_cgu_get_pin_desc_e823(struct ice_hw *hw, bool input, int *size) +{ + static const struct ice_cgu_pin_desc *t; + + if (hw->cgu_part_number == + ICE_ACQ_GET_LINK_TOPO_NODE_NR_ZL30632_80032) { + if (input) { + t = ice_e823_zl_cgu_inputs; + *size = ARRAY_SIZE(ice_e823_zl_cgu_inputs); + } else { + t = ice_e823_zl_cgu_outputs; + *size = ARRAY_SIZE(ice_e823_zl_cgu_outputs); + } + } else if (hw->cgu_part_number == + ICE_ACQ_GET_LINK_TOPO_NODE_NR_SI5383_5384) { + if (input) { + t = ice_e823_si_cgu_inputs; + *size = ARRAY_SIZE(ice_e823_si_cgu_inputs); + } else { + t = ice_e823_si_cgu_outputs; + *size = ARRAY_SIZE(ice_e823_si_cgu_outputs); + } + } else { + t = NULL; + *size = 0; + } + + return t; +} + +/** + * ice_cgu_get_pin_desc + * @hw: pointer to the hw struct + * @input: if request is done against input or output pins + * @size: size of array returned by function + * + * Return: pointer to pin description array associated to given hw. + */ +static const struct ice_cgu_pin_desc * +ice_cgu_get_pin_desc(struct ice_hw *hw, bool input, int *size) +{ + const struct ice_cgu_pin_desc *t = NULL; + + switch (hw->device_id) { + case ICE_DEV_ID_E810C_SFP: + if (input) { + t = ice_e810t_sfp_cgu_inputs; + *size = ARRAY_SIZE(ice_e810t_sfp_cgu_inputs); + } else { + t = ice_e810t_sfp_cgu_outputs; + *size = ARRAY_SIZE(ice_e810t_sfp_cgu_outputs); + } + break; + case ICE_DEV_ID_E810C_QSFP: + if (input) { + t = ice_e810t_qsfp_cgu_inputs; + *size = ARRAY_SIZE(ice_e810t_qsfp_cgu_inputs); + } else { + t = ice_e810t_qsfp_cgu_outputs; + *size = ARRAY_SIZE(ice_e810t_qsfp_cgu_outputs); + } + break; + case ICE_DEV_ID_E823L_10G_BASE_T: + case ICE_DEV_ID_E823L_1GBE: + case ICE_DEV_ID_E823L_BACKPLANE: + case ICE_DEV_ID_E823L_QSFP: + case ICE_DEV_ID_E823L_SFP: + case ICE_DEV_ID_E823C_10G_BASE_T: + case ICE_DEV_ID_E823C_BACKPLANE: + case ICE_DEV_ID_E823C_QSFP: + case ICE_DEV_ID_E823C_SFP: + case ICE_DEV_ID_E823C_SGMII: + t = ice_cgu_get_pin_desc_e823(hw, input, size); + break; + default: + break; + } + + return t; +} + +/** + * ice_cgu_get_pin_type + * @hw: pointer to the hw struct + * @pin: pin index + * @input: if request is done against input or output pin + * + * Return: type of a pin. + */ +enum dpll_pin_type ice_cgu_get_pin_type(struct ice_hw *hw, u8 pin, bool input) +{ + const struct ice_cgu_pin_desc *t; + int t_size; + + t = ice_cgu_get_pin_desc(hw, input, &t_size); + + if (!t) + return 0; + + if (pin >= t_size) + return 0; + + return t[pin].type; +} + +/** + * ice_cgu_get_pin_sig_type_mask + * @hw: pointer to the hw struct + * @pin: pin index + * @input: if request is done against input or output pin + * + * Return: signal type bit mask of a pin. + */ +unsigned long +ice_cgu_get_pin_freq_mask(struct ice_hw *hw, u8 pin, bool input) +{ + const struct ice_cgu_pin_desc *t; + int t_size; + + t = ice_cgu_get_pin_desc(hw, input, &t_size); + + if (!t) + return 0; + + if (pin >= t_size) + return 0; + + return t[pin].sig_type_mask; +} + +/** + * ice_cgu_get_pin_name + * @hw: pointer to the hw struct + * @pin: pin index + * @input: if request is done against input or output pin + * + * Return: + * * null terminated char array with name + * * NULL in case of failure + */ +const char *ice_cgu_get_pin_name(struct ice_hw *hw, u8 pin, bool input) +{ + const struct ice_cgu_pin_desc *t; + int t_size; + + t = ice_cgu_get_pin_desc(hw, input, &t_size); + + if (!t) + return NULL; + + if (pin >= t_size) + return NULL; + + return t[pin].name; +} + +/** + * ice_get_cgu_state - get the state of the DPLL + * @hw: pointer to the hw struct + * @dpll_idx: Index of internal DPLL unit + * @last_dpll_state: last known state of DPLL + * @pin: pointer to a buffer for returning currently active pin + * @ref_state: reference clock state + * @phase_offset: pointer to a buffer for returning phase offset + * @dpll_state: state of the DPLL (output) + * + * This function will read the state of the DPLL(dpll_idx). Non-null + * 'pin', 'ref_state', 'eec_mode' and 'phase_offset' parameters are used to + * retrieve currently active pin, state, mode and phase_offset respectively. + * + * Return: state of the DPLL + */ +int ice_get_cgu_state(struct ice_hw *hw, u8 dpll_idx, + enum ice_cgu_state last_dpll_state, u8 *pin, + u8 *ref_state, u8 *eec_mode, s64 *phase_offset, + enum ice_cgu_state *dpll_state) +{ + u8 hw_ref_state, hw_eec_mode; + s64 hw_phase_offset; + u16 hw_dpll_state; + int status; + + status = ice_aq_get_cgu_dpll_status(hw, dpll_idx, &hw_ref_state, + &hw_dpll_state, &hw_phase_offset, + &hw_eec_mode); + if (status) { + *dpll_state = ICE_CGU_STATE_INVALID; + return status; + } + + if (pin) { + /* current ref pin in dpll_state_refsel_status_X register */ + *pin = (hw_dpll_state & + ICE_AQC_GET_CGU_DPLL_STATUS_STATE_CLK_REF_SEL) >> + ICE_AQC_GET_CGU_DPLL_STATUS_STATE_CLK_REF_SHIFT; + } + + if (phase_offset) + *phase_offset = hw_phase_offset; + + if (ref_state) + *ref_state = hw_ref_state; + + if (eec_mode) + *eec_mode = hw_eec_mode; + + if (!dpll_state) + return status; + + /* According to ZL DPLL documentation, once state reach LOCKED_HO_ACQ + * it would never return to FREERUN. This aligns to ITU-T G.781 + * Recommendation. We cannot report HOLDOVER as HO memory is cleared + * while switching to another reference. + * Only for situations where previous state was either: "LOCKED without + * HO_ACQ" or "HOLDOVER" we actually back to FREERUN. + */ + if (hw_dpll_state & ICE_AQC_GET_CGU_DPLL_STATUS_STATE_LOCK) { + if (hw_dpll_state & ICE_AQC_GET_CGU_DPLL_STATUS_STATE_HO_READY) + *dpll_state = ICE_CGU_STATE_LOCKED_HO_ACQ; + else + *dpll_state = ICE_CGU_STATE_LOCKED; + } else if (last_dpll_state == ICE_CGU_STATE_LOCKED_HO_ACQ || + last_dpll_state == ICE_CGU_STATE_HOLDOVER) { + *dpll_state = ICE_CGU_STATE_HOLDOVER; + } else { + *dpll_state = ICE_CGU_STATE_FREERUN; + } + + return status; +} + +/** + * ice_get_cgu_rclk_pin_info - get info on available recovered clock pins + * @hw: pointer to the hw struct + * @base_idx: returns index of first recovered clock pin on device + * @pin_num: returns number of recovered clock pins available on device + * + * Based on hw provide caller info about recovery clock pins available on the + * board. + * + * Return: + * * 0 - success, information is valid + * * negative - failure, information is not valid + */ +int ice_get_cgu_rclk_pin_info(struct ice_hw *hw, u8 *base_idx, u8 *pin_num) +{ + u8 phy_idx; + int ret; + + switch (hw->device_id) { + case ICE_DEV_ID_E810C_SFP: + case ICE_DEV_ID_E810C_QSFP: + + ret = ice_get_pf_c827_idx(hw, &phy_idx); + if (ret) + return ret; + *base_idx = E810T_CGU_INPUT_C827(phy_idx, ICE_RCLKA_PIN); + *pin_num = ICE_E810_RCLK_PINS_NUM; + ret = 0; + break; + case ICE_DEV_ID_E823L_10G_BASE_T: + case ICE_DEV_ID_E823L_1GBE: + case ICE_DEV_ID_E823L_BACKPLANE: + case ICE_DEV_ID_E823L_QSFP: + case ICE_DEV_ID_E823L_SFP: + case ICE_DEV_ID_E823C_10G_BASE_T: + case ICE_DEV_ID_E823C_BACKPLANE: + case ICE_DEV_ID_E823C_QSFP: + case ICE_DEV_ID_E823C_SFP: + case ICE_DEV_ID_E823C_SGMII: + *pin_num = ICE_E822_RCLK_PINS_NUM; + ret = 0; + if (hw->cgu_part_number == + ICE_ACQ_GET_LINK_TOPO_NODE_NR_ZL30632_80032) + *base_idx = ZL_REF1P; + else if (hw->cgu_part_number == + ICE_ACQ_GET_LINK_TOPO_NODE_NR_SI5383_5384) + *base_idx = SI_REF1P; + else + ret = -ENODEV; + + break; + default: + ret = -ENODEV; + break; + } + + return ret; +} diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h index 3b68cb91bd81..d09e5bca0ff1 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h @@ -3,6 +3,7 @@ #ifndef _ICE_PTP_HW_H_ #define _ICE_PTP_HW_H_ +#include enum ice_ptp_tmr_cmd { INIT_TIME, @@ -109,6 +110,232 @@ struct ice_cgu_pll_params_e822 { u32 post_pll_div; }; +#define E810C_QSFP_C827_0_HANDLE 2 +#define E810C_QSFP_C827_1_HANDLE 3 +enum ice_e810_c827_idx { + C827_0, + C827_1 +}; + +enum ice_phy_rclk_pins { + ICE_RCLKA_PIN = 0, /* SCL pin */ + ICE_RCLKB_PIN, /* SDA pin */ +}; + +#define ICE_E810_RCLK_PINS_NUM (ICE_RCLKB_PIN + 1) +#define ICE_E822_RCLK_PINS_NUM (ICE_RCLKA_PIN + 1) +#define E810T_CGU_INPUT_C827(_phy, _pin) ((_phy) * ICE_E810_RCLK_PINS_NUM + \ + (_pin) + ZL_REF1P) +enum ice_cgu_state { + ICE_CGU_STATE_UNKNOWN = -1, + ICE_CGU_STATE_INVALID, /* state is not valid */ + ICE_CGU_STATE_FREERUN, /* clock is free-running */ + ICE_CGU_STATE_LOCKED, /* clock is locked to the reference, + * but the holdover memory is not valid + */ + ICE_CGU_STATE_LOCKED_HO_ACQ, /* clock is locked to the reference + * and holdover memory is valid + */ + ICE_CGU_STATE_HOLDOVER, /* clock is in holdover mode */ + ICE_CGU_STATE_MAX +}; + +#define MAX_CGU_STATE_NAME_LEN 14 +struct ice_cgu_state_desc { + char name[MAX_CGU_STATE_NAME_LEN]; + enum ice_cgu_state state; +}; + +enum ice_zl_cgu_in_pins { + ZL_REF0P = 0, + ZL_REF0N, + ZL_REF1P, + ZL_REF1N, + ZL_REF2P, + ZL_REF2N, + ZL_REF3P, + ZL_REF3N, + ZL_REF4P, + ZL_REF4N, + NUM_ZL_CGU_INPUT_PINS +}; + +enum ice_zl_cgu_out_pins { + ZL_OUT0 = 0, + ZL_OUT1, + ZL_OUT2, + ZL_OUT3, + ZL_OUT4, + ZL_OUT5, + ZL_OUT6, + NUM_ZL_CGU_OUTPUT_PINS +}; + +enum ice_si_cgu_in_pins { + SI_REF0P = 0, + SI_REF0N, + SI_REF1P, + SI_REF1N, + SI_REF2P, + SI_REF2N, + SI_REF3, + SI_REF4, + NUM_SI_CGU_INPUT_PINS +}; + +enum ice_si_cgu_out_pins { + SI_OUT0 = 0, + SI_OUT1, + SI_OUT2, + SI_OUT3, + SI_OUT4, + NUM_SI_CGU_OUTPUT_PINS +}; + +#define MAX_CGU_PIN_NAME_LEN 16 +#define ICE_SIG_TYPE_MASK_1PPS_10MHZ (BIT(DPLL_PIN_FREQ_SUPP_1_HZ) | \ + BIT(DPLL_PIN_FREQ_SUPP_10_MHZ)) +struct ice_cgu_pin_desc { + char name[MAX_CGU_PIN_NAME_LEN]; + u8 index; + enum dpll_pin_type type; + unsigned long sig_type_mask; +}; + +static const struct ice_cgu_pin_desc ice_e810t_sfp_cgu_inputs[] = { + { "CVL-SDP22", ZL_REF0P, DPLL_PIN_TYPE_INT_OSCILLATOR, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "CVL-SDP20", ZL_REF0N, DPLL_PIN_TYPE_INT_OSCILLATOR, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "SMA1", ZL_REF3P, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "SMA2/U.FL2", ZL_REF3N, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "GNSS-1PPS", ZL_REF4P, DPLL_PIN_TYPE_GNSS, + BIT(DPLL_PIN_FREQ_SUPP_1_HZ) }, + { "OCXO", ZL_REF4N, DPLL_PIN_TYPE_INT_OSCILLATOR, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, +}; + +static const struct ice_cgu_pin_desc ice_e810t_qsfp_cgu_inputs[] = { + { "CVL-SDP22", ZL_REF0P, DPLL_PIN_TYPE_INT_OSCILLATOR, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "CVL-SDP20", ZL_REF0N, DPLL_PIN_TYPE_INT_OSCILLATOR, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "C827_0-RCLKA", ZL_REF1P, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "C827_0-RCLKB", ZL_REF1N, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "C827_1-RCLKA", ZL_REF2P, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "C827_1-RCLKB", ZL_REF2N, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "SMA1", ZL_REF3P, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "SMA2/U.FL2", ZL_REF3N, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "GNSS-1PPS", ZL_REF4P, DPLL_PIN_TYPE_GNSS, + BIT(DPLL_PIN_FREQ_SUPP_1_HZ) }, + { "OCXO", ZL_REF4N, DPLL_PIN_TYPE_INT_OSCILLATOR, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, +}; + +static const struct ice_cgu_pin_desc ice_e810t_sfp_cgu_outputs[] = { + { "REF-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "REF-SMA2/U.FL2", ZL_OUT1, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "MAC-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "CVL-SDP21", ZL_OUT4, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "CVL-SDP23", ZL_OUT5, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, +}; + +static const struct ice_cgu_pin_desc ice_e810t_qsfp_cgu_outputs[] = { + { "REF-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "REF-SMA2/U.FL2", ZL_OUT1, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "PHY2-CLK", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "MAC-CLK", ZL_OUT4, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "CVL-SDP21", ZL_OUT5, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "CVL-SDP23", ZL_OUT6, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, +}; + +static const struct ice_cgu_pin_desc ice_e823_si_cgu_inputs[] = { + { "NONE", SI_REF0P, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "NONE", SI_REF0N, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "SYNCE0_DP", SI_REF1P, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "SYNCE0_DN", SI_REF1N, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "EXT_CLK_SYNC", SI_REF2P, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "NONE", SI_REF2N, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "EXT_PPS_OUT", SI_REF3, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "INT_PPS_OUT", SI_REF4, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, +}; + +static const struct ice_cgu_pin_desc ice_e823_si_cgu_outputs[] = { + { "1588-TIME_SYNC", SI_OUT0, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "PHY-CLK", SI_OUT1, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "10MHZ-SMA2", SI_OUT2, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "PPS-SMA1", SI_OUT3, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, +}; + +static const struct ice_cgu_pin_desc ice_e823_zl_cgu_inputs[] = { + { "NONE", ZL_REF0P, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "INT_PPS_OUT", ZL_REF0N, DPLL_PIN_TYPE_EXT, + BIT(DPLL_PIN_FREQ_SUPP_1_HZ) }, + { "SYNCE0_DP", ZL_REF1P, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "SYNCE0_DN", ZL_REF1N, DPLL_PIN_TYPE_MUX, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "NONE", ZL_REF2P, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "NONE", ZL_REF2N, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "EXT_CLK_SYNC", ZL_REF3P, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "NONE", ZL_REF3N, DPLL_PIN_TYPE_UNSPEC, 0 }, + { "EXT_PPS_OUT", ZL_REF4P, DPLL_PIN_TYPE_EXT, + BIT(DPLL_PIN_FREQ_SUPP_1_HZ) }, + { "OCXO", ZL_REF4N, DPLL_PIN_TYPE_INT_OSCILLATOR, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, +}; + +static const struct ice_cgu_pin_desc ice_e823_zl_cgu_outputs[] = { + { "PPS-SMA1", ZL_OUT0, DPLL_PIN_TYPE_EXT, + BIT(DPLL_PIN_FREQ_SUPP_1_HZ) }, + { "10MHZ-SMA2", ZL_OUT1, DPLL_PIN_TYPE_EXT, + BIT(DPLL_PIN_FREQ_SUPP_10_MHZ) }, + { "PHY-CLK", ZL_OUT2, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "1588-TIME_REF", ZL_OUT3, DPLL_PIN_TYPE_SYNCE_ETH_PORT, + BIT(DPLL_PIN_FREQ_SUPP_UNSPEC) }, + { "CPK-TIME_SYNC", ZL_OUT4, DPLL_PIN_TYPE_EXT, + ICE_SIG_TYPE_MASK_1PPS_10MHZ }, + { "NONE", ZL_OUT5, DPLL_PIN_TYPE_UNSPEC, 0 }, +}; + extern const struct ice_cgu_pll_params_e822 e822_cgu_params[NUM_ICE_TIME_REF_FREQ]; @@ -197,6 +424,19 @@ int ice_read_sma_ctrl_e810t(struct ice_hw *hw, u8 *data); int ice_write_sma_ctrl_e810t(struct ice_hw *hw, u8 data); int ice_read_pca9575_reg_e810t(struct ice_hw *hw, u8 offset, u8 *data); bool ice_is_pca9575_present(struct ice_hw *hw); +bool ice_is_phy_rclk_present(struct ice_hw *hw); +bool ice_is_clock_mux_present_e810t(struct ice_hw *hw); +int ice_get_pf_c827_idx(struct ice_hw *hw, u8 *idx); +bool ice_is_cgu_present(struct ice_hw *hw); +enum dpll_pin_type ice_cgu_get_pin_type(struct ice_hw *hw, u8 pin, bool input); +unsigned long +ice_cgu_get_pin_freq_mask(struct ice_hw *hw, u8 pin, bool input); +const char *ice_cgu_get_pin_name(struct ice_hw *hw, u8 pin, bool input); +int ice_get_cgu_state(struct ice_hw *hw, u8 dpll_idx, + enum ice_cgu_state last_dpll_state, u8 *pin, + u8 *ref_state, u8 *eec_mode, s64 *phase_offset, + enum ice_cgu_state *dpll_state); +int ice_get_cgu_rclk_pin_info(struct ice_hw *hw, u8 *base_idx, u8 *pin_num); #define PFTSYN_SEM_BYTES 4 diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h index e3f622cad425..c49f573d724f 100644 --- a/drivers/net/ethernet/intel/ice/ice_type.h +++ b/drivers/net/ethernet/intel/ice/ice_type.h @@ -962,6 +962,7 @@ struct ice_hw { DECLARE_BITMAP(hw_ptype, ICE_FLOW_PTYPE_MAX); u8 dvm_ena; u16 io_expander_handle; + u8 cgu_part_number; }; /* Statistics collected by each port, VSI, VEB, and S-channel */ From patchwork Sun Mar 12 02:28:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171099 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 040DAC74A5B for ; Sun, 12 Mar 2023 02:29:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230048AbjCLC3G (ORCPT ); Sat, 11 Mar 2023 21:29:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35810 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230071AbjCLC25 (ORCPT ); Sat, 11 Mar 2023 21:28:57 -0500 Received: from mx0a-00082601.pphosted.com (mx0a-00082601.pphosted.com [67.231.145.42]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EB78E3401A; Sat, 11 Mar 2023 18:28:50 -0800 (PST) Received: from pps.filterd (m0148461.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32C1lZsF021512; Sat, 11 Mar 2023 18:28:30 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=a2Zap7RqAaZ766bVqHe9CUQuTykVExZ83z+3xAgTqIo=; b=k8xIgg3ymGepxHoK3Ts4zc3i2LLr59nxZTbPKcKMFQHfBSKpy/Ivap2Cy5lQ6hhR4yBM GsrV7hV5tgWClgVMsu9VvVe89FIjZyQNk8sGjY6QL3BAMuH+Vl+1HvME6AVUudb0IAM6 yGoRICx0hr/RXpMPTtFJo92Z1w675uVC86miyxEvFbCjQK1kf9d9BQ7orj6ByZoPZeFS VzQup8cSdlaZaKn5xX5F/fm+58Jrngn3EiMPIGLqW/OJ6egmwoq4tnkT3JfykuOcuykW RS2sJtWYB7WkpE4A3GZdtMHMn/ogdzBJS3d2/yEd7oWtfY/bh+SJBjDl198w7UlBbTBG 1A== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8qn58ujs-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:30 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub202.TheFacebook.com (2620:10d:c0a8:83::6) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:29 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:27 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , "Arkadiusz Kubalewski" , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , , , , , , "Milena Olech" , Michal Michalik Subject: [PATCH RFC v6 5/6] ice: implement dpll interface to control cgu Date: Sat, 11 Mar 2023 18:28:06 -0800 Message-ID: <20230312022807.278528-6-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-GUID: 03h37UsQFpPjpPm2HM9pMPkLlOEMbXNR X-Proofpoint-ORIG-GUID: 03h37UsQFpPjpPm2HM9pMPkLlOEMbXNR X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC From: Arkadiusz Kubalewski Control over clock generation unit is required for further development of Synchronous Ethernet feature. Interface provides ability to obtain current state of a dpll, its sources and outputs which are pins, and allows their configuration. Co-developed-by: Milena Olech Signed-off-by: Milena Olech Co-developed-by: Michal Michalik Signed-off-by: Michal Michalik Signed-off-by: Arkadiusz Kubalewski --- drivers/net/ethernet/intel/Kconfig | 1 + drivers/net/ethernet/intel/ice/Makefile | 3 +- drivers/net/ethernet/intel/ice/ice.h | 4 + drivers/net/ethernet/intel/ice/ice_dpll.c | 1845 +++++++++++++++++++++ drivers/net/ethernet/intel/ice/ice_dpll.h | 96 ++ drivers/net/ethernet/intel/ice/ice_main.c | 7 + 6 files changed, 1955 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/intel/ice/ice_dpll.c create mode 100644 drivers/net/ethernet/intel/ice/ice_dpll.h diff --git a/drivers/net/ethernet/intel/Kconfig b/drivers/net/ethernet/intel/Kconfig index c18c3b373846..d6ea4edd552a 100644 --- a/drivers/net/ethernet/intel/Kconfig +++ b/drivers/net/ethernet/intel/Kconfig @@ -301,6 +301,7 @@ config ICE select DIMLIB select NET_DEVLINK select PLDMFW + select DPLL help This driver supports Intel(R) Ethernet Connection E800 Series of devices. For more information on how to identify your adapter, go diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile index 5d89392f969b..6c198cd92d49 100644 --- a/drivers/net/ethernet/intel/ice/Makefile +++ b/drivers/net/ethernet/intel/ice/Makefile @@ -33,7 +33,8 @@ ice-y := ice_main.o \ ice_lag.o \ ice_ethtool.o \ ice_repr.o \ - ice_tc_lib.o + ice_tc_lib.o \ + ice_dpll.o ice-$(CONFIG_PCI_IOV) += \ ice_sriov.o \ ice_virtchnl.o \ diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index 116eb64db969..5cc2a99f00b7 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -75,6 +75,7 @@ #include "ice_lag.h" #include "ice_vsi_vlan_ops.h" #include "ice_gnss.h" +#include "ice_dpll.h" #define ICE_BAR0 0 #define ICE_REQ_DESC_MULTIPLE 32 @@ -202,6 +203,7 @@ enum ice_feature { ICE_F_DSCP, ICE_F_PTP_EXTTS, + ICE_F_PHY_RCLK, ICE_F_SMA_CTRL, ICE_F_CGU, ICE_F_GNSS, @@ -512,6 +514,7 @@ enum ice_pf_flags { ICE_FLAG_PLUG_AUX_DEV, ICE_FLAG_MTU_CHANGED, ICE_FLAG_GNSS, /* GNSS successfully initialized */ + ICE_FLAG_DPLL, /* SyncE/PTP dplls initialized */ ICE_PF_FLAGS_NBITS /* must be last */ }; @@ -635,6 +638,7 @@ struct ice_pf { #define ICE_VF_AGG_NODE_ID_START 65 #define ICE_MAX_VF_AGG_NODES 32 struct ice_agg_node vf_agg_node[ICE_MAX_VF_AGG_NODES]; + struct ice_dplls dplls; }; struct ice_netdev_priv { diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c new file mode 100644 index 000000000000..a97ccf5840d5 --- /dev/null +++ b/drivers/net/ethernet/intel/ice/ice_dpll.c @@ -0,0 +1,1845 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2022, Intel Corporation. */ + +#include "ice.h" +#include "ice_lib.h" +#include "ice_trace.h" +#include +#include + +#define CGU_STATE_ACQ_ERR_THRESHOLD 50 +#define ICE_DPLL_LOCK_TRIES 1000 + +/** + * dpll_lock_status - map ice cgu states into dpll's subsystem lock status + */ +static const enum dpll_lock_status +ice_dpll_status[__DPLL_LOCK_STATUS_MAX] = { + [ICE_CGU_STATE_INVALID] = DPLL_LOCK_STATUS_UNSPEC, + [ICE_CGU_STATE_FREERUN] = DPLL_LOCK_STATUS_UNLOCKED, + [ICE_CGU_STATE_LOCKED] = DPLL_LOCK_STATUS_CALIBRATING, + [ICE_CGU_STATE_LOCKED_HO_ACQ] = DPLL_LOCK_STATUS_LOCKED, + [ICE_CGU_STATE_HOLDOVER] = DPLL_LOCK_STATUS_HOLDOVER, +}; + +/** + * ice_dpll_pin_type - enumerate ice pin types + */ +enum ice_dpll_pin_type { + ICE_DPLL_PIN_INVALID = 0, + ICE_DPLL_PIN_TYPE_SOURCE, + ICE_DPLL_PIN_TYPE_OUTPUT, + ICE_DPLL_PIN_TYPE_RCLK_SOURCE, +}; + +/** + * pin_type_name - string names of ice pin types + */ +static const char * const pin_type_name[] = { + [ICE_DPLL_PIN_TYPE_SOURCE] = "source", + [ICE_DPLL_PIN_TYPE_OUTPUT] = "output", + [ICE_DPLL_PIN_TYPE_RCLK_SOURCE] = "rclk-source", +}; + +/** + * ice_find_pin_idx - find ice_dpll_pin index on a pf + * @pf: private board structure + * @pin: kernel's dpll_pin pointer to be searched for + * @pin_type: type of pins to be searched for + * + * Find and return internal ice pin index of a searched dpll subsystem + * pin pointer. + * + * Return: + * * valid index for a given pin & pin type found on pf internal dpll struct + * * PIN_IDX_INVALID - if pin was not found. + */ +static u32 +ice_find_pin_idx(struct ice_pf *pf, const struct dpll_pin *pin, + enum ice_dpll_pin_type pin_type) + +{ + struct ice_dpll_pin *pins; + int pin_num, i; + + if (!pin || !pf) + return PIN_IDX_INVALID; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + pins = pf->dplls.inputs; + pin_num = pf->dplls.num_inputs; + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + pins = pf->dplls.outputs; + pin_num = pf->dplls.num_outputs; + } else { + return PIN_IDX_INVALID; + } + + for (i = 0; i < pin_num; i++) + if (pin == pins[i].pin) + return i; + + return PIN_IDX_INVALID; +} + +/** + * ice_dpll_cb_lock - lock dplls mutex in callback context + * @pf: private board structure + * + * Lock the mutex from the callback operations invoked by dpll subsystem. + * Prevent dead lock caused by `rmmod ice` when dpll callbacks are under stress + * tests. + * + * Return: + * 0 - if lock acquired + * negative - lock not acquired or dpll was deinitialized + */ +static int ice_dpll_cb_lock(struct ice_pf *pf) +{ + int i; + + for (i = 0; i < ICE_DPLL_LOCK_TRIES; i++) { + if (mutex_trylock(&pf->dplls.lock)) + return 0; + usleep_range(100, 150); + if (!test_bit(ICE_FLAG_DPLL, pf->flags)) + return -EFAULT; + } + + return -EBUSY; +} + +/** + * ice_dpll_cb_unlock - unlock dplls mutex in callback context + * @pf: private board structure + * + * Unlock the mutex from the callback operations invoked by dpll subsystem. + */ +static void ice_dpll_cb_unlock(struct ice_pf *pf) +{ + mutex_unlock(&pf->dplls.lock); +} + +/** + * ice_find_pin - find ice_dpll_pin on a pf + * @pf: private board structure + * @pin: kernel's dpll_pin pointer to be searched for + * @pin_type: type of pins to be searched for + * + * Find and return internal ice pin info pointer holding data of given dpll + * subsystem pin pointer. + * + * Return: + * * valid 'struct ice_dpll_pin'-type pointer - if given 'pin' pointer was + * found in pf internal pin data. + * * NULL - if pin was not found. + */ +static struct ice_dpll_pin +*ice_find_pin(struct ice_pf *pf, const struct dpll_pin *pin, + enum ice_dpll_pin_type pin_type) + +{ + struct ice_dpll_pin *pins; + int pin_num, i; + + if (!pin || !pf) + return NULL; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + pins = pf->dplls.inputs; + pin_num = pf->dplls.num_inputs; + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + pins = pf->dplls.outputs; + pin_num = pf->dplls.num_outputs; + } else if (pin_type == ICE_DPLL_PIN_TYPE_RCLK_SOURCE) { + if (pin == pf->dplls.rclk.pin) + return &pf->dplls.rclk; + } else { + return NULL; + } + + for (i = 0; i < pin_num; i++) + if (pin == pins[i].pin) + return &pins[i]; + + return NULL; +} + +/** + * ice_dpll_pin_freq_set - set pin's frequency + * @pf: Board private structure + * @pin: pointer to a pin + * @pin_type: type of pin being configured + * @freq: frequency to be set + * + * Set requested frequency on a pin. + * + * Return: + * * 0 - success + * * negative - error on AQ or wrong pin type given + */ +static int +ice_dpll_pin_freq_set(struct ice_pf *pf, struct ice_dpll_pin *pin, + const enum ice_dpll_pin_type pin_type, const u32 freq) +{ + u8 flags; + int ret; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + flags = ICE_AQC_SET_CGU_IN_CFG_FLG1_UPDATE_FREQ; + ret = ice_aq_set_input_pin_cfg(&pf->hw, pin->idx, flags, + pin->flags[0], freq, 0); + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + flags = pin->flags[0] | ICE_AQC_SET_CGU_OUT_CFG_UPDATE_FREQ; + ret = ice_aq_set_output_pin_cfg(&pf->hw, pin->idx, flags, + 0, freq, 0); + } else { + ret = -EINVAL; + } + + if (ret) { + dev_dbg(ice_pf_to_dev(pf), + "err:%d %s failed to set pin freq:%u on pin:%u\n", + ret, ice_aq_str(pf->hw.adminq.sq_last_status), + freq, pin->idx); + } else { + pin->freq = freq; + } + + return ret; +} + +/** + * ice_dpll_frequency_set - wrapper for pin callback for set frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: frequency to be set + * @extack: netlink extack + * @pin_type: type of pin being configured + * + * Wraps internal set frequency command on a pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't set in hw + */ +static int +ice_dpll_frequency_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack, + const enum ice_dpll_pin_type pin_type) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, pin_type); + if (!p) { + NL_SET_ERR_MSG(extack, "pin not found"); + goto unlock; + } + + ret = ice_dpll_pin_freq_set(pf, p, pin_type, frequency); + if (ret) + NL_SET_ERR_MSG_FMT(extack, "freq not set, err:%d", ret); +unlock: + ice_dpll_cb_unlock(pf); + + return ret; +} + +/** + * ice_dpll_source_frequency_set - source pin callback for set frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: frequency to be set + * @extack: netlink extack + * + * Wraps internal set frequency command on a pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't set in hw + */ +static int +ice_dpll_source_frequency_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack) +{ + return ice_dpll_frequency_set(pin, dpll, frequency, extack, + ICE_DPLL_PIN_TYPE_SOURCE); +} + +/** + * ice_dpll_output_frequency_set - output pin callback for set frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: frequency to be set + * @extack: netlink extack + * + * Wraps internal set frequency command on a pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't set in hw + */ +static int +ice_dpll_output_frequency_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack) +{ + return ice_dpll_frequency_set(pin, dpll, frequency, extack, + ICE_DPLL_PIN_TYPE_OUTPUT); +} + +/** + * ice_dpll_frequency_get - wrapper for pin callback for get frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: on success holds pin's frequency + * @extack: netlink extack + * @pin_type: type of pin being configured + * + * Wraps internal get frequency command of a pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't get from hw + */ +static int +ice_dpll_frequency_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, + struct netlink_ext_ack *extack, + const enum ice_dpll_pin_type pin_type) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, pin_type); + if (!p) { + NL_SET_ERR_MSG(extack, "pin not found"); + goto unlock; + } + *frequency = p->freq; + ret = 0; +unlock: + ice_dpll_cb_unlock(pf); + + return ret; +} + +/** + * ice_dpll_source_frequency_get - source pin callback for get frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: on success holds pin's frequency + * @extack: netlink extack + * + * Wraps internal get frequency command of a source pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't get from hw + */ +static int +ice_dpll_source_frequency_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, + struct netlink_ext_ack *extack) +{ + return ice_dpll_frequency_get(pin, dpll, frequency, extack, + ICE_DPLL_PIN_TYPE_SOURCE); +} + +/** + * ice_dpll_output_frequency_get - output pin callback for get frequency + * @pin: pointer to a pin + * @dpll: pointer to dpll + * @frequency: on success holds pin's frequency + * @extack: netlink extack + * + * Wraps internal get frequency command of a pin. + * + * Return: + * * 0 - success + * * negative - error pin not found or couldn't get from hw + */ +static int +ice_dpll_output_frequency_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, + struct netlink_ext_ack *extack) +{ + return ice_dpll_frequency_get(pin, dpll, frequency, extack, + ICE_DPLL_PIN_TYPE_OUTPUT); +} + +/** + * ice_dpll_pin_enable - enable a pin on dplls + * @hw: board private hw structure + * @pin: pointer to a pin + * @pin_type: type of pin being enabled + * + * Enable a pin on both dplls. Store current state in pin->flags. + * + * Return: + * * 0 - OK + * * negative - error + */ +static int +ice_dpll_pin_enable(struct ice_hw *hw, struct ice_dpll_pin *pin, + const enum ice_dpll_pin_type pin_type) +{ + u8 flags = pin->flags[0]; + int ret; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + flags |= ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN; + ret = ice_aq_set_input_pin_cfg(hw, pin->idx, 0, flags, 0, 0); + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + flags |= ICE_AQC_SET_CGU_OUT_CFG_OUT_EN; + ret = ice_aq_set_output_pin_cfg(hw, pin->idx, flags, 0, 0, 0); + } + if (ret) + dev_dbg(ice_pf_to_dev((struct ice_pf *)(hw->back)), + "err:%d %s failed to enable %s pin:%u\n", + ret, ice_aq_str(hw->adminq.sq_last_status), + pin_type_name[pin_type], pin->idx); + else + pin->flags[0] = flags; + + return ret; +} + +/** + * ice_dpll_pin_disable - disable a pin on dplls + * @hw: board private hw structure + * @pin: pointer to a pin + * @pin_type: type of pin being disabled + * + * Disable a pin on both dplls. Store current state in pin->flags. + * + * Return: + * * 0 - OK + * * negative - error + */ +static int +ice_dpll_pin_disable(struct ice_hw *hw, struct ice_dpll_pin *pin, + enum ice_dpll_pin_type pin_type) +{ + u8 flags = pin->flags[0]; + int ret; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + flags &= ~(ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN); + ret = ice_aq_set_input_pin_cfg(hw, pin->idx, 0, flags, 0, 0); + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + flags &= ~(ICE_AQC_SET_CGU_OUT_CFG_OUT_EN); + ret = ice_aq_set_output_pin_cfg(hw, pin->idx, flags, 0, 0, 0); + } + if (ret) + dev_dbg(ice_pf_to_dev((struct ice_pf *)(hw->back)), + "err:%d %s failed to disable %s pin:%u\n", + ret, ice_aq_str(hw->adminq.sq_last_status), + pin_type_name[pin_type], pin->idx); + else + pin->flags[0] = flags; + + return ret; +} + +/** + * ice_dpll_pin_state_update - update pin's state + * @hw: private board struct + * @pin: structure with pin attributes to be updated + * @pin_type: type of pin being updated + * + * Determine pin current mode, frequency and signal type. Then update struct + * holding the pin info. + * + * Return: + * * 0 - OK + * * negative - error + */ +int +ice_dpll_pin_state_update(struct ice_pf *pf, struct ice_dpll_pin *pin, + const enum ice_dpll_pin_type pin_type) +{ + int ret; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + ret = ice_aq_get_input_pin_cfg(&pf->hw, pin->idx, NULL, NULL, + NULL, &pin->flags[0], + &pin->freq, NULL); + if (!!(ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN & pin->flags[0])) + pin->state[0] = DPLL_PIN_STATE_CONNECTED; + else + pin->state[0] = DPLL_PIN_STATE_DISCONNECTED; + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + ret = ice_aq_get_output_pin_cfg(&pf->hw, pin->idx, + &pin->flags[0], NULL, + &pin->freq, NULL); + if (!!(ICE_AQC_SET_CGU_OUT_CFG_OUT_EN & pin->flags[0])) + pin->state[0] = DPLL_PIN_STATE_CONNECTED; + else + pin->state[0] = DPLL_PIN_STATE_DISCONNECTED; + } else if (pin_type == ICE_DPLL_PIN_TYPE_RCLK_SOURCE) { + u8 parent, port_num = ICE_AQC_SET_PHY_REC_CLK_OUT_CURR_PORT; + + for (parent = 0; parent < pf->dplls.rclk.num_parents; + parent++) { + ret = ice_aq_get_phy_rec_clk_out(&pf->hw, parent, + &port_num, + &pin->flags[parent], + &pin->freq); + if (ret) + return ret; + if (!!(ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN & + pin->flags[parent])) + pin->state[parent] = DPLL_PIN_STATE_CONNECTED; + else + pin->state[parent] = + DPLL_PIN_STATE_DISCONNECTED; + } + } + + return ret; +} + +/** + * ice_find_dpll - find ice_dpll on a pf + * @pf: private board structure + * @dpll: kernel's dpll_device pointer to be searched + * + * Return: + * * pointer if ice_dpll with given device dpll pointer is found + * * NULL if not found + */ +static struct ice_dpll +*ice_find_dpll(struct ice_pf *pf, const struct dpll_device *dpll) +{ + if (!pf || !dpll) + return NULL; + + return dpll == pf->dplls.eec.dpll ? &pf->dplls.eec : + dpll == pf->dplls.pps.dpll ? &pf->dplls.pps : NULL; +} + +/** + * ice_dpll_hw_source_prio_set - set source priority value in hardware + * @pf: board private structure + * @dpll: ice dpll pointer + * @pin: ice pin pointer + * @prio: priority value being set on a dpll + * + * Internal wrapper for setting the priority in the hardware. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int +ice_dpll_hw_source_prio_set(struct ice_pf *pf, struct ice_dpll *dpll, + struct ice_dpll_pin *pin, const u32 prio) +{ + int ret; + + ret = ice_aq_set_cgu_ref_prio(&pf->hw, dpll->dpll_idx, pin->idx, + (u8)prio); + if (ret) + dev_dbg(ice_pf_to_dev(pf), + "err:%d %s failed to set pin prio:%u on pin:%u\n", + ret, ice_aq_str(pf->hw.adminq.sq_last_status), + prio, pin->idx); + else + dpll->input_prio[pin->idx] = prio; + + return ret; +} + +/** + * ice_dpll_lock_status_get - get dpll lock status callback + * @dpll: registered dpll pointer + * @status: on success holds dpll's lock status + * + * Dpll subsystem callback, provides dpll's lock status. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_lock_status_get(const struct dpll_device *dpll, + enum dpll_lock_status *status, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_priv(dpll); + struct ice_dpll *d; + + if (!pf) + return -EINVAL; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + d = ice_find_dpll(pf, dpll); + if (!d) + return -EFAULT; + dev_dbg(ice_pf_to_dev(pf), "%s: dpll:%p, pf:%p\n", __func__, dpll, pf); + *status = ice_dpll_status[d->dpll_state]; + ice_dpll_cb_unlock(pf); + + return 0; +} + +/** + * ice_dpll_source_idx_get - get dpll's source index + * @dpll: registered dpll pointer + * @pin_idx: on success holds currently selected source pin index + * + * Dpll subsystem callback. Provides index of a source dpll is trying to lock + * with. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_source_idx_get(const struct dpll_device *dpll, u32 *pin_idx, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_priv(dpll); + struct ice_dpll *d; + + if (!pf) + return -EINVAL; + + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + d = ice_find_dpll(pf, dpll); + if (!d) { + ice_dpll_cb_unlock(pf); + return -EFAULT; + } + if (d->dpll_state == ICE_CGU_STATE_INVALID || + d->dpll_state == ICE_CGU_STATE_FREERUN) + *pin_idx = PIN_IDX_INVALID; + else + *pin_idx = (u32)d->source_idx; + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), "%s: dpll:%p, pf:%p d:%p, idx:%u\n", + __func__, dpll, pf, d, *pin_idx); + + return 0; +} + +/** + * ice_dpll_mode_get - get dpll's working mode + * @dpll: registered dpll pointer + * @mode: on success holds current working mode of dpll + * + * Dpll subsystem callback. Provides working mode of dpll. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_mode_get(const struct dpll_device *dpll, + enum dpll_mode *mode, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_priv(dpll); + struct ice_dpll *d; + + if (!pf) + return -EINVAL; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + d = ice_find_dpll(pf, dpll); + ice_dpll_cb_unlock(pf); + if (!d) + return -EFAULT; + *mode = DPLL_MODE_AUTOMATIC; + + return 0; +} + +/** + * ice_dpll_mode_get - check if dpll's working mode is supported + * @dpll: registered dpll pointer + * @mode: mode to be checked for support + * + * Dpll subsystem callback. Provides information if working mode is supported + * by dpll. + * + * Return: + * * true - mode is supported + * * false - mode is not supported + */ +static bool ice_dpll_mode_supported(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_priv(dpll); + struct ice_dpll *d; + + if (!pf) + return false; + + if (ice_dpll_cb_lock(pf)) + return false; + d = ice_find_dpll(pf, dpll); + ice_dpll_cb_unlock(pf); + if (!d) + return false; + if (mode == DPLL_MODE_AUTOMATIC) + return true; + + return false; +} + +/** + * ice_dpll_pin_state_set - set pin's state on dpll + * @pf: Board private structure + * @pin: pointer to a pin + * @pin_type: type of modified pin + * @mode: requested mode + * + * Determine requested pin mode set it on a pin. + * + * Return: + * * 0 - OK or no change required + * * negative - error + */ +static int +ice_dpll_pin_state_set(const struct dpll_device *dpll, + const struct dpll_pin *pin, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack, + const enum ice_dpll_pin_type pin_type) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, pin_type); + if (!p) + goto unlock; + if (state == DPLL_PIN_STATE_CONNECTED) + ret = ice_dpll_pin_enable(&pf->hw, p, pin_type); + else + ret = ice_dpll_pin_disable(&pf->hw, p, pin_type); + if (!ret) + ret = ice_dpll_pin_state_update(pf, p, pin_type); +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), + "%s: dpll:%p, pin:%p, p:%p pf:%p state: %d ret:%d\n", + __func__, dpll, pin, p, pf, state, ret); + + return ret; +} + +/** + * ice_dpll_output_state_set - enable/disable output pin on dpll device + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * @state: state to be set + * + * Dpll subsystem callback. Enables given mode on output type pin. + * + * Return: + * * 0 - successfully enabled mode + * * negative - failed to enable mode + */ +static int ice_dpll_output_state_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + return ice_dpll_pin_state_set(dpll, pin, state, extack, + ICE_DPLL_PIN_TYPE_OUTPUT); +} + +/** + * ice_dpll_source_state_set - enable/disable source pin on dpll levice + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * @state: state to be set + * + * Dpll subsystem callback. Enables given mode on source type pin. + * + * Return: + * * 0 - successfully enabled mode + * * negative - failed to enable mode + */ +static int ice_dpll_source_state_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + return ice_dpll_pin_state_set(dpll, pin, state, extack, + ICE_DPLL_PIN_TYPE_SOURCE); +} + +/** + * ice_dpll_pin_state_get - set pin's state on dpll + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * @state: on success holds state of the pin + * @extack: error reporting + * @pin_type: type of questioned pin + * + * Determine pin state set it on a pin. + * + * Return: + * * 0 - success + * * negative - failed to get state + */ +static int +ice_dpll_pin_state_get(const struct dpll_device *dpll, + const struct dpll_pin *pin, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack, + const enum ice_dpll_pin_type pin_type) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, pin_type); + if (!p) { + NL_SET_ERR_MSG(extack, "pin not found"); + goto unlock; + } + if ((pin_type == ICE_DPLL_PIN_TYPE_SOURCE && + !!(p->flags[0] & ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN)) || + (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT && + !!(p->flags[0] & ICE_AQC_SET_CGU_OUT_CFG_OUT_EN))) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + ret = 0; +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), + "%s: dpll:%p, pin:%p, pf:%p state: %d ret:%d\n", + __func__, dpll, pin, pf, *state, ret); + + return ret; +} + +/** + * ice_dpll_output_state_get - get output pin state on dpll device + * @pin: pointer to a pin + * @dpll: registered dpll pointer + * @state: on success holds state of the pin + * @extack: error reporting + * + * Dpll subsystem callback. Check state of a pin. + * + * Return: + * * 0 - success + * * negative - failed to get state + */ +static int ice_dpll_output_state_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + return ice_dpll_pin_state_get(dpll, pin, state, extack, + ICE_DPLL_PIN_TYPE_OUTPUT); +} + +/** + * ice_dpll_source_state_get - get source pin state on dpll device + * @pin: pointer to a pin + * @dpll: registered dpll pointer + * @state: on success holds state of the pin + * @extack: error reporting + * + * Dpll subsystem callback. Check state of a pin. + * + * Return: + * * 0 - success + * * negative - failed to get state + */ +static int ice_dpll_source_state_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + return ice_dpll_pin_state_get(dpll, pin, state, extack, + ICE_DPLL_PIN_TYPE_SOURCE); +} + +/** + * ice_dpll_source_prio_get - get dpll's source prio + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * @prio: on success - returns source priority on dpll + * + * Dpll subsystem callback. Handler for getting priority of a source pin. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_source_prio_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, u32 *prio, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll *d = NULL; + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, ICE_DPLL_PIN_TYPE_SOURCE); + if (!p) { + NL_SET_ERR_MSG(extack, "pin not found"); + goto unlock; + } + d = ice_find_dpll(pf, dpll); + if (!d) { + NL_SET_ERR_MSG(extack, "dpll not found"); + goto unlock; + } + *prio = d->input_prio[p->idx]; + ret = 0; +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), "%s: dpll:%p, pin:%p, pf:%p ret:%d\n", + __func__, dpll, pin, pf, ret); + + return ret; +} + +/** + * ice_dpll_source_prio_set - set dpll source prio + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * @prio: source priority to be set on dpll + * + * Dpll subsystem callback. Handler for setting priority of a source pin. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_source_prio_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 prio, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_pin_on_dpll_priv(dpll, pin); + struct ice_dpll *d = NULL; + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + + if (prio > ICE_DPLL_PRIO_MAX) { + NL_SET_ERR_MSG(extack, "prio out of range"); + return ret; + } + + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, ICE_DPLL_PIN_TYPE_SOURCE); + if (!p) { + NL_SET_ERR_MSG(extack, "pin not found"); + goto unlock; + } + d = ice_find_dpll(pf, dpll); + if (!d) { + NL_SET_ERR_MSG(extack, "dpll not found"); + goto unlock; + } + ret = ice_dpll_hw_source_prio_set(pf, d, p, prio); + if (ret) + NL_SET_ERR_MSG_FMT(extack, "unable to set prio: %d", ret); +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), "%s: dpll:%p, pin:%p, pf:%p ret:%d\n", + __func__, dpll, pin, pf, ret); + + return ret; +} + +static int ice_dpll_source_direction(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + *direction = DPLL_PIN_DIRECTION_SOURCE; + + return 0; +} + +static int ice_dpll_output_direction(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + *direction = DPLL_PIN_DIRECTION_OUTPUT; + + return 0; +} + +/** + * ice_dpll_rclk_state_on_pin_set - set a state on rclk pin + * @dpll: registered dpll pointer + * @pin: pointer to a pin + * + * dpll subsystem callback, set a state of a rclk pin + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_rclk_state_on_pin_set(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + bool enable = state == DPLL_PIN_STATE_CONNECTED ? true : false; + struct ice_pf *pf = dpll_pin_on_pin_priv(parent_pin, pin); + u32 parent_idx, hw_idx = PIN_IDX_INVALID, i; + struct ice_dpll_pin *p; + int ret = -EINVAL; + + if (!pf) + return ret; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, ICE_DPLL_PIN_TYPE_RCLK_SOURCE); + if (!p) { + ret = -EFAULT; + goto unlock; + } + parent_idx = ice_find_pin_idx(pf, parent_pin, + ICE_DPLL_PIN_TYPE_SOURCE); + if (parent_idx == PIN_IDX_INVALID) { + ret = -EFAULT; + goto unlock; + } + for (i = 0; i < pf->dplls.rclk.num_parents; i++) + if (pf->dplls.rclk.parent_idx[i] == parent_idx) + hw_idx = i; + if (hw_idx == PIN_IDX_INVALID) + goto unlock; + + if ((enable && !!(p->flags[hw_idx] & + ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN)) || + (!enable && !(p->flags[hw_idx] & + ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN))) { + ret = -EINVAL; + goto unlock; + } + ret = ice_aq_set_phy_rec_clk_out(&pf->hw, hw_idx, enable, + &p->freq); +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), "%s: parent:%p, pin:%p, pf:%p ret:%d\n", + __func__, parent_pin, pin, pf, ret); + + return ret; +} + +/** + * ice_dpll_rclk_state_on_pin_get - get a state of rclk pin + * @pin: pointer to a pin + * @parent_pin: pointer to a parent pin + * @state: on success holds valid pin state + * @extack: used for error reporting + * + * dpll subsystem callback, get a state of a recovered clock pin. + * + * Return: + * * 0 - success + * * negative - failure + */ +static int ice_dpll_rclk_state_on_pin_get(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct ice_pf *pf = dpll_pin_on_pin_priv(parent_pin, pin); + u32 parent_idx, hw_idx = PIN_IDX_INVALID, i; + struct ice_dpll_pin *p; + int ret = -EFAULT; + + if (!pf) + return ret; + if (ice_dpll_cb_lock(pf)) + return -EBUSY; + p = ice_find_pin(pf, pin, ICE_DPLL_PIN_TYPE_RCLK_SOURCE); + if (!p) + goto unlock; + parent_idx = ice_find_pin_idx(pf, parent_pin, + ICE_DPLL_PIN_TYPE_SOURCE); + if (parent_idx == PIN_IDX_INVALID) + goto unlock; + for (i = 0; i < pf->dplls.rclk.num_parents; i++) + if (pf->dplls.rclk.parent_idx[i] == parent_idx) + hw_idx = i; + if (hw_idx == PIN_IDX_INVALID) + goto unlock; + + ret = ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_RCLK_SOURCE); + if (ret) + goto unlock; + + if (!!(p->flags[hw_idx] & + ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + ret = 0; +unlock: + ice_dpll_cb_unlock(pf); + dev_dbg(ice_pf_to_dev(pf), "%s: parent:%p, pin:%p, pf:%p ret:%d\n", + __func__, parent_pin, pin, pf, ret); + + return ret; +} + +static struct dpll_pin_ops ice_dpll_rclk_ops = { + .state_on_pin_set = ice_dpll_rclk_state_on_pin_set, + .state_on_pin_get = ice_dpll_rclk_state_on_pin_get, + .direction_get = ice_dpll_source_direction, +}; + +static struct dpll_pin_ops ice_dpll_source_ops = { + .frequency_get = ice_dpll_source_frequency_get, + .frequency_set = ice_dpll_source_frequency_set, + .state_on_dpll_get = ice_dpll_source_state_get, + .state_on_dpll_set = ice_dpll_source_state_set, + .prio_get = ice_dpll_source_prio_get, + .prio_set = ice_dpll_source_prio_set, + .direction_get = ice_dpll_source_direction, +}; + +static struct dpll_pin_ops ice_dpll_output_ops = { + .frequency_get = ice_dpll_output_frequency_get, + .frequency_set = ice_dpll_output_frequency_set, + .state_on_dpll_get = ice_dpll_output_state_get, + .state_on_dpll_set = ice_dpll_output_state_set, + .direction_get = ice_dpll_output_direction, +}; + +static struct dpll_device_ops ice_dpll_ops = { + .lock_status_get = ice_dpll_lock_status_get, + .source_pin_idx_get = ice_dpll_source_idx_get, + .mode_get = ice_dpll_mode_get, + .mode_supported = ice_dpll_mode_supported, +}; + +/** + * ice_dpll_release_info - release memory allocated for pins + * @pf: board private structure + * + * Release memory allocated for pins by ice_dpll_init_info function. + */ +static void ice_dpll_release_info(struct ice_pf *pf) +{ + kfree(pf->dplls.inputs); + pf->dplls.inputs = NULL; + kfree(pf->dplls.outputs); + pf->dplls.outputs = NULL; + kfree(pf->dplls.eec.input_prio); + pf->dplls.eec.input_prio = NULL; + kfree(pf->dplls.pps.input_prio); + pf->dplls.pps.input_prio = NULL; +} + +/** + * ice_dpll_release_rclk_pin - release rclk pin from its parents + * @pf: board private structure + * + * Deregister from parent pins and release resources in dpll subsystem. + */ +static void +ice_dpll_release_rclk_pin(struct ice_pf *pf) +{ + struct ice_dpll_pin *rclk = &pf->dplls.rclk; + struct dpll_pin *parent; + int i; + + for (i = 0; i < rclk->num_parents; i++) { + parent = pf->dplls.inputs[rclk->parent_idx[i]].pin; + if (!parent) + continue; + dpll_pin_on_pin_unregister(parent, rclk->pin); + } + dpll_pin_put(rclk->pin); + rclk->pin = NULL; +} + +/** + * ice_dpll_release_pins - release pin's from dplls registered in subsystem + * @dpll_eec: dpll_eec dpll pointer + * @dpll_pps: dpll_pps dpll pointer + * @pins: pointer to pins array + * @count: number of pins + * + * Deregister and free pins of a given array of pins from dpll devices + * registered in dpll subsystem. + * + * Return: + * * 0 - success + * * positive - number of errors encounterd on pin's deregistration. + */ +static int +ice_dpll_release_pins(struct dpll_device *dpll_eec, + struct dpll_device *dpll_pps, struct ice_dpll_pin *pins, + int count, bool cgu) +{ + int i, ret, err = 0; + + for (i = 0; i < count; i++) { + struct ice_dpll_pin *p = &pins[i]; + + if (p && !IS_ERR_OR_NULL(p->pin)) { + if (cgu && dpll_eec) { + ret = dpll_pin_unregister(dpll_eec, p->pin); + if (ret) + err++; + } + if (cgu && dpll_pps) { + ret = dpll_pin_unregister(dpll_pps, p->pin); + if (ret) + err++; + } + dpll_pin_put(p->pin); + p->pin = NULL; + } + } + + return err; +} + +/** + * ice_dpll_register_pins - register pins with a dpll + * @pf: board private structure + * @cgu: if cgu is present and controlled by this NIC + * + * Register source or output pins within given DPLL in a Linux dpll subsystem. + * + * Return: + * * 0 - success + * * negative - error + */ +static int ice_dpll_register_pins(struct ice_pf *pf, bool cgu) +{ + struct device *dev = ice_pf_to_dev(pf); + struct ice_dpll_pin *pins; + struct dpll_pin_ops *ops; + u32 rclk_idx; + int ret, i; + + ops = &ice_dpll_source_ops; + pins = pf->dplls.inputs; + for (i = 0; i < pf->dplls.num_inputs; i++) { + pins[i].pin = dpll_pin_get(pf->dplls.clock_id, i, + THIS_MODULE, &pins[i].prop); + if (IS_ERR_OR_NULL(pins[i].pin)) { + pins[i].pin = NULL; + return -ENOMEM; + } + if (cgu) { + ret = dpll_pin_register(pf->dplls.eec.dpll, + pins[i].pin, + ops, pf, NULL); + if (ret) + return ret; + ret = dpll_pin_register(pf->dplls.pps.dpll, + pins[i].pin, + ops, pf, NULL); + if (ret) + return ret; + } + } + if (cgu) { + ops = &ice_dpll_output_ops; + pins = pf->dplls.outputs; + for (i = 0; i < pf->dplls.num_outputs; i++) { + pins[i].pin = dpll_pin_get(pf->dplls.clock_id, + i + pf->dplls.num_inputs, + THIS_MODULE, &pins[i].prop); + if (IS_ERR_OR_NULL(pins[i].pin)) { + pins[i].pin = NULL; + return -ENOMEM; + } + ret = dpll_pin_register(pf->dplls.eec.dpll, pins[i].pin, + ops, pf, NULL); + if (ret) + return ret; + ret = dpll_pin_register(pf->dplls.pps.dpll, pins[i].pin, + ops, pf, NULL); + if (ret) + return ret; + } + } + rclk_idx = pf->dplls.num_inputs + pf->dplls.num_outputs + pf->hw.pf_id; + pf->dplls.rclk.pin = dpll_pin_get(pf->dplls.clock_id, rclk_idx, + THIS_MODULE, &pf->dplls.rclk.prop); + if (IS_ERR_OR_NULL(pf->dplls.rclk.pin)) { + pf->dplls.rclk.pin = NULL; + return -ENOMEM; + } + ops = &ice_dpll_rclk_ops; + for (i = 0; i < pf->dplls.rclk.num_parents; i++) { + struct dpll_pin *parent = + pf->dplls.inputs[pf->dplls.rclk.parent_idx[i]].pin; + + ret = dpll_pin_on_pin_register(parent, pf->dplls.rclk.pin, + ops, pf, dev); + if (ret) + return ret; + } + + return 0; +} + +/** + * ice_generate_clock_id - generates unique clock_id for registering dpll. + * @pf: board private structure + * @clock_id: holds generated clock_id + * + * Generates unique (per board) clock_id for allocation and search of dpll + * devices in Linux dpll subsystem. + */ +static void ice_generate_clock_id(struct ice_pf *pf, u64 *clock_id) +{ + *clock_id = pci_get_dsn(pf->pdev); +} + +/** + * ice_dpll_init_dplls + * @pf: board private structure + * @cgu: if cgu is present and controlled by this NIC + * + * Get dplls instances for this board, if cgu is controlled by this NIC, + * register dpll with callbacks ops + * + * Return: + * * 0 - success + * * negative - allocation fails + */ +static int ice_dpll_init_dplls(struct ice_pf *pf, bool cgu) +{ + struct device *dev = ice_pf_to_dev(pf); + int ret = -ENOMEM; + u64 clock_id; + + ice_generate_clock_id(pf, &clock_id); + pf->dplls.eec.dpll = dpll_device_get(clock_id, pf->dplls.eec.dpll_idx, + THIS_MODULE); + if (!pf->dplls.eec.dpll) { + dev_err(ice_pf_to_dev(pf), "dpll_device_get failed (eec)\n"); + return ret; + } + pf->dplls.pps.dpll = dpll_device_get(clock_id, pf->dplls.pps.dpll_idx, + THIS_MODULE); + if (!pf->dplls.pps.dpll) { + dev_err(ice_pf_to_dev(pf), "dpll_device_get failed (pps)\n"); + goto put_eec; + } + + if (cgu) { + ret = dpll_device_register(pf->dplls.eec.dpll, DPLL_TYPE_EEC, + &ice_dpll_ops, pf, dev); + if (ret) + goto put_pps; + ret = dpll_device_register(pf->dplls.pps.dpll, DPLL_TYPE_PPS, + &ice_dpll_ops, pf, dev); + if (ret) + goto put_pps; + } + + return 0; + +put_pps: + dpll_device_put(pf->dplls.pps.dpll); + pf->dplls.pps.dpll = NULL; +put_eec: + dpll_device_put(pf->dplls.eec.dpll); + pf->dplls.eec.dpll = NULL; + + return ret; +} + +/** + * ice_dpll_update_state - update dpll state + * @hw: board private structure + * @d: pointer to queried dpll device + * + * Poll current state of dpll from hw and update ice_dpll struct. + * Return: + * * 0 - success + * * negative - AQ failure + */ +static int ice_dpll_update_state(struct ice_hw *hw, struct ice_dpll *d) +{ + int ret; + + ret = ice_get_cgu_state(hw, d->dpll_idx, d->prev_dpll_state, + &d->source_idx, &d->ref_state, &d->eec_mode, + &d->phase_offset, &d->dpll_state); + + dev_dbg(ice_pf_to_dev((struct ice_pf *)(hw->back)), + "update dpll=%d, src_idx:%u, state:%d, prev:%d\n", + d->dpll_idx, d->source_idx, + d->dpll_state, d->prev_dpll_state); + + if (ret) + dev_err(ice_pf_to_dev((struct ice_pf *)(hw->back)), + "update dpll=%d state failed, ret=%d %s\n", + d->dpll_idx, ret, + ice_aq_str(hw->adminq.sq_last_status)); + + return ret; +} + +/** + * ice_dpll_notify_changes - notify dpll subsystem about changes + * @d: pointer do dpll + * + * Once change detected appropriate event is submitted to the dpll subsystem. + */ +static void ice_dpll_notify_changes(struct ice_dpll *d) +{ + if (d->prev_dpll_state != d->dpll_state) { + d->prev_dpll_state = d->dpll_state; + dpll_device_notify(d->dpll, DPLL_A_LOCK_STATUS); + } + if (d->prev_source_idx != d->source_idx) { + d->prev_source_idx = d->source_idx; + dpll_device_notify(d->dpll, DPLL_A_SOURCE_PIN_IDX); + } +} + +/** + * ice_dpll_periodic_work - DPLLs periodic worker + * @work: pointer to kthread_work structure + * + * DPLLs periodic worker is responsible for polling state of dpll. + */ +static void ice_dpll_periodic_work(struct kthread_work *work) +{ + struct ice_dplls *d = container_of(work, struct ice_dplls, work.work); + struct ice_pf *pf = container_of(d, struct ice_pf, dplls); + struct ice_dpll *de = &pf->dplls.eec; + struct ice_dpll *dp = &pf->dplls.pps; + int ret = 0; + + if (!test_bit(ICE_FLAG_DPLL, pf->flags)) + return; + if (ice_dpll_cb_lock(pf)) + return; + ret = ice_dpll_update_state(&pf->hw, de); + if (!ret) + ret = ice_dpll_update_state(&pf->hw, dp); + if (ret) { + d->cgu_state_acq_err_num++; + /* stop rescheduling this worker */ + if (d->cgu_state_acq_err_num > + CGU_STATE_ACQ_ERR_THRESHOLD) { + dev_err(ice_pf_to_dev(pf), + "EEC/PPS DPLLs periodic work disabled\n"); + return; + } + } + ice_dpll_cb_unlock(pf); + ice_dpll_notify_changes(de); + ice_dpll_notify_changes(dp); + /* Run twice a second or reschedule if update failed */ + kthread_queue_delayed_work(d->kworker, &d->work, + ret ? msecs_to_jiffies(10) : + msecs_to_jiffies(500)); +} + +/** + * ice_dpll_init_worker - Initialize DPLLs periodic worker + * @pf: board private structure + * + * Create and start DPLLs periodic worker. + * + * Return: + * * 0 - success + * * negative - create worker failure + */ +static int ice_dpll_init_worker(struct ice_pf *pf) +{ + struct ice_dplls *d = &pf->dplls; + struct kthread_worker *kworker; + + ice_dpll_update_state(&pf->hw, &d->eec); + ice_dpll_update_state(&pf->hw, &d->pps); + kthread_init_delayed_work(&d->work, ice_dpll_periodic_work); + kworker = kthread_create_worker(0, "ice-dplls-%s", + dev_name(ice_pf_to_dev(pf))); + if (IS_ERR(kworker)) + return PTR_ERR(kworker); + d->kworker = kworker; + d->cgu_state_acq_err_num = 0; + kthread_queue_delayed_work(d->kworker, &d->work, 0); + + return 0; +} + +/** + * ice_dpll_release_all - disable support for DPLL and unregister dpll device + * @pf: board private structure + * @cgu: if cgu is controlled by this driver instance + * + * This function handles the cleanup work required from the initialization by + * freeing resources and unregistering the dpll. + * + * Context: Called under pf->dplls.lock + */ +static void ice_dpll_release_all(struct ice_pf *pf, bool cgu) +{ + struct ice_dplls *d = &pf->dplls; + struct ice_dpll *de = &d->eec; + struct ice_dpll *dp = &d->pps; + int ret; + + mutex_lock(&pf->dplls.lock); + ice_dpll_release_rclk_pin(pf); + ret = ice_dpll_release_pins(de->dpll, dp->dpll, d->inputs, + d->num_inputs, cgu); + mutex_unlock(&pf->dplls.lock); + if (ret) + dev_warn(ice_pf_to_dev(pf), + "source pins release dplls err=%d\n", ret); + if (cgu) { + mutex_lock(&pf->dplls.lock); + ret = ice_dpll_release_pins(de->dpll, dp->dpll, d->outputs, + d->num_outputs, cgu); + mutex_unlock(&pf->dplls.lock); + if (ret) + dev_warn(ice_pf_to_dev(pf), + "output pins release on dplls err=%d\n", ret); + } + ice_dpll_release_info(pf); + if (dp->dpll) { + mutex_lock(&pf->dplls.lock); + if (cgu) + dpll_device_unregister(dp->dpll); + dpll_device_put(dp->dpll); + mutex_unlock(&pf->dplls.lock); + dev_dbg(ice_pf_to_dev(pf), "PPS dpll removed\n"); + } + + if (de->dpll) { + mutex_lock(&pf->dplls.lock); + if (cgu) + dpll_device_unregister(de->dpll); + dpll_device_put(de->dpll); + mutex_unlock(&pf->dplls.lock); + dev_dbg(ice_pf_to_dev(pf), "EEC dpll removed\n"); + } + + if (cgu) { + mutex_lock(&pf->dplls.lock); + kthread_cancel_delayed_work_sync(&d->work); + if (d->kworker) { + kthread_destroy_worker(d->kworker); + d->kworker = NULL; + dev_dbg(ice_pf_to_dev(pf), "DPLLs worker removed\n"); + } + mutex_unlock(&pf->dplls.lock); + } +} + +/** + * ice_dpll_release - Disable the driver/HW support for DPLLs and unregister + * the dpll device. + * @pf: board private structure + * + * This function handles the cleanup work required from the initialization by + * freeing resources and unregistering the dpll. + */ +void ice_dpll_release(struct ice_pf *pf) +{ + if (test_bit(ICE_FLAG_DPLL, pf->flags)) { + ice_dpll_release_all(pf, + ice_is_feature_supported(pf, ICE_F_CGU)); + mutex_destroy(&pf->dplls.lock); + clear_bit(ICE_FLAG_DPLL, pf->flags); + } +} + +/** + * ice_dpll_init_direct_pins - initializes source or output pins information + * @pf: Board private structure + * @pin_type: type of pins being initialized + * + * Init information about input or output pins, cache them in pins struct. + * + * Return: + * * 0 - success + * * negative - init failure + */ +static int +ice_dpll_init_direct_pins(struct ice_pf *pf, enum ice_dpll_pin_type pin_type) +{ + struct ice_dpll *de = &pf->dplls.eec, *dp = &pf->dplls.pps; + int num_pins, i, ret = -EINVAL; + struct ice_hw *hw = &pf->hw; + struct ice_dpll_pin *pins; + bool input; + + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) { + pins = pf->dplls.inputs; + num_pins = pf->dplls.num_inputs; + input = true; + } else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) { + pins = pf->dplls.outputs; + num_pins = pf->dplls.num_outputs; + input = false; + } else { + return -EINVAL; + } + + for (i = 0; i < num_pins; i++) { + pins[i].idx = i; + pins[i].prop.description = ice_cgu_get_pin_name(hw, i, input); + pins[i].prop.type = ice_cgu_get_pin_type(hw, i, input); + if (input) { + ret = ice_aq_get_cgu_ref_prio(hw, de->dpll_idx, i, + &de->input_prio[i]); + if (ret) + return ret; + ret = ice_aq_get_cgu_ref_prio(hw, dp->dpll_idx, i, + &dp->input_prio[i]); + if (ret) + return ret; + pins[i].prop.capabilities += + DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE; + } + pins[i].prop.capabilities += DPLL_PIN_CAPS_STATE_CAN_CHANGE; + ret = ice_dpll_pin_state_update(pf, &pins[i], pin_type); + if (ret) + return ret; + pins[i].prop.any_freq_min = 0; + pins[i].prop.any_freq_max = 0; + pins[i].prop.freq_supported = ice_cgu_get_pin_freq_mask(hw, i, + input); + } + + return ret; +} + +/** + * ice_dpll_init_rclk_pin - initializes rclk pin information + * @pf: Board private structure + * @pin_type: type of pins being initialized + * + * Init information for rclk pin, cache them in pf->dplls.rclk. + * + * Return: + * * 0 - success + * * negative - init failure + */ +static int ice_dpll_init_rclk_pin(struct ice_pf *pf) +{ + struct ice_dpll_pin *pin = &pf->dplls.rclk; + struct device *dev = ice_pf_to_dev(pf); + + pin->prop.description = dev_name(dev); + pin->prop.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT; + pin->prop.capabilities += DPLL_PIN_CAPS_STATE_CAN_CHANGE; + + return ice_dpll_pin_state_update(pf, pin, + ICE_DPLL_PIN_TYPE_RCLK_SOURCE); +} + +/** + * ice_dpll_init_pins - init pins wrapper + * @pf: Board private structure + * @pin_type: type of pins being initialized + * + * Wraps functions for pin inti. + * + * Return: + * * 0 - success + * * negative - init failure + */ +static int ice_dpll_init_pins(struct ice_pf *pf, + const enum ice_dpll_pin_type pin_type) +{ + if (pin_type == ICE_DPLL_PIN_TYPE_SOURCE) + return ice_dpll_init_direct_pins(pf, pin_type); + else if (pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) + return ice_dpll_init_direct_pins(pf, pin_type); + else if (pin_type == ICE_DPLL_PIN_TYPE_RCLK_SOURCE) + return ice_dpll_init_rclk_pin(pf); + else + return -EINVAL; +} + +/** + * ice_dpll_init_info - prepare pf's dpll information structure + * @pf: board private structure + * @cgu: if cgu is present and controlled by this NIC + * + * Acquire (from HW) and set basic dpll information (on pf->dplls struct). + * + * Return: + * 0 - success + * negative - error + */ +static int ice_dpll_init_info(struct ice_pf *pf, bool cgu) +{ + struct ice_aqc_get_cgu_abilities abilities; + struct ice_dpll *de = &pf->dplls.eec; + struct ice_dpll *dp = &pf->dplls.pps; + struct ice_dplls *d = &pf->dplls; + struct ice_hw *hw = &pf->hw; + int ret, alloc_size, i; + u8 base_rclk_idx; + + ice_generate_clock_id(pf, &d->clock_id); + ret = ice_aq_get_cgu_abilities(hw, &abilities); + if (ret) { + dev_err(ice_pf_to_dev(pf), + "err:%d %s failed to read cgu abilities\n", + ret, ice_aq_str(hw->adminq.sq_last_status)); + return ret; + } + + de->dpll_idx = abilities.eec_dpll_idx; + dp->dpll_idx = abilities.pps_dpll_idx; + d->num_inputs = abilities.num_inputs; + d->num_outputs = abilities.num_outputs; + + alloc_size = sizeof(*d->inputs) * d->num_inputs; + d->inputs = kzalloc(alloc_size, GFP_KERNEL); + if (!d->inputs) + return -ENOMEM; + + alloc_size = sizeof(*de->input_prio) * d->num_inputs; + de->input_prio = kzalloc(alloc_size, GFP_KERNEL); + if (!de->input_prio) + return -ENOMEM; + + dp->input_prio = kzalloc(alloc_size, GFP_KERNEL); + if (!dp->input_prio) + return -ENOMEM; + + ret = ice_dpll_init_pins(pf, ICE_DPLL_PIN_TYPE_SOURCE); + if (ret) + goto release_info; + + if (cgu) { + alloc_size = sizeof(*d->outputs) * d->num_outputs; + d->outputs = kzalloc(alloc_size, GFP_KERNEL); + if (!d->outputs) + goto release_info; + + ret = ice_dpll_init_pins(pf, ICE_DPLL_PIN_TYPE_OUTPUT); + if (ret) + goto release_info; + } + + ret = ice_get_cgu_rclk_pin_info(&pf->hw, &base_rclk_idx, + &pf->dplls.rclk.num_parents); + if (ret) + return ret; + for (i = 0; i < pf->dplls.rclk.num_parents; i++) + pf->dplls.rclk.parent_idx[i] = base_rclk_idx + i; + ret = ice_dpll_init_pins(pf, ICE_DPLL_PIN_TYPE_RCLK_SOURCE); + if (ret) + return ret; + + dev_dbg(ice_pf_to_dev(pf), + "%s - success, inputs:%u, outputs:%u rclk-parents:%u\n", + __func__, d->num_inputs, d->num_outputs, d->rclk.num_parents); + + return 0; + +release_info: + dev_err(ice_pf_to_dev(pf), + "%s - fail: d->inputs:%p, de->input_prio:%p, dp->input_prio:%p, d->outputs:%p\n", + __func__, d->inputs, de->input_prio, + dp->input_prio, d->outputs); + ice_dpll_release_info(pf); + return ret; +} + +/** + * ice_dpll_init - Initialize DPLLs support + * @pf: board private structure + * + * Set up the device dplls registering them and pins connected within Linux dpll + * subsystem. Allow userpsace to obtain state of DPLL and handling of DPLL + * configuration requests. + * + * Return: + * * 0 - success + * * negative - init failure + */ +int ice_dpll_init(struct ice_pf *pf) +{ + bool cgu_present = ice_is_feature_supported(pf, ICE_F_CGU); + struct ice_dplls *d = &pf->dplls; + int err = 0; + + mutex_init(&d->lock); + mutex_lock(&d->lock); + err = ice_dpll_init_info(pf, cgu_present); + if (err) + goto release; + err = ice_dpll_init_dplls(pf, cgu_present); + if (err) + goto release; + err = ice_dpll_register_pins(pf, cgu_present); + if (err) + goto release; + set_bit(ICE_FLAG_DPLL, pf->flags); + if (cgu_present) { + err = ice_dpll_init_worker(pf); + if (err) + goto release; + } + mutex_unlock(&d->lock); + dev_info(ice_pf_to_dev(pf), "DPLLs init successful\n"); + + return err; + +release: + ice_dpll_release_all(pf, cgu_present); + clear_bit(ICE_FLAG_DPLL, pf->flags); + mutex_unlock(&d->lock); + mutex_destroy(&d->lock); + dev_warn(ice_pf_to_dev(pf), "DPLLs init failure\n"); + + return err; +} diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h b/drivers/net/ethernet/intel/ice/ice_dpll.h new file mode 100644 index 000000000000..defe54262ab9 --- /dev/null +++ b/drivers/net/ethernet/intel/ice/ice_dpll.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2022, Intel Corporation. */ + +#ifndef _ICE_DPLL_H_ +#define _ICE_DPLL_H_ + +#include "ice.h" + +#define ICE_DPLL_PRIO_MAX 0xF +#define ICE_DPLL_RCLK_NUM_MAX 4 +/** ice_dpll_pin - store info about pins + * @pin: dpll pin structure + * @flags: pin flags returned from HW + * @idx: ice pin private idx + * @state: state of a pin + * @type: type of a pin + * @freq_mask: mask of supported frequencies + * @freq: current frequency of a pin + * @caps: capabilities of a pin + * @name: pin name + */ +struct ice_dpll_pin { + struct dpll_pin *pin; + u8 idx; + u8 num_parents; + u8 parent_idx[ICE_DPLL_RCLK_NUM_MAX]; + u8 flags[ICE_DPLL_RCLK_NUM_MAX]; + u8 state[ICE_DPLL_RCLK_NUM_MAX]; + struct dpll_pin_properties prop; + u32 freq; +}; + +/** ice_dpll - store info required for DPLL control + * @dpll: pointer to dpll dev + * @dpll_idx: index of dpll on the NIC + * @source_idx: source currently selected + * @prev_source_idx: source previously selected + * @ref_state: state of dpll reference signals + * @eec_mode: eec_mode dpll is configured for + * @phase_offset: phase delay of a dpll + * @input_prio: priorities of each input + * @dpll_state: current dpll sync state + * @prev_dpll_state: last dpll sync state + */ +struct ice_dpll { + struct dpll_device *dpll; + int dpll_idx; + u8 source_idx; + u8 prev_source_idx; + u8 ref_state; + u8 eec_mode; + s64 phase_offset; + u8 *input_prio; + enum ice_cgu_state dpll_state; + enum ice_cgu_state prev_dpll_state; +}; + +/** ice_dplls - store info required for CCU (clock controlling unit) + * @kworker: periodic worker + * @work: periodic work + * @lock: locks access to configuration of a dpll + * @eec: pointer to EEC dpll dev + * @pps: pointer to PPS dpll dev + * @inputs: input pins pointer + * @outputs: output pins pointer + * @rclk: recovered pins pointer + * @num_inputs: number of input pins available on dpll + * @num_outputs: number of output pins available on dpll + * @num_rclk: number of recovered clock pins available on dpll + * @cgu_state_acq_err_num: number of errors returned during periodic work + */ +struct ice_dplls { + struct kthread_worker *kworker; + struct kthread_delayed_work work; + struct mutex lock; + struct ice_dpll eec; + struct ice_dpll pps; + struct ice_dpll_pin *inputs; + struct ice_dpll_pin *outputs; + struct ice_dpll_pin rclk; + u32 num_inputs; + u32 num_outputs; + int cgu_state_acq_err_num; + u8 base_rclk_idx; + u64 clock_id; +}; + +int ice_dpll_init(struct ice_pf *pf); + +void ice_dpll_release(struct ice_pf *pf); + +int ice_dpll_rclk_init(struct ice_pf *pf); + +void ice_dpll_rclk_release(struct ice_pf *pf); + +#endif diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 567694bf098b..1af7b2ba4665 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4810,6 +4810,10 @@ static void ice_init_features(struct ice_pf *pf) if (ice_is_feature_supported(pf, ICE_F_GNSS)) ice_gnss_init(pf); + if (ice_is_feature_supported(pf, ICE_F_CGU) || + ice_is_feature_supported(pf, ICE_F_PHY_RCLK)) + ice_dpll_init(pf); + /* Note: Flow director init failure is non-fatal to load */ if (ice_init_fdir(pf)) dev_err(dev, "could not initialize flow director\n"); @@ -4836,6 +4840,9 @@ static void ice_deinit_features(struct ice_pf *pf) ice_gnss_exit(pf); if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) ice_ptp_release(pf); + if (ice_is_feature_supported(pf, ICE_F_PHY_RCLK) || + ice_is_feature_supported(pf, ICE_F_CGU)) + ice_dpll_release(pf); } static void ice_init_wakeup(struct ice_pf *pf) From patchwork Sun Mar 12 02:28:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13171097 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id AEE55C61DA4 for ; Sun, 12 Mar 2023 02:28:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229735AbjCLC2y (ORCPT ); Sat, 11 Mar 2023 21:28:54 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35128 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230034AbjCLC2t (ORCPT ); Sat, 11 Mar 2023 21:28:49 -0500 Received: from mx0b-00082601.pphosted.com (mx0b-00082601.pphosted.com [67.231.153.30]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 870C934327; Sat, 11 Mar 2023 18:28:46 -0800 (PST) Received: from pps.filterd (m0148460.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 32BK7wL0004410; Sat, 11 Mar 2023 18:28:31 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=meta.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=s2048-2021-q4; bh=ggbvdIb1jxpDfp7KaumFFjElGm6Nvg9XcVu+wvvPnlQ=; b=SEoc4CB3+fNTGaIaWPpJVLU1m9ctfuLFAOcHJDnGNqyysDZr4DQHNv8H/Jiv0z7yIi2w 61ipbKy0vhmY4OIapNuo467PAH6piZFNuc9pEhkp2QgOH+h/758n44TjJEhtdBdBiof/ UUcoODGL+/GTouGIWjlVxwvEEOnxXJwb0xD54hNKkapUF4XtLZWggIfvDhObUvvrwgVl mtLy5cUhzALYU06TjVIGdQqv2JOqVr09YqjbvjBsACGmCy4JRkRD6l2tbR2CTVx8TlCs //U0lo7RyaudzP4csRmUCe4JywrfgMIZS+SZ6TTD8gKqdIAdfRji1Putq7mVVGWAVFlN kA== Received: from maileast.thefacebook.com ([163.114.130.16]) by mx0a-00082601.pphosted.com (PPS) with ESMTPS id 3p8r25h4bd-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Sat, 11 Mar 2023 18:28:31 -0800 Received: from ash-exhub204.TheFacebook.com (2620:10d:c0a8:83::4) by ash-exhub102.TheFacebook.com (2620:10d:c0a8:82::f) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.17; Sat, 11 Mar 2023 18:28:30 -0800 Received: from devvm1736.cln0.facebook.com (2620:10d:c0a8:1b::d) by mail.thefacebook.com (2620:10d:c0a8:83::4) with Microsoft SMTP Server id 15.1.2507.17; Sat, 11 Mar 2023 18:28:28 -0800 From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni CC: Vadim Fedorenko , Vadim Fedorenko , , , , , Subject: [PATCH RFC v6 6/6] ptp_ocp: implement DPLL ops Date: Sat, 11 Mar 2023 18:28:07 -0800 Message-ID: <20230312022807.278528-7-vadfed@meta.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230312022807.278528-1-vadfed@meta.com> References: <20230312022807.278528-1-vadfed@meta.com> MIME-Version: 1.0 X-Originating-IP: [2620:10d:c0a8:1b::d] X-Proofpoint-GUID: LGcuQrBwPad_9xncnzpnhpW2xR2RN_Jf X-Proofpoint-ORIG-GUID: LGcuQrBwPad_9xncnzpnhpW2xR2RN_Jf X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-03-11_04,2023-03-10_01,2023-02-09_01 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC Implement basic DPLL operations in ptp_ocp driver as the simplest example of using new subsystem. Signed-off-by: Vadim Fedorenko --- drivers/ptp/Kconfig | 1 + drivers/ptp/ptp_ocp.c | 209 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 200 insertions(+), 10 deletions(-) diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index fe4971b65c64..8c4cfabc1bfa 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -177,6 +177,7 @@ config PTP_1588_CLOCK_OCP depends on COMMON_CLK select NET_DEVLINK select CRC16 + select DPLL help This driver adds support for an OpenCompute time card. diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 4bbaccd543ad..02c95e724ec8 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #define PCI_VENDOR_ID_FACEBOOK 0x1d9b #define PCI_DEVICE_ID_FACEBOOK_TIMECARD 0x0400 @@ -267,6 +269,7 @@ struct ptp_ocp_sma_connector { bool fixed_dir; bool disabled; u8 default_fcn; + struct dpll_pin *dpll_pin; }; struct ocp_attr_group { @@ -353,6 +356,7 @@ struct ptp_ocp { struct ptp_ocp_signal signal[4]; struct ptp_ocp_sma_connector sma[4]; const struct ocp_sma_op *sma_op; + struct dpll_device *dpll; }; #define OCP_REQ_TIMESTAMP BIT(0) @@ -2689,16 +2693,9 @@ sma4_show(struct device *dev, struct device_attribute *attr, char *buf) } static int -ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr) +ptp_ocp_sma_store_val(struct ptp_ocp *bp, int val, enum ptp_ocp_sma_mode mode, int sma_nr) { struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; - enum ptp_ocp_sma_mode mode; - int val; - - mode = sma->mode; - val = sma_parse_inputs(bp->sma_op->tbl, buf, &mode); - if (val < 0) - return val; if (sma->fixed_dir && (mode != sma->mode || val & SMA_DISABLE)) return -EOPNOTSUPP; @@ -2733,6 +2730,21 @@ ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr) return val; } +static int +ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr) +{ + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; + enum ptp_ocp_sma_mode mode; + int val; + + mode = sma->mode; + val = sma_parse_inputs(bp->sma_op->tbl, buf, &mode); + if (val < 0) + return val; + + return ptp_ocp_sma_store_val(bp, val, mode, sma_nr); +} + static ssize_t sma1_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -4171,12 +4183,151 @@ ptp_ocp_detach(struct ptp_ocp *bp) device_unregister(&bp->dev); } +static int ptp_ocp_dpll_pin_to_sma(const struct ptp_ocp *bp, const struct dpll_pin *pin) +{ + int i; + + for (i = 0; i < 4; i++) { + if (bp->sma[i].dpll_pin == pin) + return i; + } + return -1; +} + +static int ptp_ocp_dpll_lock_status_get(const struct dpll_device *dpll, + enum dpll_lock_status *status, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sync; + + sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; + *status = sync ? DPLL_LOCK_STATUS_LOCKED : DPLL_LOCK_STATUS_UNLOCKED; + + return 0; +} + +static int ptp_ocp_dpll_source_idx_get(const struct dpll_device *dpll, + u32 *idx, struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + + if (bp->pps_select) { + *idx = ioread32(&bp->pps_select->gpio1); + return 0; + } + return -EINVAL; +} + +static int ptp_ocp_dpll_mode_get(const struct dpll_device *dpll, + u32 *mode, struct netlink_ext_ack *extack) +{ + *mode = DPLL_MODE_AUTOMATIC; + + return 0; +} + +static bool ptp_ocp_dpll_mode_supported(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack) +{ + if (mode == DPLL_MODE_AUTOMATIC) + return true; + + return false; +} + +static int ptp_ocp_dpll_direction_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sma_nr = ptp_ocp_dpll_pin_to_sma(bp, pin); + + if (sma_nr < 0) + return -EINVAL; + + *direction = bp->sma[sma_nr].mode == SMA_MODE_IN ? DPLL_PIN_DIRECTION_SOURCE : + DPLL_PIN_DIRECTION_OUTPUT; + return 0; +} + +static int ptp_ocp_dpll_direction_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_direction direction, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sma_nr = ptp_ocp_dpll_pin_to_sma(bp, pin); + enum ptp_ocp_sma_mode mode; + + if (sma_nr < 0) + return -EINVAL; + + mode = direction == DPLL_PIN_DIRECTION_SOURCE ? SMA_MODE_IN : SMA_MODE_OUT; + return ptp_ocp_sma_store_val(bp, 0, mode, sma_nr); +} + +static int ptp_ocp_dpll_frequency_set(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sma_nr = ptp_ocp_dpll_pin_to_sma(bp, pin); + int val = frequency == 10000000 ? 0 : 1; + + if (sma_nr < 0) + return -EINVAL; + + + return ptp_ocp_sma_store_val(bp, val, bp->sma[sma_nr].mode, sma_nr); +} + +static int ptp_ocp_dpll_frequency_get(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sma_nr = ptp_ocp_dpll_pin_to_sma(bp, pin); + u32 val; + + if (sma_nr < 0) + return -EINVAL; + + val = bp->sma_op->get(bp, sma_nr); + if (!val) + *frequency = 1000000; + else + *frequency = 0; + return 0; +} + +static struct dpll_device_ops dpll_ops = { + .lock_status_get = ptp_ocp_dpll_lock_status_get, + .source_pin_idx_get = ptp_ocp_dpll_source_idx_get, + .mode_get = ptp_ocp_dpll_mode_get, + .mode_supported = ptp_ocp_dpll_mode_supported, +}; + +static struct dpll_pin_ops dpll_pins_ops = { + .frequency_get = ptp_ocp_dpll_frequency_get, + .frequency_set = ptp_ocp_dpll_frequency_set, + .direction_get = ptp_ocp_dpll_direction_get, + .direction_set = ptp_ocp_dpll_direction_set, +}; + static int ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) { + struct dpll_pin_properties prop; struct devlink *devlink; + char sma[4] = "SMA0"; struct ptp_ocp *bp; - int err; + int err, i; + u64 clkid; devlink = devlink_alloc(&ptp_ocp_devlink_ops, sizeof(*bp), &pdev->dev); if (!devlink) { @@ -4226,8 +4377,44 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) ptp_ocp_info(bp); devlink_register(devlink); - return 0; + clkid = pci_get_dsn(pdev); + bp->dpll = dpll_device_get(clkid, 0, THIS_MODULE); + if (!bp->dpll) { + dev_err(&pdev->dev, "dpll_device_alloc failed\n"); + goto out; + } + + err = dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp, &pdev->dev); + if (err) + goto out; + + prop.description = &sma[0]; + prop.freq_supported = DPLL_PIN_FREQ_SUPP_MAX; + prop.type = DPLL_PIN_TYPE_EXT; + prop.any_freq_max = 10000000; + prop.any_freq_min = 0; + prop.capabilities = DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE; + + for (i = 0; i < 4; i++) { + sma[3] = 0x31 + i; + bp->sma[i].dpll_pin = dpll_pin_get(clkid, i, THIS_MODULE, &prop); + if (IS_ERR_OR_NULL(bp->sma[i].dpll_pin)) { + bp->sma[i].dpll_pin = NULL; + goto out_dpll; + } + err = dpll_pin_register(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, bp, NULL); + if (err) + goto out_dpll; + } + + return 0; +out_dpll: + for (i = 0; i < 4; i++) { + if (bp->sma[i].dpll_pin) + dpll_pin_put(bp->sma[i].dpll_pin); + } + dpll_device_put(bp->dpll); out: ptp_ocp_detach(bp); out_disable: @@ -4243,6 +4430,8 @@ ptp_ocp_remove(struct pci_dev *pdev) struct ptp_ocp *bp = pci_get_drvdata(pdev); struct devlink *devlink = priv_to_devlink(bp); + dpll_device_unregister(bp->dpll); + dpll_device_put(bp->dpll); devlink_unregister(devlink); ptp_ocp_detach(bp); pci_disable_device(pdev);