From nobody Mon Sep 28 23:12:42 2026 Received: from dispatch1-us1.ppe-hosted.com (dispatch1-us1.ppe-hosted.com [148.163.129.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A75F73218BA; Sat, 15 Aug 2026 22:19:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=148.163.129.52 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832375; cv=fail; b=X3o6AIbwCZy4m0v2X2yfgykqHW/6+c8zXuIcBP14GT6IUGP3ghAd1kyc+3BuUg+nmX2wbJ2vNZLS1RNgseyg1tvfb84GUJEI12VSjZZ7ptZOE93gLvt7zKvL9RS1GJwApmD8Upq7/xFBi7u61qOnV08OVeEeTntZR69I0gnzF2w= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832375; c=relaxed/simple; bh=OS7ssmK13Zzn8x0KCbP7FWd7bvgxkw0woQ2cX3ZTJxM=; h=From:To:CC:Subject:Date:Message-ID:References:In-Reply-To: Content-Type:MIME-Version; b=iybaMjH/y+JWsN7GeTRBeVPvBv1bANyiPeDVTpDIpJb51l9FiRFJQCS+N0tv7TVci9Lef9P0ERE0yn1ZDobrRw2m3HCnQDVIsHwd6pU+cnEBImlpZFSK8JqRFxi+5xRMgK8yzYaQx/nJ+VDwDDCVhUrEsjBVT6ehYEnVf54CIbQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com; spf=pass smtp.mailfrom=sitime.com; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b=aH5j5J7x; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b=WCGTnP7V; arc=fail smtp.client-ip=148.163.129.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=sitime.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b="aH5j5J7x"; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b="WCGTnP7V" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sitime.com; h=cc:cc:content-transfer-encoding:content-transfer-encoding:content-type:content-type:date:date:from:from:in-reply-to:in-reply-to:message-id:message-id:mime-version:mime-version:references:references:subject:subject:to:to; s=mail; bh=OS7ssmK13Zzn8x0KCbP7FWd7bvgxkw0woQ2cX3ZTJxM=; b=aH5j5J7xF0vF+RU+vENpOYcRVV7S2z+cd52tWNKLugaoHVeEaQ4XlcknYQ+ZjsKr/jZHRhxtkOW3eTOAUr5GXcYz81DHAAOiX88fujDXariut2mRPypnhd1H+GG9D04r5vOo+htKW5FRof7OQERfiwk8T+7AtDpCkJwiNS/hO92sDWGAoxtnIXJrvnVvo9WHPa11NgSLk8o6Zbb2AiiMciBZyqWBzO9sYmCqokkxQnFgN7mLWlpiCBM5fyqJVt4sx0JK1rQoJ0uSJjoef5TORBlDpouMAHEN5OHOgi1LIDmZM3EuOd8YxWvtVDTBbB/urZ4pH1NmeHSLpm/9chvkUQ== X-Virus-Scanned: Proofpoint Essentials engine Received: from CH4PR04CU002.outbound.protection.outlook.com (mail-northcentralusazon11023089.outbound.protection.outlook.com [40.107.201.89]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mx1-us1.ppe-hosted.com (PPE Hosted ESMTP Server) with ESMTPS id EF5B7340068; Sat, 15 Aug 2026 22:19:24 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=PRKt1WEnksPfsI2irtQwgsBuxbFwlZRfiUEt2awggYGGXoepwZHmE2mGAm84sDEYAB8uzEHzO22S7bsRDSRLN9zamO3u2yA7KbiDK/dEGJ54NeUPgIDiydo6kJguElHYO+qPfVqxN3lqBIgdhxRG7tnYfp0auwMobNBFW74kNdsYHSJEjleVTnN9YPD1x+z9piSdguJ5OHg/OylWI4P/YYk6UBoyGjD0jiCw92qkmQhTmwYMu9MNyzw0w9FIiLyQbf+Zgs1bOC+cYLXSPyq5Za8sRw2SSkOilr3Lyg0XA11mxN7EUfgpuA2MTD/Ym0SV+RT+2Cw6375JQBJ7PRDDtg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=OS7ssmK13Zzn8x0KCbP7FWd7bvgxkw0woQ2cX3ZTJxM=; b=Hgc12qDUcTdcPgxdusDFOvShv5ldRKKnQm0RLvn4a2bAitGIIQzCW/iO8jJHuv0Gs/DFeOeA+8KlXHb+5nZI6TfMBu7BFYWCe/NlgfRyfoCY3qSfb0ZCo93QRKtdkoZ2jo1bmkHWzlCLE3+e2WXgTkJiMVmpcVpYdyIg2PODsF7ohakPbh1F3vWWY8DXy4dglV5FhyFgV/DIcMxQ+kClIqJgKDa8fsxR5btT+MNL8wELjeIh4OMUyq/zXtQvcevxcP4K+LNGKWzBUo6GOjzyB+Gzdx9Nu6TxpUqXNPzQh/7WRvMAUIc/P7ruO4oyAUAuVRrRxgqfOUdHbvVa3fJ21Q== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=sitime.com; dmarc=pass action=none header.from=sitime.com; dkim=pass header.d=sitime.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Sitime.onmicrosoft.com; s=selector1-Sitime-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=OS7ssmK13Zzn8x0KCbP7FWd7bvgxkw0woQ2cX3ZTJxM=; b=WCGTnP7VlrfAw1yUL29Bk8edppxFrwbu/VgEQw3pf0HDhwS+hxn2jddSAbqjl7GQK0PEu0+qW5DrAl8n5I7WcDo15qGopxiOjeHJ89QMATtS40q9Nn0bsBYt/t742VsZ6MiZOOYvkp9yCZTEF2BfMQDNDHgKtZx8VUydgwBzkmKjmFl7CS/eXlXaLGMuTrC/IZ4cFoUvd2ujn4PuGErcyXmGvWeufeuj796m4zTa8YA5Sm8F/3U0pAwK7aho0J1y4gqCKTnRs/o3hvP8tV/rYfIIuRKaiayMGxdXW64fQV59Bp9ssubUCK4GbWOsabzSCROYQCWaOaZ2R6WjI9r/oA== Received: from LVWPR20MB994915.namprd20.prod.outlook.com (2603:10b6:408:3bf::16) by SA1PR20MB5128.namprd20.prod.outlook.com (2603:10b6:806:254::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.315.13; Sat, 15 Aug 2026 22:19:21 +0000 Received: from LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01]) by LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01%5]) with mapi id 15.21.0315.014; Sat, 15 Aug 2026 22:19:21 +0000 From: Ali Rouhi To: "jiri@resnulli.us" CC: "vadim.fedorenko@linux.dev" , "arkadiusz.kubalewski@intel.com" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" , "cjubran@nvidia.com" , "Oleg.Zadorozhnyi@devoxsoftware.com" , "devicetree@vger.kernel.org" , "netdev@vger.kernel.org" , "linux-kernel@vger.kernel.org" , Ali Rouhi , Conor Dooley Subject: [PATCH net-next v7 1/3] dt-bindings: vendor-prefixes: add SiTime Corporation Thread-Topic: [PATCH net-next v7 1/3] dt-bindings: vendor-prefixes: add SiTime Corporation Thread-Index: AQHdLQQeGaxciuf/skOeKIC1+mfLdw== Date: Sat, 15 Aug 2026 22:19:21 +0000 Message-ID: <20260815221919.64226-2-arouhi@sitime.com> References: <20260815221919.64226-1-arouhi@sitime.com> In-Reply-To: <20260815221919.64226-1-arouhi@sitime.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=sitime.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: LVWPR20MB994915:EE_|SA1PR20MB5128:EE_ x-ms-office365-filtering-correlation-id: 7ca3c503-2cdb-456a-a65d-08defb1b416a x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|23010399003|1800799024|366016|376014|38070700021|56012099006|10067099003|22082099003|18002099003; x-microsoft-antispam-message-info: hELUojJmkx7+lM5nTWFeW9XDxYfOVkQXg+hV0i3TAxsP1bXSWHcscah1DxPXw0aTS5NR3tlQqF4COjS3MFD/fObu3J9wubTwr0EiAzJWuyalBMJ+CerTeqMfYdAv3sVEGpJi3yeF1timsQKL0YLt5eTmiYVTPZsryd4+mcYp3E5Fkn2rf39DSkAHllCPlUWrPy8AUlNaq/UuTf0HJN6sxTGEPE9YU0YcPgaImQnhw0nqa2I5Hx9xy+XKVFlBNkzSDa6QZJyyApRIocRB18q3P2RhWtG6pTqwCs0YGSCUZN3turUkQ9ufukKJfzfzfDZzFFdlLBEPBAhmSNd1/BcQDb71xyeNV3fXRAdOSy//Er0zdq6O49UvsqL69GLVqzBZxainCFJCB7bmezyE/7meQpqtsoyoo8U/AcMOMYgo/Cv6sXUtBYN/Ngph8gdqxTFQp37X6KOXbcpaEG38ihVSbNox5DZVeRVFj+YbsXqqKbDFYXQKrSZejfT3QarrdKBedYFCSFIcGgfntgHUJMM8MFWsJ3Q6TWZhOMS88MA5ZpSnAcqK3OUEttiI8+eKbjzhROILEUtL7MSXZcPWAaADqdY/Q6sj/k/VGG2EdZyyVtFYevbUigf6EJMupSidylZpaY6Ej8o051D4xh3SK0abJex+NT7GzXQqMFHuNvCuLV9mLD/kxr2+/ADFVz5fDz5qXKdevCRBHnZ49uTsxmWfbUPQwPVioLTq1P0zq2M/xDM= x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:LVWPR20MB994915.namprd20.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(1800799024)(366016)(376014)(38070700021)(56012099006)(10067099003)(22082099003)(18002099003);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?utf-8?B?Vmk5WFI4TW90bmU4dUVreGV6NnhjalVrY3NoMGRTeE9XV21DTnBiWkRqRGIz?= =?utf-8?B?Nit2ZXRmb1oyNlhscUR5bFltL04yV3hxaDZQZGhmeDllMnk2UXpDNjdrRklH?= =?utf-8?B?aVhVdVFJVEpCdldScHhrV1lsMExJZ1RPcWo0U0QvNEVITXlqWS9PSmU1TlJm?= =?utf-8?B?QTAvb2I1THBwZ3NNYkEzUi92c1pYOEkzaDN4b3A3OUd5L3E4MzdreWtIYm9q?= =?utf-8?B?cTRkYXJZb1VpMlBLWDhlWkN6bXB1MEcxNFNwSWkxT2xzQ3FkQWxXTmFLRnM1?= =?utf-8?B?dVFiV1J3MDYrTCt4M0ZCMmQwWU14RXZwQmQrVTRDK2RhSnVmRmRhbXl2czVJ?= =?utf-8?B?em80ZVYxQWFLclBxNENuLytteDFCc3didUV4MFQ4eDEvVlNCaHBrL29oY21E?= =?utf-8?B?Y3FEQ1ozL3h5d0VNR1d0WVluMG1BWXl2Nm8wM1RRejdYVXkvV29CckllUE1v?= =?utf-8?B?dktTbXNKR0ZYNFloV1JiTHRQT0g2WjRHc2Z5NjNvNy9RWGlWQ3ZCVkNWSkJy?= =?utf-8?B?aGFubjhNT1RHM3VMM3plTEliUzVtUDZla3h6V3c5K2k3MUw1Z3pKb1RiS3ZH?= =?utf-8?B?YlZJbkZxeEdmRXRjelVaaVd0dmtiVkVwak05YXREZkJEVzdRRnBhbGUyVkxO?= =?utf-8?B?VUtrMmo2THFZZEJhR1lIM0NiVmtiSGZUOU9SVHhoSW9CY2MvV0lGcjF6T0lj?= =?utf-8?B?dVljc0FtOUNVcCsxbnc1anpSekNoMTloOC8rNFFJeUJLNzBIL3ZZczJ4SGVP?= =?utf-8?B?a1RDbnU5dk5YQU8vR0hzeDZVRTlyNDc2QVA1amhoeVBCVGhpaWNma0MxYlh5?= =?utf-8?B?S2xBS2RVWXZQeVp3Rm1Fd3luSE1HeWs4QlhYUXZ0NFZzZlc1T0gxWko0V2dU?= =?utf-8?B?WHN5L0pSZndhSFM0b0E4TmtrU0tlMHhnaW0vY05mMUpaQmhUUmQ3WVVmS3B6?= =?utf-8?B?dXpibktjd3NWZmxHUEVCYkhITW43TmhQSW5qRXpZa2pHSHZzZDBJVE9BWFR1?= =?utf-8?B?NHJxRFZTd0ZwcGNQRTk4all6MnJrTUJPeUtVWE5aT0VITmxMNlR0UVZhQ2xz?= =?utf-8?B?VG81USsvUXpwSUFKS3dTa3p0RUZzWVc3RjF3cjVFdzBoTFlQM0dWTzhzSkNE?= =?utf-8?B?NFBLaitWbkc4cEtYdlNxLy9UUEVHQ21YRU95UXVwVU56NnhpVDV2T09EZWxN?= =?utf-8?B?V2NSenNzdzRuNkFsK253OFZTcjRPeGx4MGhrNUc4NUtMVFRxeitkWThGK042?= =?utf-8?B?VnNqSDMvNm4xTWFpQmpwSjVBVGtFUkFpeXl0UE1jV3dXTFB0VGduM1c0UmRh?= =?utf-8?B?clFxMjZFSVJhRVRzTUFObys4Unk5cTc1Q1V5c1JpcVVSOEZDN1JQMlo3M1Fv?= =?utf-8?B?eEhmSUJ1amZLcE1LMWFWOGczVjhhUUFEQ1hTVGV3Y21iWmFMYXZSRU5BZTV5?= =?utf-8?B?MllJc0hMRHdENU14eXdvdCt6OFRRTEViaS9yV0hmZVc3dXlSTWtFUlI2dG9q?= =?utf-8?B?Y0JucjdVS0g0WTFQWi9DZzNsYXAwUVJVbXJXdStHSUIweWNDMFVTajFpL1JN?= =?utf-8?B?OHJMOE5FcE1nZis1VEFBdHZGaGF3NFdRdnlZMU9leE93c2RycjZxSmd3SDZR?= =?utf-8?B?Ukt4VE9IQ0xLRWFUNksyWUFxWFJvcUE5VzhpaTN5dnBZTjZlU252TklNVGty?= =?utf-8?B?NWFwZEgzenpmOGZOWTVsUzFNcjdHWTE3Mk5EWkRpQ0dKejNZRkR3bGxnbjhD?= =?utf-8?B?SmZ3TmRNQm9LZWMveGlVRjRadk9kMDA0UjZMTnBlQXpxTXVzcW43QkpvbXFl?= =?utf-8?B?TXNpd3lES3dDOVpRcHlWdFNYbnBZTTRxUTVJditaMDcwQWhiRVRQSnJnMzdU?= =?utf-8?B?VnFMcmFsa0wwbjh4UUFDUXZsZWYwSlZrMFdqYm1SQmJhbmtqQlRaMGEvWFlX?= =?utf-8?B?MXhNQXhHbjB4M1NqcDdhTVVHdUUyVGU2azFJNVlPVWlBV3o4a0tYRTk4SEk2?= =?utf-8?B?VTIxWGMxTW04UVUybFJWdlhuSHB6Mms1dlloSnR3Z2M2ZU5YK0U3OVFjZnZu?= =?utf-8?B?T2pjSHk1alJDWi9aZTZ3NjdOZlk2eE9MT2g5QzZlYWFtMkwvUFIxZVVlQ0JY?= =?utf-8?B?bkVCaTJQWkVHOERMQ3h3Mk9neGxZVGtQK29IQW5hbHdNVWlTRUlVSHhmYTcv?= =?utf-8?B?TDdYc01odEpqL1B0SUVNcCtxOC9BQjBMblBYQU9sVnhRVHNabjVZTDNFQldP?= =?utf-8?B?eVpzTDdKeEd4Mk9FUVRJTE1NYW0rRE1lT0J3VmJnZWxXSloxMnRrM0JTRkdu?= =?utf-8?Q?QMB6Y75iqgEtbcSKeF?= Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Exchange-RoutingPolicyChecked: ifu0ypo9Sz77bzS09JRy5fzilpBE4A3uhxbb2ehaLJLkLffc4rbeFXHIx2+BtvZsIQpyYqcl1J5muw8tlUgbK6iJN5B0QgDd7/CRnmdmV2qMyGHssvej9+krdBhTSSuf1VXcxwZbXy5OcQ3mz6L/f0j8nIVxVYa6VzEUbOSZNdCDaTgKmBgl+2gdavrNf+4/QurHLUk2rCNAPD1KI0doqYJfHBoTwE5q1Y1sH+eArrwatGQMKHpe2kmHqmA0aG2munCQl1DhUyVmjH+o/Ctq/XJ/w2dg5yXYs0CD8p026BIlbiPPW9HhZIHYS6sbTJ8/sVr/DZdodQ1D7XmcaMPWfA== X-OriginatorOrg: sitime.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: LVWPR20MB994915.namprd20.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: 7ca3c503-2cdb-456a-a65d-08defb1b416a X-MS-Exchange-CrossTenant-originalarrivaltime: 15 Aug 2026 22:19:21.3159 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 8fb55916-cf10-4b0d-96f4-cf3952657263 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: Cp6u95uPz1iJznGc7PX9mYDthU+lQ0YHDncHblFxgAjXeoUm5g6H0igc4TYiEGhs7nw0YuCQN1NqqblXQqmN9Q== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR20MB5128 X-MDID: 1786832365-mwL8S8b7K2SV X-PPE-STACK: {"stack":"us1"} X-MDID-O: us1;ut7;1786832365;mwL8S8b7K2SV;;ee4c095510f4a2bc549243be2f087a38 X-PPE-TRUSTED: V=1;DIR=OUT; Add vendor prefix for SiTime Corporation, manufacturer of programmable clock generators and MEMS oscillators. Signed-off-by: Ali Rouhi Acked-by: Conor Dooley --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Docum= entation/devicetree/bindings/vendor-prefixes.yaml index 396044f368e7..1921718a5f82 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1535,6 +1535,8 @@ patternProperties: description: SiRF Technology, Inc. "^sis,.*": description: Silicon Integrated Systems Corp. + "^sitime,.*": + description: SiTime Corporation "^sitronix,.*": description: Sitronix Technology Corporation "^skov,.*": --=20 2.34.1 From nobody Mon Sep 28 23:12:42 2026 Received: from dispatch1-us1.ppe-hosted.com (dispatch1-us1.ppe-hosted.com [148.163.129.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9A1CF271456; Sat, 15 Aug 2026 22:19:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=148.163.129.49 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832376; cv=fail; b=F/2x3wDOWOXcJiNEYRKvJVv5gPEVrshCsE2IE69upJWAVJ6B4Vp/tprVjUhlMrWVBDW3W3Skj/IGlXvkOMMuk7zE04EDxGQmKje2dn+IGiRgdynzI+d9Oum0w4IV9dK4l1huBJcnr9jRi6T3fehvWluhFIfDwviLKvmzEbi87a0= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832376; c=relaxed/simple; bh=73YCIRUlaQMncaKOinbBORpYU9KcZu/2R6JtLnqti5g=; h=From:To:CC:Subject:Date:Message-ID:References:In-Reply-To: Content-Type:MIME-Version; b=qOEvVw1DbmuzBGQXe1eXG8lJC0FKCXjUwjRnc6HV5t+x77Tf3+74xfsH5IponR4R+v6rfRvnWNbbTZF6T+2g202TDZdsC8EP9D1vg0xxuik5vnHe0yBfNfNc8q05Y4BEmkYM3Q3iAQb6NwWZ6XVRkTaZ+8ROHx0CUZGSKbQklPY= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com; spf=pass smtp.mailfrom=sitime.com; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b=C4fuVNpl; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b=ReXzg2kN; arc=fail smtp.client-ip=148.163.129.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=sitime.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b="C4fuVNpl"; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b="ReXzg2kN" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sitime.com; h=cc:cc:content-transfer-encoding:content-transfer-encoding:content-type:content-type:date:date:from:from:in-reply-to:in-reply-to:message-id:message-id:mime-version:mime-version:references:references:subject:subject:to:to; s=mail; bh=73YCIRUlaQMncaKOinbBORpYU9KcZu/2R6JtLnqti5g=; b=C4fuVNplRqaYz+z12ji+aiblJcJMzBK8gSK9zhDJPwEhGbHaf9UP54WKDLspG1rsQfNNEIoba1WObIgXX5kKhyQQImtD+yJsJb4M3j2hScjhQyQQa2dMzW9fUOwYuoCpFPK67QobWGTwMmpe640+QVHkaYAcyYsb3Ob/7uEc+rWfvPNPeZWChqAYYTZFJbxIlx1cEfm7aE7q2ag26ATsuFJWK+NImsjupl61GYQxyuOrRxYb0HXlPK914KzCL6boknaxvlkkhHHEfYNYwu3cF8egjadO+D83dnIEZxrBXVjm2ASCBKtrmf1NA2DetNdDgsKz1LMuIfcfmVh0jU+Ndw== X-Virus-Scanned: Proofpoint Essentials engine Received: from CH4PR04CU002.outbound.protection.outlook.com (mail-northcentralusazon11023089.outbound.protection.outlook.com [40.107.201.89]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mx1-us1.ppe-hosted.com (PPE Hosted ESMTP Server) with ESMTPS id BDB95340070; Sat, 15 Aug 2026 22:19:25 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=wVxe5mLf/FyQon0iZPpyeHYcSj2EfS8zuWOYULwge+EZyYX9O0Gx4W2MnO5Et1r8LzeJPZP10rcRLHF2mszYb96H5vao7Aw+LHrZO4Q5e01ilROSJzQNAKM/g69xxGKG20YeTQvtnNPVPTupYvze3LXYnrYc1ljjPpESkO8s9qMuP3UjiJrEktIdi2A89KUvcdE6GLk58NdCejgt6e1LIltq1KuG5oCQC2M8toz03LT1BflOborVOlHxm34Rw7rMJ+0IbjmMOXCuCY2Pxh8PMyb8Wse6DmTmu9zWnMrTzfAbeTzkclOPE1zx7fnN3EPDh0zIHiV1FUfbWUfBplYIMg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=73YCIRUlaQMncaKOinbBORpYU9KcZu/2R6JtLnqti5g=; b=tDIihlV6Y/YGPp1RgUf3u11MHpnBD3zorqXgIdtRQ8D3sfztVaRH8SqVtLfwBU35fjSKsIks0tD22+yQMjwF3AcQJJj6HxohFrzvRamUl70kcGBchs00hWg9dp29hzGSuKb818PY3XzqYd9CMSNygK0qDGgVzGNlU+Tw+cfR0EhY48L5j/5zZCP8QNAv8a1VHoaYTm99pj12rMNX5MO7RMKaawEHFWwY94Oh8lnDxXSf20W1ScW95TRGu+sQpiXcSEmGSHUkjr6Se+sz7u+0XLjdYs8chq3JB7T3Mcc+fwZBOerE7kDUYt4AlMULslvX08rsz/D68GXq9Y0QhyqwVg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=sitime.com; dmarc=pass action=none header.from=sitime.com; dkim=pass header.d=sitime.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Sitime.onmicrosoft.com; s=selector1-Sitime-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=73YCIRUlaQMncaKOinbBORpYU9KcZu/2R6JtLnqti5g=; b=ReXzg2kNkpK1XWu2WZ7l6nBdf+9yyBdkkcbGJRLDSYS+s+JoYPja9AiHvZjJCjyrNjSJoqxzYVsKlu/WKa07/eO9ki3NpMTgeZUOa7R6ZES7f/hnnFm/mL5YMJaUI5y1BSv7bftyM1zT9if2ktwmIune3P0V3g0cj8TgyiWsgFtDenRNBJi+8mg+67qyS2hHV94qeQhbtz7won/gtQYg4QMmAj+MqpFzRo6l8LF+Apz6dOucAdrKejQ1wJ4PQGgk7c32wfVap7oEja4Lj1AQ8f03cw1Vg3MXx3isHocMG4hTvmxdDuEdSAh93QjtDaPTIUvNboF+KM7IyrHugGHyWg== Received: from LVWPR20MB994915.namprd20.prod.outlook.com (2603:10b6:408:3bf::16) by SA1PR20MB5128.namprd20.prod.outlook.com (2603:10b6:806:254::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.315.13; Sat, 15 Aug 2026 22:19:21 +0000 Received: from LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01]) by LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01%5]) with mapi id 15.21.0315.014; Sat, 15 Aug 2026 22:19:21 +0000 From: Ali Rouhi To: "jiri@resnulli.us" CC: "vadim.fedorenko@linux.dev" , "arkadiusz.kubalewski@intel.com" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" , "cjubran@nvidia.com" , "Oleg.Zadorozhnyi@devoxsoftware.com" , "devicetree@vger.kernel.org" , "netdev@vger.kernel.org" , "linux-kernel@vger.kernel.org" , Ali Rouhi , Krzysztof Kozlowski Subject: [PATCH net-next v7 2/3] dt-bindings: dpll: add SiTime SiT95316 clock generator Thread-Topic: [PATCH net-next v7 2/3] dt-bindings: dpll: add SiTime SiT95316 clock generator Thread-Index: AQHdLQQfhmEF51C4eUuIUja6+gxBFQ== Date: Sat, 15 Aug 2026 22:19:21 +0000 Message-ID: <20260815221919.64226-3-arouhi@sitime.com> References: <20260815221919.64226-1-arouhi@sitime.com> In-Reply-To: <20260815221919.64226-1-arouhi@sitime.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=sitime.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: LVWPR20MB994915:EE_|SA1PR20MB5128:EE_ x-ms-office365-filtering-correlation-id: 407a85fa-b3b6-4e6e-2ef6-08defb1b41c1 x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|23010399003|1800799024|366016|376014|38070700021|56012099006|10067099003|22082099003|5023799004|18002099003|6133799003|3023799007; x-microsoft-antispam-message-info: +TbD6IoxLXyez6uoQkik8T5uGSqRVXbYVloeGiPf+HTxl3rA3Rw50dILL3hl5QG5eZP7gCvroXXO114Lr6735v0IK3ZHgug8bLSJErKwWEjhYA4B+0eqEflETPMtBqlOdiWNjN68awF0i2sROthkgdv7EarTBxRe7oK5xhWewNaq6J2l+5/HeNxVr/vOxmSY/J9x1k/4ZqSl1coJZ1UrdjRICfL4jcSAmzkIhdlmGntc+22wQVic5JftsoujHthGjmUAikKZdIboH6kui/EF6W73cZVN5UA9XqOCyjpOcO3m1LuOAePE/SXWllnt9J+Z5gwL5+dndLvwgdGi8rDbauD3IS9dxHyhEsVt7woWt3FVzXcadgLBocc1TBSN9PvDs7YIbdtS3j58HvGyR/kBTJPMRSVZM1b9r6KSHGq5lz31q4KokDmSJu3EFhLEr8mHmAHFF2lfhxmYiAqB/QJpnnKe+mHK1N+OZxvkbwb7+kE29f0zLAoX/oXZofu5DyckFke02OyKdi//RJizIUivpql88ClmcW2gQkcykS6nE13rswxw0jJueVqNCxvbqWb00FpbKduayiDAzAU7MQ5Cig77I817AKjtfko1k4r9tEEOJQtwfHWYEKWZVjsteMmZcx2tw13+n/fyg4qCqMNb7GlV1kdgkq5mZpfVdTqBb1w= x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:LVWPR20MB994915.namprd20.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(1800799024)(366016)(376014)(38070700021)(56012099006)(10067099003)(22082099003)(5023799004)(18002099003)(6133799003)(3023799007);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?utf-8?B?ZTY5S3Y4RGRNL2drSzdISUVlZ3RIUk1qa1FsMGZzcjRReS93YmJmN2dVbnhI?= =?utf-8?B?YTY1L2JZNTQ1K1pDM2RaR1FkTEhtUUZUVks5RUIwQzFOUGUxT1JsM01UeDBU?= =?utf-8?B?RGxGSis3NnFsZ3lESFp5TjdVczN6MjNVNHdnVUxSODN1dXZINHgyN2NRaDlp?= =?utf-8?B?bDByMlZoMHFvZ3UvR0lpeGRrSUpwNS9YMUJlanUwUFdNOERlVHNVeGZ6QkNy?= =?utf-8?B?RUJQUWcyY1dVbHdFaVlaaUlidHR5TnVERFh5VUdkRVFVeVFGb3JreW1LUzJq?= =?utf-8?B?RGN6K2FZU2tmcFhQTEVXQ0FYT3IveitPT1FwdWxMaERQNjQ1R2krbXhjZkps?= =?utf-8?B?MTU5a2hGTG0zd3p1bWs3SmFFYUpPWlhmKzVZUGpaMVdyMkcxVWorSldtczJv?= =?utf-8?B?eHpvdUNjYlludmh1ZVRqbXRhbVY4Vk1DL3ROeUY2TmF3SndiUDA5by9hVGdJ?= =?utf-8?B?WkU1ZEZ4VFVDOVZLTWRUbnJwZjg1eERpdjhseHNicUFKa0JvSDFTSlpxVzY2?= =?utf-8?B?dGt3WGFwc295eU0ySDdlQnlyUmpyclNtcDFpNmJBSjRDRGhjNDdma1Z3N0lG?= =?utf-8?B?YmVzNWZCTGFTU25WT2dXTnlwS2dIcS91N2UvR292azRMQVgzQy9zcW1ldGlN?= =?utf-8?B?enVnK09xcGF2SjBLMUx3ZWk0ZHUybFlIVFZ2VHRoZGZMNUF4Z1pMV285UEV5?= =?utf-8?B?bzM5cWxGZHNGMkRmaWp4Rjd1UnJrcForWDk5djFpTTk3cy9tY21aTldPUUkv?= =?utf-8?B?eUhvRDVpUWdPVFhhczExdHY5UWhWK1BvZkhxaHE1WStTdlNMYlVNUXdTYVNI?= =?utf-8?B?cVNRWUU1clJrMVhWd3A2RkRDN1MwS1NOOWNTT1BDdVJVWlJiengxSFBGNWE1?= =?utf-8?B?UHczN1lGYVdUeEdVRS9Ncm95QWFQMkE0dWFvb0xldW9KbEIySFhJN1FkYWdP?= =?utf-8?B?Z3VsMlpkVzdRcG9WOEx0ZjI1aDZhMmhJcWNXWHBHYXpwWkx0ZjZDNWtlbFE2?= =?utf-8?B?N051K3o0SFEwVkVlNmhVOW1tZHJ5VndaUllrN3JRbVowWVZtdHRIYmxZaTdH?= =?utf-8?B?cmJ2azQ5RkZYNzBpd1pFRGYyVDdHWTRJUGpHNHJqWHI3SXhkdFlFVUlXWG9U?= =?utf-8?B?dXJranh0eWVxUmRydE1lT2dLU1l0MnlvK3VyTzByRWpGN210aUJPb1V1bUNs?= =?utf-8?B?bzFSVlFORmpwYmdGWmFib1pDUlllNmdVY3Y3RS9qSklLbENzcWNGVnlnRHc2?= =?utf-8?B?OWFCejNTWGJWN1Z3TFFwUHRMRDFHWm15bEJ3ZHBOZG5IaG1aVTZTZS80NWQ2?= =?utf-8?B?QlRJZ3F0ODVEQ1ZuU0Q1dVZwcEF3MEJwNS91SlRPbG95cktpYkV2TEIrU2Vr?= =?utf-8?B?cXY5YzM2Szg0TFhxbi8vbmYyV2xLRnhERkVtRkFVL1o3OG42aWZObUJTWTlw?= =?utf-8?B?aHQ5RGV6N3pRS1c0R2ZydExRRWRadm1KbVZUNklWNDdibXc2N1drdFFIM0tq?= =?utf-8?B?bVZXTUNzTEY3RDBTZ3ltZkVBQ1hrRGk4LzR2ZDVTcDMydVQwYm9heStBL1lt?= =?utf-8?B?VkZ2UjhpMSsxNTNja0NlVW14L0luYWRMVFV3Mm4zVDVWWHBmTU1DMUhadzJ6?= =?utf-8?B?NUFVUzl1YjBJZ0lsWE1nUjdObnZUSjlLMGt4d1BHak1SbkdCSS9KZTdaSnZ3?= =?utf-8?B?MzE2SjNLM2l3NFdJWVJSSGsvWHFRTlBYR1dzUmo0UWRvTGZhZVQ5YjVHUDFH?= =?utf-8?B?RUlHem5xZDRCaUE4REgxYWtkd3JpRVZpTVZHT29MeHpoZmNsWUljNE1xc3pS?= =?utf-8?B?Q2ZrSlVqd2t6MnJTQVVTd1dabDl4MWt3eko5R0ZpWWppRnZZbEx4OTVsZ0xs?= =?utf-8?B?ODdTeEgwbEtoTVBVdjEzOGM5RUF0SjFGbGVPUk1PVkdFV1VqVkpKQmhPZ3hV?= =?utf-8?B?OHlObkVNRWQ4UlNoT0xrWUM4V1Q0NEtYODBER3Y3YldyRkRPQy81ZlUrbURT?= =?utf-8?B?ZUxxWHRVUnRTY2pNMjJ0M1lpUmw1WithZjlGN1YrTEx6dlhZbmUyWU9MK00x?= =?utf-8?B?Q1hnKzBNdVZXNWdZVFcyd2tCU0V3bUNjNUhmcXVOVmE0dXYwWkc2YXBxVVVq?= =?utf-8?B?RDJDb2UxM2tiZ0RRaFc0Q2FlNkRGVGZFdEZQSFBUS3dsei91RkI3MHE3ZEcw?= =?utf-8?B?UEZuR2Nzcko5TlduMUI3RGZkeXZ3Wk9KYjdaajJkbXBuWUJ2NE96TndhYVNV?= =?utf-8?B?NVhhdVVkV2ZZWDdmaE8wclNicFNIYjZsNnZYeC9USElRSUxoQTNTUjlndnFU?= =?utf-8?Q?5+Ubmptzf87gqEDGjG?= Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Exchange-RoutingPolicyChecked: oheUWwBaS/oOqOBX+EGZbo3SJMyRkKd0JWplAxsgueI1GLbABoCbMSm1PQvVDBsE8tSN3zj0P9KyhU3+K3HfLQLPCxE/4qdfyBDSwm1LIoBB49MGeo/B1NyM6uMm1m2GkUqAdGIgoMUcwn1zuR/yQn+mVG/N+XdUo7cJcWQPwll7pn2Gqai6IZ87KU7Hkd2hRSn7VDrjToS98UgFFjyOQp42OXOLqvQ2F3ukAgj0RHTbp0sTDLvPN4K4Po3sdG3PL/+Ev+9eIbVTb7Th69ANExxRPDq+nNDzTwSQ0i7yqKfe0Rvmi8FFgGNIiiNN9DU+7CMggfccc4d+8gL4njsUHg== X-OriginatorOrg: sitime.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: LVWPR20MB994915.namprd20.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: 407a85fa-b3b6-4e6e-2ef6-08defb1b41c1 X-MS-Exchange-CrossTenant-originalarrivaltime: 15 Aug 2026 22:19:21.8529 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 8fb55916-cf10-4b0d-96f4-cf3952657263 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: gyPCjRABr20GX43wFnwxl7YA0kLKYrcQ6t2d8PtLXoGitnp3mpi1ZU8yVHPcPb2V7eClBgisezHo5IuZwIhT8Q== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR20MB5128 X-MDID: 1786832366-963yrTuGDisv X-PPE-STACK: {"stack":"us1"} X-MDID-O: us1;ut7;1786832366;963yrTuGDisv;;ee4c095510f4a2bc549243be2f087a38 X-PPE-TRUSTED: V=1;DIR=OUT; Add device tree binding documentation for the SiTime SiT95316 and SiT95317 DPLL clock generators. Signed-off-by: Ali Rouhi Reviewed-by: Krzysztof Kozlowski --- .../bindings/dpll/sitime,sit95316.yaml | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 Documentation/devicetree/bindings/dpll/sitime,sit95316.= yaml diff --git a/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml b/= Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml new file mode 100644 index 000000000000..591a8c0d7bbf --- /dev/null +++ b/Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml @@ -0,0 +1,171 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dpll/sitime,sit95316.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SiTime SiT95316/SiT95317 DPLL Clock Generator + +maintainers: + - Ali Rouhi + +description: | + SiTime SiT95316 and SiT95317 are I2C-controlled programmable clock + generators with integrated DPLL for synchronization applications. Both + variants contain four PLLs with automatic/manual reference selection, + DCO frequency adjustment, and phase offset measurement via an on-chip + TDC (Time-to-Digital Converter). + + Both parts have 4 differential input pairs whose lanes can also be + driven independently as single-ended references, so 8 inputs are + individually selectable. SiT95317 drives 8 outputs, SiT95316 + drives 12. + +properties: + compatible: + enum: + - sitime,sit95316 + - sitime,sit95317 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: xtal + + reset-gpios: + maxItems: 1 + description: + GPIO connected to the chip's active-low reset pin (RESETB). + + interrupts: + maxItems: 1 + description: + Interrupt from the chip's active-low INTRB output. Asserted when + the device detects a status change such as lock acquisition or loss. + + sitime,pll-fvco: + $ref: /schemas/types.yaml#/definitions/uint64-array + minItems: 4 + maxItems: 4 + description: + Per-PLL VCO frequency in Hz for PLLA, PLLB, PLLC, PLLD. Neither + standard frequency suffix fits. The VCO bands are 4915.2 MHz to + 5898.24 MHz and 6875 MHz to 7812.5 MHz, so every valid value + exceeds the 32 bits "-hz" allows, and the band edges are not + whole megahertz either, so "-mhz" cannot express them. Hence a + plain uint64-array. Override where the standard + Fvco =3D Fref * DIVN derivation does not match the running VCO -- + for example a PLL operating in INTSYNC mode. A value of 0 keeps + the register-derived computation for that PLL. + + sitime,output-pll-map: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 12 + items: + enum: [0, 1, 2, 3, 255] + description: + Source PLL index (0=3DPLLA .. 3=3DPLLD) for each output 0..11. The + value 255 (0xff) marks an output as unmapped and prevents the + driver from registering it as a DPLL pin. Override for + configurations where the chip's per-PLL OUTPUT_ENABLE bitmaps do + not unambiguously describe output-to-PLL routing. + +required: + - compatible + - reg + - clocks + - clock-names + +allOf: + - $ref: /schemas/dpll/dpll-device.yaml# + # SiT95317 exposes 8 outputs, SiT95316 exposes 12. Bound the + # output-pll-map length to the variant so a SiT95317 node cannot + # describe more outputs than the part has. + - if: + properties: + compatible: + contains: + const: sitime,sit95317 + then: + properties: + sitime,output-pll-map: + maxItems: 8 + # SiT95316 has 12 outputs. When the map is supplied it must describe + # all of them, otherwise the trailing outputs are left ambiguous. + - if: + properties: + compatible: + contains: + const: sitime,sit95316 + then: + properties: + sitime,output-pll-map: + minItems: 12 + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + dpll@68 { + compatible =3D "sitime,sit95316"; + reg =3D <0x68>; + clocks =3D <&xo2>; + clock-names =3D "xtal"; + reset-gpios =3D <&gpio 78 GPIO_ACTIVE_LOW>; + interrupts =3D <12 IRQ_TYPE_LEVEL_LOW>; + dpll-types =3D "eec", "eec", "eec", "eec"; + sitime,pll-fvco =3D /bits/ 64 <6251500000 0 0 0>; + sitime,output-pll-map =3D <0 0 0 0 0 0 0 0 0 0 0 0>; + + input-pins { + #address-cells =3D <1>; + #size-cells =3D <0>; + + pin@0 { + reg =3D <0>; + label =3D "clkin0"; + connection-type =3D "ext"; + supported-frequencies-hz =3D /bits/ 64 <10000000>; + }; + + pin@1 { + reg =3D <1>; + label =3D "clkin1"; + connection-type =3D "synce"; + supported-frequencies-hz =3D /bits/ 64 <156250000>; + }; + }; + + output-pins { + #address-cells =3D <1>; + #size-cells =3D <0>; + + pin@0 { + reg =3D <0>; + label =3D "clkout0"; + esync-control; + supported-frequencies-hz =3D /bits/ 64 <156250000>; + }; + + pin@1 { + reg =3D <1>; + label =3D "clkout1"; + supported-frequencies-hz =3D /bits/ 64 <25000000>; + }; + }; + }; + }; +... --=20 2.34.1 From nobody Mon Sep 28 23:12:42 2026 Received: from dispatch1-us1.ppe-hosted.com (dispatch1-us1.ppe-hosted.com [148.163.129.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9FC163932CE; Sat, 15 Aug 2026 22:19:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=148.163.129.52 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832383; cv=fail; b=qeada1KYjY939Ugda7t/PbNk0LcEFFf07wwjobfUJADA5jM65qhNPNuoXCGWqyvkOaauNruLKSNic6IGK3iSO2l2iXShUf0s+nXuSU0cgnwAcT8IoF5EuwodKFRYTYx+8XLwL/aOuFnt1zxTAobAS43jzDBkFNm8u85oJuJT/nw= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786832383; c=relaxed/simple; bh=wHYY0363Hx6pLLChIeHboN5zQ6j8Z2gNh39rUis6GaM=; h=From:To:CC:Subject:Date:Message-ID:References:In-Reply-To: Content-Type:MIME-Version; b=YqcI6q8KkM7CKmsYBb0Ya2Kl4iWDvuKTxm7/py0+1WTkAT5DoeLpeFv7ni89D55DoWOjFEsHgswD4kgq5jR8AMON36woxt8kNcXyIlGfFmJX/j+PVPx/CDL90jLUGjEtUhsQ/JXIFG9ZBwXVKTsDlX5yKUXZzC9fjOH8UwVahU8= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com; spf=pass smtp.mailfrom=sitime.com; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b=cnd8WfZE; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b=xVRgRZoo; arc=fail smtp.client-ip=148.163.129.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sitime.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=sitime.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=sitime.com header.i=@sitime.com header.b="cnd8WfZE"; dkim=pass (2048-bit key) header.d=Sitime.onmicrosoft.com header.i=@Sitime.onmicrosoft.com header.b="xVRgRZoo" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sitime.com; h=cc:cc:content-transfer-encoding:content-transfer-encoding:content-type:content-type:date:date:from:from:in-reply-to:in-reply-to:message-id:message-id:mime-version:mime-version:references:references:subject:subject:to:to; s=mail; bh=wHYY0363Hx6pLLChIeHboN5zQ6j8Z2gNh39rUis6GaM=; b=cnd8WfZEqqXGSyowPVwwDRery+U8YoJrspTxBGIosktdGGukg9EN44bI2OJ84nWNV/pqFqEnI/1uAjhO/8amttwtJp2zzUvhNKhXTYBZ8k2PP3SybVpj/7cp1sKbalZNl9ga3Gli+0vhALOaCahznZqpfv9+3cP0Dq8vEM1/MmRT9XWp3+Zn5vm6LkItuNW/+ekdijqMi/EFkojO67Hs9k0Pb5fpRNejnzntg9cFF9ZNZIuj5Hcw+NNKNkc6kEDxjF5hZr/TKhVzBZCjpz0Gvfoex1cb62gH1grcSHypv0JOe8snxmLMMb+O2wfuNkyoBbU/CfFnGnL0xe/zxvBVuw== X-Virus-Scanned: Proofpoint Essentials engine Received: from CH4PR04CU002.outbound.protection.outlook.com (mail-northcentralusazon11023083.outbound.protection.outlook.com [40.107.201.83]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mx1-us1.ppe-hosted.com (PPE Hosted ESMTP Server) with ESMTPS id F014924006A; Sat, 15 Aug 2026 22:19:26 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=irQXWGn2GM21Ll26UrYnvZnQXKWAZc3AK/zRHvZYvUwktE/khh6HwxywZ/ZnC6Lys+4DFBANaijXOo1iQr4CPOf/RfpLFwqcUjxXZ8ttkWz5+I8K2oNwq8Sb48drEYZ/QvoSX2a5l8W+cUcu/iX2m4EtRF1cGqPjJwE01Sjv3Fx86jHL9h28+2lAZFLcQIIhuqPURoTIu231kKnDZtPa2KTKJrgJ+S79UxOoo2wTlHd0/sUHPj8bGji8/VvVAPJeypaqvKctgJetFv0EHgDosTJ0856aif2pnf2sLszxLAi7dnURlU1zzHFrwBFx6rbo//ejJITqwkWepHDVZPUHUA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=wHYY0363Hx6pLLChIeHboN5zQ6j8Z2gNh39rUis6GaM=; b=ubieuc3XB0Wm1LPuH41Mw8cmFYladSjUrHoxzjgKz5oK9EoxgVHXlXQo1oHzK40FMAiDtfioOjyCS6caAgCXed2nKMsHhLr4GSrIqn2AOJGzhEbrJEFHiVbFG00Tj1vStPWX76w+fEmONEGEwLgfinoilBT+b67yzneJNR5bQowkduA6QVzSp4l/dXZn0ToKRXRPjRgfjxUHPPEG+VCdId/iWEesb/2hGmll6B5kLalhwlaJSVHQcuKhDv2eHcf3BxQ6FbmUX7SI2BPWnRCbmdtA2E7HWCGUHk3BlL7TsiBtBEDU6uwgPwq2YcqoBl+nn0zA75FC1WaOhe+s7Y28Vg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=sitime.com; dmarc=pass action=none header.from=sitime.com; dkim=pass header.d=sitime.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Sitime.onmicrosoft.com; s=selector1-Sitime-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=wHYY0363Hx6pLLChIeHboN5zQ6j8Z2gNh39rUis6GaM=; b=xVRgRZooaP7TU8I4qAIGVYv9F2c8//47PGUCMvuz7jjoayvqIJYgeZVzxd/9fy8qU5jQBIeKUzfvPoKFgLyrVd56TOW+bnOb5DYuiP4ke+Dzr0sRLeuQL0TfFCwIu+XVj/MpbkwgF6BfxTEXKX5AwMrCTYxdoG86cuZC7+1rJ8e+j56ImSg1CM42t/MAKRrI2Zxq/KuNe8VRy7tcAPGcr11XrOulDS+J7Laopa/bp5Int59UdoHJvoqWduKsxlgN9aJqCQz6+16B/clxz6zaT0cEj8jsDJo9USkpZwGG77hKWR4NnZaMBdG876VATVaTnkN8Sm2Zs6PUl1UStjFaEg== Received: from LVWPR20MB994915.namprd20.prod.outlook.com (2603:10b6:408:3bf::16) by SA1PR20MB5128.namprd20.prod.outlook.com (2603:10b6:806:254::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.315.13; Sat, 15 Aug 2026 22:19:23 +0000 Received: from LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01]) by LVWPR20MB994915.namprd20.prod.outlook.com ([fe80::9551:3864:128b:8c01%5]) with mapi id 15.21.0315.014; Sat, 15 Aug 2026 22:19:23 +0000 From: Ali Rouhi To: "jiri@resnulli.us" CC: "vadim.fedorenko@linux.dev" , "arkadiusz.kubalewski@intel.com" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" , "cjubran@nvidia.com" , "Oleg.Zadorozhnyi@devoxsoftware.com" , "devicetree@vger.kernel.org" , "netdev@vger.kernel.org" , "linux-kernel@vger.kernel.org" , Ali Rouhi Subject: [PATCH net-next v7 3/3] dpll: add SiTime SiT9531x DPLL clock driver Thread-Topic: [PATCH net-next v7 3/3] dpll: add SiTime SiT9531x DPLL clock driver Thread-Index: AQHdLQQfSg/FkjsHkkS39O51qCIspQ== Date: Sat, 15 Aug 2026 22:19:23 +0000 Message-ID: <20260815221919.64226-4-arouhi@sitime.com> References: <20260815221919.64226-1-arouhi@sitime.com> In-Reply-To: <20260815221919.64226-1-arouhi@sitime.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=sitime.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: LVWPR20MB994915:EE_|SA1PR20MB5128:EE_ x-ms-office365-filtering-correlation-id: 4399c2b9-f872-419f-db82-08defb1b4277 x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|23010399003|1800799024|366016|376014|38070700021|56012099006|10067099003|22082099003|5023799004|18002099003|6133799003|3023799007; x-microsoft-antispam-message-info: LhNQnCHFlH9asKzqARVDrOUUj3MF1y+yGhNwCoYhIorZDNf52xlAqWbPP3DoMqRKm0ZvxyLHd/y7DPWfoPFGOn+VcEqcE9nn8lgC+dsv3bg9Euv4pRIonbAvjhfEa/ccYi4pse7yDwRjxnX1cqRnDAeJpbM3aHK+QTu6X8U1hPfbILNyY4naWERx3oSDn0r9oRcasu29GYNNi881YZRTb2QG7dcg/T3B0lExK/0NJn/Pj9Fvyn6tJ/9Ht1aPVq9tbPn43ItPHcOQMKG8TkIbnTdFUiLQTDsMMfAcZewBCRo1sqe14pM9QuVCMpYeg1JKnLI0tEtgTniPy2NabOYs6Z+kyW6byUW+HSVYi1mwc9zjb9NOSkbuAVv3xiQ5Ty98LWWsT64U/aPQteQvpgaSXNrFsKiBYtYNOgFt6Rqi2Kb1Xfs3er/LS5YRA/zaFWNWOWGhquSop+Fb01x3v1AE/Yg12cMOOUQ9GEBn4CH09cEJ8iyZSCSePUVsX05LZlI4YsumKlZSyfUPcgg3zHR9GTZ31FILQYk6TJJU39EOqdueYWVbZU1JF5mMO2WkcCzoSPiux4tgQ+zI8zng1qvGmugZmrhnd2idRhhA8rs7p/PFFRdVJh6RZdzPqVOPx1r1HugBj6Ry8gVH+gvgq9kSgS+hoe89cg4qPYl06mlvTx8= x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:LVWPR20MB994915.namprd20.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(1800799024)(366016)(376014)(38070700021)(56012099006)(10067099003)(22082099003)(5023799004)(18002099003)(6133799003)(3023799007);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?utf-8?B?SUlSeGExUHNuTHNXM25iamY4REtFUDVxeWpZQVZKNkZ3ay9MY3EzWnpFMWtj?= =?utf-8?B?VC9jMlhFRE9zUkNyaTgrTXZSMytIeVh0WGxKTEpQcE9ReDU0amtoNnJMZW1l?= =?utf-8?B?bW5sRTZ3WHZQVjJ5TnRTVWFTMHVHWnBaM1NoNDVDY3lQVzZ5ZEZyUHgrK1lG?= =?utf-8?B?QS9hT0p4MlIzaG5KckVzY1ZhZUJqemJ3eEZZV29TWHFmYklqWGZGTnBLaVpo?= =?utf-8?B?amdldTUzbEFZY2tGSjBOV3RjVlA0VzJ0UFo2TnhxRHBQSmJWbUwxaTA1MDZX?= =?utf-8?B?NkZSOVVKZ0xteC9MaGRucXV1VFpzNWs1WFJ6T0xUT0tlVlFmMmVXeThERnpa?= =?utf-8?B?cE9sUmJDZFVFZ1dXR3VNb2hOODZ3Q3J1MUVya2ZlOHRTOWFjbFE5S210bS8v?= =?utf-8?B?bExjMEQxVnMra29GdjJWWmZlcUtTSkhuRm90bGsyNzUvV1ZFZ215eHhSamFM?= =?utf-8?B?SGZWcDRUWlEyNFZqUlh1Si96Rkw0MTg5SzFYalNLK3pxVkJxMmVOZGgzTTFM?= =?utf-8?B?cHlzM3VFUEdLYy80Z3pScThFMVNzRzNvcmFuZVRaNTRYbHo0NzhVTW1vZUxk?= =?utf-8?B?VDkwZkgzUkNvTzdXRmI1ZWYrejhBTHNvaHhjaHBtYllWSEpSek5ocFpERi9n?= =?utf-8?B?V0NoMHZxM0hSb3BMZnorNXpuK3FVRTlsNXJlOGpWamFWb0prN0ZFazhOcHE4?= =?utf-8?B?elpONFY0LzRDZTZYT2Q0S1NNMlQxVm42Q3FnSE42aTNSYW1yNEgxeTV4V21O?= =?utf-8?B?TWhNdzRZejVLZFlESWZiN2lQTXBPSHpMSStJbHhZUUh5R1p0TExINU9LSnph?= =?utf-8?B?NXg5Nmh5OWQram9jYTgzd0lyYUpYd3VtVXUzNm1sM2ZKaGhkemo0WkhXaXBD?= =?utf-8?B?SEtLRjVQdzB2L2JUV2kwcnhvN0xieVBMTWdDSGJMcGdoYnIwSHV3TVhLckl4?= =?utf-8?B?cEppZHhRSzV3V09RTHNjYUJ4UDd5S0tZSFV0Y1o5azZtcjFFRmRJNjc0M0xT?= =?utf-8?B?ZmZaOXhEVnNQdUVxak9BdzY4VHBRWW8wQmlzMVVTZXRMVHlRblBsSHlaOWFr?= =?utf-8?B?THBQaDBaMzJ5bEJ0S2xzZ09IMmZITlVxUHJBSjhjSWtIcGZtZFJ5U2l4VGNX?= =?utf-8?B?eXBPRGc0bG01bm5ZczhobzRNWURFbThpNUhiN2tyOThiZk9yYU1rajJzc3gv?= =?utf-8?B?UU05OVV2NCtnOFdtcGtDa0RZUGtPcVdpY1pKclMzbEhDa0N5WUN2dnVrd0lP?= =?utf-8?B?d01Cam5WY1djTnZOYWZzMm9Zd052QW5wMkV0cFRkZTcwbHppRncweUlIbVVV?= =?utf-8?B?S3FqT0gyZmVYc3M5SDhidXlnWmJhclQyZFZ1Mk5TOHRHRHRoSEtkM0c2QTls?= =?utf-8?B?VkpBQTV2T2piNjh2bzg3UlVyVk1HcFFqcUp6QW5EY2VhL3BBUFB6N1R1OG0x?= =?utf-8?B?a1QzbFB4RHJtbjMwZ0JMOS9BZGoyYmNxcXRNRjZVcUN2SG1qVUk1VFU2R21y?= =?utf-8?B?V0ZqNDFaZ0VycFpUMGFzeWxyNmlMYnF1VEs1WmVWK0FWczE2U1JRTWJ3SXdW?= =?utf-8?B?ck13T0lJNzM4NmlNWHJCQzFsWWhlNlMvMk41Rm9ET0htelkwdFRpVTFnb3R0?= =?utf-8?B?aDQ3VUd5YlJvZzVNaWZTeFZXNCtkckMzdmR5SmRJMjJib09kUTFNVlhNM2Fs?= =?utf-8?B?Q0pUV0d2cDY3WDJEeFJ0QnJqR0Vnbm9ycmgycUFuOXJ0Q2R6cTJ3bEJoeGh5?= =?utf-8?B?QXVWckJkaU1xMmF4azN0Zno1ckV6UVhNL1MvaFVRVTFXaldtYVkybjRXT3k3?= =?utf-8?B?MHNLTzhibzU4bmpKcTVLZlRxR0d1K1ZoSE5QTEQvbEdxOVBxUUQ3STg3Rlh5?= =?utf-8?B?SGcvVGRIMU44aGlDenZ0d0pLelAvSU0wTm4rTHA5NzAvN0VXelA1RGoyaXJT?= =?utf-8?B?T1c2cG1oNUtaUGJkWjRHNGd5L0FadWF0KzlFMVlxa1F5emdEaGZ1UW8vSHND?= =?utf-8?B?d2hUWWdOOGxOUFJ4RFhLbFNqcUpJZVJoam02MHJXUVFQVGpHQ1l5SVlnR3JR?= =?utf-8?B?L2wxdjhzbElNcnA1Q1QrcUlxdHREcXo1azhiSVpVUjRTWFZQSWM0aStyZ01J?= =?utf-8?B?U2VFWGZoWVJZOUZtbStLYUwvYmIwN3YrTWlqbHU1b09nelZCQ0F0cUJ1OE9J?= =?utf-8?B?MWJHR2REOFAwQnhsQTkxZmhXU3BJSzgvUjlzL2M3YTJSMFBjenp2ZHpvT0Jm?= =?utf-8?B?V0VNbWM2ZCtsMDZBbTRRT1JRdzlYUG9BK0ZCemY1YUZSeWliNE9obG1BakM3?= =?utf-8?Q?zSklTg9J+1gL6m0N2N?= Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Exchange-RoutingPolicyChecked: besz+oI0ZICqZAXulslcKtrztNpv9/WW+vyLlQShdHbJRzn3uYoow7UjVHCKkdWRhn7P/08vHwgMQy7b2JfoEA0zJMI+FXWD8hWj1KrQzTKp1tccP5jtNkI5h9JjjQeMukfKiMYc6/do1hg/SXi5NOH4EqaOGUDH4kc8KCl+EzWJhmcLL1TZCVpFqtkdIiGPj1uH9Nm6Ub3u48UcyPsAmnjnprzviGnSXr5r2weYPoiAN5T/p7dMyz9sag5KVT592wtQZ4EYBsmD305BxdmvxwCitCNkbJvwoGqD2Qnp6dX8ZUvuaa8RW1SRwr9DTbc4T2oKbQ4yLAXbLmhmWoT7sw== X-OriginatorOrg: sitime.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: LVWPR20MB994915.namprd20.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: 4399c2b9-f872-419f-db82-08defb1b4277 X-MS-Exchange-CrossTenant-originalarrivaltime: 15 Aug 2026 22:19:23.0456 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 8fb55916-cf10-4b0d-96f4-cf3952657263 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: tiKnByR2hhlVbLV3YzZBaMLug8vkwrum5UP+Cm6JDrBvTj/ltniWZZAT4wTTqEVIkllH4HjRI6Z3+VNrxFPI+A== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR20MB5128 X-MDID: 1786832367-IHe8ML9zEidw X-PPE-STACK: {"stack":"us1"} X-MDID-O: us1;ut7;1786832367;IHe8ML9zEidw;;ee4c095510f4a2bc549243be2f087a38 X-PPE-TRUSTED: V=1;DIR=OUT; Add a DPLL subsystem driver for the SiTime SiT95316 and SiT95317 clock generators. These devices provide low-jitter clock outputs commonly used in telecom, networking, and data center timing applications. The driver exposes all inputs and outputs through the Linux DPLL subsystem, supporting: - Lock status monitoring via register polling or optional INTRB IRQ - Input priority management for automatic reference switchover - Per-output frequency readback from hardware state - Phase offset measurement via TDC (time-to-digital converter) - Phase adjustment for fine output alignment - Embedded sync (esync) pulse control on outputs - Fractional frequency offset of the selected reference - Optional reset-gpios for hardware reset The driver reads all configuration from the device's on-chip NVM at probe time -- no firmware loading is required. Co-developed-by: Oleg Zadorozhnyi Signed-off-by: Oleg Zadorozhnyi Assisted-by: Claude:claude-4-opus [chat] Signed-off-by: Ali Rouhi --- MAINTAINERS | 7 + drivers/dpll/Kconfig | 1 + drivers/dpll/Makefile | 1 + drivers/dpll/sit9531x/Kconfig | 17 + drivers/dpll/sit9531x/Makefile | 4 + drivers/dpll/sit9531x/core.c | 3111 ++++++++++++++++++++++++++++++++ drivers/dpll/sit9531x/core.h | 372 ++++ drivers/dpll/sit9531x/dpll.c | 1232 +++++++++++++ drivers/dpll/sit9531x/dpll.h | 69 + drivers/dpll/sit9531x/prop.c | 397 ++++ drivers/dpll/sit9531x/prop.h | 39 + drivers/dpll/sit9531x/regs.h | 371 ++++ 12 files changed, 5621 insertions(+) create mode 100644 drivers/dpll/sit9531x/Kconfig create mode 100644 drivers/dpll/sit9531x/Makefile create mode 100644 drivers/dpll/sit9531x/core.c create mode 100644 drivers/dpll/sit9531x/core.h create mode 100644 drivers/dpll/sit9531x/dpll.c create mode 100644 drivers/dpll/sit9531x/dpll.h create mode 100644 drivers/dpll/sit9531x/prop.c create mode 100644 drivers/dpll/sit9531x/prop.h create mode 100644 drivers/dpll/sit9531x/regs.h diff --git a/MAINTAINERS b/MAINTAINERS index a82b6ec9c567..8dfab3ac6a17 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24905,6 +24905,13 @@ S: Maintained W: http://www.winischhofer.at/linuxsisusbvga.shtml F: drivers/usb/misc/sisusbvga/ =20 +SITIME SIT9531X DPLL DRIVER +M: Ali Rouhi +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml +F: drivers/dpll/sit9531x/ + SL28 CPLD MFD DRIVER M: Michael Walle S: Maintained diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig index be98969f040a..ea77b9c11ab1 100644 --- a/drivers/dpll/Kconfig +++ b/drivers/dpll/Kconfig @@ -23,6 +23,7 @@ config DPLL_REFCNT_TRACKER =20 If unsure, say N. =20 +source "drivers/dpll/sit9531x/Kconfig" source "drivers/dpll/zl3073x/Kconfig" =20 endmenu diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile index 9e7a3a3e592e..4adc50d748d4 100644 --- a/drivers/dpll/Makefile +++ b/drivers/dpll/Makefile @@ -8,4 +8,5 @@ dpll-y +=3D dpll_core.o dpll-y +=3D dpll_netlink.o dpll-y +=3D dpll_nl.o =20 +obj-$(CONFIG_SIT9531X_DPLL) +=3D sit9531x/ obj-$(CONFIG_ZL3073X) +=3D zl3073x/ diff --git a/drivers/dpll/sit9531x/Kconfig b/drivers/dpll/sit9531x/Kconfig new file mode 100644 index 000000000000..47aea8674327 --- /dev/null +++ b/drivers/dpll/sit9531x/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config SIT9531X_DPLL + tristate "SiTime SiT9531x DPLL driver" + depends on I2C && NET + select DPLL + select REGMAP_I2C + help + Driver for SiTime SiT9531x family clock generators + (SiT95317, SiT95316). + + This driver registers each on-chip PLL as a DPLL device + and exposes input/output clocks as DPLL pins, providing + runtime configuration via Generic Netlink. + + To compile this driver as a module, choose M here: the + module will be called sit9531x. diff --git a/drivers/dpll/sit9531x/Makefile b/drivers/dpll/sit9531x/Makefile new file mode 100644 index 000000000000..b97d2656a460 --- /dev/null +++ b/drivers/dpll/sit9531x/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SIT9531X_DPLL) +=3D sit9531x.o +sit9531x-y :=3D core.o dpll.o prop.o diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c new file mode 100644 index 000000000000..cd283922e7ff --- /dev/null +++ b/drivers/dpll/sit9531x/core.c @@ -0,0 +1,3111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiTime SiT9531x DPLL core driver + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + * + * Core I2C probe, regmap configuration, hardware state management, + * and periodic work thread. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "dpll.h" +#include "prop.h" +#include "regs.h" + +/* Number of input + output pin positions for pin index allocation */ +#define SIT9531X_NUM_INPUT_PINS (SIT9531X_MAX_INPUTS + 2) /* +xtal +INTSY= NC */ +#define SIT9531X_NUM_OUTPUT_PINS (SIT9531X_MAX_OUTPUTS + 1) /* +INTSYNC sr= c */ +#define SIT9531X_NUM_PINS_TOTAL (SIT9531X_NUM_INPUT_PINS + SIT9531X_NUM_O= UTPUT_PINS) + +#define SIT9531X_CHIP(_id, _nin, _nout, _name, _map) \ + { .id =3D (_id), .num_inputs =3D (_nin), .num_outputs =3D (_nout), \ + .name =3D (_name), .clkout_map =3D (_map) } + +/* Per-variant output index -> physical slot mapping */ +static const u8 clkout_map_95317[] =3D {0, 3, 4, 5, 7, 8, 9, 11}; +static const u8 clkout_map_95316[] =3D {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, = 11}; + +static const struct sit9531x_chip_info sit9531x_chip_ids[] =3D { + SIT9531X_CHIP(SIT9531X_VARIANT_ID_95317, 8, 8, "SiT95317", clkout_map_9= 5317), + SIT9531X_CHIP(SIT9531X_VARIANT_ID_95316, 8, 12, "SiT95316", clkout_map_9= 5316), +}; + +#define SIT9531X_RANGE_OFFSET SIT9531X_PAGE_SIZE + +static const struct regmap_range_cfg sit9531x_regmap_range =3D { + .range_min =3D SIT9531X_RANGE_OFFSET, + .range_max =3D SIT9531X_RANGE_OFFSET + + (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1, + .selector_reg =3D SIT9531X_PAGE_SEL, + .selector_mask =3D GENMASK(7, 0), + .selector_shift =3D 0, + .window_start =3D 0, + .window_len =3D SIT9531X_PAGE_SIZE, +}; + +const struct regmap_config sit9531x_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D SIT9531X_RANGE_OFFSET + + (SIT9531X_NUM_PAGES * SIT9531X_PAGE_SIZE) - 1, + .ranges =3D &sit9531x_regmap_range, + .num_ranges =3D 1, + .cache_type =3D REGCACHE_NONE, +}; + +/* + * sit9531x_read_u8 - read an 8-bit register + * @reg: register in SIT9531X_REG(page, offset) form + * @val: output value + */ +int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg, + u8 *val) +{ + unsigned int tmp; + int rc; + + reg =3D (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) + + SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET; + + rc =3D regmap_read(sitdev->regmap, reg, &tmp); + if (rc) + dev_err(sitdev->dev, "Failed to read reg 0x%04x: %d\n", + reg, rc); + else + *val =3D (u8)tmp; + + return rc; +} + +/* + * sit9531x_write_u8 - write an 8-bit register + * @reg: register in SIT9531X_REG(page, offset) form + * @val: value to write + */ +int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg, + u8 val) +{ + int rc; + + reg =3D (SIT9531X_REG_PAGE(reg) * SIT9531X_PAGE_SIZE) + + SIT9531X_REG_OFFSET(reg) + SIT9531X_RANGE_OFFSET; + + rc =3D regmap_write(sitdev->regmap, reg, val); + if (rc) + dev_err(sitdev->dev, "Failed to write reg 0x%04x: %d\n", + reg, rc); + + return rc; +} + +/* + * sit9531x_read_pll_u8 - read a register on a PLL page + * @val: output value + */ +int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 *val) +{ + return sit9531x_read_u8(sitdev, + SIT9531X_REG(sit9531x_pll_page(pll_idx), offset), + val); +} + +/* + * sit9531x_write_pll_u8 - write a register on a PLL page + * @val: value to write + */ +int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 val) +{ + return sit9531x_write_u8(sitdev, + SIT9531X_REG(sit9531x_pll_page(pll_idx), offset), + val); +} + +/* + * sit9531x_update_pll_u8 - read-modify-write a register on a PLL page + * @mask: bits to modify + * @val: new value for masked bits + */ +int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 mask, u8 val) +{ + unsigned int reg; + + reg =3D (sit9531x_pll_page(pll_idx) * SIT9531X_PAGE_SIZE) + + offset + SIT9531X_RANGE_OFFSET; + + return regmap_update_bits(sitdev->regmap, reg, mask, val); +} + +/* + * sit9531x_input_get_regs - get force mask and state register addresses + * @index: logical input index + * @force_reg: output force mask register address + * @state_reg: output state register address + * + * Selects the correct Page 0x02 register pair based on the pair's + * signal mode and the lane (P/N) the index refers to. + */ +static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev, + u8 index, + unsigned int *force_reg, + unsigned int *state_reg) +{ + if (sitdev->ref[index].sig_mode =3D=3D SIT9531X_MODE_DE) { + *force_reg =3D SIT9531X_REG_IN_DE_FORCE; + *state_reg =3D SIT9531X_REG_IN_DE_STATE; + } else if (sit9531x_input_is_n(index)) { + *force_reg =3D SIT9531X_REG_IN_SEN_FORCE; + *state_reg =3D SIT9531X_REG_IN_SEN_STATE; + } else { + *force_reg =3D SIT9531X_REG_IN_SEP_FORCE; + *state_reg =3D SIT9531X_REG_IN_SEP_STATE; + } +} + +/* + * sit9531x_input_disable - disable an input reference + * @index: logical input index (0-N) + * + * Sets the force mask bit and clears the state bit for the given + * input, effectively disabling it. Register selection depends on + * the pair's signal mode (SE/DE) and the lane (P/N); the bit within + * each register addresses the input pair. + */ +int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index) +{ + struct sit9531x_ref *ref =3D &sitdev->ref[index]; + unsigned int force_reg, state_reg; + u8 pair =3D sit9531x_input_pair(index); + u8 val; + int rc; + + sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg); + + rc =3D sit9531x_read_u8(sitdev, force_reg, &val); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, force_reg, val | BIT(pair)); + if (rc) + return rc; + + rc =3D sit9531x_read_u8(sitdev, state_reg, &val); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, state_reg, val & ~BIT(pair)); + if (rc) + return rc; + + ref->enabled =3D false; + + return 0; +} + +/* + * sit9531x_input_enable - enable an input reference + * @index: logical input index (0-N) + * + * Clears the force mask bit for the given input, returning it to + * hardware default (enabled). + */ +int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index) +{ + struct sit9531x_ref *ref =3D &sitdev->ref[index]; + unsigned int force_reg, state_reg; + u8 pair =3D sit9531x_input_pair(index); + u8 val; + int rc; + + sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg); + + rc =3D sit9531x_read_u8(sitdev, force_reg, &val); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, force_reg, val & ~BIT(pair)); + if (rc) + return rc; + + ref->enabled =3D true; + + return 0; +} + +/* + * Output enable / disable (Hi-Z control) + * + * SiT9531x outputs can be configured as differential (DIFF) or + * single-ended (SE) depending on the factory blob. Each output slot + * has TWO Hi-Z force/state register pairs on Page 0x03 -- one for the + * DIFF path, one for the SE path. + * + * We write to BOTH pairs so the function mutes the output regardless + * of whether it's been configured DIFF or SE on this board. + * + * slot 0-7 : + * DIFF mask=3D0xF2 state=3D0xF3 SE mask=3D0xF8 state=3D0xF9 + * slot 8-11: + * DIFF mask=3D0xF4 state=3D0xF5 SE mask=3D0xFA state=3D0xFB + * + * MASK bit =3D 1 -> driver takes control of that output's Hi-Z state + * STATE bit =3D 0 -> output is forced to Hi-Z (muted) + * STATE bit =3D 1 -> output is driven (active) + * + * The output "index" in the driver is logical; the physical slot comes + * from info->clkout_map[]. + */ + +struct sit9531x_hiz_regs { + unsigned int diff_mask; + unsigned int diff_state; + unsigned int se_mask; + unsigned int se_state; + u8 bit; +}; + +static void sit9531x_output_get_hiz_regs(u8 slot, + struct sit9531x_hiz_regs *r) +{ + if (slot <=3D 7) { + r->diff_mask =3D SIT9531X_REG_HIZ_DIFF_07_MASK; + r->diff_state =3D SIT9531X_REG_HIZ_DIFF_07_STATE; + r->se_mask =3D SIT9531X_REG_HIZ_SE_07_MASK; + r->se_state =3D SIT9531X_REG_HIZ_SE_07_STATE; + r->bit =3D slot; + } else { + r->diff_mask =3D SIT9531X_REG_HIZ_DIFF_811_MASK; + r->diff_state =3D SIT9531X_REG_HIZ_DIFF_811_STATE; + r->se_mask =3D SIT9531X_REG_HIZ_SE_811_MASK; + r->se_state =3D SIT9531X_REG_HIZ_SE_811_STATE; + r->bit =3D slot - 8; + } +} + +/* + * Report whether a slot is currently forced into Hi-Z, i.e. the driver + * (or the blob) took control of the Hi-Z state (MASK bit set) and drives + * it low (STATE bit clear). Either register pair muting the slot counts, + * mirroring what sit9531x_output_disable() programs. + */ +static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev, u8 slot, + bool *muted) +{ + struct sit9531x_hiz_regs r; + u8 mask, state; + int rc; + + sit9531x_output_get_hiz_regs(slot, &r); + + rc =3D sit9531x_read_u8(sitdev, r.diff_mask, &mask); + if (rc) + return rc; + rc =3D sit9531x_read_u8(sitdev, r.diff_state, &state); + if (rc) + return rc; + + *muted =3D (mask & BIT(r.bit)) && !(state & BIT(r.bit)); + if (*muted) + return 0; + + rc =3D sit9531x_read_u8(sitdev, r.se_mask, &mask); + if (rc) + return rc; + rc =3D sit9531x_read_u8(sitdev, r.se_state, &state); + if (rc) + return rc; + + *muted =3D (mask & BIT(r.bit)) && !(state & BIT(r.bit)); + + return 0; +} + +static int sit9531x_hiz_set_bit(struct sit9531x_dev *sitdev, + unsigned int reg, u8 bit, bool set) +{ + u8 cur, new_val; + int rc; + + rc =3D sit9531x_read_u8(sitdev, reg, &cur); + if (rc) + return rc; + + new_val =3D set ? (cur | BIT(bit)) : (cur & ~BIT(bit)); + + return sit9531x_write_u8(sitdev, reg, new_val); +} + +/* + * Enter the output-system programming state: unlock the debug + * registers on Page 3 and issue the PRG_CMD state command. Register + * writes that reconfigure the output system only take effect when + * they are made inside this state. + */ +static int sit9531x_prg_enter(struct sit9531x_dev *sitdev) +{ + int rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG, + SIT9531X_DEBUG_UNLOCK_VAL); + if (rc) + return rc; + + return sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, + SIT9531X_PRG_CMD_STATE); +} + +/* + * Commit a programming sequence started by sit9531x_prg_enter(): + * update the NVM shadow and re-lock the loops. The sleep gives the + * hardware its required settling time after the loop-lock command; + * it is intentional despite the caller holding multiop_lock, as the + * whole NVM + lock sequence must be atomic. + */ +static int sit9531x_prg_commit(struct sit9531x_dev *sitdev) +{ + int rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, + SIT9531X_UPDATE_NVM); + if (rc) + return rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, + SIT9531X_LOOP_LOCK); + if (rc) + return rc; + + msleep(100); + + return 0; +} + +/* + * sit9531x_output_disable - mute an output (force Hi-Z) + * @index: logical output index (0..info->num_outputs-1) + * + * Sets MASK+STATE on BOTH the DIFF and SE register pairs so that the + * output is muted regardless of its electrical configuration. The + * writes are wrapped in the PRG_CMD / NVM update / loop lock sequence + * so the new state is applied by the hardware. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + struct sit9531x_hiz_regs r; + u8 slot; + int rc, ret; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (index >=3D info->num_outputs) + return -EINVAL; + + slot =3D info->clkout_map[index]; + sit9531x_output_get_hiz_regs(slot, &r); + + rc =3D sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + /* Take control (MASK=3D1) and mute (STATE=3D0) on both DIFF and SE */ + rc =3D sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, true); + if (rc) + goto commit; + rc =3D sit9531x_hiz_set_bit(sitdev, r.diff_state, r.bit, false); + if (rc) + goto commit; + rc =3D sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, true); + if (rc) + goto commit; + rc =3D sit9531x_hiz_set_bit(sitdev, r.se_state, r.bit, false); + +commit: + /* + * Always leave the PRG_CMD programming state, even on a mid-sequence + * write failure: prg_enter() unlocked the output loops, so returning + * without prg_commit() would strand the chip in the programming state + * with the loops unlocked. Best effort -- keep the first error. + */ + ret =3D sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc =3D ret; + if (!rc) + sitdev->out[index].enabled =3D false; + + return rc; +} + +/* + * sit9531x_output_enable - un-mute an output (active state) + * @index: logical output index (0..info->num_outputs-1) + * + * Releases MASK on BOTH register pairs so the output returns to + * whatever the initial_config blob programmed. The writes are wrapped + * in the PRG_CMD / NVM update / loop lock sequence so the new state is + * applied by the hardware. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + struct sit9531x_hiz_regs r; + u8 slot; + int rc, ret; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (index >=3D info->num_outputs) + return -EINVAL; + + slot =3D info->clkout_map[index]; + sit9531x_output_get_hiz_regs(slot, &r); + + rc =3D sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + rc =3D sit9531x_hiz_set_bit(sitdev, r.diff_mask, r.bit, false); + if (rc) + goto commit; + rc =3D sit9531x_hiz_set_bit(sitdev, r.se_mask, r.bit, false); + +commit: + /* + * Always leave the PRG_CMD programming state, even on a mid-sequence + * write failure: prg_enter() unlocked the output loops, so returning + * without prg_commit() would strand the chip in the programming state + * with the loops unlocked. Best effort -- keep the first error. + */ + ret =3D sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc =3D ret; + if (!rc) + sitdev->out[index].enabled =3D true; + + return rc; +} + +/* + * Input priority selection + * + * The SiT9531x has a 12-slot priority table per PLL on Page 1. Each + * register holds two slots nibble-packed (even slot in [3:0], odd slot + * in [7:4]). + * + * The procedure: + * 1. Force PLL into holdover (PLL page reg 0x6F bit 4) + * 2. Write priority slots on Page 1 + * 3. Small change update (Page 0 reg 0x0F bit 1) + * 4. Release holdover + * + * Caller must hold sitdev->multiop_lock. + */ + +/* Page-1 register holding priority slot @slot of @pll_idx. */ +static u16 sit9531x_prio_reg(u8 pll_idx, u8 slot) +{ + return SIT9531X_REG(SIT9531X_PAGE_PRIOSYS, + SIT9531X_PRIO_BASE_REG + + SIT9531X_PRIO_REGS_PER_PLL * pll_idx + + slot / SIT9531X_PRIO_SLOTS_PER_REG); +} + +/* + * Extract priority slot @slot from its register value. The register + * holding slots 2n and 2n+1 keeps the earlier slot in the high nibble + * (CLK_SPARE<2n>SEL) and the later one in the low nibble. + */ +static u8 sit9531x_prio_slot_get(u8 val, u8 slot) +{ + if (slot & 1) + return val & SIT9531X_PRIO_NIBBLE_MASK; + + return val >> SIT9531X_PRIO_HI_SHIFT; +} + +/* Place source @src in priority slot @slot of a register value. */ +static u8 sit9531x_prio_slot_set(u8 val, u8 slot, u8 src) +{ + if (slot & 1) + return (val & (SIT9531X_PRIO_NIBBLE_MASK << + SIT9531X_PRIO_HI_SHIFT)) | + (src & SIT9531X_PRIO_NIBBLE_MASK); + + return (val & SIT9531X_PRIO_NIBBLE_MASK) | + ((src & SIT9531X_PRIO_NIBBLE_MASK) << + SIT9531X_PRIO_HI_SHIFT); +} + +/* + * Commit a priority-table programming sequence through the Page-0 + * programming directive register. + * + * A small change update is all the table needs. The NVM-bank and + * loop-lock directives that the output system issues do not belong + * here: the former programs non-volatile storage from the efuse and + * the latter only means anything after an escape to the PRG_CMD + * state. This matches the vendor input_priority_sel() procedure. + */ +static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev) +{ + int rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + usleep_range(1000, 2000); + + return 0; +} + +/* + * sit9531x_input_prio_get - read an input's priority slot for a PLL + * @input_idx: input source in hardware encoding (see + * sit9531x_input_hw_src()) + * @prio: output slot position (0 =3D highest); set to + * SIT9531X_PRIO_MAX_SLOTS when the source is not in the table + * + * Scans the PLL's 12-slot priority table on Page 1 and returns the + * highest-priority (lowest-numbered) slot that references the source. + * This reads the value the chip actually holds rather than a cached + * default, so pin-get reflects the real hardware priority. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx, u8 *prio) +{ + u8 val, slot, src; + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) { + rc =3D sit9531x_read_u8(sitdev, + sit9531x_prio_reg(pll_idx, slot), &val); + if (rc) + return rc; + + src =3D sit9531x_prio_slot_get(val, slot); + + if (src =3D=3D input_idx) { + *prio =3D slot; + return 0; + } + } + + *prio =3D SIT9531X_PRIO_MAX_SLOTS; + return 0; +} + +/* + * sit9531x_prio_table_commit - write a full priority table for a PLL + * @srcs: array of SIT9531X_PRIO_MAX_SLOTS source codes, slot 0 first + * + * Programs all priority slots (nibble-packed, two per register) for + * the PLL using the same holdover / small-update sequence as + * sit9531x_input_prio_set(). Caller must hold sitdev->multiop_lock. + */ +static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_= idx, + const u8 *srcs) +{ + u8 val, slot; + int rc, prg_rc, ho_rc; + u16 reg; + + rc =3D sit9531x_update_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL, + BIT(SIT9531X_PLL_HO_FORCE_BIT), + BIT(SIT9531X_PLL_HO_FORCE_BIT)); + if (rc) + return rc; + + usleep_range(10000, 12000); + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) { + reg =3D sit9531x_prio_reg(pll_idx, slot); + + rc =3D sit9531x_read_u8(sitdev, reg, &val); + if (rc) + goto commit; + + val =3D sit9531x_prio_slot_set(val, slot, srcs[slot]); + + rc =3D sit9531x_write_u8(sitdev, reg, val); + if (rc) + goto commit; + } + +commit: + /* Latch unconditionally, as in sit9531x_input_prio_set(). */ + prg_rc =3D sit9531x_prio_prg_commit(sitdev); + if (prg_rc && !rc) + rc =3D prg_rc; + + ho_rc =3D sit9531x_update_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_HO_CTRL, + BIT(SIT9531X_PLL_HO_FORCE_BIT), 0); + if (ho_rc && !rc) + rc =3D ho_rc; + + return rc; +} + +/* + * sit9531x_prio_table_read - read a PLL's priority-table source codes + * @srcs: output array of SIT9531X_PRIO_MAX_SLOTS source codes + * + * Caller must hold sitdev->multiop_lock. + */ +static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_id= x, + u8 *srcs) +{ + u8 val, slot; + int rc; + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) { + rc =3D sit9531x_read_u8(sitdev, + sit9531x_prio_reg(pll_idx, slot), &val); + if (rc) + return rc; + + srcs[slot] =3D sit9531x_prio_slot_get(val, slot); + } + + return 0; +} + +/* + * sit9531x_input_prio_set - move an input to a priority slot + * @input_idx: input source in hardware encoding (0-11, see + * sit9531x_input_hw_src()) + * @prio: priority slot position (0 =3D highest) + * + * Reads the PLL's table, takes the source out of wherever it sits and + * reinserts it at @prio, shifting the entries in between. The rest keep + * their relative order: a priority change asks about one input, so the + * fallbacks configured behind it have to survive it. + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, <0 on error + */ +int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx, u8 prio) +{ + u8 srcs[SIT9531X_PRIO_MAX_SLOTS]; + u8 slot, from; + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + if (input_idx >=3D SIT9531X_PRIO_NUM_SRC) + return -EINVAL; + if (prio >=3D SIT9531X_PRIO_MAX_SLOTS) + return -EINVAL; + + rc =3D sit9531x_prio_table_read(sitdev, pll_idx, srcs); + if (rc) + return rc; + + for (from =3D 0; from < SIT9531X_PRIO_MAX_SLOTS; from++) + if (srcs[from] =3D=3D input_idx) + break; + + if (from =3D=3D prio) + return 0; + + if (from > prio) { + /* + * Moving up, or not in the table at all: push the tail down + * to open the slot. For a source that is new, whatever sat + * in the last slot falls off -- the entry the chip would have + * tried last. + */ + if (from =3D=3D SIT9531X_PRIO_MAX_SLOTS) + from =3D SIT9531X_PRIO_MAX_SLOTS - 1; + for (slot =3D from; slot > prio; slot--) + srcs[slot] =3D srcs[slot - 1]; + } else { + for (slot =3D from; slot < prio; slot++) + srcs[slot] =3D srcs[slot + 1]; + } + + srcs[prio] =3D input_idx; + + return sit9531x_prio_table_commit(sitdev, pll_idx, srcs); +} + +/* + * sit9531x_input_prio_remove - drop an input from a PLL's priority table + * @input_idx: input source in hardware encoding + * + * Rewrites the priority table with the source removed: the remaining + * sources are compacted toward the highest-priority slots and the freed + * tail slots are backfilled with the lowest-priority remaining source + * (matching sit9531x_input_prio_set()). This makes a disconnected + * input ineligible for automatic reference selection, not just gated at + * the input buffer. It is a no-op when the source is absent, or when it + * is the only source (the buffer disable then drives the PLL to + * holdover and the table must not be left empty). + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx) +{ + u8 srcs[SIT9531X_PRIO_MAX_SLOTS]; + u8 kept[SIT9531X_PRIO_MAX_SLOTS]; + u8 slot, count =3D 0; + bool found =3D false; + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + rc =3D sit9531x_prio_table_read(sitdev, pll_idx, srcs); + if (rc) + return rc; + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) { + if (srcs[slot] =3D=3D input_idx) + found =3D true; + else + kept[count++] =3D srcs[slot]; + } + + if (!found || count =3D=3D 0) + return 0; + + /* Backfill freed tail slots with the lowest-priority remaining src */ + while (count < SIT9531X_PRIO_MAX_SLOTS) { + kept[count] =3D kept[count - 1]; + count++; + } + + return sit9531x_prio_table_commit(sitdev, pll_idx, kept); +} + +/* + * sit9531x_input_prio_add - make an input eligible in a PLL's table + * @input_idx: input source in hardware encoding + * + * Ensures the source appears in the priority table so it can be picked + * by automatic reference selection again after a disconnect. If the + * source is already listed the table is left untouched; otherwise it is + * placed in the lowest-priority slot. The original priority is not + * restored -- use sit9531x_input_prio_set() to reassign it. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx) +{ + u8 srcs[SIT9531X_PRIO_MAX_SLOTS]; + u8 slot; + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + rc =3D sit9531x_prio_table_read(sitdev, pll_idx, srcs); + if (rc) + return rc; + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) + if (srcs[slot] =3D=3D input_idx) + return 0; + + srcs[SIT9531X_PRIO_MAX_SLOTS - 1] =3D input_idx; + + return sit9531x_prio_table_commit(sitdev, pll_idx, srcs); +} + +/* Per-slot DIVO base register offsets (6 slots per page) */ +static const u8 clkout_odr_divn_base[] =3D { + 0x14, 0x24, 0x34, 0x44, 0x54, 0x64 +}; + +/* XO doubler register */ +#define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D) +#define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 =3D enabled */ + +/* VCO frequency bands (Hz) */ +#define SIT9531X_FVCO_LOWBAND_MIN 4915200000ULL +#define SIT9531X_FVCO_LOWBAND_MAX 5898240000ULL +#define SIT9531X_FVCO_HIGHBAND_MIN 6875000000ULL +#define SIT9531X_FVCO_HIGHBAND_MAX 7812500000ULL + +/* + * sit9531x_is_xo_doubler_enabled - check if Fref doubler is active + * + * Register 0x2D bit 7 is active-low: 0 =3D doubler enabled, 1 =3D disable= d. + * + * Return: 1 if enabled, 0 if disabled, <0 on error + */ +static int sit9531x_is_xo_doubler_enabled(struct sit9531x_dev *sitdev) +{ + u8 val; + int rc; + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_XO2_GENERIC, &val); + if (rc) + return rc; + + return (~val >> SIT9531X_XO_DOUBLER_ENB_BIT) & 1u; +} + +/* + * sit9531x_dbg_sample - latch and read a signal pathway debug sample + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @read_code: which tap of the pathway to sample + * @buf: result, least significant byte first + * @len: bytes to read, at most SIT9531X_DBG_DATA_BYTES + * + * Return: 0 on success, <0 on error + */ +static int sit9531x_dbg_sample(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 read_code, u8 *buf, unsigned int len) +{ + unsigned int i; + int rc; + u8 v; + + if (len > SIT9531X_DBG_DATA_BYTES) + return -EINVAL; + + rc =3D sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG, + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + return rc; + + rc =3D sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_READ_CODE, read_code); + if (rc) + return rc; + + /* Reading the trigger latches a fresh sample of the selected tap. */ + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_TRIGGER, &v); + if (rc) + return rc; + + for (i =3D 0; i < len; i++) { + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_0 + i, + &buf[i]); + if (rc) + return rc; + } + + return 0; +} + +/* + * DIVN as a fixed-point value: int_part plus fracn/fracd, carried with + * SIT9531X_DIVN_SCALE steps per unit. The scale keeps a whole DIVN + * well inside s64 while resolving far below the parts-per-trillion the + * frequency offset is reported in. + */ +static s64 sit9531x_divn_fixed(u32 int_part, s64 fracn, u64 fracd) +{ + s64 whole =3D (s64)int_part * SIT9531X_DIVN_SCALE; + u64 frac; + + if (!fracd) + return whole; + + frac =3D mul_u64_u64_div_u64(abs(fracn), SIT9531X_DIVN_SCALE, fracd); + + return fracn < 0 ? whole - (s64)frac : whole + (s64)frac; +} + +/* + * sit9531x_divn_static - read the configured DIVN of a PLL + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @divn: result, fixed point as per sit9531x_divn_fixed() + * + * Reads PLL page regs 0x30 (integer part), 0x32-0x35 (numerator) and + * 0x38-0x3B (denominator). The numerator is a two's complement 32-bit + * value, so DIVN can sit below the integer part, and the denominator + * register holds the divisor minus one. + * + * Return: 0 on success, <0 on error + */ +static int sit9531x_divn_static(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *divn) +{ + u32 int_part, fracn_raw =3D 0, fracd_raw =3D 0; + int rc, i; + u8 v; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DIVN_INT, &v); + if (rc) + return rc; + int_part =3D v; + + for (i =3D 3; i >=3D 0; i--) { + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DIVN_NUM + i, &v); + if (rc) + return rc; + fracn_raw =3D (fracn_raw << 8) | v; + } + + for (i =3D 3; i >=3D 0; i--) { + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DIVN_DEN + i, &v); + if (rc) + return rc; + fracd_raw =3D (fracd_raw << 8) | v; + } + + *divn =3D sit9531x_divn_fixed(int_part, (s32)fracn_raw, + (u64)fracd_raw + 1); + + return 0; +} + +/* + * sit9531x_divn_runtime - read the DIVN the digital loop is commanding + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @divn: result, fixed point as per sit9531x_divn_fixed() + * + * Same quantity as sit9531x_divn_static(), but sampled from the running + * loop rather than from the configuration registers, and carried at a + * wider precision: the numerator is 48 bits, two's complement, the + * denominator 49. The integer part shares its tap with the numerator. + * + * Return: 0 on success, <0 on error + */ +static int sit9531x_divn_runtime(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *divn) +{ + u8 buf[SIT9531X_DBG_DATA_BYTES]; + u64 fracn_raw =3D 0, fracd =3D 0; + u32 int_part; + int rc, i; + + rc =3D sit9531x_dbg_sample(sitdev, pll_idx, SIT9531X_DBG_READ_CODE_DIVN, + buf, SIT9531X_DBG_DATA_BYTES); + if (rc) + return rc; + + for (i =3D 5; i >=3D 0; i--) + fracn_raw =3D (fracn_raw << 8) | buf[i]; + + int_part =3D buf[6] | ((u32)(buf[7] & SIT9531X_DIVN_RT_INT_HI_BIT) << 8); + + rc =3D sit9531x_dbg_sample(sitdev, pll_idx, + SIT9531X_DBG_READ_CODE_DIVN_DEN, buf, + SIT9531X_DBG_DATA_BYTES); + if (rc) + return rc; + + for (i =3D 5; i >=3D 0; i--) + fracd =3D (fracd << 8) | buf[i]; + + fracd |=3D (u64)(buf[6] & SIT9531X_DIVN_RT_INT_HI_BIT) << 48; + + *divn =3D sit9531x_divn_fixed(int_part, + sign_extend64(fracn_raw, + SIT9531X_DIVN_RT_NUM_BITS - 1), + fracd); + + return 0; +} + +/** + * sit9531x_pll_ffo_ppt - fractional frequency offset of a PLL's reference + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @ffo: result in parts per trillion + * + * A locked PLL commands whatever DIVN keeps its VCO tracking the + * reference. How far that sits from the configured DIVN is how far the + * reference sits from the local oscillator, which is the fractional + * frequency offset the DPLL ABI reports for the pin feeding the device. + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, -ENODATA when DIVN is not programmed, <0 on + * error. + */ +int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo) +{ + s64 configured, running, delta; + u64 magnitude; + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + rc =3D sit9531x_divn_static(sitdev, pll_idx, &configured); + if (rc) + return rc; + if (configured <=3D 0) + return -ENODATA; + + rc =3D sit9531x_divn_runtime(sitdev, pll_idx, &running); + if (rc) + return rc; + + delta =3D running - configured; + magnitude =3D mul_u64_u64_div_u64(abs(delta), SIT9531X_PPT_PER_UNIT, + (u64)configured); + + *ffo =3D delta < 0 ? -(s64)magnitude : (s64)magnitude; + + return 0; +} + +/* + * sit9531x_get_fvco - read VCO frequency from chip's DIVN registers + * + * Fvco =3D Fref * DIVN, where DIVN comes from sit9531x_divn_static() and + * Fref =3D xtal_freq << doubler. DIVN is the + * steady-state Fvco/Fref target programmed by the NVM blob and is + * authoritative in both free-run and sync modes; the previous split + * between free-run and sync formulas returned 0 on chips that didn't + * have a sync input populated, which broke the TDC phase readback. + * + * Return: Fvco in Hz, or 0 on error + */ +static u64 sit9531x_get_fvco(struct sit9531x_dev *sitdev, u8 pll_idx) +{ + int doubler, rc; + s64 divn; + u64 fref; + + /* + * DT board-config override: some configs (e.g. an INTSYNC PLL) + * run a VCO that Fref*DIVN does not reproduce. When the board + * supplies the measured VCO, use it verbatim. + */ + if (pll_idx < SIT9531X_NUM_PLLS && sitdev->pll_fvco[pll_idx]) + return sitdev->pll_fvco[pll_idx]; + + rc =3D sit9531x_divn_static(sitdev, pll_idx, &divn); + if (rc || divn <=3D 0) + return 0; + + doubler =3D sit9531x_is_xo_doubler_enabled(sitdev); + if (doubler < 0) + return 0; + + fref =3D (u64)sitdev->xtal_freq << doubler; + + return mul_u64_u64_div_u64(fref, (u64)divn, SIT9531X_DIVN_SCALE); +} + +/* + * sit9531x_output_phase_flush - flush the output phase of a PLL + * + * Fires the chip's on-demand phase-flush (PHFL) so every output divider + * of @pll_idx restarts aligned to the PLL phase. Without it a rewritten + * DIVO keeps counting from an arbitrary point and the output edge lands + * with a persistent offset against the tracked reference (only a power + * cycle realigned it). + * + * The sequence mirrors the vendor procedure: arm the on-demand PHFL and + * latch it with the PLL-page small-change update, then select the + * in-register phase trigger on Page 0 and pulse it. The Page 0 trigger + * register is touched read-modify-write so the unrelated OEb bits are + * preserved. + * + * Caller must hold sitdev->multiop_lock. + */ +static int sit9531x_output_phase_flush(struct sit9531x_dev *sitdev, u8 pll= _idx) +{ + u8 ctrl, orig; + int rc, ret; + + /* Arm the on-demand phase-flush on the PLL page. */ + rc =3D sit9531x_update_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_PHFL_CTRL, + SIT9531X_PLL_PHFL_ON_DEMAND_EN, + SIT9531X_PLL_PHFL_ON_DEMAND_EN); + if (rc) + return rc; + + /* Latch it with the PLL small-change update. */ + rc =3D sit9531x_update_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_SMALL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + /* + * Select the in-register phase trigger, preserving the OEb bits. + * Remember the original register value (with the trigger de-asserted) + * so the trigger-source select can be restored once the pulse has + * fired. + */ + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, &ctrl); + if (rc) + return rc; + + orig =3D ctrl & ~SIT9531X_DIVO_PHASE_TRIG; + ctrl =3D orig | SIT9531X_DIVO_PHASE_SEL_REG; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, ctrl); + if (rc) + return rc; + + /* + * Pulse the phase trigger. No explicit delay is needed between the + * set and clear writes: each I2C transaction takes far longer than + * any minimum pulse width. + */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, + ctrl | SIT9531X_DIVO_PHASE_TRIG); + + /* + * Restore the original trigger-source select. The pulse above has + * already latched the flush, so a one-shot flush must not leave the + * phase trigger permanently pinned to the in-register source. This + * runs even when the pulse write failed, otherwise a failed flush + * would keep a hardware trigger source hijacked; the restore error + * is only surfaced when it would not mask the pulse failure. + */ + ret =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, orig); + if (ret && !rc) + rc =3D ret; + + return rc; +} + +/* + * sit9531x_output_freq_set - set output clock frequency + * @out_idx: output index (0-N for this chip variant) + * @pll_idx: PLL driving this output (0-3) + * @frequency: desired output frequency in Hz + * + * Computes DIVO =3D Fvco / frequency and writes the 34-bit output divider + * to the output system registers on Pages 3/4. The write sequence is: + * 1. Unlock debug registers (Page 3) + * 2. Enter PRG_CMD state + * 3. Write 5-byte DIVO to the correct page/slot + * 4. NVM update + * 5. Loop lock + * 6. Wait for lock to settle + * 7. Flush the output phase so the new divider starts aligned + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, <0 on error. Actual frequency may differ + * due to integer division; the output state is updated with + * the effective frequency (Fvco / DIVO). + */ +int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx, + u8 pll_idx, u64 frequency) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + u8 slot, page, base_reg, divo_bytes[5], msb_old; + u64 fvco, divo, fvco_min, fvco_max; + int rc, j, ret; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (out_idx >=3D info->num_outputs || pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + if (!frequency) + return -EINVAL; + + /* Determine VCO frequency band limits */ + if (pll_idx =3D=3D 1 || pll_idx =3D=3D 3) { + /* PLLB, PLLD: high band */ + fvco_min =3D SIT9531X_FVCO_HIGHBAND_MIN; + fvco_max =3D SIT9531X_FVCO_HIGHBAND_MAX; + } else { + /* PLLA, PLLC: low band */ + fvco_min =3D SIT9531X_FVCO_LOWBAND_MIN; + fvco_max =3D SIT9531X_FVCO_LOWBAND_MAX; + } + + /* + * Read current VCO frequency. When the board supplies an explicit + * Fvco via "sitime,pll-fvco" the override is the source of truth + * (e.g. a chip variant that runs out of the documented band, or a + * mode like INTSYNC where Fref*DIVN does not reproduce the VCO), so + * skip the band clamp in that case. + */ + fvco =3D sit9531x_get_fvco(sitdev, pll_idx); + if (!fvco) { + fvco =3D fvco_min; + } else if (!sitdev->pll_fvco[pll_idx]) { + if (fvco < fvco_min) + fvco =3D fvco_min; + else if (fvco > fvco_max) + fvco =3D fvco_max; + } + + divo =3D div64_u64(fvco, frequency); + if (!divo) + return -EINVAL; + + dev_dbg(sitdev->dev, + "out%u: Fvco=3D%llu freq=3D%llu DIVO=3D%llu (effective %llu Hz)\n", + out_idx, fvco, frequency, divo, div64_u64(fvco, divo)); + + /* Map output index to physical slot */ + slot =3D info->clkout_map[out_idx]; + + /* Determine page and per-page slot register */ + if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) + page =3D SIT9531X_PAGE_OUTSYS1; + else + page =3D SIT9531X_PAGE_OUTSYS0; + base_reg =3D clkout_odr_divn_base[slot % 6]; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG, + SIT9531X_DEBUG_UNLOCK_VAL); + if (rc) + return rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN, + SIT9531X_PRG_CMD_STATE); + if (rc) + return rc; + + divo_bytes[0] =3D (divo >> 0) & 0xFF; + divo_bytes[1] =3D (divo >> 8) & 0xFF; + divo_bytes[2] =3D (divo >> 16) & 0xFF; + divo_bytes[3] =3D (divo >> 24) & 0xFF; + divo_bytes[4] =3D (divo >> 32) & 0x03; /* only bits [1:0] */ + + rc =3D sit9531x_read_u8(sitdev, + SIT9531X_REG(page, base_reg - 4), &msb_old); + if (rc) + goto commit; + divo_bytes[4] |=3D msb_old & 0xFC; + + for (j =3D 0; j < 5; j++) { + rc =3D sit9531x_write_u8(sitdev, + SIT9531X_REG(page, base_reg - j), + divo_bytes[j]); + if (rc) + goto commit; + } + +commit: + /* + * Step 4: NVM update + loop lock. Always run prg_commit() so the chip + * leaves the PRG_CMD state with the output loops re-locked, even when a + * write above failed; keep the first error to return. It also carries + * the required post-lock settling sleep. + */ + ret =3D sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc =3D ret; + if (rc) + return rc; + + /* + * Step 5: flush the PLL's output phase so the new DIVO starts + * aligned instead of keeping the arbitrary phase the divider + * happened to be at. + */ + rc =3D sit9531x_output_phase_flush(sitdev, pll_idx); + if (rc) + return rc; + + sitdev->out[out_idx].freq =3D (u32)div64_u64(fvco, divo); + + return 0; +} + +/* + * sit9531x_output_freq_get - read output clock frequency from hardware + * @out_idx: output index (0-N for this chip variant) + * @frequency: output frequency in Hz + * + * Reads the 34-bit DIVO divider back from the output system registers + * and computes the live output frequency as Fvco / DIVO. This stays + * correct even when the divider was reprogrammed behind the driver's + * back (e.g. by a direct-I2C userspace tool), where the cached value + * would be stale. + * + * The cached output state is refreshed with the computed value. + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, -ENODEV when the output divider or VCO rate + * is not resolvable, <0 on register access error + */ +int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx, + u64 *frequency) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + u8 slot, page, base_reg, pll_idx, v; + u64 fvco, divo =3D 0; + int rc, j; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (out_idx >=3D info->num_outputs) + return -EINVAL; + + pll_idx =3D sitdev->out[out_idx].pll_idx; + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -ENODEV; + + fvco =3D sit9531x_get_fvco(sitdev, pll_idx); + if (!fvco) + return -ENODEV; + + slot =3D info->clkout_map[out_idx]; + if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) + page =3D SIT9531X_PAGE_OUTSYS1; + else + page =3D SIT9531X_PAGE_OUTSYS0; + base_reg =3D clkout_odr_divn_base[slot % 6]; + + for (j =3D 4; j >=3D 0; j--) { + rc =3D sit9531x_read_u8(sitdev, + SIT9531X_REG(page, base_reg - j), &v); + if (rc) + return rc; + if (j =3D=3D 4) + v &=3D 0x03; + divo =3D (divo << 8) | v; + } + + if (!divo) + return -ENODEV; + + *frequency =3D div64_u64(fvco, divo); + sitdev->out[out_idx].freq =3D (u32)*frequency; + + return 0; +} + +/* + * Phase adjust (PRG_RST_DELAY register-based). + * + * The chip exposes a per-output 34-bit coarse delay measured in VCO + * clock periods plus a 3-bit fine delay in fixed 30 ps steps. The + * five bytes PROG6..PROG2 hold the field across registers: + * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserved via RMW) + * [4:2] PRG_RST_FINE_DELAY + * [1:0] PRG_RST_DELAY[33:32] + * base + 1 PROG5 PRG_RST_DELAY[31:24] + * base + 2 PROG4 PRG_RST_DELAY[23:16] + * base + 3 PROG3 PRG_RST_DELAY[15:8] + * base + 4 PROG2 PRG_RST_DELAY[7:0] + * + * Outputs 0-5 live on Page 3, outputs 6-11 on Page 4, with each + * output's block at base =3D 0x15 + 16 * (out_idx % 6). + * + * The chip only supports unsigned positive delay. A negative phase + * adjustment (advance) is wrapped to (T_out - |phase|) modulo one + * output period, which is identical for a periodic signal. + */ + +int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev, + u8 out_idx, s32 phase_ps) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + u64 abs_ps, fvco, coarse, coarse_ps, rem_ps; + u8 page, base, prog6_val, fine =3D 0; + u8 pll_idx, slot; + u32 freq; + int rc, ret; + + if (out_idx >=3D info->num_outputs) + return -EINVAL; + + pll_idx =3D sitdev->out[out_idx].pll_idx; + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + freq =3D sitdev->out[out_idx].freq; + if (!freq) + return -EINVAL; + + fvco =3D sit9531x_get_fvco(sitdev, pll_idx); + if (!fvco) + return -EIO; + + /* + * Convert to unsigned absolute delay. Negative phase (advance) + * is rendered as T_out - |phase|, modulo the output period. + */ + if (phase_ps =3D=3D 0) { + abs_ps =3D 0; + } else if (phase_ps > 0) { + abs_ps =3D (u64)phase_ps; + } else { + u64 t_out_ps =3D div64_u64(1000000000000ULL, freq); + u64 advance =3D (u64)(-(s64)phase_ps); + + if (t_out_ps =3D=3D 0) + return -EINVAL; + advance %=3D t_out_ps; + abs_ps =3D (advance =3D=3D 0) ? 0 : (t_out_ps - advance); + } + + /* + * coarse_cycles =3D abs_ps * Fvco / 1e12 ps/s. + * mul_u64_u64_div_u64() avoids overflow when abs_ps approaches + * one second of 1 PPS wrap-around. + */ + coarse =3D mul_u64_u64_div_u64(abs_ps, fvco, 1000000000000ULL); + if (coarse >=3D (1ULL << SIT9531X_OUT_PRG_COARSE_BITS)) + return -ERANGE; + + /* Fine delay =3D round((abs_ps - coarse * vco_period_ps) / 30 ps) */ + coarse_ps =3D mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco); + rem_ps =3D (abs_ps > coarse_ps) ? (abs_ps - coarse_ps) : 0; + if (rem_ps) { + u64 steps; + + steps =3D div64_u64(rem_ps + SIT9531X_OUT_PRG_FINE_STEP_PS / 2, + SIT9531X_OUT_PRG_FINE_STEP_PS); + if (steps > SIT9531X_OUT_PRG_FINE_MAX) + steps =3D SIT9531X_OUT_PRG_FINE_MAX; + fine =3D (u8)steps; + } + + /* + * Map logical output index to the chip's physical output slot. + * On SiT95317 the eight logical outputs land on chip slots + * {0, 3, 4, 5, 7, 8, 9, 11}; on SiT95316 the map is identity. + * Page/base must address the slot, not the logical index. + */ + slot =3D info->clkout_map[out_idx]; + page =3D (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ? + SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0; + base =3D SIT9531X_OUT_PRG_DELAY_BASE + + SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6); + + /* Caller (dpll.c) holds multiop_lock around the whole sequence. */ + lockdep_assert_held(&sitdev->multiop_lock); + + /* + * The PRG_RST_DELAY bytes live in the output system, so the writes + * only take effect when made inside the PRG_CMD programming state and + * committed to the NVM shadow, exactly like sit9531x_output_freq_set(). + */ + rc =3D sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + /* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */ + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG(page, base), + &prog6_val); + if (rc) + goto commit; + + prog6_val &=3D SIT9531X_OUT_PRG_OPSTG_MASK; + prog6_val |=3D (fine << SIT9531X_OUT_PRG_FINE_SHIFT) & + SIT9531X_OUT_PRG_FINE_MASK; + prog6_val |=3D (u8)((coarse >> 32) & SIT9531X_OUT_PRG_COARSE_HI_MASK); + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, base), + prog6_val); + if (rc) + goto commit; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 1), + (u8)((coarse >> 24) & 0xFF)); + if (rc) + goto commit; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 2), + (u8)((coarse >> 16) & 0xFF)); + if (rc) + goto commit; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 3), + (u8)((coarse >> 8) & 0xFF)); + if (rc) + goto commit; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, base + 4), + (u8)(coarse & 0xFF)); + +commit: + /* + * Always leave the PRG_CMD state via prg_commit(), even on a + * mid-sequence write failure, so the output loops are re-locked rather + * than stranded unlocked; keep the first error. + */ + ret =3D sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc =3D ret; + if (rc) + return rc; + + /* + * Restart the output divider phase so the freshly programmed delay is + * applied against a known edge instead of the divider's arbitrary + * running phase. + */ + return sit9531x_output_phase_flush(sitdev, pll_idx); +} + +/* + * sit9531x_clear_notifications - clear all notification registers + * + * Clears all write-1-to-clear notification registers: + * - PLL outer LOL notification (Page 0, reg 0x07) + * - PLL holdover freeze notification (Page 0, reg 0x0B) + * - PLL inner LOL notification (Page 0, reg 0x93) + * - Clock monitor XO/PLL notification (Page 0, reg 0x9E) + * - Clock input notifications (Page 6, regs 0x03/0x07/0x93/0x97) + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_clear_notifications(struct sit9531x_dev *sitdev) +{ + int rc; + + lockdep_assert_held(&sitdev->multiop_lock); + + /* Page 0x00 W1C notification registers */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_OUTER_LOL_NOTIF, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_HO_FREEZE_NOTIF, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_NOTIF, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_CMON_NOTIF, 0xFF); + if (rc) + return rc; + + /* Page 0x06 clock input monitor notifications */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_01, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_23, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_01, 0xFF); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_23, 0xFF); + if (rc) + return rc; + + dev_dbg(sitdev->dev, "All notification registers cleared\n"); + return 0; +} + +/* + * INTSYNC configuration register values. + * These are written to the source PLL's EXT page to enable/disable + * inter-PLL synchronization (lock frequency PLL to phase PLL). + */ +struct sit9531x_intsync_reg { + u8 offset; + u8 en_val; + u8 dis_val; +}; + +static const struct sit9531x_intsync_reg intsync_config[] =3D { + { 0x2D, 0x02, 0x00 }, + { 0x50, 0x08, 0x00 }, + { 0x51, 0x04, 0x00 }, + { 0x54, 0x02, 0x00 }, + { 0x55, 0x28, 0x20 }, + { 0x5C, 0x0F, 0x00 }, + { 0x5D, 0xFF, 0x00 }, + { 0x6C, 0xDD, 0x00 }, +}; + +/* + * sit9531x_intsync_enable - enable inter-PLL synchronization + * @src_pll_idx: source (frequency) PLL index (0-3) + * + * Enables INTSYNC global bit, unlocks the source PLL's EXT page + * debug registers, writes configuration, and triggers a small + * update on the source PLL. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx) +{ + u8 ext_page, val; + int rc, i; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (src_pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + ext_page =3D SIT9531X_PLL_EXT_PAGE(src_pll_idx); + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, + val | BIT(SIT9531X_INTSYNC_EN_BIT)); + if (rc) + return rc; + + /* Small update on Page 0 */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + /* Unlock debug on EXT page */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_= DEBUG), + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + return rc; + + for (i =3D 0; i < ARRAY_SIZE(intsync_config); i++) { + rc =3D sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + intsync_config[i].offset), + intsync_config[i].en_val); + if (rc) + return rc; + } + + /* Small update on source PLL */ + rc =3D sit9531x_write_pll_u8(sitdev, src_pll_idx, + SIT9531X_PLL_REG_SMALL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + return 0; +} + +/* + * sit9531x_intsync_disable - disable inter-PLL synchronization + * @src_pll_idx: source (frequency) PLL index (0-3) + * + * Clears INTSYNC global bit, writes disable values to the source + * PLL's EXT page, and triggers a small update. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx) +{ + u8 ext_page, val; + int rc, i; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (src_pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + ext_page =3D SIT9531X_PLL_EXT_PAGE(src_pll_idx); + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, &val); + if (rc) + return rc; + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_INTSYNC_GLOBAL, + val & ~BIT(SIT9531X_INTSYNC_EN_BIT)); + if (rc) + return rc; + + /* Small update on Page 0 */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + /* Unlock debug on EXT page */ + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(ext_page, SIT9531X_PLL_REG_= DEBUG), + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + return rc; + + for (i =3D 0; i < ARRAY_SIZE(intsync_config); i++) { + rc =3D sit9531x_write_u8(sitdev, + SIT9531X_REG(ext_page, + intsync_config[i].offset), + intsync_config[i].dis_val); + if (rc) + return rc; + } + + /* Small update on source PLL */ + rc =3D sit9531x_write_pll_u8(sitdev, src_pll_idx, + SIT9531X_PLL_REG_SMALL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) + return rc; + + return 0; +} + +/* + * sit9531x_output_pulse_ctrl_set - program per-output PULSE_CTRL byte + * @out_idx: logical output index (translated to chip slot internally) + * @pulse_ctrl: 8-bit PULSE_CTRL value (PROG0) + * + * Writes ODRn_PROG0 on the output page (Page 3 for slots 0..5, + * Page 4 for slots 6..11) at offset 0x1B + 16 * (slot % 6). + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev, + u8 out_idx, u8 pulse_ctrl) +{ + const struct sit9531x_chip_info *info =3D sitdev->info; + u8 slot, page, reg; + int rc, ret; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (out_idx >=3D info->num_outputs) + return -EINVAL; + + slot =3D info->clkout_map[out_idx]; + page =3D (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ? + SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0; + reg =3D SIT9531X_OUT_PROG0_BASE + + SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6); + + /* + * PROG0 lives in the output system, so like the DIVO and + * PRG_RST_DELAY writes it only takes effect inside the PRG_CMD + * programming state committed to the NVM shadow. + */ + rc =3D sit9531x_prg_enter(sitdev); + if (rc) + return rc; + + rc =3D sit9531x_write_u8(sitdev, SIT9531X_REG(page, reg), pulse_ctrl); + + /* + * Always leave the PRG_CMD state via prg_commit(), even if the write + * failed, so the output loops are re-locked rather than stranded + * unlocked; keep the first error. + */ + ret =3D sit9531x_prg_commit(sitdev); + if (ret && !rc) + rc =3D ret; + + return rc; +} + +/* Retries (at 1 s each) to wait for a PLL to reach its active state. */ +#define SIT9531X_PLL_ACTIVE_RETRIES 90 + +/* + * sit9531x_phase_offset_read - read phase difference via TDC + * @phase_ps: output phase difference in picoseconds + * + * Reads the Time-to-Digital Converter (TDC) 40-bit code from the + * PLL page registers, then converts to picoseconds using the VCO + * frequency: phase_diff =3D tdc_code / fvco. + * + * Caller must hold sitdev->multiop_lock. + */ +int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *phase_ps) +{ + u64 fvco, fvco_mhz; + s64 tdc_signed; + u64 tdc_raw; + int rc, i; + bool sign; + u8 v; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >=3D SIT9531X_NUM_PLLS) + return -EINVAL; + + /* Unlock the debug page so the TDC registers are accessible. */ + rc =3D sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DEBUG, + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + return rc; + + /* + * Select the debug clock for taps below 200 kHz, then point the + * readback at the TDC. Only the one bit is touched: writing the + * modifier register whole would clear the fields belonging to + * other taps. + */ + rc =3D sit9531x_update_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_WRITE_CODE, + SIT9531X_DBG_LOW_FREQ_CLK_BIT, + SIT9531X_DBG_LOW_FREQ_CLK_BIT); + if (rc) + return rc; + rc =3D sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_READ_CODE, + SIT9531X_DBG_READ_CODE_TDC); + if (rc) + return rc; + + /* Latch a sample by reading the trigger register 3 times. */ + for (i =3D 0; i < 3; i++) { + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_TRIGGER, &v); + if (rc) + return rc; + } + + tdc_raw =3D 0; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_4, &v); + if (rc) + return rc; + sign =3D !!(v & BIT(SIT9531X_TDC_SIGN_BIT)); + tdc_raw =3D (u64)(v & 0x07) << 32; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_3, &v); + if (rc) + return rc; + tdc_raw |=3D (u64)v << 24; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_2, &v); + if (rc) + return rc; + tdc_raw |=3D (u64)v << 16; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_1, &v); + if (rc) + return rc; + tdc_raw |=3D (u64)v << 8; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_0, &v); + if (rc) + return rc; + tdc_raw |=3D v; + + /* + * Apply sign. Per the vendor reference the sign bit is active-high + * for a positive offset: bit set -> +code, bit clear -> -code. + */ + tdc_signed =3D sign ? (s64)tdc_raw : -(s64)tdc_raw; + + /* + * Get VCO frequency for conversion. Fvco=3D=3D0 means DIVN is not + * programmed (PLL unused on this board) -- skip silently rather + * than spamming the log on every poll cycle. + */ + fvco =3D sit9531x_get_fvco(sitdev, pll_idx); + if (!fvco) { + dev_dbg(sitdev->dev, "PLL%c: Fvco unknown, skip TDC\n", + 'A' + pll_idx); + return -ENODEV; + } + + /* + * phase_diff (seconds) =3D tdc_code / fvco + * phase_diff (ps) =3D tdc_code * 1e12 / fvco + * + * To avoid 64-bit overflow: + * phase_ps =3D tdc_code * 1e6 / (fvco / 1e6) + */ + fvco_mhz =3D div64_u64(fvco, 1000000ULL); + if (!fvco_mhz) + return -EIO; + + *phase_ps =3D div64_s64(tdc_signed * 1000000LL, (s64)fvco_mhz); + + return 0; +} + +/* + * sit9531x_ref_state_fetch - read input reference status from hardware + * @index: logical input index + * + * Reads LOS and OOF status for the given input lane from the Page 0x06 + * clock monitor registers. P and N lanes have separate register banks; + * each register carries two input pairs nibble-packed (even pair in + * bits [3:0], odd pair in [7:4]). + */ +static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index) +{ + unsigned int reg, force_reg, state_reg; + u8 pair, status, force, state; + struct sit9531x_ref *ref; + int rc; + + /* + * The XTAL/XO reference (index SIT9531X_MAX_INPUTS) is the on-chip + * oscillator that feeds every PLL. It cannot be routed or deselected, + * so its pin is modelled as permanently connected (see + * sit9531x_dpll_xo_pin_ops) and its LOS/OOF flags are never consulted. + * Only the routable per-lane inputs (0..num_inputs-1) are polled here. + */ + if (index >=3D SIT9531X_MAX_INPUTS) + return -EINVAL; + + ref =3D &sitdev->ref[index]; + pair =3D sit9531x_input_pair(index); + + if (sit9531x_input_is_n(index)) + reg =3D pair < 2 ? SIT9531X_CLKMON_N_STATUS_01 + : SIT9531X_CLKMON_N_STATUS_23; + else + reg =3D pair < 2 ? SIT9531X_CLKMON_P_STATUS_01 + : SIT9531X_CLKMON_P_STATUS_23; + + rc =3D sit9531x_read_u8(sitdev, reg, &status); + if (rc) + return rc; + + if (pair & 1) + status >>=3D 4; + + ref->los =3D !!(status & (BIT(SIT9531X_CLKMON_CLK_LOSS) | + BIT(SIT9531X_CLKMON_CLK_LOSS_FD))); + ref->oof =3D !!(status & (BIT(SIT9531X_CLKMON_FREQ_FINE) | + BIT(SIT9531X_CLKMON_FREQ_COARSE))); + + /* + * Whether the receiver is on. This has to come from the chip: it + * is the loaded configuration that decides, and without reading it + * back every input would look disabled until something called + * sit9531x_input_enable(). A lane counts as disabled only while + * the force bit overrides it to the off state; with the force bit + * clear it follows the configuration, which is the enabled case. + */ + sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg); + + rc =3D sit9531x_read_u8(sitdev, force_reg, &force); + if (rc) + return rc; + rc =3D sit9531x_read_u8(sitdev, state_reg, &state); + if (rc) + return rc; + + ref->enabled =3D !((force & BIT(pair)) && !(state & BIT(pair))); + + return 0; +} + +/* + * sit9531x_input_mode_fetch - detect SE/DE configuration of an input pair + * @pair: input pair number (0-3) + * + * Reads CLKINx_INPUT_MODE and stores the detected signal mode on both + * lanes of the pair. A pair with neither SE lane enabled is running + * differential. + */ +static int sit9531x_input_mode_fetch(struct sit9531x_dev *sitdev, u8 pair) +{ + enum sit9531x_signal_mode sig_mode; + u8 mode; + int rc; + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_IN_MODE(pair), &mode); + if (rc) + return rc; + + if (mode & (SIT9531X_IN_MODE_SE_P_EN | SIT9531X_IN_MODE_SE_N_EN)) + sig_mode =3D SIT9531X_MODE_SE; + else + sig_mode =3D SIT9531X_MODE_DE; + + sitdev->ref[pair * 2].sig_mode =3D sig_mode; + sitdev->ref[pair * 2 + 1].sig_mode =3D sig_mode; + + dev_dbg(sitdev->dev, "CLKIN%u mode reg 0x%02x -> %s\n", pair, mode, + sig_mode =3D=3D SIT9531X_MODE_DE ? "differential" : "single-ended"); + + return 0; +} + +/* + * sit9531x_chan_state_fetch - read PLL channel status from hardware + * + * Reads lock status and mode from the PLL status register. + */ +/* Read the PLL active-state bit (PLL page reg 0x02 bit 0). */ +static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx, + bool *active) +{ + u8 v; + int rc; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_ACTIVE, &v); + if (rc) + return rc; + + *active =3D !!(v & SIT9531X_PLL_ACTIVE_BIT); + + return 0; +} + +static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_i= dx) +{ + u8 status, outer_lol, input_sel, inner_lol, ho_freeze, activesel_reg; + struct sit9531x_chan *chan =3D &sitdev->chan[pll_idx]; + bool active; + int rc; + + /* + * Whether the PLL is running at all. The loss-of-lock bit read + * below is driven by the PLL itself, so on one the loaded + * configuration leaves unused it simply stays clear and would + * otherwise read as a lock. + */ + rc =3D sit9531x_pll_is_active(sitdev, pll_idx, &active); + if (rc) + return rc; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_STATUS, &status); + if (rc) + return rc; + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_OUTER_LOL_STATUS, + &outer_lol); + if (rc) + return rc; + + /* + * Read the input source the PLL has currently selected as its + * active reference. This lives in the low nibble of the last + * register of the PLL's page-1 priority block (CLK_ACTIVESEL_PLL), + * not on the PLL page -- PLL-page 0x29 is a config register. + */ + activesel_reg =3D SIT9531X_PRIO_BASE_REG + + SIT9531X_PRIO_REGS_PER_PLL * pll_idx + + SIT9531X_PRIO_ACTIVESEL_OFF; + rc =3D sit9531x_read_u8(sitdev, + SIT9531X_REG(SIT9531X_PAGE_PRIOSYS, + activesel_reg), + &input_sel); + if (rc) + return rc; + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_STATUS, + &inner_lol); + if (rc) + return rc; + + rc =3D sit9531x_read_u8(sitdev, SIT9531X_REG_HO_FREEZE_STATUS, &ho_freeze= ); + if (rc) + return rc; + + /* STATUS_1_GENERIC reports loss of lock, so invert it. */ + chan->active =3D active; + chan->locked =3D active && !(outer_lol & BIT(pll_idx)); + chan->mode =3D !!(status & SIT9531X_PLL_STATUS_OUTER_DIS); + chan->selected_ref =3D + sit9531x_hw_src_input(input_sel & SIT9531X_PRIO_NIBBLE_MASK); + chan->inner_lol =3D !!(inner_lol & BIT(pll_idx)); + chan->ho_freeze =3D !!(ho_freeze & BIT(pll_idx)); + + return 0; +} + +/* + * sit9531x_out_state_fetch - read output status from hardware + * + * Reads the output PLL association from the PLL page output map + * registers into out->routed / out->pll_idx, and the current drive + * state from the Hi-Z force bits into out->enabled. The two are + * separate: routing decides whether the output gets a DPLL pin at all, + * while a muted but routed output keeps its pin and reports + * DPLL_PIN_STATE_DISCONNECTED until it is un-muted. + */ +static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index) +{ + struct sit9531x_out *out =3D &sitdev->out[index]; + u8 map_lo, map_hi, slot; + int pll_idx; + bool muted; + int rc; + + slot =3D sitdev->info->clkout_map[index]; + + rc =3D sit9531x_output_forced_hiz(sitdev, slot, &muted); + if (rc) + return rc; + + /* + * DT board-config override: the per-PLL OUTPUT_ENABLE bitmaps + * (0x27/0x28) do not unambiguously express output->PLL routing on + * every config (overlaps, and some outputs routed outside that + * path). When the board supplies an explicit map, trust it. + */ + if (sitdev->out_pll_map_valid) { + u8 m =3D sitdev->out_pll_map[index]; + + if (m < SIT9531X_NUM_PLLS) { + out->pll_idx =3D m; + out->routed =3D true; + out->enabled =3D !muted; + } else { + out->pll_idx =3D 0; + out->routed =3D false; + out->enabled =3D false; + } + return 0; + } + + /* + * The OUT_MAP_LO/HI bitmaps are indexed by the physical slot the + * output occupies on the chip, not by the driver's logical output + * index (translated above via the chip-info clkout_map[]: identity + * on SiT95316, non-contiguous on SiT95317). + * + * Determine which PLL drives this output by checking each PLL's + * output map registers (0x27 =3D slots 8-11, 0x28 =3D slots 0-7). + */ + for (pll_idx =3D 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) { + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_OUT_MAP_LO, &map_lo); + if (rc) + return rc; + + rc =3D sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_OUT_MAP_HI, &map_hi); + if (rc) + return rc; + + if (slot < 8) { + if (map_lo & BIT(slot)) { + out->pll_idx =3D pll_idx; + out->routed =3D true; + out->enabled =3D !muted; + return 0; + } + } else { + if (map_hi & BIT(slot - 8)) { + out->pll_idx =3D pll_idx; + out->routed =3D true; + out->enabled =3D !muted; + return 0; + } + } + } + + /* Output not mapped to any PLL */ + out->pll_idx =3D 0; + out->routed =3D false; + out->enabled =3D false; + + return 0; +} + +/* + * sit9531x_ref_pll_mask_fetch - seed the input-to-PLL usage masks + * + * ref->pll_mask is the refcount the disconnect path uses to decide when + * an input receiver may be powered down: the physical input is only + * disabled once the last DPLL has released it. It therefore has to + * start out matching the hardware. Without this pass every mask starts + * at zero, and disconnecting an input from one DPLL drops the mask to + * zero and disables a receiver the other DPLLs are still locked to. + * + * An input is counted for a PLL when it appears in that PLL's Page-1 + * priority table, which is exactly the condition the connect and + * disconnect callbacks maintain. Sources that are not physical inputs + * (OCXO, INTSYNC) and reserved codes are skipped. + * + * Caller must hold sitdev->multiop_lock. + */ +static int sit9531x_ref_pll_mask_fetch(struct sit9531x_dev *sitdev) +{ + u8 srcs[SIT9531X_PRIO_MAX_SLOTS]; + u8 pll_idx, slot, index; + int rc; + + for (pll_idx =3D 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) { + rc =3D sit9531x_prio_table_read(sitdev, pll_idx, srcs); + if (rc) + return rc; + + for (slot =3D 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) { + index =3D sit9531x_hw_src_input(srcs[slot]); + if (index >=3D sitdev->info->num_inputs) + continue; + + /* + * On a differentially configured pair only the P lane + * has a DPLL pin, so that is the lane the connect and + * disconnect callbacks account for. Fold an N-lane + * table entry onto its P lane, otherwise the count + * would land on a lane nothing ever releases. The + * signalling mode is already known here: + * sit9531x_input_mode_fetch() runs first. + */ + if (sit9531x_input_is_n(index) && + sitdev->ref[index].sig_mode =3D=3D SIT9531X_MODE_DE) + index--; + + sitdev->ref[index].pll_mask |=3D BIT(pll_idx); + } + } + + return 0; +} + +/* + * sit9531x_dev_state_fetch - read all hardware state at startup + * + * Called once during probe to populate the initial state cache. + */ +static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev) +{ + int rc; + u8 i; + + /* Detect SE/DE configuration before any per-lane access */ + for (i =3D 0; i < sitdev->info->num_inputs / 2; i++) { + rc =3D sit9531x_input_mode_fetch(sitdev, i); + if (rc) { + dev_err(sitdev->dev, + "Failed to fetch CLKIN%u mode: %d\n", i, rc); + return rc; + } + } + + for (i =3D 0; i < sitdev->info->num_inputs; i++) { + rc =3D sit9531x_ref_state_fetch(sitdev, i); + if (rc) { + dev_err(sitdev->dev, + "Failed to fetch input %u state: %d\n", i, rc); + return rc; + } + } + + /* + * The priority-table read walks the Page-1 registers, so it runs + * with multiop_lock held like every other multi-register sequence. + * Nothing can race with it here -- the DPLLs are not registered and + * the monitor is not running yet -- but the page handling stays + * serialized the same way as at runtime. + */ + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_ref_pll_mask_fetch(sitdev); + mutex_unlock(&sitdev->multiop_lock); + if (rc) { + dev_err(sitdev->dev, + "Failed to fetch input priority tables: %d\n", rc); + return rc; + } + + for (i =3D 0; i < sitdev->info->num_outputs; i++) { + rc =3D sit9531x_out_state_fetch(sitdev, i); + if (rc) { + dev_err(sitdev->dev, + "Failed to fetch output %u state: %d\n", i, rc); + return rc; + } + } + + for (i =3D 0; i < SIT9531X_NUM_PLLS; i++) { + rc =3D sit9531x_chan_state_fetch(sitdev, i); + if (rc) { + dev_err(sitdev->dev, + "Failed to fetch PLL%c state: %d\n", + 'A' + i, rc); + return rc; + } + } + + return 0; +} + +static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev) +{ + int i, rc; + + for (i =3D 0; i < sitdev->info->num_inputs; i++) { + rc =3D sit9531x_ref_state_fetch(sitdev, i); + if (rc) + dev_warn(sitdev->dev, + "Failed to get REF%u status: %d\n", i, rc); + } +} + +static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev) +{ + int i, rc; + + for (i =3D 0; i < SIT9531X_NUM_PLLS; i++) { + rc =3D sit9531x_chan_state_fetch(sitdev, i); + if (rc) + dev_warn(sitdev->dev, + "Failed to get PLL%c state: %d\n", + 'A' + i, rc); + } +} + +/* + * sit9531x_dev_periodic_work - periodic hardware state polling + * @work: kthread_work pointer + * + * Polls hardware state at SIT9531X_STATUS_POLL_MS intervals. + * Updates reference and channel states, then delegates change + * detection to sit9531x_dpll_changes_check() for each registered DPLL. + */ +static void sit9531x_dev_periodic_work(struct kthread_work *work) +{ + struct sit9531x_dev *sitdev =3D container_of(work, struct sit9531x_dev, + work.work); + struct sit9531x_dpll *sitdpll; + int rc; + + /* + * Update the cached ref[]/chan[] arrays under multiop_lock so the + * fetches are serialized against the DPLL callbacks that read + * these fields and against the chip's page selector. + * + * The lock is then dropped before sit9531x_dpll_changes_check(), + * which calls dpll_pin_change_ntf() / dpll_device_change_ntf(). + * Those notification helpers take DPLL-subsystem locks that are + * already held when our callbacks are invoked from netlink + * context, and nesting multiop_lock around them would invert the + * lock order. changes_check() reads the cache published above, + * which is already consistent. + */ + mutex_lock(&sitdev->multiop_lock); + sit9531x_dev_ref_states_update(sitdev); + sit9531x_dev_chan_states_update(sitdev); + mutex_unlock(&sitdev->multiop_lock); + + list_for_each_entry(sitdpll, &sitdev->dplls, list) + sit9531x_dpll_changes_check(sitdpll); + + /* + * Acknowledge the chip's notification latches after the tick has + * read and acted on them. Without this, the W1C bits remain set + * and -- on boards that wire INTRB -- the line stays asserted, + * re-firing the threaded handler back to back. The helper writes + * W1C bits across page 0 and page 6 and must run under + * multiop_lock to serialize the page selector against userspace + * dpll ops. Failure is non-fatal: status was already consumed + * for this tick and the next tick re-processes whatever stayed + * latched. + */ + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_clear_notifications(sitdev); + mutex_unlock(&sitdev->multiop_lock); + if (rc) + dev_warn_ratelimited(sitdev->dev, + "Failed to clear notifications: %d\n", + rc); + + /* Run twice a second */ + kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, + msecs_to_jiffies(SIT9531X_STATUS_POLL_MS)); +} + +/* + * sit9531x_irq_thread_fn - threaded IRQ handler for the chip's INTRB line + * + * Triggered when the chip asserts INTRB (and only when DT wires up the + * client interrupt; absent property =3D=3D handler never installed). The + * action mirrors a periodic-work tick: queue an immediate run so status + * registers are read and DPLL changes_check fires without waiting for + * the next poll deadline. Polling continues to run as a fallback. + */ +static irqreturn_t sit9531x_irq_thread_fn(int irq, void *data) +{ + struct sit9531x_dev *sitdev =3D data; + int rc; + + /* + * Acknowledge the chip's notification latches from the threaded + * handler itself. With IRQF_ONESHOT the line is unmasked on + * return, so deferring the W1C clear to the async kworker would + * let a still-asserted INTRB re-fire immediately (interrupt storm). + * Clear here, then kick the poll worker to read state and run + * changes_check. + */ + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_clear_notifications(sitdev); + mutex_unlock(&sitdev->multiop_lock); + if (rc) + dev_warn_ratelimited(sitdev->dev, + "IRQ: failed to clear notifications: %d\n", + rc); + + kthread_mod_delayed_work(sitdev->kworker, &sitdev->work, 0); + return IRQ_HANDLED; +} + +/* + * sit9531x_dev_start - start normal operation + * + * Fetches initial hardware state, registers all DPLL devices and + * their pins, and starts the periodic monitoring thread. + */ +int sit9531x_dev_start(struct sit9531x_dev *sitdev) +{ + struct sit9531x_dpll *sitdpll; + int rc; + + /* Fetch device state */ + rc =3D sit9531x_dev_state_fetch(sitdev); + if (rc) + return rc; + + list_for_each_entry(sitdpll, &sitdev->dplls, list) { + rc =3D sit9531x_dpll_register(sitdpll); + if (rc) { + dev_err_probe(sitdev->dev, rc, + "Failed to register DPLL%u\n", + sitdpll->id); + goto err_unregister; + } + } + + kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0); + + return 0; + +err_unregister: + /* + * Unregister what did register. The caller frees the list on this + * path, so leaving a DPLL registered would hand the subsystem a + * pointer to freed memory. + */ + list_for_each_entry_continue_reverse(sitdpll, &sitdev->dplls, list) + sit9531x_dpll_unregister(sitdpll); + + return rc; +} + +/* + * sit9531x_dev_stop - stop normal operation + * + * Cancels the monitoring thread and unregisters all DPLL devices + * and their pins. + */ +void sit9531x_dev_stop(struct sit9531x_dev *sitdev) +{ + struct sit9531x_dpll *sitdpll; + + kthread_cancel_delayed_work_sync(&sitdev->work); + + list_for_each_entry(sitdpll, &sitdev->dplls, list) { + if (sitdpll->dpll_dev) + sit9531x_dpll_unregister(sitdpll); + } +} + +static struct sit9531x_dpll_pin * +sit9531x_dpll_pin_alloc(struct sit9531x_dpll *sitdpll, + enum dpll_pin_direction dir, u8 id) +{ + struct sit9531x_dpll_pin *pin; + + pin =3D kzalloc_obj(*pin, GFP_KERNEL); + if (!pin) + return ERR_PTR(-ENOMEM); + + pin->dpll =3D sitdpll; + pin->dir =3D dir; + pin->id =3D id; + + return pin; +} + +/* + * sit9531x_dpll_pin_register - register a DPLL pin with the subsystem + * @index: absolute pin index for clock_id namespace + * + * Gets pin properties from firmware, creates or gets a dpll_pin, + * and registers it with the parent DPLL device. + */ +static int sit9531x_dpll_pin_register(struct sit9531x_dpll_pin *pin, + u32 index) +{ + struct sit9531x_dpll *sitdpll =3D pin->dpll; + struct sit9531x_pin_props *props; + const struct dpll_pin_ops *ops; + int rc; + + props =3D sit9531x_pin_props_get(sitdpll->dev, pin->dir, pin->id); + if (IS_ERR(props)) + return PTR_ERR(props); + + strscpy(pin->label, props->package_label, sizeof(pin->label)); + pin->fwnode =3D fwnode_handle_get(props->fwnode); + pin->esync_control =3D props->esync_control; + + pin->dpll_pin =3D dpll_pin_get(sitdpll->dev->clock_id, index, + THIS_MODULE, &props->dpll_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc =3D PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + dpll_pin_fwnode_set(pin->dpll_pin, props->fwnode); + + ops =3D sit9531x_dpll_pin_ops_get(pin); + + rc =3D dpll_pin_register(sitdpll->dpll_dev, pin->dpll_pin, ops, pin); + if (rc) + goto err_register; + + sit9531x_pin_props_put(props); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + /* dpll_pin_get() left an ERR_PTR here. */ + pin->dpll_pin =3D NULL; + fwnode_handle_put(pin->fwnode); + pin->fwnode =3D NULL; + sit9531x_pin_props_put(props); + + return rc; +} + +static void sit9531x_dpll_pin_unregister(struct sit9531x_dpll_pin *pin) +{ + struct sit9531x_dpll *sitdpll =3D pin->dpll; + const struct dpll_pin_ops *ops; + + ops =3D sit9531x_dpll_pin_ops_get(pin); + + dpll_pin_unregister(sitdpll->dpll_dev, pin->dpll_pin, ops, pin); + dpll_pin_put(pin->dpll_pin, &pin->tracker); + pin->dpll_pin =3D NULL; + + fwnode_handle_put(pin->fwnode); + pin->fwnode =3D NULL; +} + +static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll) +{ + struct sit9531x_dpll_pin *pin, *next; + + list_for_each_entry_safe(pin, next, &sitdpll->pins, list) { + sit9531x_dpll_pin_unregister(pin); + list_del(&pin->list); + kfree(pin); + } +} + +/* + * sit9531x_input_pin_is_registrable - check if an input pin is registrable + * + * Split out so input-model changes stay local to this helper. + * + * Return: true if the input pin should be registered, false otherwise + */ +static bool sit9531x_input_pin_is_registrable(struct sit9531x_dev *sitdev, + u8 index) +{ + if (index >=3D sitdev->info->num_inputs) + return false; + + /* + * The N lane of a differentially-configured pair is not a + * standalone input and is skipped (zl3073x model). + */ + if (sit9531x_input_is_n(index) && + sitdev->ref[index].sig_mode =3D=3D SIT9531X_MODE_DE) + return false; + + return true; +} + +/* + * sit9531x_dpll_pin_is_registrable - check if a pin should be registered + * @dir: pin direction + * @index: pin hardware index + * + * For input pins: delegate to sit9531x_input_pin_is_registrable(). + * For output pins: the pin is registrable if this DPLL is routed to it, + * whether or not it is currently driving. + * + * Return: true if pin should be registered, false otherwise + */ +static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll, + enum dpll_pin_direction dir, + u8 index) +{ + struct sit9531x_dev *sitdev =3D sitdpll->dev; + + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT) { + /* The internal INTSYNC and XO pins are always registrable */ + if (index =3D=3D SIT9531X_INTSYNC_PIN_ID || + index =3D=3D SIT9531X_MAX_INPUTS) + return true; + + return sit9531x_input_pin_is_registrable(sitdev, index); + } + + /* The internal INTSYNC source pin is always registrable */ + if (index =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID) + return true; + + /* Output -- check if driven by this DPLL */ + if (index >=3D sitdev->info->num_outputs) + return false; + + /* + * Routing, not the current drive state: an output the initial + * configuration muted still gets a pin, so userspace can see it and + * un-mute it. Only outputs no PLL drives are left unregistered. + */ + return sitdev->out[index].pll_idx =3D=3D sitdpll->id && + sitdev->out[index].routed; +} + +/* + * sit9531x_dpll_pins_register - register all registrable pins + * + * Enumerates all possible input and output pins, checks registrability, + * and registers each one. Input pins come first, then output pins, + * with input pins first, then output pins. + */ +static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll) +{ + struct sit9531x_dpll_pin *pin; + enum dpll_pin_direction dir; + u8 id, index; + int rc; + + for (index =3D 0; index < SIT9531X_NUM_PINS_TOTAL; index++) { + if (index < SIT9531X_NUM_INPUT_PINS) { + id =3D index; + dir =3D DPLL_PIN_DIRECTION_INPUT; + } else { + id =3D index - SIT9531X_NUM_INPUT_PINS; + dir =3D DPLL_PIN_DIRECTION_OUTPUT; + } + + if (!sit9531x_dpll_pin_is_registrable(sitdpll, dir, id)) + continue; + + pin =3D sit9531x_dpll_pin_alloc(sitdpll, dir, id); + if (IS_ERR(pin)) { + rc =3D PTR_ERR(pin); + goto error; + } + + rc =3D sit9531x_dpll_pin_register(pin, index); + if (rc) { + kfree(pin); + goto error; + } + + list_add(&pin->list, &sitdpll->pins); + } + + return 0; + +error: + sit9531x_dpll_pins_unregister(sitdpll); + return rc; +} + +static int sit9531x_dpll_device_register(struct sit9531x_dpll *sitdpll) +{ + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + sitdpll->ops =3D sit9531x_dpll_device_ops; + + sitdpll->dpll_dev =3D dpll_device_get(sitdev->clock_id, sitdpll->id, + THIS_MODULE, &sitdpll->tracker); + if (IS_ERR(sitdpll->dpll_dev)) { + rc =3D PTR_ERR(sitdpll->dpll_dev); + sitdpll->dpll_dev =3D NULL; + return rc; + } + + rc =3D dpll_device_register(sitdpll->dpll_dev, + sit9531x_prop_dpll_type_get(sitdev, + sitdpll->id), + &sitdpll->ops, sitdpll); + if (rc) { + dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker); + sitdpll->dpll_dev =3D NULL; + } + + return rc; +} + +static void sit9531x_dpll_device_unregister(struct sit9531x_dpll *sitdpll) +{ + dpll_device_unregister(sitdpll->dpll_dev, &sitdpll->ops, sitdpll); + dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker); + sitdpll->dpll_dev =3D NULL; +} + +/* + * sit9531x_dpll_alloc - allocate a DPLL device structure + * @sitdev: parent device + * @ch: PLL channel number (0-3) + * + * Return: pointer to allocated DPLL on success, error pointer on error + */ +struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 = ch) +{ + struct sit9531x_dpll *sitdpll; + + sitdpll =3D kzalloc_obj(*sitdpll, GFP_KERNEL); + if (!sitdpll) + return ERR_PTR(-ENOMEM); + + sitdpll->dev =3D sitdev; + sitdpll->id =3D ch; + sitdpll->lock_status =3D DPLL_LOCK_STATUS_UNLOCKED; + INIT_LIST_HEAD(&sitdpll->pins); + + return sitdpll; +} + +/* + * sit9531x_dpll_free - deallocate a DPLL device structure + * @sitdpll: DPLL to free + */ +void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll) +{ + kfree(sitdpll); +} + +/* + * sit9531x_dpll_register - register DPLL device and all its pins + * + * Registers the DPLL device with the subsystem and then registers + * all input and output pins that are connected to this PLL. + */ +int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll) +{ + int rc; + + rc =3D sit9531x_dpll_device_register(sitdpll); + if (rc) + return rc; + + rc =3D sit9531x_dpll_pins_register(sitdpll); + if (rc) { + sit9531x_dpll_device_unregister(sitdpll); + return rc; + } + + return 0; +} + +/* sit9531x_dpll_unregister - unregister DPLL device and its pins */ +void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll) +{ + sit9531x_dpll_pins_unregister(sitdpll); + sit9531x_dpll_device_unregister(sitdpll); +} + +static void sit9531x_dpll_list_free(struct sit9531x_dev *sitdev) +{ + struct sit9531x_dpll *sitdpll, *next; + + list_for_each_entry_safe(sitdpll, next, &sitdev->dplls, list) { + list_del(&sitdpll->list); + sit9531x_dpll_free(sitdpll); + } +} + +/* Runs only once the device is fully started, see the caller. */ +static void sit9531x_dev_dpll_fini(void *ptr) +{ + struct sit9531x_dev *sitdev =3D ptr; + + sit9531x_dev_stop(sitdev); + kthread_destroy_worker(sitdev->kworker); + sit9531x_dpll_list_free(sitdev); +} + +static int sit9531x_devm_dpll_init(struct sit9531x_dev *sitdev) +{ + struct kthread_worker *kworker; + struct sit9531x_dpll *sitdpll; + unsigned int i; + int rc; + + INIT_LIST_HEAD(&sitdev->dplls); + kthread_init_delayed_work(&sitdev->work, sit9531x_dev_periodic_work); + + for (i =3D 0; i < SIT9531X_NUM_PLLS; i++) { + sitdpll =3D sit9531x_dpll_alloc(sitdev, i); + if (IS_ERR(sitdpll)) { + rc =3D dev_err_probe(sitdev->dev, PTR_ERR(sitdpll), + "Failed to alloc DPLL%u\n", i); + goto err_free_dplls; + } + + list_add_tail(&sitdpll->list, &sitdev->dplls); + } + + kworker =3D kthread_run_worker(0, "sit9531x-%s", dev_name(sitdev->dev)); + if (IS_ERR(kworker)) { + rc =3D PTR_ERR(kworker); + goto err_free_dplls; + } + sitdev->kworker =3D kworker; + + rc =3D sit9531x_dev_start(sitdev); + if (rc) { + rc =3D dev_err_probe(sitdev->dev, rc, "Failed to start device\n"); + goto err_destroy_worker; + } + + /* + * Only now is every field the cleanup touches valid, so this is the + * first point at which the action may be registered. On failure it + * runs the action itself, which is correct here and only here. + */ + return devm_add_action_or_reset(sitdev->dev, sit9531x_dev_dpll_fini, + sitdev); + +err_destroy_worker: + kthread_destroy_worker(sitdev->kworker); +err_free_dplls: + sit9531x_dpll_list_free(sitdev); + + return rc; +} + +/* + * sit9531x_read_variant_id - read chip variant ID byte from hardware + * @id: output variant ID byte + * + * Reads the single-byte variant identification register from Page 0 + * reg 0x02 (95317 =3D 0x17, 95316 =3D 0x31). Reg 0x03 holds a separate + * revision byte and is intentionally not consumed here. + */ +static int sit9531x_read_variant_id(struct sit9531x_dev *sitdev, u8 *id) +{ + return sit9531x_read_u8(sitdev, SIT9531X_REG_VARIANT_ID, id); +} + +static const struct sit9531x_chip_info *sit9531x_match_variant(u8 id) +{ + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(sit9531x_chip_ids); i++) { + if (sit9531x_chip_ids[i].id =3D=3D id) + return &sit9531x_chip_ids[i]; + } + + return NULL; +} + +/* + * sit9531x_derive_clock_id - build EUI-64 clock identifier + * + * Generates a deterministic 64-bit identifier from the SiTime OUI, + * the chip ID, and the I2C address. This provides a stable clock_id + * across reboots. + * + * Return: 64-bit clock identifier + */ +static u64 sit9531x_derive_clock_id(struct sit9531x_dev *sitdev) +{ + u64 clkid; + + clkid =3D SIT9531X_OUI << 24; + clkid |=3D (u64)sitdev->info->id << 8; + clkid |=3D (u64)sitdev->client->addr; + + return clkid; +} + +/* + * Board-config overrides for fixed efuse/blob routing the chip registers = do + * not describe unambiguously. Absent properties leave pll_fvco[] zeroed + * (derive from DIVN) and out_pll_map_valid false (use the OUT_MAP registe= rs). + */ +static void sit9531x_parse_board_config(struct sit9531x_dev *sitdev) +{ + u32 map[SIT9531X_MAX_OUTPUTS]; + int n, i; + + device_property_read_u64_array(sitdev->dev, "sitime,pll-fvco", + sitdev->pll_fvco, SIT9531X_NUM_PLLS); + + if (!device_property_present(sitdev->dev, "sitime,output-pll-map")) + return; + + /* + * Any 1..MAX_OUTPUTS length is accepted so the 8-output SiT95317 need + * not pad to 12; variant detection has not run yet and entries past + * the detected num_outputs are never indexed. Trailing entries of a + * short map must read as unmapped rather than 0 (=3D=3D PLLA), which + * would mark unrouted outputs active in sit9531x_out_state_fetch(). + */ + memset(sitdev->out_pll_map, SIT9531X_OUT_PLL_UNMAPPED, + sizeof(sitdev->out_pll_map)); + + n =3D device_property_count_u32(sitdev->dev, "sitime,output-pll-map"); + if (n <=3D 0 || n > SIT9531X_MAX_OUTPUTS || + device_property_read_u32_array(sitdev->dev, "sitime,output-pll-map", + map, n)) + return; + + for (i =3D 0; i < n; i++) + sitdev->out_pll_map[i] =3D map[i]; + sitdev->out_pll_map_valid =3D true; +} + +int sit9531x_dev_probe(struct sit9531x_dev *sitdev) +{ + struct clk *xtal_clk; + u8 variant_id; + int rc; + + /* + * Fvco =3D Fref * (DIVN + frac/2^32) with Fref =3D xtal_freq << doubler, + * so every freq_set and phase_adjust path divides by a rate derived + * from the XO feeding XIN/XO_CLK. + */ + xtal_clk =3D devm_clk_get_enabled(sitdev->dev, "xtal"); + if (IS_ERR(xtal_clk)) + return dev_err_probe(sitdev->dev, PTR_ERR(xtal_clk), + "Failed to get xtal clock\n"); + sitdev->xtal_freq =3D clk_get_rate(xtal_clk); + if (!sitdev->xtal_freq) + return dev_err_probe(sitdev->dev, -EINVAL, + "xtal clock has no rate\n"); + + /* + * Held deasserted, never pulsed: the chip configuration comes from + * efuse or an NVM blob applied before probe, and a reset would + * discard it. Must precede the first I2C access, as a board that + * powers up asserted keeps the chip unreachable until released. + */ + sitdev->reset_gpio =3D devm_gpiod_get_optional(sitdev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(sitdev->reset_gpio)) + return dev_err_probe(sitdev->dev, PTR_ERR(sitdev->reset_gpio), + "Failed to request reset gpio\n"); + if (sitdev->reset_gpio) + fsleep(10000); /* internal boot after release */ + + sit9531x_parse_board_config(sitdev); + + rc =3D sit9531x_read_variant_id(sitdev, &variant_id); + if (rc) + return rc; + + sitdev->info =3D sit9531x_match_variant(variant_id); + if (!sitdev->info) + return dev_err_probe(sitdev->dev, -ENODEV, + "Unknown variant ID: 0x%02x\n", variant_id); + + sitdev->clock_id =3D sit9531x_derive_clock_id(sitdev); + sitdev->intsync_src =3D -1; + + rc =3D devm_mutex_init(sitdev->dev, &sitdev->multiop_lock); + if (rc) + return dev_err_probe(sitdev->dev, rc, + "Failed to initialize mutex\n"); + + /* + * Before the IRQ: the handler reaches sitdev->kworker through + * kthread_mod_delayed_work(), so the worker has to exist before an + * INTRB assertion can land. + */ + rc =3D sit9531x_devm_dpll_init(sitdev); + if (rc) + return rc; + + /* Absent "interrupts" leaves client->irq 0 and the poll in charge. */ + sitdev->irq =3D sitdev->client ? sitdev->client->irq : 0; + if (sitdev->irq > 0) { + rc =3D devm_request_threaded_irq(sitdev->dev, sitdev->irq, + NULL, sit9531x_irq_thread_fn, + IRQF_ONESHOT, + dev_name(sitdev->dev), sitdev); + if (rc) + return dev_err_probe(sitdev->dev, rc, + "Failed to request IRQ %d\n", + sitdev->irq); + } + + return 0; +} + +static int sit9531x_i2c_probe(struct i2c_client *client) +{ + struct sit9531x_dev *sitdev; + struct regmap *regmap; + + regmap =3D devm_regmap_init_i2c(client, &sit9531x_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Failed to initialize regmap\n"); + + sitdev =3D devm_kzalloc(&client->dev, sizeof(*sitdev), GFP_KERNEL); + if (!sitdev) + return -ENOMEM; + + sitdev->dev =3D &client->dev; + sitdev->client =3D client; + sitdev->regmap =3D regmap; + i2c_set_clientdata(client, sitdev); + + return sit9531x_dev_probe(sitdev); +} + +static const struct of_device_id sit9531x_of_match[] =3D { + { .compatible =3D "sitime,sit95316" }, + { .compatible =3D "sitime,sit95317" }, + { } +}; +MODULE_DEVICE_TABLE(of, sit9531x_of_match); + +static struct i2c_driver sit9531x_i2c_driver =3D { + .driver =3D { + .name =3D "sit9531x", + .of_match_table =3D sit9531x_of_match, + }, + .probe =3D sit9531x_i2c_probe, +}; +module_i2c_driver(sit9531x_i2c_driver); + +MODULE_AUTHOR("Ali Rouhi "); +MODULE_AUTHOR("Oleg Zadorozhnyi "); +MODULE_DESCRIPTION("SiTime SiT9531x DPLL subsystem driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h new file mode 100644 index 000000000000..05d4ee0c712e --- /dev/null +++ b/drivers/dpll/sit9531x/core.h @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SiTime SiT9531x DPLL core driver + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + * + * Device structure, register access helpers, and core function + * declarations. + */ + +#ifndef _SIT9531X_CORE_H +#define _SIT9531X_CORE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "regs.h" + +#define SIT9531X_NUM_PLLS 4 +#define SIT9531X_MAX_INPUTS 8 +#define SIT9531X_NUM_INPUT_PAIRS (SIT9531X_MAX_INPUTS / 2) +#define SIT9531X_MAX_OUTPUTS 12 +/* out_pll_map[] entry meaning "this output is not routed to any PLL" */ +#define SIT9531X_OUT_PLL_UNMAPPED 0xFF +/* + * INTSYNC (the inter-PLL sync net) is modelled as two pins. The + * destination PLL that locks to INTSYNC sees an input pin + * (SIT9531X_INTSYNC_PIN_ID, in the input id namespace after the physical + * inputs and the xtal); the source PLL that drives INTSYNC sees an output + * pin (SIT9531X_INTSYNC_OUT_PIN_ID, appended after the physical outputs). + */ +#define SIT9531X_INTSYNC_PIN_ID (SIT9531X_MAX_INPUTS + 1) +#define SIT9531X_INTSYNC_OUT_PIN_ID SIT9531X_MAX_OUTPUTS +#define SIT9531X_NUM_PINS (SIT9531X_MAX_INPUTS + 2 + SIT9531X_MAX_OUTPUTS= + 1) +#define SIT9531X_STATUS_POLL_MS 500 + +/* selected_ref value when the active source is not a registered input */ +#define SIT9531X_REF_INVALID 0xFF + +/* SiTime IEEE OUI for EUI-64 generation */ +#define SIT9531X_OUI 0x0090C2FFFEULL + +struct sit9531x_dpll; + +/* + * struct sit9531x_chip_info - chip variant identification + * @id: variant ID byte read from register + * @num_inputs: number of input clock pins + * @num_outputs: number of output clock pins + * @name: human-readable variant name + * @clkout_map: per-output slot mapping (output index -> physical slot) + */ +struct sit9531x_chip_info { + u8 id; + u8 num_inputs; + u8 num_outputs; + const char *name; + const u8 *clkout_map; +}; + +/* + * enum sit9531x_signal_mode - input signal electrical mode + * @SIT9531X_MODE_SE: single-ended + * @SIT9531X_MODE_DE: differential + */ +enum sit9531x_signal_mode { + SIT9531X_MODE_SE =3D 0, + SIT9531X_MODE_DE, +}; + +/* + * struct sit9531x_ref - input reference state + * @freq: configured frequency in Hz + * @enabled: reference is enabled for monitoring + * @los: loss-of-signal detected + * @oof: out-of-frequency detected + * @pll_mask: bitmask of PLLs this input feeds (bit 0 =3D PLLA) + * @label: board label from DT or default + * @sig_mode: signal mode of the pair this lane belongs to + * (detected from CLKINx_INPUT_MODE at probe) + */ +struct sit9531x_ref { + u32 freq; + bool enabled; + bool los; + bool oof; + u8 pll_mask; + const char *label; + enum sit9531x_signal_mode sig_mode; +}; + +/* + * struct sit9531x_out - output state + * @enabled: output is driving, i.e. not forced into Hi-Z + * @routed: output is mapped to @pll_idx by the initial + * configuration; an unrouted output has no DPLL pin + * @pll_idx: PLL driving this output (0-3) + * @label: board label from DT or default + */ +struct sit9531x_out { + u32 freq; + bool enabled; + bool routed; + u8 pll_idx; + const char *label; +}; + +/* + * struct sit9531x_chan - per-PLL channel state + * @active: PLL has reached its active state; a PLL the loaded + * configuration leaves unused never does, and its + * loss-of-lock bit stays clear because nothing drives it + * @locked: PLL is locked (raw status register bit) + * @mode: 0 =3D sync (outer loop enabled), 1 =3D free-run + * @selected_ref: logical input index of the currently selected + * reference (the INTSYNC net maps to + * SIT9531X_INTSYNC_PIN_ID), or SIT9531X_REF_INVALID + * when the hardware source encoding is reserved + * @inner_lol: PLL inner loop loss-of-lock detected + * @ho_freeze: holdover freeze active + */ +struct sit9531x_chan { + bool active; + bool locked; + u8 mode; + u8 selected_ref; + bool inner_lol; + bool ho_freeze; +}; + +/* + * struct sit9531x_dev - SiT9531x device instance + * @info: detected chip variant info + * @multiop_lock: mutex for multi-register atomic operations + * @ref: array of input reference states + * @out: array of output states + * @chan: array of per-PLL channel states + * @xtal_freq: crystal oscillator frequency in Hz + * @kworker: kthread worker for periodic polling + * @work: delayed work for periodic state checks + * @clock_id: IEEE 1588 EUI-64 clock identifier + * @reset_gpio: optional reset line (DT "reset-gpios"), NULL if absent + * @irq: optional INTRB IRQ number (from DT "interrupts" via the + * I2C client), 0 if no IRQ is wired + * @pll_fvco: optional per-PLL VCO in Hz from DT + * "sitime,pll-fvco"; 0 means derive from DIVN + * @out_pll_map: optional per-output source PLL (0-3, 0xff =3D + * unmapped) from DT "sitime,output-pll-map" + * @out_pll_map_valid: true when out_pll_map[] was populated from DT; + * false means use the chip's OUT_MAP registers + * @intsync_src: PLL index currently sourcing inter-PLL + * synchronization (INTSYNC), or -1 when disabled + */ +struct sit9531x_dev { + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + const struct sit9531x_chip_info *info; + /* Serializes multi-step register sequences */ + struct mutex multiop_lock; + + /* Hardware state */ + struct sit9531x_ref ref[SIT9531X_MAX_INPUTS + 1]; /* +1 for xtal */ + struct sit9531x_out out[SIT9531X_MAX_OUTPUTS]; + struct sit9531x_chan chan[SIT9531X_NUM_PLLS]; + u32 xtal_freq; + + /* DPLL channels */ + struct list_head dplls; + + /* Monitor */ + struct kthread_worker *kworker; + struct kthread_delayed_work work; + + /* Device identity */ + u64 clock_id; + + /* Optional DT-described GPIO / IRQ lines */ + struct gpio_desc *reset_gpio; + int irq; + + /* Optional DT board-config overrides */ + u64 pll_fvco[SIT9531X_NUM_PLLS]; + u8 out_pll_map[SIT9531X_MAX_OUTPUTS]; + bool out_pll_map_valid; + + /* Inter-PLL synchronization state */ + s8 intsync_src; + +}; + +extern const struct regmap_config sit9531x_regmap_config; + +/* ---- Core lifecycle ---- */ +int sit9531x_dev_probe(struct sit9531x_dev *sitdev); +int sit9531x_dev_start(struct sit9531x_dev *sitdev); +void sit9531x_dev_stop(struct sit9531x_dev *sitdev); + +/* ---- Register access ---- */ +int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg, + u8 *val); +int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg, + u8 val); +int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 *val); +int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 val); +int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 offset, u8 mask, u8 val); + +/* ---- Input enable/disable ---- */ +int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index); +int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index); + +/* ---- Input priority ---- */ +int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx, u8 prio); +int sit9531x_input_prio_get(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx, u8 *prio); +int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx); +int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 input_idx); + +/* ---- Output enable/disable (Hi-Z control) ---- */ +int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index); +int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index); + +/* ---- Output frequency ---- */ +int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx, + u8 pll_idx, u64 frequency); +int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx, + u64 *frequency); + +/* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */ +int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev, + u8 out_idx, s32 phase_ps); + +/* ---- Notification clear ---- */ +int sit9531x_clear_notifications(struct sit9531x_dev *sitdev); + +/* ---- INTSYNC (inter-PLL synchronization) ---- */ +int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx); +int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx); + +/* ---- Output pulse control ---- */ +int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev, + u8 out_idx, u8 pulse_ctrl); + +/* ---- Phase offset (TDC readback) ---- */ +int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo= ); +int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *phase_ps); + +/* ---- State helpers ---- */ + +/* + * sit9531x_pll_page - get register page for PLL index + * @pll_idx: PLL index (0 =3D PLLA, 3 =3D PLLD) + */ +static inline u8 sit9531x_pll_page(u8 pll_idx) +{ + return SIT9531X_PAGE_PLLA + pll_idx; +} + +/* + * Logical input pins are interleaved: even index =3D P lane, odd + * index =3D N lane of pair index/2 (IN0P, IN0N, IN1P, IN1N, ...). + * Index SIT9531X_MAX_INPUTS is the XO input. + */ + +/* + * sit9531x_input_pair - get input pair number for a logical input index + * @index: logical input pin index + */ +static inline u8 sit9531x_input_pair(u8 index) +{ + return index >> 1; +} + +/* + * sit9531x_input_is_n - check if a logical input index is an N lane + * @index: logical input pin index + */ +static inline bool sit9531x_input_is_n(u8 index) +{ + return index & 1; +} + +/* + * sit9531x_input_hw_src - translate logical input index to source encoding + * @index: logical input pin index + * + * The priority table and CLK_ACTIVESEL registers use a non-contiguous + * source encoding: 0-3 =3D CLK0P..CLK3P, 5 =3D OCXO, 6 =3D INTSYNC, + * 7-10 =3D CLK0N..CLK3N. + */ +static inline u8 sit9531x_input_hw_src(u8 index) +{ + if (index =3D=3D SIT9531X_MAX_INPUTS) + return SIT9531X_PRIO_SRC_OCXO; + if (index =3D=3D SIT9531X_INTSYNC_PIN_ID) + return SIT9531X_PRIO_SRC_INTSYNC; + if (sit9531x_input_is_n(index)) + return SIT9531X_PRIO_SRC_N_BASE + sit9531x_input_pair(index); + return sit9531x_input_pair(index); +} + +/* + * sit9531x_hw_src_input - translate source encoding to logical input index + * @src: 4-bit hardware source encoding + * + * Return: logical input index (INTSYNC maps to SIT9531X_INTSYNC_PIN_ID), + * or SIT9531X_REF_INVALID if @src is a reserved value + */ +static inline u8 sit9531x_hw_src_input(u8 src) +{ + if (src < SIT9531X_NUM_INPUT_PAIRS) + return src * 2; + if (src =3D=3D SIT9531X_PRIO_SRC_OCXO) + return SIT9531X_MAX_INPUTS; + if (src =3D=3D SIT9531X_PRIO_SRC_INTSYNC) + return SIT9531X_INTSYNC_PIN_ID; + if (src >=3D SIT9531X_PRIO_SRC_N_BASE && + src < SIT9531X_PRIO_SRC_N_BASE + SIT9531X_NUM_INPUT_PAIRS) + return (src - SIT9531X_PRIO_SRC_N_BASE) * 2 + 1; + return SIT9531X_REF_INVALID; +} + +/* + * sit9531x_ref_state_get - get reference state by index + * @index: logical input index + * + * Return: pointer to the cached input reference state + */ +static inline const struct sit9531x_ref * +sit9531x_ref_state_get(const struct sit9531x_dev *sitdev, u8 index) +{ + return &sitdev->ref[index]; +} + +/* + * sit9531x_out_state_get - get output state by index + * @index: logical output index + * + * Return: pointer to the cached output state + */ +static inline const struct sit9531x_out * +sit9531x_out_state_get(const struct sit9531x_dev *sitdev, u8 index) +{ + return &sitdev->out[index]; +} + +/* + * sit9531x_chan_state_get - get channel state by PLL index + * + * Return: pointer to the cached per-PLL channel state + */ +static inline const struct sit9531x_chan * +sit9531x_chan_state_get(const struct sit9531x_dev *sitdev, u8 pll_idx) +{ + return &sitdev->chan[pll_idx]; +} + +#endif /* _SIT9531X_CORE_H */ diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c new file mode 100644 index 000000000000..e258beceffdd --- /dev/null +++ b/drivers/dpll/sit9531x/dpll.c @@ -0,0 +1,1232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiTime SiT9531x DPLL subsystem callbacks and registration + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + * + * DPLL device ops, pin ops (separate input/output), pin registration, + * and periodic change detection. + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "dpll.h" +#include "prop.h" +#include "regs.h" + +#define SIT9531X_ESYNC_FREQ_10MHZ 10000000ULL +#define SIT9531X_ESYNC_PULSE_DEFAULT 50 + +static const struct dpll_pin_frequency sit9531x_esync_ranges[] =3D { + DPLL_PIN_FREQUENCY(0), + DPLL_PIN_FREQUENCY(SIT9531X_ESYNC_FREQ_10MHZ), +}; + +static inline bool +sit9531x_dpll_esync_pin_supported(const struct sit9531x_dpll_pin *dpin) +{ + return dpin->esync_control; +} + +static inline bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_p= in *pin) +{ + return pin->dir =3D=3D DPLL_PIN_DIRECTION_INPUT; +} + +static inline bool +sit9531x_dpll_is_intsync_pin(const struct sit9531x_dpll_pin *pin) +{ + return sit9531x_dpll_is_input_pin(pin) && + pin->id =3D=3D SIT9531X_INTSYNC_PIN_ID; +} + +static inline bool +sit9531x_dpll_is_intsync_src_pin(const struct sit9531x_dpll_pin *pin) +{ + return !sit9531x_dpll_is_input_pin(pin) && + pin->id =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID; +} + +static inline bool +sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin) +{ + return sit9531x_dpll_is_input_pin(pin) && + pin->id =3D=3D SIT9531X_MAX_INPUTS; +} + +/* + * The cached state this reports comes from the outer loss-of-lock byte + * (page 0, reg 0x06), the PLL mode bit (PLL page, reg 0x31), inner LOL + * (reg 0x92) and the holdover freeze byte (reg 0x0A). + */ +static int +sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_p= riv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + const struct sit9531x_chan *chan; + + if (status_error) + *status_error =3D DPLL_LOCK_STATUS_ERROR_NONE; + + chan =3D sit9531x_chan_state_get(sitdev, sitdpll->id); + + mutex_lock(&sitdev->multiop_lock); + + if (!chan->active) { + /* + * A PLL the loaded configuration leaves unused never reaches + * its active state. Nothing drives its loss-of-lock bit, so + * without this it would report a lock it does not have. + */ + *status =3D DPLL_LOCK_STATUS_UNLOCKED; + } else if (chan->locked) { + if (chan->mode) + *status =3D DPLL_LOCK_STATUS_LOCKED; /* free-run */ + else + *status =3D DPLL_LOCK_STATUS_LOCKED_HO_ACQ; /* sync */ + } else if (chan->ho_freeze) { + *status =3D DPLL_LOCK_STATUS_HOLDOVER; + } else { + *status =3D DPLL_LOCK_STATUS_UNLOCKED; + } + + /* Report inner LOL as an error condition */ + if (status_error && chan->inner_lol) + *status_error =3D DPLL_LOCK_STATUS_ERROR_UNDEFINED; + + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * sit9531x_dpll_mode_get - report current PLL operating mode + * + * reads outer loop disable bit (PLL page reg 0x31[5]). + * Free-run -> MANUAL, sync -> AUTOMATIC. + */ +static int +sit9531x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_mode *mode, struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + const struct sit9531x_chan *chan; + + chan =3D sit9531x_chan_state_get(sitdev, sitdpll->id); + + mutex_lock(&sitdev->multiop_lock); + *mode =3D chan->mode ? DPLL_MODE_MANUAL : DPLL_MODE_AUTOMATIC; + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * sit9531x_dpll_mode_set - switch PLL between free-run and sync mode + * + * writes PLL page reg 0x31[5] to enable/disable the + * outer loop, then triggers a small update via reg 0x0F. + * + * The driver maps AUTOMATIC to sync mode (outer loop enabled; the PLL + * selects its reference automatically from the priority table) and + * MANUAL to free-run (outer loop disabled via bit 0x31[5]). The chip + * also has a separate manual-active-select path (MANUAL_ACTIVE_SEL_PLL + * plus the per-PLL man_in_sel registers) that can pin one reference + * while still syncing; that path is not wired to a DPLL callback here. + */ +static int +sit9531x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_mode mode, struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + u8 val; + int rc; + + /* + * Outer loop disable bit: + * 0 =3D sync mode (outer loop enabled) -> AUTOMATIC + * 1 =3D free-run (outer loop disabled) -> MANUAL + */ + val =3D (mode =3D=3D DPLL_MODE_MANUAL) ? SIT9531X_PLL_STATUS_OUTER_DIS : = 0; + + mutex_lock(&sitdev->multiop_lock); + + rc =3D sit9531x_update_pll_u8(sitdev, sitdpll->id, + SIT9531X_PLL_REG_STATUS, + SIT9531X_PLL_STATUS_OUTER_DIS, val); + if (rc) { + NL_SET_ERR_MSG(extack, "Failed to write PLL mode register"); + goto unlock; + } + + /* Trigger small update to apply without full NVM cycle */ + rc =3D sit9531x_write_pll_u8(sitdev, sitdpll->id, + SIT9531X_PLL_REG_SMALL_UPDATE, + SIT9531X_SMALL_UPDATE_CMD); + if (rc) { + NL_SET_ERR_MSG(extack, "Failed to trigger small update"); + goto unlock; + } + + /* + * Keep the cached mode in step with the register. The periodic + * monitor refreshes it too, but mode_get and the reference-selection + * path read this cache and would otherwise report or act on the + * pre-switch mode until the next poll. + */ + sitdev->chan[sitdpll->id].mode =3D !!val; + +unlock: + mutex_unlock(&sitdev->multiop_lock); + + return rc; +} + +static int +sit9531x_dpll_supported_modes_get(const struct dpll_device *dpll, + void *dpll_priv, unsigned long *modes, + struct netlink_ext_ack *extack) +{ + __set_bit(DPLL_MODE_AUTOMATIC, modes); + __set_bit(DPLL_MODE_MANUAL, modes); + + return 0; +} + +const struct dpll_device_ops sit9531x_dpll_device_ops =3D { + .lock_status_get =3D sit9531x_dpll_lock_status_get, + .mode_get =3D sit9531x_dpll_mode_get, + .mode_set =3D sit9531x_dpll_mode_set, + .supported_modes_get =3D sit9531x_dpll_supported_modes_get, + /* temp_get not available -- SiT9531x has no on-die temp sensor */ +}; + +static int +sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + *direction =3D DPLL_PIN_DIRECTION_INPUT; + return 0; +} + +/* + * sit9531x_dpll_input_pin_frequency_get - read input pin frequency + * + * returns cached frequency from DT or last set. + */ +static int +sit9531x_dpll_input_pin_frequency_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + const struct sit9531x_ref *ref; + + ref =3D sit9531x_ref_state_get(sitdpll->dev, dpin->id); + *frequency =3D ref->freq; + + return 0; +} + +/* + * sit9531x_dpll_input_pin_state_on_dpll_get - get input pin DPLL state + * + * determines pin state from channel state: connected + * if this input is the selected reference on a locked PLL, selectable + * if enabled in automatic mode, disconnected otherwise. + */ +static int +sit9531x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + const struct sit9531x_chan *chan; + const struct sit9531x_ref *ref; + + chan =3D sit9531x_chan_state_get(sitdev, sitdpll->id); + ref =3D sit9531x_ref_state_get(sitdev, dpin->id); + + mutex_lock(&sitdev->multiop_lock); + + /* + * Report CONNECTED only when the PLL is genuinely locked to this + * input. chan->locked already excludes a PLL the configuration + * leaves unused, which would otherwise claim its default source as + * connected right after probe; the inner loop has to be locked as + * well before the reference can be called connected. + */ + if (chan->locked && !chan->inner_lol && + chan->selected_ref =3D=3D dpin->id) + *state =3D DPLL_PIN_STATE_CONNECTED; + /* + * Selectable means this DPLL may choose the input, so it has to be + * in this DPLL's priority table -- ref->enabled only says the + * receiver is powered, which is shared by every DPLL fed from the + * same lane. Signal quality is reported through the pin's own + * attributes rather than by demoting the state, so a lane that is + * routed here but momentarily in LOS stays selectable. + */ + else if (!chan->mode && (ref->pll_mask & BIT(sitdpll->id))) + *state =3D DPLL_PIN_STATE_SELECTABLE; + else + *state =3D DPLL_PIN_STATE_DISCONNECTED; + + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * sit9531x_dpll_input_pin_state_on_dpll_set - set input pin DPLL state + * + * Enables or disables the physical input receiver via Page 0x02 + * force/state registers (sit9531x_input_disable/enable()) and updates + * this DPLL's Page 1 priority table so the state is honoured by the + * PLL's automatic reference selection, not just at the input buffer. + * DISCONNECTED -> drop the input from this DPLL's priority table and + * release this DPLL's claim on the input + * SELECTABLE -> claim the input for this DPLL and make it eligible + * again in the priority table + * CONNECTED -> same as SELECTABLE (the PLL auto-switch logic does + * the actual selection) + * + * The priority table is per PLL, so it is always updated for this DPLL. + * A single physical input feeds every DPLL, so the hardware receiver is + * only cut off once the last DPLL has released it: ref->pll_mask tracks + * which DPLLs currently claim the input, and the physical disable + * happens on the transition to an empty mask. + */ +static int +sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + struct sit9531x_ref *ref =3D &sitdev->ref[dpin->id]; + u8 hw_src =3D sit9531x_input_hw_src(dpin->id); + u8 pll_bit =3D BIT(sitdpll->id); + int rc; + + mutex_lock(&sitdev->multiop_lock); + + switch (state) { + case DPLL_PIN_STATE_DISCONNECTED: + rc =3D sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src); + if (rc) + break; + ref->pll_mask &=3D ~pll_bit; + if (ref->pll_mask) + rc =3D 0; /* another DPLL still uses this input */ + else + rc =3D sit9531x_input_disable(sitdev, dpin->id); + break; + case DPLL_PIN_STATE_CONNECTED: + /* + * CONNECTED asks for this input and no other. The chip has + * no such mode: MANUAL disables the outer loop rather than + * pinning a reference, so honouring the request would mean + * emptying the priority table of every other source, which + * is not what the caller asked for. Refuse instead of + * quietly behaving like SELECTABLE. + */ + NL_SET_ERR_MSG(extack, + "Device selects its reference by priority; use selectable"); + rc =3D -EOPNOTSUPP; + break; + case DPLL_PIN_STATE_SELECTABLE: + rc =3D sit9531x_input_enable(sitdev, dpin->id); + if (rc) + break; + rc =3D sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src); + if (rc) + break; + /* + * Claim the input for this DPLL only once it is both enabled + * and present in the priority table. Setting the mask before + * prio_add would leak the claim if prio_add failed, keeping the + * shared input receiver powered even after every DPLL released + * it. + */ + ref->pll_mask |=3D pll_bit; + break; + default: + rc =3D -EINVAL; + break; + } + + mutex_unlock(&sitdev->multiop_lock); + + if (rc) + NL_SET_ERR_MSG(extack, "Failed to set input pin state"); + + return rc; +} + +/* + * sit9531x_dpll_input_pin_prio_get - read input pin priority + * + * reads the PLL's priority table on Page 1 (via + * sit9531x_input_prio_get()) and returns the slot the input + * occupies, so pin-get reports the real hardware priority rather + * than a software default. + */ +static int +sit9531x_dpll_input_pin_prio_get(const struct dpll_pin *pin, void *pin_pri= v, + const struct dpll_device *dpll, void *dpll_priv, + u32 *prio, struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + u8 slot; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_input_prio_get(sitdev, sitdpll->id, + sit9531x_input_hw_src(dpin->id), &slot); + mutex_unlock(&sitdev->multiop_lock); + if (rc) + return rc; + + dpin->prio =3D slot; + *prio =3D slot; + return 0; +} + +/* + * sit9531x_dpll_input_pin_prio_set - set input pin priority + * + * writes input priority table on Page 1 via + * core.c sit9531x_input_prio_set(). Forces holdover during update. + */ +static int +sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_pri= v, + const struct dpll_device *dpll, void *dpll_priv, + u32 prio, struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + if (dpin->dir !=3D DPLL_PIN_DIRECTION_INPUT) { + NL_SET_ERR_MSG(extack, "Priority applies only to input pins"); + return -EINVAL; + } + + if (prio >=3D SIT9531X_PRIO_MAX_SLOTS) { + NL_SET_ERR_MSG(extack, "Priority out of range (0-10)"); + return -EINVAL; + } + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_input_prio_set(sitdev, sitdpll->id, + sit9531x_input_hw_src(dpin->id), + (u8)prio); + mutex_unlock(&sitdev->multiop_lock); + + if (rc) { + NL_SET_ERR_MSG(extack, "Failed to set input priority"); + return rc; + } + + dpin->prio =3D (u8)prio; + + return 0; +} + +/* + * sit9531x_dpll_input_pin_phase_adjust_get - read phase adjustment + * + * returns cached phase adjustment value (in ps). + */ +static int +sit9531x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + + *phase_adjust =3D dpin->phase_adjust; + return 0; +} + +/* + * sit9531x_dpll_input_pin_phase_offset_get - read phase offset + * + * reads the TDC (Time-to-Digital Converter) hardware + * to measure the phase difference in picoseconds via + * sit9531x_phase_offset_read(). + */ +/* + * sit9531x_dpll_input_pin_ffo_get - read the input's frequency offset + * + * The offset is derived from how far the PLL's running DIVN sits from + * its configured one, which only says something about the reference the + * PLL is actually tracking. For every other input there is no + * measurement, and -ENODATA leaves the attribute out rather than + * reporting the active reference's figure against the wrong pin. + */ +static int +sit9531x_dpll_input_pin_ffo_get(const struct dpll_pin *pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + if (sitdev->chan[sitdpll->id].selected_ref !=3D dpin->id) + return -ENODATA; + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_pll_ffo_ppt(sitdev, sitdpll->id, &ffo->ffo); + mutex_unlock(&sitdev->multiop_lock); + + return rc; +} + +static int +sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s64 *phase_offset, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + s64 offset; + int rc; + + mutex_lock(&sitdev->multiop_lock); + + /* + * The on-chip TDC is a per-PLL resource that always measures the + * phase difference between the VCO and the PLL's currently + * selected reference; it cannot be pointed at an arbitrary input. + * For any input that is not the active reference there is no + * meaningful per-pin phase offset, so report 0 instead of the + * active reference's value. + */ + if (sitdev->chan[sitdpll->id].selected_ref !=3D dpin->id) { + mutex_unlock(&sitdev->multiop_lock); + dpin->phase_offset =3D 0; + *phase_offset =3D 0; + return 0; + } + + rc =3D sit9531x_phase_offset_read(sitdev, sitdpll->id, &offset); + mutex_unlock(&sitdev->multiop_lock); + + /* + * -ENODEV means the PLL has no programmed DIVN (unused on this + * board); report phase_offset =3D 0 so a full pin-get dump does not + * fail just because one DPLL is dormant. + */ + if (rc =3D=3D -ENODEV) { + dpin->phase_offset =3D 0; + *phase_offset =3D 0; + return 0; + } + if (rc) { + NL_SET_ERR_MSG(extack, "TDC phase readback failed"); + return rc; + } + + /* + * The ABI reports phase offset in units of 1/DPLL_PHASE_OFFSET_DIVIDER + * picoseconds: the integer part of the attribute is the value divided + * by the divider, the remainder is the fraction. The TDC resolves one + * VCO period (hundreds of picoseconds), so the fractional digits are + * always zero here, but the magnitude still has to be scaled or every + * reading would be reported a thousand times too small. + */ + offset *=3D DPLL_PHASE_OFFSET_DIVIDER; + + dpin->phase_offset =3D offset; + *phase_offset =3D offset; + return 0; +} + +static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops =3D { + .direction_get =3D sit9531x_dpll_input_pin_direction_get, + .frequency_get =3D sit9531x_dpll_input_pin_frequency_get, + .state_on_dpll_get =3D sit9531x_dpll_input_pin_state_on_dpll_get, + .state_on_dpll_set =3D sit9531x_dpll_input_pin_state_on_dpll_set, + .prio_get =3D sit9531x_dpll_input_pin_prio_get, + .prio_set =3D sit9531x_dpll_input_pin_prio_set, + .phase_adjust_get =3D sit9531x_dpll_input_pin_phase_adjust_get, + .phase_offset_get =3D sit9531x_dpll_input_pin_phase_offset_get, + /* + * The measurement compares the PLL's running feedback divider with + * its configured one, so it describes the device's own reference + * rather than a port rate. + */ + .supported_ffo =3D BIT(DPLL_FFO_PIN_DEVICE), + .ffo_get =3D sit9531x_dpll_input_pin_ffo_get, +}; + +/* + * INTSYNC pin ops + * + * INTSYNC is the chip's inter-PLL sync net: one PLL drives it and other + * PLLs may lock to it instead of to an external reference. The two + * roles are exposed as two separate pins so neither overloads the other: + * + * - a source (output) pin registered on every DPLL. Connecting it on a + * DPLL makes that DPLL drive INTSYNC; only one DPLL may drive it at a + * time. It has no priority ops -- driving the net is not a reference + * selection. + * - a destination (input) pin registered on every DPLL. Connecting it + * on a DPLL makes that DPLL eligible to lock to INTSYNC as a + * reference, so it carries the priority ops. + */ + +/* ---- INTSYNC source (output) pin ---- */ + +/* The INTSYNC source pin is an output; its direction_get is defined below= . */ +static int +sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack); + +static int +sit9531x_dpll_intsync_src_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + + mutex_lock(&sitdev->multiop_lock); + if (sitdev->intsync_src =3D=3D sitdpll->id) + *state =3D DPLL_PIN_STATE_CONNECTED; + else + *state =3D DPLL_PIN_STATE_DISCONNECTED; + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * sit9531x_dpll_intsync_src_state_on_dpll_set - drive INTSYNC from a PLL + * + * CONNECTED -> this PLL drives the INTSYNC net + * DISCONNECTED -> stop driving INTSYNC if this PLL drives it + * + * SELECTABLE is rejected: driving the net is an explicit output routing, + * not an automatic-selection candidate, matching the regular output pin. + */ +static int +sit9531x_dpll_intsync_src_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc =3D 0; + u8 prio; + + mutex_lock(&sitdev->multiop_lock); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (sitdev->intsync_src =3D=3D sitdpll->id) + break; + if (sitdev->intsync_src >=3D 0) { + NL_SET_ERR_MSG(extack, + "INTSYNC is already sourced by another PLL"); + rc =3D -EBUSY; + break; + } + /* + * A PLL that already lists INTSYNC among its references must + * not also drive it: the destination side refuses the mirror + * of this, and without the check here the net could be routed + * back into the PLL feeding it. + */ + rc =3D sit9531x_input_prio_get(sitdev, sitdpll->id, + sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID), + &prio); + if (rc) + break; + if (prio < SIT9531X_PRIO_MAX_SLOTS) { + NL_SET_ERR_MSG(extack, + "PLL selects INTSYNC as a reference; it cannot drive it"); + rc =3D -EBUSY; + break; + } + rc =3D sit9531x_intsync_enable(sitdev, sitdpll->id); + if (!rc) + sitdev->intsync_src =3D sitdpll->id; + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (sitdev->intsync_src !=3D sitdpll->id) + break; + rc =3D sit9531x_intsync_disable(sitdev, sitdpll->id); + if (!rc) + sitdev->intsync_src =3D -1; + break; + default: + rc =3D -EINVAL; + break; + } + + mutex_unlock(&sitdev->multiop_lock); + + if (rc && rc !=3D -EBUSY) + NL_SET_ERR_MSG(extack, "Failed to set INTSYNC source state"); + + return rc; +} + +static const struct dpll_pin_ops sit9531x_dpll_intsync_src_pin_ops =3D { + .direction_get =3D sit9531x_dpll_output_pin_direction_get, + .state_on_dpll_get =3D sit9531x_dpll_intsync_src_state_on_dpll_get, + .state_on_dpll_set =3D sit9531x_dpll_intsync_src_state_on_dpll_set, +}; + +/* ---- INTSYNC destination (input) pin ---- */ + +/* + * sit9531x_dpll_intsync_dst_state_on_dpll_get - INTSYNC reference state + * + * CONNECTED when this PLL is locked to INTSYNC as its reference, + * SELECTABLE when it runs in automatic mode and a source PLL is driving + * INTSYNC, DISCONNECTED otherwise. The PLL that drives INTSYNC can never + * be its own destination. + */ +static int +sit9531x_dpll_intsync_dst_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + const struct sit9531x_chan *chan; + + chan =3D sit9531x_chan_state_get(sitdev, sitdpll->id); + + mutex_lock(&sitdev->multiop_lock); + if (sitdev->intsync_src =3D=3D sitdpll->id) + *state =3D DPLL_PIN_STATE_DISCONNECTED; + else if (chan->locked && !chan->inner_lol && + chan->selected_ref =3D=3D SIT9531X_INTSYNC_PIN_ID) + *state =3D DPLL_PIN_STATE_CONNECTED; + else if (!chan->mode && sitdev->intsync_src >=3D 0) + *state =3D DPLL_PIN_STATE_SELECTABLE; + else + *state =3D DPLL_PIN_STATE_DISCONNECTED; + mutex_unlock(&sitdev->multiop_lock); + + return 0; +} + +/* + * sit9531x_dpll_intsync_dst_state_on_dpll_set - lock a PLL to INTSYNC + * + * CONNECTED/SELECTABLE -> add INTSYNC to this PLL's priority table + * DISCONNECTED -> drop INTSYNC from this PLL's priority table + * + * INTSYNC is an internal net with no physical receiver, so only the + * per-PLL priority table is touched; the source pin controls generation. + */ +static int +sit9531x_dpll_intsync_dst_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + u8 hw_src =3D sit9531x_input_hw_src(SIT9531X_INTSYNC_PIN_ID); + int rc; + + mutex_lock(&sitdev->multiop_lock); + + switch (state) { + case DPLL_PIN_STATE_DISCONNECTED: + rc =3D sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src); + break; + case DPLL_PIN_STATE_SELECTABLE: + case DPLL_PIN_STATE_CONNECTED: + if (sitdev->intsync_src =3D=3D sitdpll->id) { + NL_SET_ERR_MSG(extack, + "PLL cannot lock to the INTSYNC it drives"); + rc =3D -EINVAL; + break; + } + rc =3D sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src); + break; + default: + rc =3D -EINVAL; + break; + } + + mutex_unlock(&sitdev->multiop_lock); + + if (rc && rc !=3D -EINVAL) + NL_SET_ERR_MSG(extack, "Failed to set INTSYNC input state"); + + return rc; +} + +/* + * Do not add .frequency_get / the generic input state getter here: the + * destination pin id is SIT9531X_INTSYNC_PIN_ID, one past the end of the + * ref[] array (INTSYNC is an internal net with no ref[] entry). The ops + * below only ever key on chan[] and the priority table, never ref[id]. + */ +static const struct dpll_pin_ops sit9531x_dpll_intsync_dst_pin_ops =3D { + .direction_get =3D sit9531x_dpll_input_pin_direction_get, + .state_on_dpll_get =3D sit9531x_dpll_intsync_dst_state_on_dpll_get, + .state_on_dpll_set =3D sit9531x_dpll_intsync_dst_state_on_dpll_set, + .prio_get =3D sit9531x_dpll_input_pin_prio_get, + .prio_set =3D sit9531x_dpll_input_pin_prio_set, +}; + +/* + * XO (crystal oscillator) pin ops + * + * The XO is the chip's internal reference oscillator that feeds every + * PLL. It is exposed so userspace can see the on-chip reference, but it + * cannot be routed or disconnected, so it is reported permanently + * connected and offers no state_on_dpll_set / prio ops. + */ + +static int +sit9531x_dpll_xo_pin_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + *state =3D DPLL_PIN_STATE_CONNECTED; + return 0; +} + +static const struct dpll_pin_ops sit9531x_dpll_xo_pin_ops =3D { + .direction_get =3D sit9531x_dpll_input_pin_direction_get, + .frequency_get =3D sit9531x_dpll_input_pin_frequency_get, + .state_on_dpll_get =3D sit9531x_dpll_xo_pin_state_on_dpll_get, +}; + +static int +sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + *direction =3D DPLL_PIN_DIRECTION_OUTPUT; + return 0; +} + +/* + * sit9531x_dpll_output_pin_frequency_get - read output pin frequency + * + * Reads the DIVO divider back from the chip and computes the live + * frequency as Fvco / DIVO. Falls back to the cached value when the + * output is not resolvable through the divider chain (e.g. not mapped + * to a PLL), so a netlink dump never fails on such pins. + */ +static int +sit9531x_dpll_output_pin_frequency_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_output_freq_get(sitdev, dpin->id, frequency); + mutex_unlock(&sitdev->multiop_lock); + + if (rc) + *frequency =3D sit9531x_out_state_get(sitdev, dpin->id)->freq; + + return 0; +} + +/* + * sit9531x_dpll_output_pin_frequency_set - set output pin frequency + * + * computes DIVO =3D Fvco / frequency and writes the + * 34-bit output divider to the output system registers via + * sit9531x_output_freq_set(). + */ +static int +sit9531x_dpll_output_pin_frequency_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + u8 actual_pll; + int rc; + + /* + * Read the PLL that drives this output from its OUT_MAP state + * (populated by out_state_fetch from the chip's OUT_MAP registers). + * That is the index the output register programming below is keyed + * by; the output is registered under the DPLL matching this PLL. + */ + actual_pll =3D sitdev->out[dpin->id].pll_idx; + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_output_freq_set(sitdev, dpin->id, actual_pll, + frequency); + mutex_unlock(&sitdev->multiop_lock); + + if (rc) + NL_SET_ERR_MSG(extack, "Output frequency set failed"); + + return rc; +} + +/* + * sit9531x_dpll_output_pin_state_on_dpll_get - get output pin state + * + * reports CONNECTED when the output is driven and + * DISCONNECTED when it has been muted via sit9531x_output_disable(). + */ +static int +sit9531x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + const struct sit9531x_out *out; + + out =3D sit9531x_out_state_get(sitdpll->dev, dpin->id); + *state =3D out->enabled ? DPLL_PIN_STATE_CONNECTED + : DPLL_PIN_STATE_DISCONNECTED; + return 0; +} + +/* + * sit9531x_dpll_output_pin_state_on_dpll_set - mute/un-mute an output + * + * forces Hi-Z on the output pin via the Page 0x03 + * force/state register pair. + * CONNECTED -> enable (release force, back to factory default) + * DISCONNECTED -> disable (force Hi-Z) + */ +static int +sit9531x_dpll_output_pin_state_on_dpll_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + rc =3D sit9531x_output_enable(sitdev, dpin->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + rc =3D sit9531x_output_disable(sitdev, dpin->id); + break; + default: + rc =3D -EINVAL; + break; + } + + mutex_unlock(&sitdev->multiop_lock); + + if (rc) + NL_SET_ERR_MSG(extack, "Failed to set output pin state"); + + return rc; +} + +/* + * sit9531x_dpll_output_pin_phase_adjust_get - read output phase adjustment + * + * returns cached value. + */ +static int +sit9531x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + + *phase_adjust =3D dpin->phase_adjust; + return 0; +} + +/* + * sit9531x_dpll_output_pin_phase_adjust_set - set output phase adjustment + * + * Programs the per-output PRG_RST_DELAY registers for deterministic + * phase offset; see sit9531x_output_phase_adjust_set() in core.c. + */ +static int +sit9531x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s32 phase_adjust, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + int rc; + + mutex_lock(&sitdev->multiop_lock); + rc =3D sit9531x_output_phase_adjust_set(sitdev, dpin->id, phase_adjust); + mutex_unlock(&sitdev->multiop_lock); + + if (rc) { + NL_SET_ERR_MSG(extack, "Phase adjust failed"); + return rc; + } + + dpin->phase_adjust =3D phase_adjust; + return 0; +} + +static int +sit9531x_dpll_output_pin_esync_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + struct dpll_pin_esync *esync, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + + if (!sit9531x_dpll_esync_pin_supported(dpin)) + return -EOPNOTSUPP; + + esync->range =3D sit9531x_esync_ranges; + esync->range_num =3D ARRAY_SIZE(sit9531x_esync_ranges); + esync->pulse =3D SIT9531X_ESYNC_PULSE_DEFAULT; + esync->freq =3D dpin->esync_freq; + + return 0; +} + +static int +sit9531x_dpll_output_pin_esync_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + u64 freq, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin =3D pin_priv; + struct sit9531x_dpll *sitdpll =3D dpll_priv; + struct sit9531x_dev *sitdev =3D sitdpll->dev; + u8 actual_pll; + int rc; + + if (!sit9531x_dpll_esync_pin_supported(dpin)) { + NL_SET_ERR_MSG(extack, + "Embedded sync not enabled for this pin"); + return -EOPNOTSUPP; + } + + actual_pll =3D sitdev->out[dpin->id].pll_idx; + + mutex_lock(&sitdev->multiop_lock); + + /* + * This output is a dedicated embedded-sync pin. + * Treat freq=3D0 as a request to disable the entire output. + */ + if (!freq) { + rc =3D sit9531x_output_disable(sitdev, dpin->id); + if (!rc) + dpin->esync_freq =3D 0; + mutex_unlock(&sitdev->multiop_lock); + return rc; + } + + if (freq !=3D SIT9531X_ESYNC_FREQ_10MHZ) { + mutex_unlock(&sitdev->multiop_lock); + NL_SET_ERR_MSG(extack, + "Only 10 MHz esync frequency is supported"); + return -EINVAL; + } + + rc =3D sit9531x_output_freq_set(sitdev, dpin->id, actual_pll, + SIT9531X_ESYNC_FREQ_10MHZ); + /* + * Program the pulse generator (PROG0 PULSE_CTRL) so the embedded-sync + * pulse is actually emitted; without it the output carries the clock + * but no esync marker. SIT9531X_ESYNC_PULSE_DEFAULT is the same duty + * the esync_get callback advertises. + */ + if (!rc) + rc =3D sit9531x_output_pulse_ctrl_set(sitdev, dpin->id, + SIT9531X_ESYNC_PULSE_DEFAULT); + if (!rc) + rc =3D sit9531x_output_enable(sitdev, dpin->id); + + mutex_unlock(&sitdev->multiop_lock); + + if (!rc) + dpin->esync_freq =3D SIT9531X_ESYNC_FREQ_10MHZ; + + return rc; +} + +static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops =3D { + .direction_get =3D sit9531x_dpll_output_pin_direction_get, + .frequency_get =3D sit9531x_dpll_output_pin_frequency_get, + .frequency_set =3D sit9531x_dpll_output_pin_frequency_set, + .state_on_dpll_get =3D sit9531x_dpll_output_pin_state_on_dpll_get, + .state_on_dpll_set =3D sit9531x_dpll_output_pin_state_on_dpll_set, + .phase_adjust_get =3D sit9531x_dpll_output_pin_phase_adjust_get, + .phase_adjust_set =3D sit9531x_dpll_output_pin_phase_adjust_set, + .esync_get =3D sit9531x_dpll_output_pin_esync_get, + .esync_set =3D sit9531x_dpll_output_pin_esync_set, +}; + +const struct dpll_pin_ops * +sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin) +{ + if (!sit9531x_dpll_is_input_pin(pin)) { + if (sit9531x_dpll_is_intsync_src_pin(pin)) + return &sit9531x_dpll_intsync_src_pin_ops; + return &sit9531x_dpll_output_pin_ops; + } + if (sit9531x_dpll_is_intsync_pin(pin)) + return &sit9531x_dpll_intsync_dst_pin_ops; + if (sit9531x_dpll_is_xo_pin(pin)) + return &sit9531x_dpll_xo_pin_ops; + return &sit9531x_dpll_input_pin_ops; +} + +/* + * sit9531x_dpll_changes_check - check for state changes and notify + * + * Called from sit9531x_dev_periodic_work(). Compares current hardware + * state against cached values and sends netlink notifications on changes. + */ +void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll) +{ + struct sit9531x_dev *sitdev =3D sitdpll->dev; + enum dpll_lock_status lock_status; + struct sit9531x_dpll_pin *pin; + int rc; + + rc =3D sit9531x_dpll_lock_status_get(sitdpll->dpll_dev, sitdpll, + &lock_status, NULL, NULL); + if (rc) { + dev_err(sitdev->dev, "Failed to get DPLL%u lock status: %d\n", + sitdpll->id, rc); + return; + } + + /* If lock status changed, notify DPLL core */ + if (sitdpll->lock_status !=3D lock_status) { + sitdpll->lock_status =3D lock_status; + dpll_device_change_ntf(sitdpll->dpll_dev); + } + + list_for_each_entry(pin, &sitdpll->pins, list) { + const struct dpll_pin_ops *ops; + enum dpll_pin_state state; + + /* + * Poll input pins whose state can change autonomously: regular + * references and the INTSYNC destination pin. Outputs (incl. + * the INTSYNC source) change only through their own set + * callback and the XO is permanently connected, so skip those. + * Each pin's own state_on_dpll_get resolves to the right getter. + */ + if (!sit9531x_dpll_is_input_pin(pin) || + sit9531x_dpll_is_xo_pin(pin)) + continue; + + ops =3D sit9531x_dpll_pin_ops_get(pin); + rc =3D ops->state_on_dpll_get(pin->dpll_pin, pin, + sitdpll->dpll_dev, sitdpll, + &state, NULL); + if (rc) + continue; + + if (state !=3D pin->pin_state) { + dev_dbg(sitdev->dev, "%s state changed: %u->%u\n", + pin->label, pin->pin_state, state); + pin->pin_state =3D state; + dpll_pin_change_ntf(pin->dpll_pin); + } + } +} diff --git a/drivers/dpll/sit9531x/dpll.h b/drivers/dpll/sit9531x/dpll.h new file mode 100644 index 000000000000..e5eef4514bcd --- /dev/null +++ b/drivers/dpll/sit9531x/dpll.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SiTime SiT9531x DPLL subsystem interface + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + * + * DPLL device and pin structures, and function declarations for + * the DPLL registration and callback layer. + */ + +#ifndef _SIT9531X_DPLL_H +#define _SIT9531X_DPLL_H + +#include +#include +#include + +struct sit9531x_dev; + +/* Per-pin DPLL state. */ +struct sit9531x_dpll_pin { + struct list_head list; + struct sit9531x_dpll *dpll; + struct dpll_pin *dpll_pin; + dpll_tracker tracker; + struct fwnode_handle *fwnode; + char label[8]; /* "IN0", "OUT3" */ + enum dpll_pin_direction dir; + u8 id; /* hardware index */ + u8 prio; + enum dpll_pin_state pin_state; + s32 phase_adjust; /* picoseconds */ + s64 phase_offset; /* picoseconds */ + bool esync_control; + u64 esync_freq; /* 0 =3D=3D disabled */ +}; + +/* Per-PLL DPLL device state. */ +struct sit9531x_dpll { + struct list_head list; + struct sit9531x_dev *dev; + struct dpll_device *dpll_dev; + dpll_tracker tracker; + struct dpll_device_ops ops; /* per-instance copy */ + struct list_head pins; + u8 id; /* 0 =3D PLLA .. 3 =3D PLLD */ + enum dpll_lock_status lock_status; +}; + +/* ---- DPLL allocation and registration ---- */ +/* + * The callback tables stay with the callbacks; the registration code that + * hands them to the subsystem lives next to probe() in core.c. + */ +extern const struct dpll_device_ops sit9531x_dpll_device_ops; +const struct dpll_pin_ops * +sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin); + +struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 = ch); +void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll); +int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll); +void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll); + +/* ---- Periodic change detection ---- */ +void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll); + +#endif /* _SIT9531X_DPLL_H */ diff --git a/drivers/dpll/sit9531x/prop.c b/drivers/dpll/sit9531x/prop.c new file mode 100644 index 000000000000..8a0105c8c647 --- /dev/null +++ b/drivers/dpll/sit9531x/prop.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiTime SiT9531x firmware node property parsing + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + * + * Retrieves pin properties from Device Tree firmware nodes (or + * applies defaults when no firmware node exists). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "prop.h" + +/* + * sit9531x_input_pin_label - fill the package label for an input pin + * + * Split out so input-naming changes stay local to this helper. + */ +static void sit9531x_input_pin_label(struct sit9531x_dev *sitdev, + struct sit9531x_pin_props *props, u8 id) +{ + u8 pair =3D sit9531x_input_pair(id); + + if (sitdev->ref[id].sig_mode =3D=3D SIT9531X_MODE_DE) + snprintf(props->package_label, + sizeof(props->package_label), "IN%u", pair); + else + snprintf(props->package_label, + sizeof(props->package_label), "IN%u%c", pair, + sit9531x_input_is_n(id) ? 'N' : 'P'); +} + +/* + * sit9531x_prop_pin_package_label_set - generate package label + * @dir: pin direction + * @id: pin index + * + * Generates a package label string. Output pins are named "OUT0", + * "OUT1", ... Input pins are named after the physical pair and lane: + * "IN0P", "IN0N", "IN1P", ... for single-ended lanes, or "IN0", + * "IN1", ... when the pair is configured differential (the N lane is + * not registered in that case). + */ +static void +sit9531x_prop_pin_package_label_set(struct sit9531x_dev *sitdev, + struct sit9531x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + /* The internal INTSYNC pin has a fixed label */ + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && + id =3D=3D SIT9531X_INTSYNC_PIN_ID) { + strscpy(props->package_label, "INTSYNC", + sizeof(props->package_label)); + props->dpll_props.package_label =3D props->package_label; + return; + } + + /* The internal XO reference has a fixed label */ + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && id =3D=3D SIT9531X_MAX_INPUTS)= { + strscpy(props->package_label, "XO", + sizeof(props->package_label)); + props->dpll_props.package_label =3D props->package_label; + return; + } + + /* The internal INTSYNC source (output) pin has a fixed label */ + if (dir =3D=3D DPLL_PIN_DIRECTION_OUTPUT && + id =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID) { + strscpy(props->package_label, "SYNCOUT", + sizeof(props->package_label)); + props->dpll_props.package_label =3D props->package_label; + return; + } + + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT) + sit9531x_input_pin_label(sitdev, props, id); + else + snprintf(props->package_label, sizeof(props->package_label), + "OUT%u", id); + + props->dpll_props.package_label =3D props->package_label; +} + +/* + * sit9531x_prop_pin_fwnode_get - find firmware node for a pin + * @dir: pin direction + * @id: pin index + * + * Searches for input-pins/output-pins child nodes in DT, looking + * for a child whose "reg" property matches @id. + * + * Return: 0 on success, -ENOENT if no firmware node exists + */ +static int +sit9531x_prop_pin_fwnode_get(struct sit9531x_dev *sitdev, + struct sit9531x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + struct fwnode_handle *pins_node, *pin_node; + const char *node_name; + + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT) + node_name =3D "input-pins"; + else + node_name =3D "output-pins"; + + pins_node =3D device_get_named_child_node(sitdev->dev, node_name); + if (!pins_node) { + dev_dbg(sitdev->dev, "'%s' sub-node is missing\n", node_name); + return -ENOENT; + } + + /* Enumerate child pin nodes and find the requested one */ + fwnode_for_each_child_node(pins_node, pin_node) { + u32 reg; + + if (fwnode_property_read_u32(pin_node, "reg", ®)) + continue; + + if (id =3D=3D reg) + break; + } + + fwnode_handle_put(pins_node); + + props->fwnode =3D pin_node; + + dev_dbg(sitdev->dev, "Firmware node for %s %sfound\n", + props->package_label, pin_node ? "" : "NOT "); + + return pin_node ? 0 : -ENOENT; +} + +/* + * sit9531x_pin_props_get - get pin properties for a given pin + * @dir: pin direction (INPUT or OUTPUT) + * @index: pin index + * + * Allocates a pin properties structure, generates a package label, + * looks up the firmware node if available, and reads optional + * properties (label, connection-type, supported-frequencies-hz, + * esync-control). + * + * Call sit9531x_pin_props_put() to free the returned structure. + * + * Return: pointer to pin properties on success, error pointer on error + */ +struct sit9531x_pin_props * +sit9531x_pin_props_get(struct sit9531x_dev *sitdev, + enum dpll_pin_direction dir, u8 index) +{ + struct dpll_pin_frequency *ranges; + struct sit9531x_pin_props *props; + int i, j, num_freqs =3D 0, rc; + u64 *freqs =3D NULL; + const char *type; + u32 curr_freq; + + props =3D kzalloc_obj(*props, GFP_KERNEL); + if (!props) + return ERR_PTR(-ENOMEM); + + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && + index =3D=3D SIT9531X_INTSYNC_PIN_ID) { + /* + * INTSYNC destination pin: a PLL locks to the INTSYNC net as a + * reference, so it can be connected and re-prioritised. + */ + props->dpll_props.type =3D DPLL_PIN_TYPE_INT_OSCILLATOR; + props->dpll_props.capabilities =3D + DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + curr_freq =3D 0; + } else if (dir =3D=3D DPLL_PIN_DIRECTION_OUTPUT && + index =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID) { + /* + * INTSYNC source pin: a PLL drives the INTSYNC net. It can be + * connected/disconnected but carries no priority (driving the + * net is not a reference selection) and no frequency. + */ + props->dpll_props.type =3D DPLL_PIN_TYPE_INT_OSCILLATOR; + props->dpll_props.capabilities =3D + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + curr_freq =3D 0; + } else if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && + index =3D=3D SIT9531X_MAX_INPUTS) { + /* The XO reference is fixed: no state or priority control. */ + props->dpll_props.type =3D DPLL_PIN_TYPE_INT_OSCILLATOR; + props->dpll_props.capabilities =3D 0; + sitdev->ref[index].freq =3D sitdev->xtal_freq; + curr_freq =3D sitdev->xtal_freq; + } else if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT) { + props->dpll_props.type =3D DPLL_PIN_TYPE_EXT; + props->dpll_props.capabilities =3D + DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + curr_freq =3D sitdev->ref[index].freq; + } else { + props->dpll_props.type =3D DPLL_PIN_TYPE_GNSS; + props->dpll_props.capabilities =3D + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + curr_freq =3D sitdev->out[index].freq; + } + + /* Allow phase-adjust over +/-1 ms window. The subsystem rejects + * pin_set(phase-adjust, X) when X falls outside [min, max], so + * leaving these at 0 silently blocks every netlink call. 1 ms is + * well beyond the DCO dynamic range but costs nothing. + */ + props->dpll_props.phase_range.min =3D -1000000000; /* -1 ms in ps */ + props->dpll_props.phase_range.max =3D 1000000000; /* +1 ms in ps */ + + /* Generate package label */ + sit9531x_prop_pin_package_label_set(sitdev, props, dir, index); + + /* + * Both INTSYNC pins are internal to the chip and have no board-level + * wiring, so they take no properties from the firmware node. + */ + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && + index =3D=3D SIT9531X_INTSYNC_PIN_ID) + goto skip_fwnode_props; + if (dir =3D=3D DPLL_PIN_DIRECTION_OUTPUT && + index =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID) + goto skip_fwnode_props; + + rc =3D sit9531x_prop_pin_fwnode_get(sitdev, props, dir, index); + if (rc) + goto skip_fwnode_props; + + /* Look for "label" property -> board label */ + fwnode_property_read_string(props->fwnode, "label", + &props->dpll_props.board_label); + + /* Look for "connection-type" property -> pin type enum */ + if (!fwnode_property_read_string(props->fwnode, "connection-type", + &type)) { + if (!strcmp(type, "ext")) + props->dpll_props.type =3D DPLL_PIN_TYPE_EXT; + else if (!strcmp(type, "gnss")) + props->dpll_props.type =3D DPLL_PIN_TYPE_GNSS; + else if (!strcmp(type, "int") || + !strcmp(type, "int-oscillator")) + props->dpll_props.type =3D DPLL_PIN_TYPE_INT_OSCILLATOR; + else if (!strcmp(type, "synce") || + !strcmp(type, "synce-eth-port")) + props->dpll_props.type =3D DPLL_PIN_TYPE_SYNCE_ETH_PORT; + else if (!strcmp(type, "mux")) + props->dpll_props.type =3D DPLL_PIN_TYPE_MUX; + else + dev_warn(sitdev->dev, + "Unknown pin type '%s'\n", type); + } + + props->esync_control =3D + fwnode_property_read_bool(props->fwnode, "esync-control"); + + num_freqs =3D fwnode_property_count_u64(props->fwnode, + "supported-frequencies-hz"); + if (num_freqs <=3D 0) { + num_freqs =3D 0; + goto skip_fwnode_props; + } + + freqs =3D kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL); + if (!freqs) { + rc =3D -ENOMEM; + goto err_alloc_freqs; + } + + fwnode_property_read_u64_array(props->fwnode, + "supported-frequencies-hz", + freqs, num_freqs); + + /* + * Seed the runtime ref->freq / out->freq with the first DT-listed + * supported frequency so the netlink frequency_get callback reports + * a sane initial value before any pin_set occurs. DT lists the + * physically-wired reference frequency for each input pin and the + * default output frequency for each output pin. + */ + if (num_freqs > 0) { + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT) + sitdev->ref[index].freq =3D (u32)freqs[0]; + else + sitdev->out[index].freq =3D (u32)freqs[0]; + curr_freq =3D (u32)freqs[0]; + } + +skip_fwnode_props: + /* Neither INTSYNC pin carries a frequency attribute */ + if (dir =3D=3D DPLL_PIN_DIRECTION_INPUT && + index =3D=3D SIT9531X_INTSYNC_PIN_ID) + return props; + if (dir =3D=3D DPLL_PIN_DIRECTION_OUTPUT && + index =3D=3D SIT9531X_INTSYNC_OUT_PIN_ID) + return props; + + /* Allocate frequency ranges list -- DT discrete entries + current + * freq + one catch-all wide range so the subsystem never pre- + * rejects a frequency_set call. The chip's real admissible set + * is bounded by VCO / divider math in sit9531x_output_freq_set(). + */ + ranges =3D kcalloc(num_freqs + 2, sizeof(*ranges), GFP_KERNEL); + if (!ranges) { + rc =3D -ENOMEM; + goto err_alloc_ranges; + } + + /* Current freq as first entry */ + ranges[0] =3D (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq); + j =3D 1; + + for (i =3D 0; i < num_freqs; i++) { + struct dpll_pin_frequency freq =3D DPLL_PIN_FREQUENCY(freqs[i]); + + if (freqs[i] =3D=3D curr_freq) + continue; + ranges[j++] =3D freq; + } + + /* Always append a wide catch-all range */ + ranges[j].min =3D 1; + ranges[j].max =3D 1000000000ULL; /* 1 GHz */ + j++; + + props->dpll_props.freq_supported =3D ranges; + props->dpll_props.freq_supported_num =3D j; + + kfree(freqs); + + return props; + +err_alloc_ranges: + kfree(freqs); +err_alloc_freqs: + fwnode_handle_put(props->fwnode); + kfree(props); + + return ERR_PTR(rc); +} + +/* + * sit9531x_pin_props_put - release pin properties + * @props: pin properties to free + */ +void sit9531x_pin_props_put(struct sit9531x_pin_props *props) +{ + kfree(props->dpll_props.freq_supported); + + if (props->fwnode) + fwnode_handle_put(props->fwnode); + + kfree(props); +} + +/* + * sit9531x_prop_dpll_type_get - get DPLL channel type from firmware + * @index: DPLL channel index (0-3) + * + * Reads the "dpll-types" string array property from the firmware node + * and returns the corresponding DPLL type enum. + * + * Return: DPLL type for the given channel (default: DPLL_TYPE_PPS) + */ +enum dpll_type +sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev, u8 index) +{ + const char *types[SIT9531X_NUM_PLLS]; + int count; + + count =3D device_property_read_string_array(sitdev->dev, "dpll-types", + types, ARRAY_SIZE(types)); + + if (index >=3D count) + return DPLL_TYPE_PPS; + + if (!strcmp(types[index], "pps")) + return DPLL_TYPE_PPS; + else if (!strcmp(types[index], "eec")) + return DPLL_TYPE_EEC; + + dev_warn(sitdev->dev, "Unknown DPLL type '%s', using default\n", + types[index]); + + return DPLL_TYPE_PPS; +} diff --git a/drivers/dpll/sit9531x/prop.h b/drivers/dpll/sit9531x/prop.h new file mode 100644 index 000000000000..f7f1c854b955 --- /dev/null +++ b/drivers/dpll/sit9531x/prop.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SiTime SiT9531x firmware node property parsing + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + */ + +#ifndef _SIT9531X_PROP_H +#define _SIT9531X_PROP_H + +#include +#include + +struct sit9531x_dev; + +/* + * struct sit9531x_pin_props - pin properties from firmware + * @fwnode: firmware node handle (NULL if no DT node) + * @dpll_props: DPLL core pin properties + * @package_label: pin package label (e.g. "IN0", "OUT3") + * @esync_control: embedded sync is controllable + */ +struct sit9531x_pin_props { + struct fwnode_handle *fwnode; + struct dpll_pin_properties dpll_props; + char package_label[8]; + bool esync_control; +}; + +enum dpll_type sit9531x_prop_dpll_type_get(struct sit9531x_dev *sitdev, + u8 index); +struct sit9531x_pin_props *sit9531x_pin_props_get(struct sit9531x_dev *sit= dev, + enum dpll_pin_direction dir, + u8 index); +void sit9531x_pin_props_put(struct sit9531x_pin_props *props); + +#endif /* _SIT9531X_PROP_H */ diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h new file mode 100644 index 000000000000..5c3dbeefd86b --- /dev/null +++ b/drivers/dpll/sit9531x/regs.h @@ -0,0 +1,371 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SiTime SiT9531x register definitions + * + * Copyright (C) 2026 SiTime Corp. + * Author: Ali Rouhi + * Author: Oleg Zadorozhnyi + */ + +#ifndef _SIT9531X_REGS_H +#define _SIT9531X_REGS_H + +/* + * I2C register model: + * - Page select register at offset 0x01 + * - Each page has 128 registers (0x00-0x7F) + * - Some pages are paired (e.g. 0x0A/0x1A for PLLA) + */ +#define SIT9531X_PAGE_SEL 0xFF +#define SIT9531X_PAGE_SIZE 0x100 +#define SIT9531X_NUM_PAGES 32 + +/* Helper macros for page:offset addressing */ +#define SIT9531X_REG(_page, _offset) (((_page) << 8) | (_offset)) +#define SIT9531X_REG_PAGE(_reg) ((_reg) >> 8) +#define SIT9531X_REG_OFFSET(_reg) ((_reg) & 0xFF) + +/* ---- Page definitions ---- */ +#define SIT9531X_PAGE_MAINSYS0 0x00 +#define SIT9531X_PAGE_MAINSYS1 0x01 +#define SIT9531X_PAGE_INPUTSYS 0x02 +#define SIT9531X_PAGE_OUTSYS0 0x03 +#define SIT9531X_PAGE_OUTSYS1 0x04 +#define SIT9531X_PAGE_CLKMON0 0x06 +#define SIT9531X_PAGE_CLKMON1 0x07 +#define SIT9531X_PAGE_PLLA 0x0A +#define SIT9531X_PAGE_PLLA_EXT 0x1A +#define SIT9531X_PAGE_PLLB 0x0B +#define SIT9531X_PAGE_PLLB_EXT 0x1B +#define SIT9531X_PAGE_PLLC 0x0C +#define SIT9531X_PAGE_PLLC_EXT 0x1C +#define SIT9531X_PAGE_PLLD 0x0D +#define SIT9531X_PAGE_PLLD_EXT 0x1D + +/* PLL index to page mapping */ +#define SIT9531X_PLL_PAGE(_idx) \ + (SIT9531X_PAGE_PLLA + (_idx)) + +/* + * VARIANT_ID is a single byte at page 0 reg 0x02 (95317 =3D 0x17, 95316 = =3D 0x31). + * Reg 0x03 carries an unrelated revision byte and must not be combined in= to + * the variant identifier. + */ +#define SIT9531X_REG_VARIANT_ID SIT9531X_REG(0x00, 0x02) + +/* DCO trigger pulse timing: minimum 6 ns required by hardware */ + +#define SIT9531X_REG_HOLDOVER_HISTORY SIT9531X_REG(0x00, 0x58) + +/* Page 0 -- PLL inner loop loss-of-lock */ +#define SIT9531X_REG_PLL_INNER_LOL_STATUS SIT9531X_REG(0x00, 0x92) +#define SIT9531X_REG_PLL_INNER_LOL_NOTIF SIT9531X_REG(0x00, 0x93) + +/* Page 0 -- Clock monitor PLL / XO status */ +#define SIT9531X_REG_CMON_NOTIF SIT9531X_REG(0x00, 0x9E) + +/* Page 0 -- PLL outer-loop loss-of-lock */ +#define SIT9531X_REG_OUTER_LOL_STATUS SIT9531X_REG(0x00, 0x06) +#define SIT9531X_REG_OUTER_LOL_NOTIF SIT9531X_REG(0x00, 0x07) + +/* Page 0 -- PLL holdover freeze status */ +#define SIT9531X_REG_HO_FREEZE_STATUS SIT9531X_REG(0x00, 0x0A) +#define SIT9531X_REG_HO_FREEZE_NOTIF SIT9531X_REG(0x00, 0x0B) + +/* Page 0 -- INTSYNC (inter-PLL synchronization) global enable */ +#define SIT9531X_REG_INTSYNC_GLOBAL SIT9531X_REG(0x00, 0x40) +#define SIT9531X_INTSYNC_EN_BIT 6 + +/* + * Priority table: 6 registers per PLL, each holds two priority slots + * nibble-packed. The register holding slots 2n and 2n+1 keeps the + * earlier slot (CLK_SPARE<2n>SEL_PLL) in [7:4] and the later one in + * [3:0]. + * + * Base registers for PLLA: 0x16-0x1B (slots 0-10 plus the + * active-reference nibble). + * For PLL N: base + 6 * N (e.g. PLLB starts at 0x1C). + * + * Input source encoding (4-bit value): + * 0=3DIN0P, 1=3DIN1P, 2=3DIN2P, 3=3DIN3P, 4=3DIN4P, + * 5=3DOCXO, 6=3DINTSYNC, + * 7=3DIN0N, 8=3DIN1N, 9=3DIN2N, 10=3DIN3N, 11=3DIN4N + */ +#define SIT9531X_PAGE_PRIOSYS 0x01 +#define SIT9531X_PRIO_BASE_REG 0x16 +#define SIT9531X_PRIO_REGS_PER_PLL 6 +#define SIT9531X_PRIO_SLOTS_PER_REG 2 +/* + * 11 priority slots, CLK_SPARE0SEL_PLL through CLK_SPARE10SEL_PLL. + * The twelfth nibble of the block is not a slot: it is + * CLK_ACTIVESEL_PLL, see SIT9531X_PRIO_ACTIVESEL_OFF below. + */ +#define SIT9531X_PRIO_MAX_SLOTS 11 +/* Number of source encodings (0-11), unrelated to the slot count */ +#define SIT9531X_PRIO_NUM_SRC 12 +#define SIT9531X_PRIO_NIBBLE_MASK 0x0F +#define SIT9531X_PRIO_HI_SHIFT 4 +/* Input source encoding values (see table above) */ +#define SIT9531X_PRIO_SRC_OCXO 5 +#define SIT9531X_PRIO_SRC_INTSYNC 6 +#define SIT9531X_PRIO_SRC_N_BASE 7 +/* + * The last register of each PLL's priority block holds, in its low + * nibble, the input source the PLL has currently selected as its + * active reference (CLK_ACTIVESEL_PLL, same 4-bit encoding as above). + */ +#define SIT9531X_PRIO_ACTIVESEL_OFF 5 + +/* + * Page 0 -- PRG_Directives_GENERIC_0, the main system's programming + * directive register. Every page carries its own copy of this + * register at offset 0x0F with the same bit layout: + * + * bit 6 proceed to loop lock / active state from the PRG_CMD state + * bit 4 update the NVM bank from the efuse contents + * bit 3 read the efuse + * bit 2 program the efuse + * bit 1 small change update (SIT9531X_SMALL_UPDATE_CMD) + * bit 0 escape to the PRG_CMD state + * + * Only the small change update belongs in a runtime path; the efuse + * and NVM directives touch non-volatile storage. + */ +#define SIT9531X_REG_GLOBAL_UPDATE SIT9531X_REG(0x00, 0x0F) +#define SIT9531X_SMALL_UPDATE_CMD 0x02 + +/* PLL holdover control (PLL page offset) */ +#define SIT9531X_PLL_REG_HO_CTRL 0x6F +#define SIT9531X_PLL_HO_FORCE_BIT 4 + +/* One bit per input PAIR (bit 0 =3D CLKIN0, ..., bit 3 =3D CLKIN3) */ +#define SIT9531X_REG_IN_DE_FORCE SIT9531X_REG(0x02, 0xE8) +#define SIT9531X_REG_IN_DE_STATE SIT9531X_REG(0x02, 0xE9) +#define SIT9531X_REG_IN_SEP_FORCE SIT9531X_REG(0x02, 0xEA) +#define SIT9531X_REG_IN_SEP_STATE SIT9531X_REG(0x02, 0xEB) +#define SIT9531X_REG_IN_SEN_FORCE SIT9531X_REG(0x02, 0xF2) +#define SIT9531X_REG_IN_SEN_STATE SIT9531X_REG(0x02, 0xF3) + +/* + * One register per input pair at 0x1B + 0x10 * pair + * (CLKIN0 =3D 0x1B, CLKIN1 =3D 0x2B, CLKIN2 =3D 0x3B, CLKIN3 =3D 0x4B). + * SE_P_EN/SE_N_EN set means the corresponding lane is configured + * single-ended; both clear means the pair runs differential. + */ +#define SIT9531X_REG_IN_MODE(_pair) \ + SIT9531X_REG(0x02, 0x1B + 0x10 * (_pair)) +#define SIT9531X_IN_MODE_SE_P_EN BIT(0) +#define SIT9531X_IN_MODE_SE_N_EN BIT(1) + +/* ---- Page 0x03 (Output System) registers -- Hi-Z control ---- */ +#define SIT9531X_REG_HIZ_DIFF_07_MASK SIT9531X_REG(0x03, 0xF2) +#define SIT9531X_REG_HIZ_DIFF_07_STATE SIT9531X_REG(0x03, 0xF3) +#define SIT9531X_REG_HIZ_DIFF_811_MASK SIT9531X_REG(0x03, 0xF4) +#define SIT9531X_REG_HIZ_DIFF_811_STATE SIT9531X_REG(0x03, 0xF5) +#define SIT9531X_REG_HIZ_SE_07_MASK SIT9531X_REG(0x03, 0xF8) +#define SIT9531X_REG_HIZ_SE_07_STATE SIT9531X_REG(0x03, 0xF9) +#define SIT9531X_REG_HIZ_SE_811_MASK SIT9531X_REG(0x03, 0xFA) +#define SIT9531X_REG_HIZ_SE_811_STATE SIT9531X_REG(0x03, 0xFB) + +/* + * Output divider registers in Pages 3/4. Each output has a 34-bit + * integer divider mapped to 5 bytes (LSB at base reg, MSB at base-4). + * Outputs 0-5 are on Page 3, outputs 6-11 are on Page 4. + * + * The base register for slot N within a page is: + * clkout_odr_divn_base[slot] =3D { 0x14, 0x24, 0x34, 0x44, 0x54, 0x64 } + * + * Layout: base=3DLSB, base-1, base-2, base-3, base-4[1:0]=3DMSB. + * + * Per-chip clkout_map[] translates output index to slot position. + */ +#define SIT9531X_PAGE_OUTSYS0_SLOT_MAX 5 /* slots 0-5 on Page 0x03 */ + +/* Misc output system registers */ +#define SIT9531X_REG_PRG_DIR_GEN SIT9531X_REG(0x03, 0x0F) +#define SIT9531X_PRG_CMD_STATE 0x01 +#define SIT9531X_UPDATE_NVM 0x10 +#define SIT9531X_LOOP_LOCK 0x40 + +/* Debug register (same offset, per-page) */ +#define SIT9531X_REG_OUTSYS_DEBUG SIT9531X_REG(0x03, 0xBD) +#define SIT9531X_DEBUG_UNLOCK_VAL 0xC3 + +/* + * Per-output programmable phase delay: 34-bit coarse (in VCO clock + * cycles) plus a 3-bit fine field with fixed 30 ps steps. Each output + * has a five-byte block PROG6..PROG2: + * + * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserve via RMW) + * [4:2] PRG_RST_FINE_DELAY[2:0] + * [1:0] PRG_RST_DELAY[33:32] + * base + 1 PROG5 [7:0] PRG_RST_DELAY[31:24] + * base + 2 PROG4 [7:0] PRG_RST_DELAY[23:16] + * base + 3 PROG3 [7:0] PRG_RST_DELAY[15:8] + * base + 4 PROG2 [7:0] PRG_RST_DELAY[7:0] + * + * Outputs 0-5 are on Page 3, outputs 6-11 on Page 4. The block base + * within a page is 0x15 + 16 * (out_idx % 6). + */ +#define SIT9531X_OUT_PRG_DELAY_BASE 0x15 +#define SIT9531X_OUT_PRG_SLOT_STRIDE 0x10 +#define SIT9531X_OUT_PRG_OPSTG_MASK 0xE0 /* bits [7:5], preserve */ +#define SIT9531X_OUT_PRG_FINE_SHIFT 2 +#define SIT9531X_OUT_PRG_FINE_MASK 0x1C /* bits [4:2] */ +#define SIT9531X_OUT_PRG_COARSE_HI_MASK 0x03 /* bits [1:0] */ +#define SIT9531X_OUT_PRG_FINE_STEP_PS 30 +#define SIT9531X_OUT_PRG_FINE_MAX 7 /* 3-bit field */ +#define SIT9531X_OUT_PRG_COARSE_BITS 34 + +/* + * Per-output pulse-count control byte used in SYSREF / SYNCB modes. + * Slot N within a page sits at 0x1B + 16 * (slot % 6). Same page + * mapping as PRG_RST_DELAY: slots 0-5 on Page 3, slots 6-11 on Page 4. + */ +#define SIT9531X_OUT_PROG0_BASE 0x1B + +/* + * On-demand phase-flush fired from a register rather than a GPIO pin. + * DIVO_PHASE_SEL_REG selects the in-register trigger source and + * DIVO_PHASE_TRIG flushes the output phase when pulsed high then low. + * The unrelated OEb trigger pair in bits [7:6] must be preserved. + */ +#define SIT9531X_REG_GPIO_FUNC_CTRL1 SIT9531X_REG(0x00, 0x65) +#define SIT9531X_DIVO_PHASE_SEL_REG BIT(5) +#define SIT9531X_DIVO_PHASE_TRIG BIT(4) + +/* ---- PLL page registers (apply to pages 0x0A-0x0D) ---- */ +#define SIT9531X_PLL_REG_SMALL_UPDATE 0x0F + +/* On-demand phase-flush enable (PLL page reg 0x3D bit 7) */ +#define SIT9531X_PLL_REG_PHFL_CTRL 0x3D +#define SIT9531X_PLL_PHFL_ON_DEMAND_EN BIT(7) + +/* + * Loop-filter coefficients on PLL_PAGE regs 0x10-0x15 (3 normal + + * 3 fast-lock) are GUI/NVM-generated by the timing configurator and must = not be + * reprogrammed at runtime; the register map flags them as + * "GUI generated configuration should not change manually". + */ + +#define SIT9531X_PLL_REG_OUT_MAP_HI 0x27 +#define SIT9531X_PLL_REG_OUT_MAP_LO 0x28 +#define SIT9531X_PLL_REG_STATUS 0x31 +#define SIT9531X_PLL_REG_NVM_UPDATE 0x3F + +/* DIVN registers (free-run divider readback) */ +#define SIT9531X_PLL_REG_DIVN_INT 0x30 +#define SIT9531X_PLL_REG_DIVN_NUM 0x32 /* 4 bytes (0x32-0x35) */ +#define SIT9531X_PLL_REG_DIVN_DEN 0x38 /* 4 bytes (0x38-0x3B) */ + +/* DIVN2 registers (sync divider readback) */ +#define SIT9531X_PLL_REG_DIVN2_INT 0x3E /* 5 bytes (0x3E-0x42) */ +#define SIT9531X_PLL_REG_DIVN2_FRAC_NUM 0x43 /* 4 bytes (0x43-0x46) */ +#define SIT9531X_PLL_REG_DIVN2_FRAC_DEN 0x49 /* 4 bytes (0x49-0x4C) */ + +/* Debug register unlock */ +#define SIT9531X_PLL_REG_DEBUG 0xBD +#define SIT9531X_PLL_DEBUG_UNLOCK 0xC3 + +/* + * Signal pathway debug readback -- PLL page. Dig_Sys_ReadCode selects + * which point of the pathway is tapped, Dig_Sys_WriteCode carries the + * modifiers for that read, Dig_Sys_read7..read0 hold the sampled bytes + * and the trigger register latches a sample. The TDC phase + * measurement is one tap among several, reached through read code 69. + */ +#define SIT9531X_PLL_REG_DBG_READ_CODE 0xB3 +#define SIT9531X_PLL_REG_DBG_WRITE_CODE 0xB4 +#define SIT9531X_DBG_LOW_FREQ_CLK_BIT BIT(7) +#define SIT9531X_PLL_REG_DBG_DATA_0 0xB5 /* [7:0] */ +#define SIT9531X_PLL_REG_DBG_DATA_1 0xB6 /* [15:8] */ +#define SIT9531X_PLL_REG_DBG_DATA_2 0xB7 /* [23:16] */ +#define SIT9531X_PLL_REG_DBG_DATA_3 0xB8 /* [31:24] */ +#define SIT9531X_PLL_REG_DBG_DATA_4 0xB9 /* [39:32] + sign */ +#define SIT9531X_PLL_REG_DBG_DATA_5 0xBA /* [47:40] */ +#define SIT9531X_PLL_REG_DBG_DATA_6 0xBB +#define SIT9531X_PLL_REG_DBG_DATA_7 0xBC +#define SIT9531X_PLL_REG_DBG_TRIGGER 0xD0 /* read to latch a sample */ +#define SIT9531X_DBG_DATA_BYTES 8 + +/* Read code of the TDC phase tap, and the sign bit of its sample */ +#define SIT9531X_DBG_READ_CODE_TDC 69 +#define SIT9531X_TDC_SIGN_BIT 3 + +/* + * Read codes of the running DIVN taps. Unlike the configuration + * registers these report what the digital loop currently commands, so + * they carry the correction the loop applies to track its reference. + * The integer part and the numerator share one tap, the denominator + * has its own. + */ +#define SIT9531X_DBG_READ_CODE_DIVN 0x57 +#define SIT9531X_DBG_READ_CODE_DIVN_DEN 0x56 +#define SIT9531X_DIVN_RT_NUM_BITS 48 +#define SIT9531X_DIVN_RT_INT_HI_BIT BIT(0) + +/* + * DIVN carried as fixed point, and the unit the DPLL ABI wants the + * fractional frequency offset in. Equal in value, distinct in meaning. + */ +#define SIT9531X_DIVN_SCALE 1000000000000ULL +#define SIT9531X_PPT_PER_UNIT 1000000000000ULL + +/* + * TDC phase-difference measurement setup (PLL page). Per the vendor + * PhaseDifferenceReader reference procedure, a valid TDC reading needs + * the digital loop filter frozen (so the VCO does not track out the + * measured offset mid-sample) and, on PLLA (the 1PPS phase DPLL), the + * automatic phase/frequency-lock helpers held off. + */ +#define SIT9531X_PLL_REG_DLPF_FREEZE0 0x8A +#define SIT9531X_PLL_REG_DLPF_FREEZE1 0x8B +#define SIT9531X_PLL_DLPF_FREEZE_BIT BIT(5) /* freeze DLPF (hitless HO) */ +#define SIT9531X_PLL_REG_APL_CTRL 0x4D +#define SIT9531X_PLL_APL_WAKEUP_DIS_BIT BIT(7) /* disable APL at wake-up = */ +#define SIT9531X_PLL_APL_SWITCH_EN_BIT BIT(5) /* APL on switchover */ +#define SIT9531X_PLL_REG_AFL_CTRL 0x4E +#define SIT9531X_PLL_AFL_WAKEUP_DIS_BIT BIT(5) /* disable AFL at wake-up = */ +#define SIT9531X_PLL_CONFIG47_PHFL_BIT BIT(7) /* PHFL enable (reg 0x47) */ +#define SIT9531X_PLL_REG_ACTIVE 0x02 +#define SIT9531X_PLL_ACTIVE_BIT BIT(0) /* PLL reached active state */ +#define SIT9531X_PLL_REG_RESTART 0x05 +#define SIT9531X_PLL_RESTART_BIT BIT(0) /* restart the PLL */ +#define SIT9531X_PLL_REG_ZDB0 0x2B +#define SIT9531X_PLL_REG_ZDB1 0x1E +#define SIT9531X_PLL_ZDB_EN_BIT BIT(4) /* zero-delay buffer enabled */ + +/* PLL EXT page INTSYNC configuration registers */ +#define SIT9531X_PLL_EXT_PAGE(_idx) (SIT9531X_PAGE_PLLA_EXT + (_idx)) + +/* PLL STATUS register bits */ +#define SIT9531X_PLL_STATUS_LOCK BIT(0) +#define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5) + +/* P-polarity status registers */ +#define SIT9531X_CLKMON_P_STATUS_01 SIT9531X_REG(0x06, 0x02) /* inputs 0= ,1 */ +#define SIT9531X_CLKMON_P_NOTIF_01 SIT9531X_REG(0x06, 0x03) +#define SIT9531X_CLKMON_P_STATUS_23 SIT9531X_REG(0x06, 0x06) /* inputs 2= ,3 */ +#define SIT9531X_CLKMON_P_NOTIF_23 SIT9531X_REG(0x06, 0x07) + +/* N-polarity status registers */ +#define SIT9531X_CLKMON_N_STATUS_01 SIT9531X_REG(0x06, 0x92) /* inputs 0= ,1 */ +#define SIT9531X_CLKMON_N_NOTIF_01 SIT9531X_REG(0x06, 0x93) +#define SIT9531X_CLKMON_N_STATUS_23 SIT9531X_REG(0x06, 0x96) /* inputs 2= ,3 */ +#define SIT9531X_CLKMON_N_NOTIF_23 SIT9531X_REG(0x06, 0x97) + +/* Per-input bit offsets within clock monitor nibble */ +#define SIT9531X_CLKMON_FREQ_FINE 0 /* bit 0 / bit 4 */ +#define SIT9531X_CLKMON_FREQ_COARSE 1 /* bit 1 / bit 5 */ +#define SIT9531X_CLKMON_CLK_LOSS 2 /* bit 2 / bit 6 */ +#define SIT9531X_CLKMON_CLK_LOSS_FD 3 /* bit 3 / bit 7 */ + +/* ---- Debug / NVM unlock registers ---- */ +#define SIT9531X_REG_DBG_UNLOCK1 0x24 +#define SIT9531X_REG_DBG_UNLOCK2 0x25 + +/* ---- Variant ID values (single byte read from SIT9531X_REG_VARIANT_ID) = ---- */ +#define SIT9531X_VARIANT_ID_95317 0x17 +#define SIT9531X_VARIANT_ID_95316 0x31 + +#endif /* _SIT9531X_REGS_H */ --=20 2.34.1