From nobody Sat Jul 25 18:53:27 2026 Received: from TYPPR03CU001.outbound.protection.outlook.com (mail-japaneastazon11022139.outbound.protection.outlook.com [52.101.126.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C04EC477E58; Tue, 14 Jul 2026 15:54:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.126.139 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044495; cv=fail; b=r6evnYShLaHJe6B4ZzZlLBSLKT/7KeVNEAurr+v4dSmHYO65/jYNd9HPZBRxvel126m3yb8gW/8+/jIyQ0CnGKDqD9hZGBEGA9zMHuyViIQhsnPANECZWcQXdGKV9JAjwZbJA0nX5SV0+NtNp/sl192fiytcgoCc0cty7I61BcQ= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044495; c=relaxed/simple; bh=5fqFSVlKcSOCk0ZAGiLrMk7+/9IwudUEG1EYu5BN+EM=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=EV7KQQSjyAyxvwjMO4c9pv7FcIT/p0kZFZz6wSROowU0m81ehY/T2DTp9ykdLOJwwfdwqiJWhXstmSjFC0e/wPJp9RaOfrUmCSNy7WNLozvotCzZspHj1sPzoavCT2nIGkK6uewUQJCJVG9Yrff/CIQrvZQyrJ09Kax4oyKJDhI= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=bWNlKIGQ; arc=fail smtp.client-ip=52.101.126.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="bWNlKIGQ" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=LqsJvOk+f9fvbySUkFZhGBCAGHV15oBMVOMCq8oEGq2VbBKo+V+BF8FRfcaV4v5k3Erl/PpSL1Blg2Vb5nsZOSaoJ6uRGCLv5X1cMtqC7x6DwqAelklLWhP87yLcPUMlraCUKBFB82Dm+sjdLHt/FfH4oBYKGFgAtZ/mzVbWaN4Ks4z9d4R4/8eT6SrDG65gj/iuvnwPQni7NH6u/Zwf8CizApfqQwVgoIwoB6xDAfyrBqa3/Rf1M748DbSEk0xNL4Aga8CBf+mAbV/IFiNUzTDvTT4zi7wi2ngiLtKxPgDMr9Y9zm2byHr3bbaVgvG8PUtWX/QHfhfhhDupN06KVg== 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=6xXh+eMv3p7Mw2MYpT/fMMYlScBr2JGMK5VDkg/IB8k=; b=OjoUjzslrOo0r17V4lnPSqGNb40n8InkxoTJJ69OYQWVLGJmFoAUneQ0u+7YnjHDinlv5wn5USSSPENZOKOUr9Jd3ykMfjKyguG3OHQPvttMQhOwYwNh1hgHINLuf/LgtVy17pPTuR+pvEo+JoK+xmVV2icD8GktPNvSM04OoOyuXlQdbjMDHqWo+hk5utJChGutxO6Q2z5Ops3KOmXk+z3opG8eQ5PADDYN7OXYCOZcOtf6XibFgoRd71qs5yZJhT2t3BeOIXmddhbw44+bfURxCdvWJPCIRJ/w6B8QIPS8nzKh5Nehk0IJwAjbuLaLEvqTKib5Y3hfHhtp3wSIzg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=6xXh+eMv3p7Mw2MYpT/fMMYlScBr2JGMK5VDkg/IB8k=; b=bWNlKIGQUJrrjZBzfDvgYqYGtvDwcOIO3KfbNtC5ICK92s2l1ZODTO8Pj9iR1hs+AUdcKrdszBoyw28s7PY1CBe40vOjEj+ByX++iDPEdDVBXcozGsdCh0xdzAGVw8oH3C3ludGY5kxhc9lZxo9U9Auf4gL0NJMkPihA2Ca5biwQ7r3cvSAhopmAyzU852Qekon/IaT6mmM2Wou0hZeTXKqvV1JbPQO2FUi52AaN1l6tJ+VmBBBGlkVtXGI0DPbDvMUdGP+Zpd2GA/bbjTGwUJvx93VbfVEozVdsxBxxkErE55fyTzpMUpJ4HcWjfi3xPDS4iqV07GLOeHMgWBC7TA== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by KL1PR02MB6189.apcprd02.prod.outlook.com (2603:1096:820:bd::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.20; Tue, 14 Jul 2026 15:54:47 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:54:47 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:15 +0200 Subject: [PATCH v2 1/8] Add Advantech EIO driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-1-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=22136; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=5fqFSVlKcSOCk0ZAGiLrMk7+/9IwudUEG1EYu5BN+EM=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu4nQIxBgJs58vtv8kZc+Pzr2XN42YpXL/KL HBM23r2jiuJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CXhaDADE7ffeWy/YIPwnpQ9vIZcbbSj04fjnGhogj7Mewz6Wi4KsHrsiTSr/9P2KqX99hCMiQ+M mqkDVgCkGD+YSA5WTjH5n2LdUBoVtfPwydMu8y1aNWl7ULndHuMXWolOmDOdio+j1V2hbiwXk3K I998scFkta3xN6dr39JHgYjMUAzfC6ifna1BOpqsjk1ajJNJIcUb/k0hU7cJkHD2vOKNsmkmSqW 764YLuTr+ceeCY/avyHMB5SyCkcHULynA89MphsFyQlOD8r08bSkh+Qh7wmfSUy6ABLwd8iK7SH m2xYEsuuKtxdIi6UzeJK2R7kkzpvOKVNCiFHKqiQaFVuPYLRv4xx3BL5S1QX/6irtFzjBya7iON d8FkMvyzi2HK/6GSjjf+FsvTlBPnSiwenRzUkXXix55NoeRTSAaqkZFS/v4I45LUfjKg+Aw5bDi feVx6wS6ib/arwd1LDnrhzZ5VIU8ov6zfQet/TSvYltqGvX1rfaHvO5kHjnxcDxxDD9mY= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|KL1PR02MB6189:EE_ X-MS-Office365-Filtering-Correlation-Id: e46a9163-5707-46fb-1d58-08dee1c03b12 X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|1800799024|23010399003|52116014|7416014|376014|921020|38350700014|3023799007|56012099006|11063799006|6133799003|22082099003|55112099003|18002099003; X-Microsoft-Antispam-Message-Info: o4rzbXB7fgSzMsC3xaRbWrtEbakb0oIaMmcbqcSFdvO1a5k7G/wBUHhmM7F71FZqOz0pM7gGDVqpA8lXZa34co4aJ+tkE6AIltSM9xgzd3cBVk1IqyraypxaivD+dr6dE5M7YQS2CQ6E7MKsrp6XcnbyiKJyITHzIaFNT6IgpKCV6D1VUx4vkYKWJXtPFVAaMG0tEg/y6FPTIS+juOXV8YlZ3UzuzCqRs1StV/pcs5rqX7W5p1bp5aRuzBl8zsjFG/kRCLLOQhcyi9MjTi3RYd38sZu7huK/2roiWTzITLNJz4AtFas7GgqrkFovIS7wjgo6oSaAsXGZ6OuDviEEdfYqRc3CVu5x6LVjc7/7E+6kszR4OWby2QpYhE51ZFQxQSM3yvlm4HI0gOlvXRH2PDZv0JiAdU/fYDKOt1bfZT5WubrDlObAvOINaCW4xQvLa2k5SCacFDla6EPRt0km3hPqUfwQQyfYama5kP6Lz+0ot7QDaC5hP5aUtKrtB0My+GwWltytH0DSVUuPwXFYsj3xOAuhf//vKSh8hn3uSopkkzNR5uijNsGqP2fiVGXEvd+RCdPIZ6mlPl0+5N/L5XG3OA5bTsXg6J1RGoHSWg8RJjtnb5iTyCcE/7+N/ppSJAQTuY1gpOZZOjjaucG7MMxJtYLx5N69cX2g+9YkzgVuqKPFkfnI0+L0IC+uihncGOs0a3Fd/ToPGAy8en0mIxR6bIh+jVkraLboBXi96/gbXagiXoZwMrSr2afNn8nB X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(366016)(1800799024)(23010399003)(52116014)(7416014)(376014)(921020)(38350700014)(3023799007)(56012099006)(11063799006)(6133799003)(22082099003)(55112099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?NGVzVFBDWCtCemdxOXFLTTFyTlBpY3Q2Uk93SS9PeERpNlZVTTY5bDBKSFpx?= =?utf-8?B?NE5nTUZ1N0hMdGRCaklQV1R3ME9HYTN2WDkyYTd3TUVqOGFzcll1ZzNiUkpO?= =?utf-8?B?NG5KOVZ1WGxNYVJMcEJnQjZjUmZvNWMyeXhkSHhNM2FUNGd5UWplL3RsZVo5?= =?utf-8?B?cGFXWEVqaE43YkpydkNPdE9IZ3MrNVZCQzhmNDZoeUloclpkL1lFR1oxbXpT?= =?utf-8?B?SitKMERyR2h0THZtelhKSjF0WnB5dXRIS2lrVmtSVzZHczFGdEk1Wi84MnNB?= =?utf-8?B?N0VwaFFJWkhWM3Nzdzg3OG9OQXN4dVpKTzFzQVpsYmZZWlR5aFJBSDlib2lZ?= =?utf-8?B?a2s3dnBkdlFiaC91SUJ3SzZEeGRoc1lzdlI3SUl6MWY4b2RJRFZzeFFCV0Fq?= =?utf-8?B?c284US9TQkR5MWpITjRJZkN2L2dHWkFWdE8wWXBOVENFL1FsNlZ6Wkh0a1Mr?= =?utf-8?B?REswQ0hSOUswYmttbG15STNXeVhNTElTRUNNOWdhTHoyM3ZMRUNVcU9Va05W?= =?utf-8?B?MERlOEFDOFpMN1pyREN4QWRqRzdRaUdvN0JFdzI1WmwrMGxGdEVlQmFUTmEw?= =?utf-8?B?dTNoUDQweS9iU0FUTVRxUUFDS3Fod2FnUWl0QmwzcldjcC84SEdENjByakVl?= =?utf-8?B?U3EycUtkcUNsekE1b3RmRWJaUm8rOUhlVmJEOHZiUzlWTnpiSjlMajlySHFE?= =?utf-8?B?UU5kSFZYZ2d4U2tqSXhVMnRDaEovQnFkajZPRlBiNkloRFJHelJmMFA3L3BW?= =?utf-8?B?OTR0RFN6V09kMVhIMEU2WEhzZlRGU0NGYTU0YlhPdDFmTjMrUFFRZ3N0d0NR?= =?utf-8?B?UThzV0FXM0x2VEdrQ3BBZEROYjZXMEJMM3YyWmUwNCtTdlVYNFlMU1NNcWti?= =?utf-8?B?NUNVRitjV1FXMndtMldzWWNPazJtTjJCeTNtS1JiQnhqVHNUcUk1elY0QkZ6?= =?utf-8?B?MU11anJhQk5abHpXSTc3UEhRSGhOd2ZKa2pCUW1DR2VKZEJTWFJ4eGVDblJi?= =?utf-8?B?TGRmbGxkT0Q1a3ZGNFNLdEE0ODFtY3RlZitQdjVSbEU5dllJZ1NPMVNrSmR1?= =?utf-8?B?N2k4aGpVT1ZtaXJETTR6Ylh5d0UyQm1mOUtYbGt3aTA4a3VzSG53cnpJRnEw?= =?utf-8?B?bDNuSEZNWnhIZDV2N2lrTkNRQ0lWL3VkM3pmTVJnRlBaaUl4bVltWWVOeFdE?= =?utf-8?B?SWJGd25ILzRrTTVOSDhHdVZVUmU3NVYvd3RyUERQQitRTnlrTXV3aENvRjY1?= =?utf-8?B?S3VKRzdCNTd0OUtpem1OUWZKenphZXpNYm14NVRvTStOWlYvY3YxdW5NZHdR?= =?utf-8?B?TG9yY2dMMEFCUHVTTkRpc3JPS0NpeWNFZFRTWDhHYm1GRVVySk9TYXF5aHFu?= =?utf-8?B?MDc2Mk00dmdVVWxvUEtEQm0zWGpaUS9sWDBaTVNGWlFMVzNhbWRNZzFXZlds?= =?utf-8?B?UXlGNC9KTVdjNUt5SHVNQWRDQVFValp0K3NMR3R1WmxXQkdNclcyaVNheGZr?= =?utf-8?B?ZFdsQTV6ay9ha1RHNTNZVHQ3R3l0b1luS1Y3SlFnZW9Md1FYVTlkNzlRd0xI?= =?utf-8?B?OHJEWk56OGw5eVhWNk1jNjNBQmxBWkoyeDQxcGJlTjl2Y2tYNHpETFMyUGs3?= =?utf-8?B?bFY2UWJKSlh5UE9VMWhpWGZQT3N6ajJyWXFTY2cxOTVEWUQ2dnhBV1VGMG9z?= =?utf-8?B?UzlVcmJRYkdWRGtZdkdGZTY0SUIzUjkyUzlUb1dwUCtFQjZIdzZGYmdNUDFE?= =?utf-8?B?ZHNCMFpyUVZJNlNTZ3ZycTRPV1ppa3BXbGJkcWFPNS85NmZaOTljR1d2QW5D?= =?utf-8?B?eVhidTd6SE9BYk12OW9BUTVDaVZUYjlRNXJzcDQ2NWh4eG1BeXBaNXdrMmZF?= =?utf-8?B?TVpRZzVWM3JGOEhKWkVqdkVzYklBbitzQnYybnpQL0Mwd2YvdVMwWVFvUUZP?= =?utf-8?B?NU40QzQ4ckZUcEJWVlNiMWdFZlhwWk5kb1JMdWtJejk0MDNjeW1keWYvbG1i?= =?utf-8?B?VWFtdHoxdTJ5NDRVQkhtOERNSnRWWUxoTUo1c0RYbW5taHpEemVTMUYzRUxF?= =?utf-8?B?Uk5KRjFDS1lpaXlzRUpWendRVUF3K2ZLUy9UYXZBTmtvMGtUU0JjZzc3NXhh?= =?utf-8?B?Z203bzJWMkFZaGRtQWphdVZCZU84TGo1bm5WNEh1QmFrM0xqbHJNM2FpRXhP?= =?utf-8?B?NFRCYVhJNzNvbUt6WmRjU21Ua1luV1NtcklSUG84UEwxSFh6TW44T3JiS3c5?= =?utf-8?B?SmorbXAxbWdLaWwrYUVsR29veHBGSXNreVRzbEpFTlZmTE1xc2tTRTIvZVVS?= =?utf-8?B?WVZ5S1RmdzdOWVJ5NHNiSVpTREhjWjhQbmxENHVHc1BCVld6dVBua3VvMXhR?= =?utf-8?Q?4zyykuSi9KMrkoSM=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: e46a9163-5707-46fb-1d58-08dee1c03b12 X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:54:47.5483 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: l81MH0F2QlYgf33id9ZRy1qa5u+o//U8+MAcXYACZCwu/KrZ6F5DAJfH5SICiK1ncxuf/7Bae06UKXILh/Za5Is8ejrofUTcDEzEeCrt1yc= X-MS-Exchange-Transport-CrossTenantHeadersStamped: KL1PR02MB6189 Creating the core driver for Advantech EIO, all other drivers depend on thi= s. This core driver provides register access and coordination for the EIO's subdevices (GPIO, watchdog, hwmon, thermal, backlight, I2C). This driver supports EIO-IS200, EIO-201, EIO-210 and EIO-211, these devices are available in several of Advantech x86-based boards, and provide several different interfaces like I2C, GPIO, Watchdog, Hardware Monitoring, Thermal Control, and Backlight control. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 6 + drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/eio_core.c | 644 ++++++++++++++++++++++++++++++++++++++++++++= ++++ include/linux/mfd/eio.h | 124 ++++++++++ 5 files changed, 785 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index dcffe9fc54f6..53b5f7412966 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -615,6 +615,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/adv_swbutton.c =20 +ADVANTECH EIO DRIVER +M: Ramiro Oliveira +S: Maintained +F: drivers/mfd/eio_core.c +F: include/linux/mfd/eio.h + ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER M: Lucas Stankus S: Supported diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 35f6e9b76d05..42d133a2eb8d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -517,6 +517,16 @@ config MFD_DLN2 etc. must be enabled in order to use the functionality of the device. =20 +config MFD_EIO + tristate "Advantech EIO core" + select MFD_CORE + help + This enables support for the Advantech EIO multi-function device. + This core driver provides register access and coordination for the + EIO's subdevices (GPIO, watchdog, hwmon, thermal, backlight, I2C). + This driver supports EIO-IS200, EIO-201, EIO-210 and EIO-211. + + config MFD_ENE_KB3930 tristate "ENE KB3930 Embedded Controller support" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c33..2013de45528c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_MFD_CROS_EC_DEV) +=3D cros_ec_dev.o obj-$(CONFIG_MFD_CS42L43) +=3D cs42l43.o obj-$(CONFIG_MFD_CS42L43_I2C) +=3D cs42l43-i2c.o obj-$(CONFIG_MFD_CS42L43_SDW) +=3D cs42l43-sdw.o +obj-$(CONFIG_MFD_EIO) +=3D eio_core.o obj-$(CONFIG_MFD_ENE_KB3930) +=3D ene-kb3930.o obj-$(CONFIG_MFD_EXYNOS_LPASS) +=3D exynos-lpass.o obj-$(CONFIG_MFD_GATEWORKS_GSC) +=3D gateworks-gsc.o diff --git a/drivers/mfd/eio_core.c b/drivers/mfd/eio_core.c new file mode 100644 index 000000000000..25873c061812 --- /dev/null +++ b/drivers/mfd/eio_core.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Advantech Embedded Controller base Driver + * + * This driver provides an interface to access the EIO Series EC + * firmware via its own Power Management Channel (PMC) for subdrivers: + * + * A system may have one or two independent EIO devices. + * + * Copyright (C) 2025 Advantech Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_TIMEOUT 5000 +#define SLEEP_MAX 200 +#define NUM_EC 1 + +struct eio_dev_port { + u16 idx_port; + u16 data_port; +}; + +static struct eio_dev_port pnp_port[] =3D { + { .idx_port =3D EIO_PNP_INDEX, .data_port =3D EIO_PNP_DATA }, + { .idx_port =3D EIO_SUB_PNP_INDEX, .data_port =3D EIO_SUB_PNP_DATA }, +}; + +static struct mfd_cell eio_devs[] =3D { + MFD_CELL_NAME("eio_wdt"), + MFD_CELL_NAME("gpio_eio"), + MFD_CELL_NAME("eio_hwmon"), + MFD_CELL_NAME("i2c_eio"), + MFD_CELL_NAME("eio_thermal"), + MFD_CELL_NAME("eio_fan"), + MFD_CELL_NAME("eio_bl"), +}; + +static const struct regmap_range eio_range[] =3D { + regmap_reg_range(EIO_PNP_INDEX, EIO_PNP_DATA), + regmap_reg_range(EIO_SUB_PNP_INDEX, EIO_SUB_PNP_DATA), + regmap_reg_range(0x200, 0x3FF), +}; + +static const struct regmap_access_table volatile_regs =3D { + .yes_ranges =3D eio_range, + .n_yes_ranges =3D ARRAY_SIZE(eio_range), +}; + +static const struct regmap_config pnp_regmap_config =3D { + .name =3D "eio_core", + .reg_bits =3D 16, + .val_bits =3D 8, + .volatile_table =3D &volatile_regs, + .io_port =3D true, + .cache_type =3D REGCACHE_NONE, +}; + +static struct { + char name[32]; + int cmd; + int ctrl; + int dev; + int size; + enum { + HEX, + NUMBER, + PNP_ID, + } type; +} attrs[] =3D { + { "board_name", 0x53, 0x10, 0, 16 }, + { "board_serial", 0x53, 0x1F, 0, 16 }, + { "board_manufacturer", 0x53, 0x11, 0, 16 }, + { "board_id", 0x53, 0x1E, 0, 4 }, + { "firmware_version", 0x53, 0x21, 0, 4 }, + { "firmware_name", 0x53, 0x22, 0, 16 }, + { "firmware_build", 0x53, 0x23, 0, 26 }, + { "firmware_date", 0x53, 0x24, 0, 16 }, + { "chip_id", 0x53, 0x12, 0, 12 }, + { "chip_detect", 0x53, 0x15, 0, 12 }, + { "platform_type", 0x53, 0x13, 0, 16 }, + { "platform_revision", 0x53, 0x04, 0x44, 4 }, + { "eapi_version", 0x53, 0x04, 0x64, 4 }, + { "eapi_id", 0x53, 0x31, 0, 4 }, + { "boot_count", 0x55, 0x10, 0, 4, NUMBER }, + { "powerup_hour", 0x55, 0x11, 0, 4, NUMBER }, + { "pnp_id", 0x53, 0x04, 0x68, 4, PNP_ID }, +}; + +static ssize_t info_show(struct device *dev, struct device_attribute *attr= , char *buf) +{ + uint i; + + for (i =3D 0; i < ARRAY_SIZE(attrs); i++) { + int ret; + char str[32] =3D ""; + int val; + + struct pmc_op op =3D { + .cmd =3D attrs[i].cmd, + .control =3D attrs[i].ctrl, + .device_id =3D attrs[i].dev, + .payload =3D (u8 *)str, + .size =3D attrs[i].size, + }; + + if (strcmp(attr->attr.name, attrs[i].name)) + continue; + + ret =3D eio_core_pmc_operation(dev, &op); + if (ret) + return ret; + + if (attrs[i].size !=3D 4) + return sysfs_emit(buf, "%s\n", str); + + val =3D *(u32 *)str; + + if (attrs[i].type =3D=3D HEX) + return sysfs_emit(buf, "0x%08X\n", val); + + if (attrs[i].type =3D=3D NUMBER) + return sysfs_emit(buf, "%d\n", val); + + if (attrs[i].type =3D=3D PNP_ID) + return sysfs_emit(buf, "%c%c%c, %X\n", + (val >> 14 & 0x3F) + 0x40, + ((val >> 9 & 0x18) | (val >> 25 & 0x07)) + 0x40, + (val >> 20 & 0x1F) + 0x40, + val & 0xFFF); + } + + return -EINVAL; +} + + +PMC_DEVICE_ATTR_RO(board_name); +PMC_DEVICE_ATTR_RO(board_serial); +PMC_DEVICE_ATTR_RO(board_manufacturer); +PMC_DEVICE_ATTR_RO(firmware_name); +PMC_DEVICE_ATTR_RO(firmware_version); +PMC_DEVICE_ATTR_RO(firmware_build); +PMC_DEVICE_ATTR_RO(firmware_date); +PMC_DEVICE_ATTR_RO(chip_id); +PMC_DEVICE_ATTR_RO(chip_detect); +PMC_DEVICE_ATTR_RO(platform_type); +PMC_DEVICE_ATTR_RO(platform_revision); +PMC_DEVICE_ATTR_RO(board_id); +PMC_DEVICE_ATTR_RO(eapi_version); +PMC_DEVICE_ATTR_RO(eapi_id); +PMC_DEVICE_ATTR_RO(boot_count); +PMC_DEVICE_ATTR_RO(powerup_hour); +PMC_DEVICE_ATTR_RO(pnp_id); + +static struct attribute *pmc_attrs[] =3D { + &dev_attr_board_name.attr, + &dev_attr_board_serial.attr, + &dev_attr_board_manufacturer.attr, + &dev_attr_firmware_name.attr, + &dev_attr_firmware_version.attr, + &dev_attr_firmware_build.attr, + &dev_attr_firmware_date.attr, + &dev_attr_chip_id.attr, + &dev_attr_chip_detect.attr, + &dev_attr_platform_type.attr, + &dev_attr_platform_revision.attr, + &dev_attr_board_id.attr, + &dev_attr_eapi_version.attr, + &dev_attr_eapi_id.attr, + &dev_attr_boot_count.attr, + &dev_attr_powerup_hour.attr, + &dev_attr_pnp_id.attr, + NULL +}; + +ATTRIBUTE_GROUPS(pmc); + +static int eio_pnp_read(struct device *dev, struct eio_dev_port *port, u8 = idx, unsigned int *val) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + + ret =3D regmap_write(eio->map, port->idx_port, idx); + if (ret) { + dev_err(dev, "Error port write 0x%X\n", port->idx_port); + return ret; + } + + ret =3D regmap_read(eio->map, port->data_port, val); + if (ret) + dev_err(dev, "Error port read 0x%X\n", port->data_port); + + return ret; +} + +static int eio_pnp_write(struct device *dev, struct eio_dev_port *port, u8= idx, u8 data) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + + ret =3D regmap_write(eio->map, port->idx_port, idx); + if (ret) { + dev_err(dev, "Error port write 0x%X %X\n", port->idx_port, port->data_po= rt); + return ret; + } + + ret =3D regmap_write(eio->map, port->data_port, data); + if (ret) + dev_err(dev, "Error port write 0x%X %X\n", port->idx_port, port->data_po= rt); + + return ret; +} + +static int eio_unlock_port(struct device *dev, struct eio_dev_port *port) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + + /* Write 0x87 to index port twice to unlock IO port */ + ret =3D regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_ENTER); + if (ret) { + dev_err(dev, "Error entering PNP mode on 0x%X: %d\n", port->idx_port, re= t); + return ret; + } + + ret =3D regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_ENTER); + if (ret) + dev_err(dev, "Error entering PNP mode on 0x%X: %d\n", port->idx_port, re= t); + + return ret; +} + +static int eio_lock_port(struct device *dev, struct eio_dev_port *port) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + /* Write 0xAA to index port once to lock IO port */ + ret =3D regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_EXIT); + if (ret) + dev_err(dev, "Error port write 0x%X\n", port->idx_port); + + return ret; +} + +static int pmc_write_data(struct device *dev, int id, u8 value) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + + ret =3D eio_core_pmc_wait(dev, id, PMC_WAIT_INPUT); + if (ret) + return ret; + + ret =3D regmap_write(eio->map, eio->pmc[id].data, value); + if (ret) + dev_err(dev, "Error PMC write %X:%X\n", + eio->pmc[id].data, value); + + return ret; +} + +static int pmc_write_cmd(struct device *dev, int id, u8 value) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + int ret; + + ret =3D eio_core_pmc_wait(dev, id, PMC_WAIT_INPUT); + if (ret) + return ret; + + ret =3D regmap_write(eio->map, eio->pmc[id].cmd, value); + if (ret) + dev_err(dev, "Error PMC write %X:%X\n", + eio->pmc[id].cmd, value); + + return ret; +} + +static int pmc_read_data(struct device *dev, int id, u8 *value) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret =3D eio_core_pmc_wait(dev, id, PMC_WAIT_OUTPUT); + if (ret) + return ret; + + ret =3D regmap_read(eio->map, eio->pmc[id].data, &val); + if (ret) + dev_err(dev, "Error PMC read %X\n", eio->pmc[id].data); + else + *value =3D (u8)(val & 0xFF); + + return ret; +} + +static int pmc_read_status(struct device *dev, int id) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + unsigned int val; + + if (regmap_read(eio->map, eio->pmc[id].status, &val)) { + dev_err(dev, "Error PMC read %X\n", + eio->pmc[id].status); + return 0; + } + + return val; +} + +static void pmc_clear(struct device *dev, int id) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + unsigned int val; + + /* Check if input buffer blocked */ + if ((pmc_read_status(dev, id) & EIO_PMC_STATUS_IBF) =3D=3D 0) + return; + + /* Read out previous garbage */ + if (regmap_read(eio->map, eio->pmc[id].data, &val)) + dev_err(dev, "Error pmc clear\n"); + + usleep_range(10, 100); +} + +int eio_core_pmc_wait(struct device *dev, int id, enum eio_pmc_wait wait) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + uint val; + int timeout =3D DEFAULT_TIMEOUT; + + if (wait =3D=3D PMC_WAIT_INPUT) + return regmap_read_poll_timeout(eio->map, eio->pmc[id].status, + val, (val & EIO_PMC_STATUS_IBF) =3D=3D 0, + SLEEP_MAX, timeout); + return regmap_read_poll_timeout(eio->map, + eio->pmc[id].status, val, + (val & EIO_PMC_STATUS_OBF) !=3D 0, + SLEEP_MAX, timeout); +} +EXPORT_SYMBOL_GPL(eio_core_pmc_wait); + +int eio_core_pmc_operation(struct device *dev, struct pmc_op *op) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + u8 i; + int ret; + bool reading =3D op->cmd & EIO_FLAG_PMC_READ; + + mutex_lock(&eio->mutex); + + pmc_clear(dev, op->chip); + + ret =3D pmc_write_cmd(dev, op->chip, op->cmd); + if (ret) + goto err; + + ret =3D pmc_write_data(dev, op->chip, op->control); + if (ret) + goto err; + + ret =3D pmc_write_data(dev, op->chip, op->device_id); + if (ret) + goto err; + + ret =3D pmc_write_data(dev, op->chip, op->size); + if (ret) + goto err; + + for (i =3D 0; i < op->size; i++) { + if (reading) + ret =3D pmc_read_data(dev, op->chip, &op->payload[i]); + else + ret =3D pmc_write_data(dev, op->chip, op->payload[i]); + + if (ret) + break; + } + +err: + mutex_unlock(&eio->mutex); + if (ret) + return ret; + return 0; + +} +EXPORT_SYMBOL_GPL(eio_core_pmc_operation); + +static int get_pmc_port(struct device *dev, int id, + struct eio_dev_port *port) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + struct _pmc_port *pmc =3D &eio->pmc[id]; + int ret; + unsigned int high, low; + + ret =3D eio_unlock_port(dev, port); + if (ret) + return ret; + + /* Switch to PMC device page */ + ret =3D eio_pnp_write(dev, port, EIO_LDN, EIO_LDN_PMC1); + if (ret) + return ret; + + /* Active this device */ + ret =3D eio_pnp_write(dev, port, EIO_LDAR, EIO_LDAR_LDACT); + if (ret) + return ret; + + /* Get PMC data port */ + ret =3D eio_pnp_read(dev, port, EIO_IOBA0H, &high); + if (ret) + return ret; + ret =3D eio_pnp_read(dev, port, EIO_IOBA0L, &low); + if (ret) + return ret; + pmc->data =3D (high << 8) | low; + + /* Get PMC cmd */ + ret =3D eio_pnp_read(dev, port, EIO_IOBA1H, &high); + if (ret) + return ret; + ret =3D eio_pnp_read(dev, port, EIO_IOBA1L, &low); + if (ret) + return ret; + pmc->cmd =3D (high << 8) | low; + + /* Disable IRQ */ + ret =3D eio_pnp_write(dev, port, EIO_IRQCTRL, 0); + if (ret) + return ret; + + ret =3D eio_lock_port(dev, port); + if (ret) + return ret; + + /* Make sure IO ports are not occupied */ + if (!devm_request_region(dev, pmc->data, 2, KBUILD_MODNAME)) { + dev_err(dev, "Request region %X error\n", pmc->data); + return -EBUSY; + } + + return 0; +} + +static int eio_init(struct device *dev) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + u16 chip_id; + unsigned int msb, lsb; + int tmp; + int ret =3D -ENOMEM; + + for (int chip =3D 0; chip < ARRAY_SIZE(pnp_port); chip++) { + struct eio_dev_port *port =3D pnp_port + chip; + + if (!devm_request_region(dev, pnp_port[chip].idx_port, + pnp_port[chip].data_port - + pnp_port[chip].idx_port, + KBUILD_MODNAME)) + continue; + + ret =3D eio_unlock_port(dev, port); + if (ret) + return ret; + + ret =3D eio_pnp_read(dev, port, EIO_CHIPID1, &msb); + if (ret) + return ret; + + ret =3D eio_pnp_read(dev, port, EIO_CHIPID2, &lsb); + if (ret) + return ret; + + chip_id =3D (msb << 8) | lsb; + + if (chip_id !=3D EIO200_CHIPID && chip_id !=3D EIO201_211_CHIPID) + continue; + + /* Turn on the enable flag */ + ret =3D eio_pnp_read(dev, port, EIO_SIOCTRL, &tmp); + if (ret) + return ret; + + tmp |=3D EIO_SIOCTRL_SIOEN; + + ret =3D eio_pnp_write(dev, port, EIO_SIOCTRL, tmp); + if (ret) + return ret; + + ret =3D eio_lock_port(dev, port); + if (ret) + return ret; + + ret =3D get_pmc_port(dev, chip, port); + if (ret) + return ret; + + if (chip =3D=3D 0) + eio->flag |=3D EIO_F_CHIP_EXIST; + else + eio->flag |=3D EIO_F_SUB_CHIP_EXIST; + } + + return ret; +} + +static uint8_t acpi_ram_access(struct device *dev, uint8_t offset) +{ + uint8_t val; + int ret; + struct eio_dev *eio =3D dev_get_drvdata(dev); + + /* We only store information on primary EC */ + int chip =3D 0; + + mutex_lock(&eio->mutex); + + pmc_clear(dev, chip); + + ret =3D pmc_write_cmd(dev, chip, EIO_PMC_CMD_ACPIRAM_READ); + if (ret) + goto err; + + ret =3D pmc_write_data(dev, chip, offset); + if (ret) + goto err; + + ret =3D pmc_write_data(dev, chip, sizeof(val)); + if (ret) + goto err; + + ret =3D pmc_read_data(dev, chip, &val); + if (ret) + goto err; + + ret =3D val; + +err: + mutex_unlock(&eio->mutex); + return ret; +} + +static int firmware_code_base(struct device *dev) +{ + struct eio_dev *eio =3D dev_get_drvdata(dev); + u8 ic_vendor, ic_code, code_base; + + ic_vendor =3D acpi_ram_access(dev, EIO_ACPIRAM_ICVENDOR); + ic_code =3D acpi_ram_access(dev, EIO_ACPIRAM_ICCODE); + code_base =3D acpi_ram_access(dev, EIO_ACPIRAM_CODEBASE); + + if (ic_vendor !=3D 'R') + return -ENODEV; + + if (ic_code !=3D EIO200_ICCODE && ic_code !=3D EIO201_ICCODE && + ic_code !=3D EIO211_ICCODE) + goto err; + + if (code_base =3D=3D EIO_ACPIRAM_CODEBASE_NEW) { + eio->flag |=3D EIO_F_NEW_CODE_BASE; + return 0; + } + + if (code_base =3D=3D 0 && + (ic_code !=3D EIO201_ICCODE && ic_code !=3D EIO211_ICCODE)) { + dev_info(dev, "Old code base not supported."); + return -ENODEV; + } + +err: + /* Codebase error. This should only happen on firmware error. */ + dev_err(dev, + "Codebase check fail: vendor: 0x%X, code: 0x%X, base: 0x%X\n", + ic_vendor, ic_code, code_base); + return -ENODEV; +} + +static int eio_probe(struct device *dev, unsigned int id) +{ + int ret =3D 0; + struct eio_dev *eio; + + eio =3D devm_kzalloc(dev, sizeof(*eio), GFP_KERNEL); + if (!eio) + return -ENOMEM; + + eio->dev =3D dev; + mutex_init(&eio->mutex); + + eio->iomem =3D devm_ioport_map(dev, 0, EIO_SUB_PNP_DATA + 1); + if (IS_ERR(eio->iomem)) + return PTR_ERR(eio->iomem); + + eio->map =3D devm_regmap_init_mmio(dev, eio->iomem, &pnp_regmap_config); + if (IS_ERR(eio->map)) + return PTR_ERR(eio->map); + + /* Publish instance for subdrivers (dev_get_drvdata(dev->parent)) */ + dev_set_drvdata(dev, eio); + + if (eio_init(dev)) + return -ENODEV; + + ret =3D firmware_code_base(dev); + if (ret) { + dev_err(dev, "Chip code base check fail\n"); + return ret; + } + + ret =3D devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + eio_devs, ARRAY_SIZE(eio_devs), + NULL, 0, NULL); + if (ret) + dev_err(dev, "Cannot register child devices (error =3D %d)\n", ret); + + return ret; +} + +static struct isa_driver eio_driver =3D { + .probe =3D eio_probe, + .driver =3D { + .name =3D "eio_core", + .dev_groups =3D pmc_groups, + }, +}; +module_isa_driver(eio_driver, NUM_EC); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Advantech EIO series EC core driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/eio.h b/include/linux/mfd/eio.h new file mode 100644 index 000000000000..c2daad41318b --- /dev/null +++ b/include/linux/mfd/eio.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Advantech Co., Ltd. + */ + +#ifndef _MFD_EIO_H_ +#define _MFD_EIO_H_ + +#include +#include + +/* CHIP ID versions */ +#define EIO_CHIPID1 0x20 +#define EIO_CHIPID2 0x21 +#define EIO_CHIPVER 0x22 +#define EIO_SIOCTRL 0x23 +#define EIO_SIOCTRL_SIOEN BIT(0) +#define EIO_SIOCTRL_SWRST BIT(1) +#define EIO_IRQCTRL 0x70 +#define EIO200_CHIPID 0x9610 +#define EIO201_211_CHIPID 0x9620 +#define EIO200_ICCODE 0x10 +#define EIO201_ICCODE 0x20 +#define EIO211_ICCODE 0x21 + +/* LPC PNP */ +#define EIO_PNP_INDEX 0x299 +#define EIO_PNP_DATA 0x29A +#define EIO_SUB_PNP_INDEX 0x499 +#define EIO_SUB_PNP_DATA 0x49A +#define EIO_EXT_MODE_ENTER 0x87 +#define EIO_EXT_MODE_EXIT 0xAA + +/* LPC LDN */ +#define EIO_LDN 0x07 +#define EIO_LDN_PMC0 0x0C +#define EIO_LDN_PMC1 0x0D + +/* PMC registers */ +#define EIO_PMC_STATUS_IBF BIT(1) +#define EIO_PMC_STATUS_OBF BIT(0) +#define EIO_LDAR 0x30 +#define EIO_LDAR_LDACT BIT(0) +#define EIO_IOBA0H 0x60 +#define EIO_IOBA0L 0x61 +#define EIO_IOBA1H 0x62 +#define EIO_IOBA1L 0x63 +#define EIO_FLAG_PMC_READ BIT(0) + +/* PMC command list */ +#define EIO_PMC_CMD_ACPIRAM_READ 0x31 +#define EIO_PMC_CMD_CFG_SAVE 0x56 + +/* OLD PMC */ +#define EIO_PMC_NO_INDEX 0xFF + +/* ACPI RAM Address Table */ +#define EIO_ACPIRAM_VERSIONSECTION (0xFA) +#define EIO_ACPIRAM_ICVENDOR (EIO_ACPIRAM_VERSIONSECTION + 0x00) +#define EIO_ACPIRAM_ICCODE (EIO_ACPIRAM_VERSIONSECTION + 0x01) +#define EIO_ACPIRAM_CODEBASE (EIO_ACPIRAM_VERSIONSECTION + 0x02) + +#define EIO_ACPIRAM_CODEBASE_NEW BIT(7) + +/* Firmware */ +#define EIO_F_SUB_NEW_CODE_BASE BIT(6) +#define EIO_F_SUB_CHANGED BIT(7) +#define EIO_F_NEW_CODE_BASE BIT(8) +#define EIO_F_CHANGED BIT(9) +#define EIO_F_SUB_CHIP_EXIST BIT(30) +#define EIO_F_CHIP_EXIST BIT(31) + +/* Others */ +#define EIO_EC_NUM 2 + +struct _pmc_port { + union { + u16 cmd; + u16 status; + }; + u16 data; +}; + +struct pmc_op { + u8 cmd; + u8 control; + u8 device_id; + u8 size; + u8 *payload; + u8 chip; +}; + +enum eio_rw_operation { + OPERATION_READ, + OPERATION_WRITE, +}; + +struct eio_dev { + struct device *dev; + struct regmap *map; + void __iomem *iomem; + struct mutex mutex; /* Protects PMC command access */ + struct _pmc_port pmc[EIO_EC_NUM]; + u32 flag; +}; + +int eio_core_pmc_operation(struct device *dev, struct pmc_op *operation); + +enum eio_pmc_wait { + PMC_WAIT_INPUT, + PMC_WAIT_OUTPUT, +}; + +int eio_core_pmc_wait(struct device *dev, int id, enum eio_pmc_wait wait); + +#define PMC_DEVICE_ATTR_RO(_name) = \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return info_show(dev, attr, buf); \ + } \ + static DEVICE_ATTR_RO(_name) + +#endif --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from TYDPR03CU002.outbound.protection.outlook.com (mail-japaneastazon11023111.outbound.protection.outlook.com [52.101.127.111]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DA42C3A4F55; Tue, 14 Jul 2026 15:54:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.127.111 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044499; cv=fail; b=WHH4+plf8MNkqd76s8GsFUyz7R8Ab4DyyxEDvU8m5dzmWEI8DtOuvSWUyr0kj5Np7cParhQMrbUgNRLUDbqsuXG6ulNLMzalWPwLUSQ3ZmJdOphajkQjg6gA01WjuNaGSDp+dlH4VCc3dg0CiXWV6rFUi4wyVxxt54yQ2zBdZPw= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044499; c=relaxed/simple; bh=yXoPXr9j/J//s4BsCd+aMWOqeFtBKfUVrROXWUw0Ha8=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=kb8toQFXgb1r/OUOn0E8wfI25eONtj8SPyNMJu4yqZ0zcU8oc8xtrzkQU0N1t9TG9hdxmt3hC0SWmFgMvbM57beXwQMeYaJruIB3KmkpUFYsBGWcvONiKC9ifxDMn60bXTp1uSjA4RBpdjd8lGLnDX7UviXpg3ZL4I5YKeMUles= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=y8eoxC2g; arc=fail smtp.client-ip=52.101.127.111 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="y8eoxC2g" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=tvycWw+b0/RtTQzYUaXpu5+FUn23po47PwbSlvWMHJOjj0gB+2lv8O8VTj+QqCuBV78X/bZVR/3bUFXGSTL9t6eGvAh5ecw7tfAE0CUSoeBNeTNZbg0x+I4nPShXl++3CRXzGks3KSllDi5f7t/XeMusxD0Of9m1GVuHagUltS0W+dhLTDUW6FuOsL/4jyP9xuVZVhpUvQzMl8pHvLs6MiRM4N74mU+TmbddTvN5wj1nRrXYG6uDPDIfdrmSVIAw3CyfpcgOBf+Yzv8SiQaLKOUtmltFIs/l6gb03ZQ7jkbAeH/nbSaCnxBN99p4V+YMK9q4t2Y/OExMv58CAHpU/Q== 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=Ft5cmZw/ESxt/eAfvxf5FBlZIIaLYIIu8rqsT+8XnbM=; b=nGXRyBfqi9CNBtBgnyTPxBRYD9nHhb1P0cNh8yTu/h5xuwxYrA9amcW8UUNGD/kM0knoZlrwteqZbS5nE9Fm/3KTpuW0DA25RJN41ie0d7LyfAPTChrTnpHtzHDuPU3t/rtPiHlnZkLfiQvN+F+B5GRUjB3vn2bcQDxLN6mvur3LmyEI4RHMj+39xsLo1PkeT6fZGP9OKEA7nl0NCrYGTS9sZUKMqQPMbGDqhXj4RMuO9SACJVFtOBHAUnAoQ0EmEd3QMDryILzOPQNIHlh+CPnHiPsNQdqCARlJOjTnLgXJb8JMygcVomDHtNOC+CcC4cUwygKWMzbX8m40xThH5w== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=Ft5cmZw/ESxt/eAfvxf5FBlZIIaLYIIu8rqsT+8XnbM=; b=y8eoxC2g5GgJlvRmLEPFXad9Vz5FzHWnUSZaIxIAN+7c+2whPthb9lhAdDx2IULqR/UkGm4LNt7UHR7YnnNCs8M/qC38rQJtgyeEDv5492q9tpRPWOQsZ/67/PrSmoyoYy+PFbcJXJNwKJ61UY3HDu+CaVC09Z9dO3tCbqVGo2M++8XBCVy5d1oFqRXliVIlVoixxrG/MCIVq6kty3xO6W1g6Kyot/l66FbXYYnWVmtejfgv3BumUbvWHQ3n+6lHvddW2l/To3Lu9XhD+vN1eIapvaBpYs08GCKXANqYOPYEKSbPS7p1RJ1PKQgf+zHPi2z2Gs6QDYOGB4Af+pADFw== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by KL1PR02MB6189.apcprd02.prod.outlook.com (2603:1096:820:bd::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.20; Tue, 14 Jul 2026 15:54:54 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:54:54 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:16 +0200 Subject: [PATCH v2 2/8] Add Advantech EIO GPIO driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-2-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=8128; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=yXoPXr9j/J//s4BsCd+aMWOqeFtBKfUVrROXWUw0Ha8=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu41d/JnUp6TYH8eh2U11mxfVt7qdWZVYmdK c6MQv/qGLqJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CcfKC/97BM/ELXZ3oJHm7oz9sctiRlpfvt3xAsGbWZiUqlkcLPr9+BT4UaetRPvKHykntE3KmFo 73nKuLtJ77HRZyHwh+O0SLYGh+QtGmQOSF/Un349QiiuXSNNGGuMBNZV1IRvLa8cImkpE7Cv3yp HSr0f3JtI3S3kBNf8T8Ogt/4EZN+nJBfh7Zs6kap4+L9NFPq+YXk9Vp+lLcBDQAd/pnEatfgDNr a2UZ3dDfmnVoCcVa0d+QG0vgtyurAEIiS98tb5A50Zx+2RMiE630J4Ek68Cc3QPuP3FTEZwA0JP kMTj0E2nA7MDHyZ3Ya3Z0SbcYQJ0bEU3zRIVNzwUITz4a6WdHrIycwkAFRgvCVkwgHS0OAScOag DzHjCLz+5ZHSWezZs8FbSeW3zbqT6z0T5EQsVxgIHalRcZeK+FJmHi/e7in8hWSkPJCoNqtJ1yC dVgdMAygUDtByGtwTpcCVl6vEEOCw2dJ08/cMDP0PYKWO1+XeYrycUkwlMRpphyJzuqsE= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|KL1PR02MB6189:EE_ X-MS-Office365-Filtering-Correlation-Id: 72895e65-baf4-42b5-2af1-08dee1c03f4e X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|1800799024|23010399003|52116014|7416014|376014|921020|38350700014|3023799007|56012099006|11063799006|22082099003|55112099003|18002099003; X-Microsoft-Antispam-Message-Info: Zx6Mr1onxSLzn+JKb9jofurSiGw0/9DQYOrgJoRHoDyDw5qiMnM8vwpVyZx75+A/0LUw2vOPy3mRu8k/m5mOe5O+BKxjZ/5OxAZcLulmKwspciA1CNYM/TMXvvv3C5XdzLsnPEfkQMNEVpcO0a9XfaQpv7KYbd1bZPDkS+y6LlEG0clysO9IXcfW/AfJDUuXEDX1Xlhj8dwImN2DqCgoiYn4CnXt15vsuq53X2IhKjvfBqTC4N/nwPZ4aAB/mBNbUN8boYcwXCMtFXsYgZZGlnvOiiqNjiHrMREJ4mcItKxAJAfECGYl/bsxsU/Q6dzarV8r1Gap8cXR78stCkg/2wjzGuQ+9cDzCma5H0R78uwBMYshAHeEUxh00n2o8DUoFQF7QrraVHhOSujR/LsQsN7PFsJn24y02rSCuckjEIUMRvjVBCuJujs1pBd2J8eQDer5FS5ZjRV5Wg3quiPrfWHqcFosSH2zw14YOWYhm2+9IcpuFDpTACBzoBTIifCGoebqGQKAzRwkqMfiR8VINW/uAUp3kCEJqwQKymYoRf85WKue7ti2i877kPk3TtdBjY6pW3/irdgoYJW0JwKRBaL5vzkIgYZ918m3O9kosiv28WiVx6SdNa+2FjDiHA71n463WIbKdY8qsFUX4EcDRcdmAlUY9zNDar9aC9g8qM7HFTDYfnaZ3GzyrMaL3cvMr80RyfE7/j03Sclfod/935T8arI1MCloXQToqce/Skcv+oliRjpSEDtNAosVRB5U X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(366016)(1800799024)(23010399003)(52116014)(7416014)(376014)(921020)(38350700014)(3023799007)(56012099006)(11063799006)(22082099003)(55112099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?T0RhRnVMMDFDQTNzMHRabHhvSmpYaE1udG1wZmRkSUtIRjFwK0FqalVYUUF5?= =?utf-8?B?dDBubEwwUXlZZ2ZaNUtWcEZyTmlnVlJOTERlakJHYUMwdXpFLzMrNngxUWc2?= =?utf-8?B?dEl5T2xYODBvWGNCZ3VUQytmeDZIM2lhNjhVMmZMWU9sV0xwcXNVSEMrQmFB?= =?utf-8?B?VmFUUGRObUJld3Z5WGJvWG42ZHp2ckZvVnVWdEZTTm0yRE1GNTZ0V1YydFNZ?= =?utf-8?B?WVNYbVlPbnUxTUlSeEdxby9UOUtEeTQ5bmJQSnhaRXhnUmFORHFiOFFuQi9X?= =?utf-8?B?NkRIci82R0RjM0tHYUJ6ejU1Ylk4Q1hVZVdsMnloZmlSaUN2UlpyY0JIRzRw?= =?utf-8?B?THlwOGNNV3JIZ0R3dWRVaVZYS0JMWEFVQkN5My96cEtxTC9VcHl0RHZpemZI?= =?utf-8?B?QVZnUjNzeGlaMERVbzZQdXNVWGU0ZTR4NmFtMll0SDE4QzI5bXA4Tk91S1hz?= =?utf-8?B?SnVhQm80SXV5ODJJV2k0U1hyVXpiaHVRNHpxYUVCbnlCYVRWUFExckRyaHEx?= =?utf-8?B?MDhZMlR5VVE3RG1YcWNJMTk5c1oyMlVpMVJiTnhIaXJ1UHQyb2ZHODVYdVZs?= =?utf-8?B?SmxEbEpySWlFUUhMeDZoZkpnUXpGRi9DRW9BaWlLZllhOStkVWRENGJscEhp?= =?utf-8?B?bWpqUmVpVzE5UDEvY1EydkE4YVpkYjBWWis2K3JDWTJzdExoR0tNNDNIbFdi?= =?utf-8?B?Wk9BR0ErK1pJT1pWdFVpbXR2eHRYSXRkT1ZXbk5KWFpxTW5CbVg2d0ZjSWo2?= =?utf-8?B?Tm9yZXAxa055NSsyUE5CMHRNU1l2dzlCc09ZTjkrdXlUVU1pNm94RDlQZE1V?= =?utf-8?B?WVJFaHl4Q28vODNvQ0p5TUx2S0lYRzdwaTUrQzEzZXQ5SjBvSFVyY29MR293?= =?utf-8?B?RTd3WVBHODdQNWxNcEY4eHljMVJxN3R0aER4S2VxN05BbmcxWVh1WEpzMkhj?= =?utf-8?B?UC9DRUdhNDgyaVo1d0R1SFVWNGdXRjRxNm5PamdOd1hwT3ZYNi82MitWaWlt?= =?utf-8?B?NWVBSFp4RjdNcmdQZkptYi83RDdjOEpXeDArRWV5KzhPUUMyOWU4d21MdGNK?= =?utf-8?B?eDZVWXRrUEE1R0pjNHV3dUNzK25lcitGWDcvMHlXVUN5cVdFYkxrKzJ2RXJI?= =?utf-8?B?ZlJRL0RRNjVhYkprWmRTejdPdE5Ga0QxYmU4Qm1LUDkvek9PbEZCdkxRUXpj?= =?utf-8?B?U3JEZWtITGNzZ1RlUWVlcndjWVJaV3FYdnJHTUNwUWJJbkNpTEswZXpBalcx?= =?utf-8?B?a3FLTVh0aGlLS3hFdDluTzhxY2hZN3FXWWFWZnQvVllSY2lhTzJjVldqZ1Fy?= =?utf-8?B?ZXhVUDNLME10TnR6NHpaRitiRUFIOFo5bTNtRUVNdjJ0WWdGcWY3NDV0dlMx?= =?utf-8?B?UjJLRTJLRDU4WnRpSllWTHI5MUlwRlZPbUowWS9zL3ZLNWJtZ3RKSElxcmFo?= =?utf-8?B?WFpsMFgxeEQwT2JXSFphNHE0TFZYeW40eWRNdFBLcHRPKzZkV0tOTW1JN0o4?= =?utf-8?B?UWFlblpKRU9VQ041ZWRFcVB4WnpsMXVQZ0o0d3p6UVlKdDRaNGxPZTFWSmM2?= =?utf-8?B?aHAzUTNjZTNPUmpVNVQwR0NtSGorTSt3WU9BcFRsc3pQOGE2dERQZDNzRGxq?= =?utf-8?B?WVVGWGoyVHRrZHdsQTJWTFF4d28zR3p4QnhRSWNjbTJGR2ZjeThlazVLUGdO?= =?utf-8?B?aEdiaXJwYWVKckJXZ2lYZ1lRc20yZkpIaXlYOWhHWnNHb1ZIWEJwNWJFNUtM?= =?utf-8?B?amUyOHFQaDdkZUlmM3VReWErZ2xhUEdkYlpuQkZOcjh1bzVkajZ2YUFQczRB?= =?utf-8?B?QjY1dHEvZW9EdFlpQ2NBeWZDQlVtTG8zYXFFbmlLTnFWN2FsVEg5QmZyWmNt?= =?utf-8?B?aW1WcHNRYXFqYUJQSTFoWDR1dmZIN0VINVpxSjVsZEt3WE5qMHptVndzSUlk?= =?utf-8?B?VmtJamhrZWVDK3hkRmNaSE12aTZvQ1BUTk1FSU9FM3gvREVOZjU0UDdmRyt1?= =?utf-8?B?djkxVVcxZVp3U1FOSlRuWVVUdjRiUTFURTNwMm5qTzA5bVd2dGIzMTA4QUZI?= =?utf-8?B?cCtkV0VrOUFGYnJrc29wRjJzUG5uVUZGNnAvLzFTQng0aGRxV0VoSTFRWUpp?= =?utf-8?B?ekxSeEtUdG13YS8zNVUrdzRKeHMzY3ZSb21JanFMdXVpRzMwdUt4R1hWQnVU?= =?utf-8?B?Nnp1NFNEMlBQeXBtL0Y0dDAvVklxWkE2aG5yTnlkR2U4NGJ4dFl5aUlPR2Rn?= =?utf-8?B?STRPSFRpOGlLVmV0OVhDVGVZV0JJY1FKR2c1dWZZbUlWU2MrbklMUmp3dGpr?= =?utf-8?B?TDNOdFU5aE5RV2I0dnhGYjhrZVlLV1RxVlQxTERDZlI5YXFONFNERVhBaVNK?= =?utf-8?Q?fDqpilz/06JzkwV8=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 72895e65-baf4-42b5-2af1-08dee1c03f4e X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:54:54.6677 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 6ih+FJ64bHb3ndB7LY2E7rd69g/Z24JPTZfiqF98l1EXONs5MTri9PEUViOBM4IIjJfJismdYeg7xFxvaiBCvTrZtvec1UwVKemECT0zfQc= X-MS-Exchange-Transport-CrossTenantHeadersStamped: KL1PR02MB6189 This driver controls the GPIO component of the Advantech EIO chip. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 7 ++ drivers/gpio/Kconfig | 6 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-eio.c | 252 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 266 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 53b5f7412966..a7da47393815 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -609,6 +609,13 @@ S: Maintained F: Documentation/scsi/advansys.rst F: drivers/scsi/advansys.c =20 +ADVANTECH EIO DRIVER +M: Ramiro Oliveira +S: Maintained +F: drivers/gpio/gpio-eio.c +F: drivers/mfd/eio_core.c +F: include/linux/mfd/eio.h + ADVANTECH SWBTN DRIVER M: Andrea Ho L: platform-driver-x86@vger.kernel.org diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f03c05288376..696065d77235 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -295,6 +295,12 @@ config GPIO_DWAPB Say Y or M here to build support for the Synopsys DesignWare APB GPIO block. =20 +config GPIO_EIO + tristate "Advantech EIO GPIO" + depends on MFD_EIO + help + Say Y or M to build support for Advantech EIO GPIO block. + config GPIO_EIC_SPRD tristate "Spreadtrum EIC support" depends on ARCH_SPRD || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fa14581e3995..628596705c21 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_GPIO_DLN2) +=3D gpio-dln2.o obj-$(CONFIG_GPIO_DS4520) +=3D gpio-ds4520.o obj-$(CONFIG_GPIO_DWAPB) +=3D gpio-dwapb.o obj-$(CONFIG_GPIO_EIC_SPRD) +=3D gpio-eic-sprd.o +obj-$(CONFIG_GPIO_EIO) +=3D gpio-eio.o obj-$(CONFIG_GPIO_ELKHARTLAKE) +=3D gpio-elkhartlake.o obj-$(CONFIG_GPIO_EM) +=3D gpio-em.o obj-$(CONFIG_GPIO_EN7523) +=3D gpio-en7523.o diff --git a/drivers/gpio/gpio-eio.c b/drivers/gpio/gpio-eio.c new file mode 100644 index 000000000000..34e1aefd0716 --- /dev/null +++ b/drivers/gpio/gpio-eio.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GPIO driver for Advantech EIO Embedded controller. + * + * Copyright (C) 2025 Advantech Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#define EIO_GPIO_MAX_PINS 48 +#define EIO_GPIO_WRITE 0x18 +#define EIO_GPIO_READ 0x19 + +struct eio_gpio_dev { + u64 avail; + int max; + struct gpio_chip chip; + struct device *dev; +}; + +static struct { + int size; + bool write; +} ctrl_para[] =3D { + { 0x01, false }, { 0x00, false }, { 0x00, false }, { 0x02, false }, + { 0x01, false }, { 0x00, false }, { 0x00, false }, { 0x00, false }, + { 0x00, false }, { 0x00, false }, { 0x00, false }, { 0x00, false }, + { 0x00, false }, { 0x00, false }, { 0x00, false }, { 0x00, false }, + { 0x01, true }, { 0x01, true }, { 0x02, true }, { 0x02, true }, + { 0x02, false }, { 0x10, false } +}; + +enum gpio_ctrl { + EIO_GPIO_STATUS =3D 0x0, + EIO_GPIO_GROUP_AVAIL =3D 0x3, + EIO_GPIO_ERROR =3D 0x04, + EIO_GPIO_PIN_DIR =3D 0x10, + EIO_GPIO_PIN_LEVEL =3D 0x11, + EIO_GPIO_GROUP_DIR =3D 0x12, + EIO_GPIO_GROUP_LEVEL =3D 0x13, + EIO_GPIO_MAPPING =3D 0x14, + EIO_GPIO_NAME =3D 0x15 +}; + +static struct { + int group; + int port; +} group_map[] =3D { + { 0, 0 }, { 0, 1 }, + { 1, 0 }, { 1, 1 }, + { 2, 0 }, { 2, 1 }, + { 3, 0 }, { 3, 1 }, + { 3, 2 }, { 3, 3 }, + { 3, 4 }, { 3, 5 }, + { 3, 6 }, { 3, 7 } +}; + +static int pmc_write(struct device *mfd_dev, u8 ctrl, u8 dev_id, void *dat= a) +{ + struct pmc_op op =3D { + .cmd =3D EIO_GPIO_WRITE, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + }; + + if (ctrl >=3D ARRAY_SIZE(ctrl_para)) + return -ENOMEM; + + if (!ctrl_para[ctrl].write) + return -EINVAL; + + op.size =3D ctrl_para[ctrl].size; + + return eio_core_pmc_operation(mfd_dev, &op); +} + +static int pmc_read(struct device *mfd_dev, u8 ctrl, u8 dev_id, void *data) +{ + struct pmc_op op =3D { + .cmd =3D EIO_GPIO_READ, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + }; + + if (ctrl > ARRAY_SIZE(ctrl_para)) + return -ENOMEM; + + op.size =3D ctrl_para[ctrl].size; + + return eio_core_pmc_operation(mfd_dev, &op); +} + +static int get_dir(struct gpio_chip *chip, unsigned int offset) +{ + u8 dir; + int ret; + + ret =3D pmc_read(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir); + if (ret) + return ret; + + return dir ? 0 : 1; +} + +static int dir_input(struct gpio_chip *chip, unsigned int offset) +{ + u8 dir =3D 0; + + return pmc_write(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir); +} + +static int dir_output(struct gpio_chip *chip, unsigned int offset, int val= ue) +{ + u8 dir =3D 1; + u8 val =3D value; + + pmc_write(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir); + + return pmc_write(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &val); +} + +static int gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + u8 level; + int ret; + + ret =3D pmc_read(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &level); + if (ret) + return ret; + + return level; +} + +static int gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + u8 val =3D value; + + return pmc_write(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &val); +} + +static int check_support(struct device *dev) +{ + u8 data; + int ret; + + ret =3D pmc_read(dev, EIO_GPIO_STATUS, 0, &data); + if (ret) + return ret; + + if ((data & 0x01) =3D=3D 0) + return -EOPNOTSUPP; + + return 0; +} + +static int check_pin(struct device *dev, int pin) +{ + int ret; + int group, bit; + u16 data; + + /* Get pin mapping */ + ret =3D pmc_read(dev, EIO_GPIO_MAPPING, pin, &data); + if (ret) + return ret; + + if ((data & 0xFF) > ARRAY_SIZE(group_map)) + return -EINVAL; + + group =3D group_map[data & 0xFF].group; + bit =3D data >> 8; + + /* Check mapped pin */ + ret =3D pmc_read(dev, EIO_GPIO_GROUP_AVAIL, group, &data); + if (ret) + return ret; + + return data & BIT(bit) ? 0 : -EOPNOTSUPP; +} + +static int gpio_init(struct device *mfd, struct eio_gpio_dev *eio_gpio) +{ + int ret, i; + + ret =3D check_support(mfd); + if (ret) + return dev_err_probe(eio_gpio->dev, ret, "GPIO not supported\n"); + + eio_gpio->avail =3D 0; + + for (i =3D 0 ; i < EIO_GPIO_MAX_PINS ; i++) { + ret =3D check_pin(mfd, i); + if (ret) + continue; + + eio_gpio->avail |=3D BIT(i); + eio_gpio->max =3D i + 1; + } + + return eio_gpio->max ? 0 : -EOPNOTSUPP; +} + +static int gpio_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct eio_gpio_dev *eio_gpio; + struct eio_dev *eio_dev =3D dev_get_drvdata(dev->parent); + + if (!eio_dev) + return dev_err_probe(dev, -ENODEV, "Error contact eio_core\n"); + + eio_gpio =3D devm_kzalloc(dev, sizeof(*eio_gpio), GFP_KERNEL); + if (!eio_gpio) + return -ENOMEM; + + eio_gpio->dev =3D dev; + + if (gpio_init(dev->parent, eio_gpio)) + return -EIO; + + eio_gpio->chip.parent =3D dev->parent; + eio_gpio->chip.ngpio =3D eio_gpio->max; + eio_gpio->chip.label =3D KBUILD_MODNAME; + eio_gpio->chip.owner =3D THIS_MODULE; + eio_gpio->chip.direction_input =3D dir_input; + eio_gpio->chip.get =3D gpio_get; + eio_gpio->chip.direction_output =3D dir_output; + eio_gpio->chip.set =3D gpio_set; + eio_gpio->chip.get_direction =3D get_dir; + eio_gpio->chip.base =3D -1; + eio_gpio->chip.can_sleep =3D true; + + return devm_gpiochip_add_data(dev, &eio_gpio->chip, eio_gpio); +} + +static struct platform_driver gpio_driver =3D { + .probe =3D gpio_probe, + .driver =3D { .name =3D KBUILD_MODNAME, }, +}; + +module_platform_driver(gpio_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("GPIO driver for Advantech EIO embedded controller"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from TYDPR03CU002.outbound.protection.outlook.com (mail-japaneastazon11023109.outbound.protection.outlook.com [52.101.127.109]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8BA5F477E58; Tue, 14 Jul 2026 15:55:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.127.109 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044507; cv=fail; b=E3L4lwUanlwZOkmicokf5kjBG88a4Eh2ZykbDbumcy/VF/Cb0bHBkmDpCZM0fhtBhLcnSA+ipvmxjpvKArjagRFkx70z/j07nWswj5E7izsEAEJu2aBhGfUwwZVEQlJYpGWfTexNZbynYq6/GHc8bfno5ZlQznND/WZKqRs0Nek= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044507; c=relaxed/simple; bh=nNBPffxiF/04OjX0aLmub0AG7jhrP3SnCqB26NFnzxE=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=GEgMC13k/Pv/j5lTme6wHk54jPtRC+V0YGWZ6V1DEzTwffzRAz0ltsQsCzHMq8NTbCPD/7KNMBkZbQkDX4E09POLIbim/MihAqpqBJ0t3Op73D2rnxs3T30ooQDNUu3qMWeUMTO8MpAGf47TLR19eU3zm6sltfbCb3Z32TRB9C8= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=KRyVV90T; arc=fail smtp.client-ip=52.101.127.109 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="KRyVV90T" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=fSU/LSs1o/HyPt1kWoZZm2q5I2g076R81Fo3g52NaXWEDHria0f8kEzkTMSLLJaXYW/4LO7wPf6bcNAJmvTph+ZZpUR9r50ygwidRdSU8sSZ9zSAnnjh054VxdLbKqoJZPJTfoM9mXWHvVPCQzTtYHcqLYKesSN4gR+GjXTdqwQRUFwnxnBGhrYGvHGMBOBM2lyK/zb9uZH8LhJKvCkM2Dzn791mz1FLZr/bNGw61Br7f1OvopTVHwi9q65nop1CrcL2Nr4sO6C3N3raRn92RVG81mokpqf3jLz3ZEh2asxo0gtiPfHdYX/QeNSNpke4ptHI3SNlgB5Rv/40Z7T5nQ== 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=z8KsiC1kd/QSpMxonxA6splEFFmcnXoGFq8xsgLEcz8=; b=U4T7Rky1plkYI5cpjkMWBLU9QzOcWpzM+VR12TC08dxplJzmmWmbOOijEXJEYyKqpj05IhEVMEcEgEOgv9lyqi6xxabkwz9D26p+kdoNrevA7HDug1KZy653SeFtT/i9iNITs5mJdsLBpeVTjrt5dkNJgzMaBWHCGvU5l6kxnw5QoeaJkoGbijwXxTL0sJxFjJvV9dT37PyUu9LxktABj8Hei3xa/ozAdPh6ItGiDa7r8BCBhp8itSxXoRpP5z2xHEy1b1nYiSQ0go6afiheayE5bv8LjFQIDCvUqdUz+FE7LSSyDmQrBbNLPvk28I7upaik1dgmZ3NTh9d9RG11BA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=z8KsiC1kd/QSpMxonxA6splEFFmcnXoGFq8xsgLEcz8=; b=KRyVV90TLrQVBHWPKKUVZHpzHMI0UV5oWPu6MGy3nWv4VTtJeO/WvG0ub4W1DQYWk/Eac8CotXlv4XrVAShfY+vOEbcKUXptzqtbqHSuudweBqiyM3JR34cbT99VrBzSytdFKXt3L+cK2jwvcT297QgCRsjLo8cvu0XKx8mwT64VliCuVMfL79N84nhjA9+XhQlwhpVRLoKsYC2STSeGUlKztLRa7d1BD49BjVCKuI3XbZ32F4krYJEmBfHGEIScdp3pKc4G9EOI0JsQUo7uZ/voQ2zEcBNNU/q9qJjL+F0WhqlxrtwAt8p22yJvWG+fL2BNl7Ek8/e3mxYMrg+VMg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by KL1PR02MB6189.apcprd02.prod.outlook.com (2603:1096:820:bd::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.20; Tue, 14 Jul 2026 15:55:02 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:02 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:17 +0200 Subject: [PATCH v2 3/8] Add Advantech EIO Hardware Monitor driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-3-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=14601; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=nNBPffxiF/04OjX0aLmub0AG7jhrP3SnCqB26NFnzxE=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu4+dWrAFRJKMOxOn4jXkBAuWeWD2wxVAYuI AtGuAXp3yiJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CRDPC/45CcstWceskOH6zNjDBmwV/rDoaD5xxd7K7EEjnKqZSrKQqzL7suizn70T4+EfaYBzVPc p5huNtD6PIc1M9N894ei4uod/3zq5rJS7obKOVz7+g03/HZmy20Ih+CGlTtRyQ1UXmuNiHC+oDT OBCJVCFsm0ddoBWcthXn947mrVcpKZrgHynHOwFG8e8qsLKOnFhTcBT90XkKBzAjuGOR8DK4cCu ohaICtBzsjyTP/wF+usmFhlFHNjftXAENDEGQbs7cDkEPv9WtUXZMJY2fbHnJwF6nkJJg+E/EUE SMGo7SguTAOE6avcuPW2yPgkpfTcJyvS6l6699KpP7TzLNqX6sevMnH7Gxg+5wk0F7T09SmVYQ0 zkUNTxcLOPEz/gwWPHwobhmaiQpsPuY4WN8XTKxEGttAb2oMj+xVhBwFlMQzhOjpKwSy4ugd1jP 362bBhyLtmiXjI3PvXMiMWnn/ZFMU86ucRKwPucTZwq1rDNJ1xG/4gtQ/x2olqZ/TUSy8= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|KL1PR02MB6189:EE_ X-MS-Office365-Filtering-Correlation-Id: 4bdaa625-b835-41db-9977-08dee1c04396 X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|1800799024|23010399003|52116014|7416014|376014|921020|38350700014|56012099006|11063799006|22082099003|55112099003|18002099003; X-Microsoft-Antispam-Message-Info: kF9kVJjls3DAXDcSibYtjhWelj/gzJzKfvgQqwTAYVauTurscg+hglxhCCKPzyXQIl/2l3EY3nglyC0VbqpnY0uToQ/R9MzV9sEzzktKkCq7qQ+2uA2RhYPzxkVbF36TD8ny2b9F+zDGfFQ3I3qj8ymKaDqA/3T6exhvX2crZec3F0VAyDInqN1dYDpgMw2WMTgnaSFBekC/U4xa8VDLlUNtUONYS9u5kague5PwGxOS/5zBO5ZpPdnLPIay4KQMFUFjqskgih7mJso54L0VbLYzSLONMjsHXSc5xEGqODt+ENwkIR2x4NO6/dXfIWYebp211Zw+i9hVg1XBjML0sXRRzi6mBPhCvhVoTL9lKrxiy+XoEWoFMAmEqovodpXsWUyJa95sg8Nsq6U0MpQTfqWmKI5B4G0b/4OEAZX5w+iNiSW0AUdUOUlP+kVEDY1aWQ21pUbIpamPTEnIqvarH6ctLzNY4xrLezr54zj7HHzjyBnON0Zlh5UevQWrr8BDtBrWzkIAlOzEw6JBoEZFh2aAzMwsF8+8xSHF04aNBt5+V7e9gZZIcya9znG+ZFxDEM0ig/4YGCWpyvobJN9gPAObrBtsiL3h4388jiXnH8i9+Uqk1GGswNYLmPWe/w9P2bcOIp6Hkc0AGJWbqEoZLiBl3e5bZG/y4hmupnXup2QTGfGQB73gNa0KFZ7Xi6QoHst4x50+PxgWyIUS7lbRzUKvvyl6FzDbgAnCeBT+604= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(366016)(1800799024)(23010399003)(52116014)(7416014)(376014)(921020)(38350700014)(56012099006)(11063799006)(22082099003)(55112099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?ZjZ2UUhMc0xGUFFiam5WMDI4d3BxdWczZHliRk1jT0xOWWViMmZ4NzY3VVFp?= =?utf-8?B?QllkMmt6VFBMb3pxbjBTR1Z5T1RzQ3NVWkl0c3p2ajYyMk5XcEZMdkRPQTJP?= =?utf-8?B?QUcwUGZma25SR3h3TFU1bUtJSTFadGNDa01CUlRjcDlkbG4xbFpLdzJDZjJZ?= =?utf-8?B?T05Bbm8wVkVPejhvVE96THVNaGlHNGNYaC82Y1NGV2JXUjB2bFNEQW5Odlls?= =?utf-8?B?TVoxNUpFNDBpVXUvSGhBT2RvSkV5dTcyVEdtdWxxRDVQd0daT09hWlV4elFR?= =?utf-8?B?M2Q3TldXR3lxSUlBeUJvUURweE1VRUFVWVd5SnluZVpSSS8yYXlaSHl6Z2xv?= =?utf-8?B?WTZHOEpsRUZDZ215T2c5c2o0MTZFL2hwUTlzVU9OWG5ZTzh1dDJmbGxhTGUx?= =?utf-8?B?Wm1aSkc2WHN3UVM2VGk3bjV1dEVjN1ExT1hRNWthRnNpRUJEc1NwYVBUOU5j?= =?utf-8?B?c29Td3REV0lIelZLbzdzUmxxakFmdXhmY09Ycmc3bWpLOU9GejRJemNWc1l4?= =?utf-8?B?TENVamZFanBmcys2Y0RRMnp4OCs1ei8vcEdIcjJMMS8rYmlTVExmVEpEeEdr?= =?utf-8?B?VzF2NTluY2p2VW1RbEh6RXlBK0ZidHAwZ1J2ZnVwNmp5NHRnZHhhSTlLTTRu?= =?utf-8?B?aVU3SGFxV0htQWZvTXhWZUJXUDJTbjBkaWhMYWJiNEE3Lys0MEZEODlEa0ZX?= =?utf-8?B?VTJ5d0ZXM1dsUHhZbFRrQS9vdmRrMlZXclJLc1Y2ckZ6THlXNEowdHpnYmJG?= =?utf-8?B?VnYzVWhUaHVOTDlGQW1DSFZFalh1RkVqWndzRlVUNnEwejVZNDNUWWhTMm9y?= =?utf-8?B?SkNpWStoc0pxbzgyb09QemNsemhxc1ZadTRocDVrczN0ZzlRMXJKZm9UL2RU?= =?utf-8?B?REF2V1pHSG1PMGpFNkdnbTVJVUZtbC9DWlpYQm95QWZ6SjZubDNVUGc3Mkg0?= =?utf-8?B?S3V2alpseXg4YVhFa1JpZmQ2UUJCSlZMNkk3OFROSXRKdUVzQkExZWVpZnJ6?= =?utf-8?B?ckV3ZXhmMXpFSzliSWdmOGh2S2phZ1lHYjg1OVI3TFhodm1PdlN5QzYxOHlu?= =?utf-8?B?dU1NMytxWGZjSEFuQThpaElTVVB3blR5RHlqT2RiOC9NRUx4VXA3cWJwUmhv?= =?utf-8?B?MDU3WUdSVTZSNnhjNDNGRVM1NGdxaDVYVEp6TTBvOG1HME1HQnBNczFjV29w?= =?utf-8?B?VzczeWJXNnFrVm1kSWJ6RWlaL2NwSnBWa3Z6ZFE5cjdweERGQWFVb1ZvS1JJ?= =?utf-8?B?RE9oOHowMCtVTlExZ0dJNmgvM0lCdVBUU2h3VmE5dE5vMkdnaG55c3pQcFEr?= =?utf-8?B?WFJBNDdGck9JSFhtTjRCZlFMUGdQRDVSMWFYZWxIVHNJaS84N0c2dCtCKzU5?= =?utf-8?B?eTUxL2dPUXpxTTdvdEwzWTNTRWUrdHd0YjlHZllrVy9mTnFlM1dBR1JJQlBt?= =?utf-8?B?U2N1S2tQdk13WE0xUE9sZmZ0ZythRWpncFNrZGtMVXpWdkhoUlpvR0cybjdw?= =?utf-8?B?amordmd1czlpSzlXWjJIZUF2UjRTcUlGQXVGQ0lNNTZrM3gzdzRkVVhtcTlG?= =?utf-8?B?ZTlVY3NBNmVyT24xOXdmbkNRampPTVRENjRPZTBYcUh6RXV5enpiMGpZRWp5?= =?utf-8?B?ekhORnFIT0dFMy90LzlodUI5MllhUmI1V0tKMEwxNUpFSzY4WVhOTG5lWDhJ?= =?utf-8?B?dmZ4MGQ4QU1lYUVJY0Fmck13R1VpdjdxSkpNeFpsSnJTSlViM2pUZ0lJcmJz?= =?utf-8?B?MWhrNWhGbnY3YWJSbE9BNzUxVVdWbFJKdG1TblNoUzRHbGhCVmVzaG5WZWhS?= =?utf-8?B?ajVWOXpvUnZFMDZRY3J6Q1ZhcHVtSmc4NmJnWnNZRkJDTVdFM0pHWVpiUUVj?= =?utf-8?B?ZEFIdnZ4djgxMGpUMmxybytLQ3ZyMWV3Z09xNmVyTE9odThIZVhEY1lhamtR?= =?utf-8?B?Q01vMG9UY0VHZWx1UTZoWlpHeEpaVHJ3ZWRwa1E0bFNvRnBDN1M4Q0N3ZWZS?= =?utf-8?B?bVJzT1RvRGw0eHkrdjZCQTBJQ3ZwUktkdFNYYzZ6clkzekhUbTNZRTZIRE9P?= =?utf-8?B?YW9SZ2YwdlNrcVc0WSszb29ZVFFjOWdFSXMydlNOdFJpelg2dzVtY04xM3Ja?= =?utf-8?B?ejR0SFBVaVJEcWgyaHlBaDhuMUdGRWNtdUhKZjM3SmVmN1p2YncxU2lLRGly?= =?utf-8?B?YzhsQW1vQ0dDdk15akVGTjYxMmIydDU2Q0wyUzVEaDhhWHE3bmJYSitWSTZW?= =?utf-8?B?enlYd2dBV0FZK2YvMmFqNTJkUUQwTkpyWUVpbVcwYVhHa29HRVVTZVYwdVd5?= =?utf-8?B?eUdDc1FrVm0wRThIRnV5enVPQmhFV1EzaFVYZCtUUVRmMy96dSt4M2ZYc2lk?= =?utf-8?Q?iTZHR6kXqnpnJe0o=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 4bdaa625-b835-41db-9977-08dee1c04396 X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:02.0602 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: bgEpztV1YGqLwoTh5tBa2sJYrsdi2CrK7+hkCCG8KQKDwluVXtReUfftK0dGGN19S9DyhQ7tcgrA9WhcpaOKIHE+85tCdgzZOwTbDWpM1ss= X-MS-Exchange-Transport-CrossTenantHeadersStamped: KL1PR02MB6189 This driver controls the Hardware Monitor block of the Advantech EIO chip. There is a separate driver for the thermal device driver since it's a use case we support currently, but if it's really necessary we can move it to the same driver. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/eio-hwmon.c | 461 ++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 473 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a7da47393815..6d727d5182e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -613,6 +613,7 @@ ADVANTECH EIO DRIVER M: Ramiro Oliveira S: Maintained F: drivers/gpio/gpio-eio.c +F: drivers/hwmon/eio-hwmon.c F: drivers/mfd/eio_core.c F: include/linux/mfd/eio.h =20 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 08c29685126a..b8fd8ded846a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2094,6 +2094,16 @@ config SENSORS_EIC7700_PVT This driver can also be built as a module. If so, the module will be called eic7700-pvt. =20 +config SENSORS_EIO + tristate "Advantech EIO HWMON" + depends on MFD_EIO + help + If you say yes here you get support for the Advantech EIO + temperature, voltage and fan speed monitoring block. + + This driver can also be built as a module. If so, the module + will be called eio-hwmon + config SENSORS_EMC1403 tristate "SMSC EMC1403/23 thermal sensor" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63809eeec2f4..8e81a2bdc99b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP) +=3D drivetemp.o obj-$(CONFIG_SENSORS_DS620) +=3D ds620.o obj-$(CONFIG_SENSORS_DS1621) +=3D ds1621.o obj-$(CONFIG_SENSORS_EIC7700_PVT) +=3D eic7700-pvt.o +obj-$(CONFIG_SENSORS_EIO) +=3D eio-hwmon.o obj-$(CONFIG_SENSORS_EMC1403) +=3D emc1403.o obj-$(CONFIG_SENSORS_EMC1812) +=3D emc1812.o obj-$(CONFIG_SENSORS_EMC2103) +=3D emc2103.o diff --git a/drivers/hwmon/eio-hwmon.c b/drivers/hwmon/eio-hwmon.c new file mode 100644 index 000000000000..dd877664e9e8 --- /dev/null +++ b/drivers/hwmon/eio-hwmon.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hardware Monitoring driver for Advantech EIO embedded controller. + * + * Copyright (C) 2025 Advantech Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#define MAX_CMD_SIZE 4 +#define MAX_NAME 32 + +#define EIO_ITEM_INPUT 1 +#define EIO_ITEM_MAX 2 +#define EIO_ITEM_MIN 3 +#define EIO_ITEM_CRIT 4 +#define EIO_ITEM_EMERGENCY 5 + +#define EIO_MAX_IN 8 +#define EIO_MAX_CURR 2 +#define EIO_MAX_TEMP 4 +#define EIO_MAX_FAN 4 +#define EIO_MAX_INTRUSION 1 + +enum _sen_type { + NONE, + VOLTAGE, + CURRENT, + TEMP, + FAN, + CASEOPEN, +}; + +struct eio_chan { + bool available; + u8 label_idx; +}; + +struct eio_hwmon_dev { + struct device *mfd; + struct eio_chan in[EIO_MAX_IN]; + struct eio_chan curr[EIO_MAX_CURR]; + struct eio_chan temp[EIO_MAX_TEMP]; + struct eio_chan fan[EIO_MAX_FAN]; + struct eio_chan intrusion; +}; + +static struct { + u8 cmd; + u8 max; + signed int shift; + char name[MAX_NAME]; + u8 ctrl[16]; + u16 multi[16]; + char item[16][MAX_NAME]; + char labels[32][MAX_NAME]; +} sen_info[] =3D { + { 0x00, 0, 0, "none" }, + { 0x12, 8, 0, "in", + { 0xFF, 0x10, 0x11, 0x12 }, + { 1, 10, 10, 10 }, + { "label", "input", "max", "min" }, + { "5V", "5Vs5", "12V", "12Vs5", + "3V3", "3V3", "5Vsb", "3Vsb", + "Vcmos", "Vbat", "Vdc", "Vstb", + "Vcore_a", "Vcore_b", "", "", + "Voem0", "Voem1", "Voem2", "Voem3" + }, + }, + { 0x1a, 2, 0, "curr", + { 0xFF, 0x10, 0x11, 0x12 }, + { 1, 10, 10, 10 }, + { "label", "input", "max", "min" }, + { "dc", "oem0" }, + }, + { 0x10, 4, -2731, "temp", + { 0xFF, 0x10, 0x11, 0x12, 0x21, 0x41 }, + { 1, 100, 100, 100, 100, 100 }, + { "label", "input", "max", "min", "crit", "emergency" }, + { "cpu0", "cpu1", "cpu2", "cpu3", + "sys0", "sys1", "sys2", "sys3", + "aux0", "aux1", "aux2", "aux3", + "dimm0", "dimm1", "dimm2", "dimm3", + "pch", "gpu", "", "", + "", "", "", "", + "", "", "", "", + "oem0", "oem1", "oem", "oem3" }, + }, + { 0x24, 4, 0, "fan", + { 0xFF, 0x1A }, + { 1, 1 }, + { "label", "input"}, + { "cpu0", "cpu1", "cpu2", "cpu3", + "sys0", "sys1", "sys2", "sys3", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", + "oem0", "oem1", "oem2", "oem3", + }, + }, + { 0x28, 1, 0, "intrusion", + { 0xFF, 0x02 }, + { 1, 1 }, + { "label", "input" }, + { "case_open" } + } +}; + +static struct { + enum _sen_type type; + u8 ctrl; + int size; + bool write; +} ctrl_para[] =3D { + { NONE, 0x00, 0, false }, + + { VOLTAGE, 0x00, 1, false }, { VOLTAGE, 0x01, 1, false }, + { VOLTAGE, 0x10, 2, false }, { VOLTAGE, 0x11, 2, false }, + { VOLTAGE, 0x12, 2, false }, + + { CURRENT, 0x00, 1, false }, { CURRENT, 0x01, 1, false }, + { CURRENT, 0x10, 2, false }, { CURRENT, 0x11, 2, false }, + { CURRENT, 0x12, 2, false }, + + { TEMP, 0x00, 2, false }, { TEMP, 0x01, 1, false }, + { TEMP, 0x04, 1, false }, { TEMP, 0x10, 2, false }, + { TEMP, 0x11, 2, false }, { TEMP, 0x12, 2, false }, + { TEMP, 0x21, 2, false }, { TEMP, 0x41, 2, false }, + + { FAN, 0x00, 1, false }, { FAN, 0x01, 1, false }, + { FAN, 0x03, 1, true }, { FAN, 0x1A, 2, false }, + + { CASEOPEN, 0x00, 1, false }, { CASEOPEN, 0x02, 1, true }, +}; + +static int para_idx(enum _sen_type type, u8 ctrl) +{ + int i; + + for (i =3D 1 ; i < ARRAY_SIZE(ctrl_para) ; i++) + if (type =3D=3D ctrl_para[i].type && + ctrl =3D=3D ctrl_para[i].ctrl) + return i; + + return 0; +} + +static int pmc_read(struct device *mfd, enum _sen_type type, u8 dev_id, u8= ctrl, void *data) +{ + int idx =3D para_idx(type, ctrl); + int ret =3D 0; + + if (idx =3D=3D 0) + return -EINVAL; + + if (WARN_ON(!data)) + return -EINVAL; + + struct pmc_op op =3D { + .cmd =3D sen_info[type].cmd | EIO_FLAG_PMC_READ, + .control =3D ctrl, + .device_id =3D dev_id, + .size =3D ctrl_para[idx].size, + .payload =3D (u8 *)data, + }; + + ret =3D eio_core_pmc_operation(mfd, &op); + return ret; +} + + +static umode_t eio_is_visible(const void *drvdata, enum hwmon_sensor_types= type, + u32 attr, int channel) +{ + const struct eio_hwmon_dev *eio =3D drvdata; + + switch (type) { + case hwmon_in: + return (channel < EIO_MAX_IN && eio->in[channel].available) ? 0444 : 0; + case hwmon_curr: + return (channel < EIO_MAX_CURR && eio->curr[channel].available) ? 0444 := 0; + case hwmon_temp: + return (channel < EIO_MAX_TEMP && eio->temp[channel].available) ? 0444 := 0; + case hwmon_fan: + return (channel < EIO_MAX_FAN && eio->fan[channel].available) ? 0444 : 0; + case hwmon_intrusion: + return (channel < EIO_MAX_INTRUSION && eio->intrusion.available) ? 0444 = : 0; + default: + return 0; + } +} + +static int eio_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct eio_hwmon_dev *eio =3D dev_get_drvdata(dev); + u8 data[MAX_CMD_SIZE] =3D {}; + enum _sen_type ec_type; + u8 item; + u32 raw; + int ret; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + item =3D EIO_ITEM_INPUT; + break; + case hwmon_in_max: + item =3D EIO_ITEM_MAX; + break; + case hwmon_in_min: + item =3D EIO_ITEM_MIN; + break; + default: + return -EOPNOTSUPP; + } + ec_type =3D VOLTAGE; + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + item =3D EIO_ITEM_INPUT; + break; + case hwmon_curr_max: + item =3D EIO_ITEM_MAX; + break; + case hwmon_curr_min: + item =3D EIO_ITEM_MIN; + break; + default: + return -EOPNOTSUPP; + } + ec_type =3D CURRENT; + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + item =3D EIO_ITEM_INPUT; + break; + case hwmon_temp_max: + item =3D EIO_ITEM_MAX; + break; + case hwmon_temp_min: + item =3D EIO_ITEM_MIN; + break; + case hwmon_temp_crit: + item =3D EIO_ITEM_CRIT; + break; + case hwmon_temp_emergency: + item =3D EIO_ITEM_EMERGENCY; + break; + default: + return -EOPNOTSUPP; + } + ec_type =3D TEMP; + break; + case hwmon_fan: + if (attr !=3D hwmon_fan_input) + return -EOPNOTSUPP; + item =3D EIO_ITEM_INPUT; + ec_type =3D FAN; + break; + case hwmon_intrusion: + if (attr !=3D hwmon_intrusion_alarm) + return -EOPNOTSUPP; + item =3D EIO_ITEM_INPUT; + ec_type =3D CASEOPEN; + channel =3D 0; + break; + default: + return -EOPNOTSUPP; + } + + ret =3D pmc_read(eio->mfd, ec_type, channel, sen_info[ec_type].ctrl[item]= , data); + if (ret) + return ret; + + raw =3D (u32)data[0] | (u32)data[1] << 8 | (u32)data[2] << 16 | (u32)data= [3] << 24; + *val =3D ((long)(signed int)raw + sen_info[ec_type].shift) * + (long)sen_info[ec_type].multi[item]; + return 0; +} + +static int eio_read_string(struct device *dev, enum hwmon_sensor_types typ= e, + u32 attr, int channel, const char **str) +{ + struct eio_hwmon_dev *eio =3D dev_get_drvdata(dev); + enum _sen_type ec_type; + u8 label_idx; + + switch (type) { + case hwmon_in: + if (attr !=3D hwmon_in_label) + return -EOPNOTSUPP; + ec_type =3D VOLTAGE; + label_idx =3D eio->in[channel].label_idx; + break; + case hwmon_curr: + if (attr !=3D hwmon_curr_label) + return -EOPNOTSUPP; + ec_type =3D CURRENT; + label_idx =3D eio->curr[channel].label_idx; + break; + case hwmon_temp: + if (attr !=3D hwmon_temp_label) + return -EOPNOTSUPP; + ec_type =3D TEMP; + label_idx =3D eio->temp[channel].label_idx; + break; + case hwmon_fan: + if (attr !=3D hwmon_fan_label) + return -EOPNOTSUPP; + ec_type =3D FAN; + label_idx =3D eio->fan[channel].label_idx; + break; + default: + return -EOPNOTSUPP; + } + + if (label_idx >=3D ARRAY_SIZE(sen_info[ec_type].labels)) + return -EOPNOTSUPP; + + *str =3D sen_info[ec_type].labels[label_idx]; + return 0; +} + +static const struct hwmon_ops eio_hwmon_ops =3D { + .is_visible =3D eio_is_visible, + .read =3D eio_read, + .read_string =3D eio_read_string, +}; + +static const struct hwmon_channel_info * const eio_hwmon_info[] =3D { + HWMON_CHANNEL_INFO(in, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN, + HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN), + HWMON_CHANNEL_INFO(curr, + HWMON_C_LABEL | HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MIN, + HWMON_C_LABEL | HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MIN), + HWMON_CHANNEL_INFO(temp, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_= CRIT | HWMON_T_EMERGENCY, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_= CRIT | HWMON_T_EMERGENCY, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_= CRIT | HWMON_T_EMERGENCY, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_= CRIT | HWMON_T_EMERGENCY), + HWMON_CHANNEL_INFO(fan, + HWMON_F_LABEL | HWMON_F_INPUT, + HWMON_F_LABEL | HWMON_F_INPUT, + HWMON_F_LABEL | HWMON_F_INPUT, + HWMON_F_LABEL | HWMON_F_INPUT), + HWMON_CHANNEL_INFO(intrusion, HWMON_INTRUSION_ALARM), + NULL, +}; + +static const struct hwmon_chip_info eio_chip_info =3D { + .ops =3D &eio_hwmon_ops, + .info =3D eio_hwmon_info, +}; + +static int hwmon_init(struct device *mfd, struct eio_hwmon_dev *eio) +{ + enum _sen_type type; + u8 i, data[MAX_CMD_SIZE]; + int n =3D 0; + int ret; + + for (type =3D VOLTAGE ; type <=3D TEMP ; type++) { + struct eio_chan *chans =3D (type =3D=3D VOLTAGE) ? eio->in : + (type =3D=3D CURRENT) ? eio->curr : eio->temp; + + for (i =3D 0 ; i < sen_info[type].max ; i++) { + memset(data, 0, sizeof(data)); + if (pmc_read(mfd, type, i, 0x00, data) || + (data[0] & 0x01) =3D=3D 0) + continue; + + memset(data, 0, sizeof(data)); + ret =3D pmc_read(mfd, type, i, 0x01, data); + if (ret !=3D 0 && ret !=3D -EINVAL) + continue; + + chans[i].available =3D true; + chans[i].label_idx =3D data[0]; + n++; + } + } + + for (i =3D 0 ; i < sen_info[FAN].max ; i++) { + memset(data, 0, sizeof(data)); + if (pmc_read(mfd, FAN, i, 0x00, data) || + (data[0] & 0x01) =3D=3D 0) + continue; + + memset(data, 0, sizeof(data)); + ret =3D pmc_read(mfd, FAN, i, 0x01, data); + if (ret !=3D 0 && ret !=3D -EINVAL) + continue; + + eio->fan[i].available =3D true; + eio->fan[i].label_idx =3D data[0]; + n++; + } + + memset(data, 0, sizeof(data)); + if (!pmc_read(mfd, CASEOPEN, 0, 0x00, data) && (data[0] & 0x01)) { + eio->intrusion.available =3D true; + n++; + } + + return n; +} + +static int hwmon_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct eio_hwmon_dev *eio; + struct eio_dev *eio_dev =3D dev_get_drvdata(dev->parent); + struct device *hwmon; + + if (!eio_dev) { + dev_err(dev, "Error contact eio_core\n"); + return -ENODEV; + } + + eio =3D devm_kzalloc(dev, sizeof(*eio), GFP_KERNEL); + if (!eio) + return -ENOMEM; + + eio->mfd =3D dev->parent; + + if (hwmon_init(dev->parent, eio) <=3D 0) + return -ENODEV; + + hwmon =3D devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME, + eio, &eio_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon); +} + +static struct platform_driver eio_hwmon_driver =3D { + .probe =3D hwmon_probe, + .driver =3D { + .name =3D "eio_hwmon", + }, +}; + +module_platform_driver(eio_hwmon_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Hardware monitor driver for Advantech EIO embedded con= troller"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from TYPPR03CU001.outbound.protection.outlook.com (mail-japaneastazon11022072.outbound.protection.outlook.com [52.101.126.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 993A43A4F55; Tue, 14 Jul 2026 15:55:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.126.72 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044516; cv=fail; b=EeiEXkHedQkB4uRkhkINLve+XfMwyheNUTr4I4Jr1fvpmOq50zoIMp/T7ta8BHS2rGLjhjXdqdjWIGsOlg96/G8W93Tjx/+s7k/I7uJvvb3XXGtCGO7Sb6mBWyGAzBgBwHvlxpRh/Bh1sMrxOI+tlnOBgj5DFvfW8tCwhwQrL4s= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044516; c=relaxed/simple; bh=V0fTlsU7PEGU29oHce0K3yDRLkeSeFvVRHQkk69MIgk=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=ewZkYTUfA75Y7uo0+35wNuQ5giR3BWaxrMksjf353QmOLVbHVRYzuciloB5vdz+0l0afiLP23aoxMGEeyjd6vAA/9YccMV/AKljvqf/9nY6n0kgT1jB/KBSe7E+cqbxLpDNlv0v7XVwvzGA7UULvl9dsHkTPzXnwkqni+xzEPpE= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=g8RbIb9B; arc=fail smtp.client-ip=52.101.126.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="g8RbIb9B" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=emPZtUxKeleFMwJbcXnEv2BEVnI0nk1HNVyXsxvHvJn4nAgfSwQSC79o+OxMMNoKBcu74MduPPWL251RiD6FJSXdUIDuodElJFg4sm6e9T8gzNjpLUP7rXdFaqTb6sWAiw0V+vaAHE00imldij2WrcToTCVvURUzJVAt/qmVBy5ykv6iYuDaBV7df1XJ3vpDsx5xF3pMJKSAYBNnh9FFkpocUeWwa2KI14qw2u2zwZA9uM5aqqO2QnDAzheiJfkDoV5iP+ux5fC1OJgU/LIl4OKpwcokbZM0v/E4g/M0xTgtjiZ+KmgmLH22lJ/KV/vRYeD1Vd8LmlaiRuCu0RiSvQ== 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=6W1dd/hBZbkTpRxnRQ0yfCZ0m4Zsmyk2yoxWsl0NBNw=; b=kOpFaXDi3c2M/9YkxPjXhXJEx+tb07PItDeGeFFVRLtccKXPuLrujcUHSLaqRkGAuxUXz593r6BRNqh3GSVL+UNLKtMjMAC+xAe27BY3lCG1wcYYIryvOApccudbsWUEMQn4sDbSKVIhQdRK0qpv8i7vXvPBck3ZJMs/3tk0fXx0Om3r8R+1E/gYdhZY+Dx0JZyTExi8qEyGHEjlUlCSLSQRgwiDAabp1xdA61uGXv9JBimOxuSIeNrn+AnHWtMErRb0KZ+iFEey8zwd8BbY0VL5CumrcOkbkx3tLDxEomLaACM6dhKG9bfrElh7/UlZpOUIiJGNdbWQ/0BSEsiVnA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=6W1dd/hBZbkTpRxnRQ0yfCZ0m4Zsmyk2yoxWsl0NBNw=; b=g8RbIb9BCnKBBBCFUkdp+2Xbpbo5a4CX1LQJ8PonKppacs9FfzNQdRZyeYeqjRCu5UpGxQ+yi0Y8O+km/GGUevxm9Uv0xLOMRxKvLNDJ6YWkg8xQWSuyi0BOUsBPqO5av0UIztcBvn8dHV+0DYtOnuJZ+rdbf6GvDh4Z3medItcdNmR9Ao7pmM9plc7u94iprlzZyCo8IV+1s9IrOoJj6yY8rPvJ8b7B1fTUnLRga2AVYh0OyEEjs7WQHKi/rSgi/FzO8hOxGItg2F2009knR/5g7wUWnIySHINqMvgkPlEty6S4n/ANnkmew9Zrs8qVK4B0zMUTEizyOv4NOpmFBg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by KL1PR02MB6189.apcprd02.prod.outlook.com (2603:1096:820:bd::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.20; Tue, 14 Jul 2026 15:55:09 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:09 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:18 +0200 Subject: [PATCH v2 4/8] Add Advantech EIO I2C driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-4-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=33040; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=V0fTlsU7PEGU29oHce0K3yDRLkeSeFvVRHQkk69MIgk=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu4Rm6EUV3fRVNw99+h0A8Js+tRuo34T41h8 ICpxROAIBGJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CfqPC/47cVPsV5CLn0pKfKRv5gcRVyP3aow906in1gRZK9SWmkgLkcUTwS3bxnTkkCuZdQdF2cu s+LeUB+SG+qIxXHsUducibNlnWzPBbcktnUI4uT1xPkadp1FE5Cw9G46gyIf0WVUtzTDqFZ/0av WYYiiXz6YpZPgTEcQ90aLE30htD1G/rt7X6VnSgWpO8pK41q5zd77UVkS1Rs1tz57Dq4MaYSc/z N16QgLdztqqXbf+W2WwSuzdTTC1x8SaK04Sl6EO+kjh82yMFBraHfJXKR30slB0WzGBb/TM0BAA jCu9N2oozPuyXtqSd19KBeOTZuYiDUgVQ/qzJzdG01PV1iOhc2Q+Mpg3rphI9v17Kh0DHmXznv+ c2nK7GvA67soe1mZ4D6rYJm3jIZ1jJ0kP4l/HvA1J/8CzGiPmiC7AQUCWEiBHXJTXgWrcc5/16g BJZh8TDu3dGe9vnHf9/Ss+pbTD0W8OvJWzVf7X2aFCODkUWlEK1oXF2sIELV+LBKf4ruQ= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|KL1PR02MB6189:EE_ X-MS-Office365-Filtering-Correlation-Id: 902c1959-62ee-4668-eb4d-08dee1c0482e X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|1800799024|23010399003|52116014|7416014|376014|921020|38350700014|3023799007|56012099006|11063799006|6133799003|22082099003|55112099003|18002099003; X-Microsoft-Antispam-Message-Info: q678+WH9ayttntgGWqFvmtE6nckXQ5d3En0YrVJza4iSI8uOQNX0IMGB2/vIGNlaM50xXZcg4OFGMdrOxoyFDSJaXKEjhfKWCi0DL6ay5wg41+mWqxnd5ILnuNy89zQmXiDV8Yx1m69El7z4tgyEphC5X7B0LKoVkFiQ7LzIJBk3vUyRSHcfj1Xusf8mhHudV32GXTUpnEdUeWoDMwWJtDhHzwB9qzeEHvzXX+M1rX4hoBmebvDkOpuoU3w51SKunm0ixrrSUXh2Y3Dnlu8hfQ4z+J5EkDRAwI9J2zcSzDwGGPpYfYBBqLYEFJT+YQWe2dwiAwPBeWsH7fYWBVgbMj89Yp4ZiZT4qICvKO7y5A34uW1jAwpwfhIDK6ZKQPPV6392pjvENBKvRNjvhfXqIkezqt4C1hrTAv4JXZQzdxDp9f7CCS2c3dfC0d+cKoCsD7a9We93VqFc2cgLI6pp230aSWSZ2PJFJn0O35N6etOYyCwnox44Jme3+vzRJ+okpFbDxbKiDlhnMFhQcFy0GRQx00uv5ZsZvIvgMt96fwfoJ3Nh4iJkdqJjQ6ySH4U0fLPrymcdeT/9Jv9vY2ePpba/jnLyu9HxTk44tAuR+iRlke8lQQhb53aYMqqJ8NuUUZXOb5a5g3vuqspVO7Tq9SBPHvzEBUQkTLzCUhwaIhBA/X9IOeQsxhjHACYypzvZByrQmIXbTN1STH8nfVIDsqXZnpQYxc08vHGndKsMSu7dtW2JhaE5Gg8VVJBzb5BW X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(366016)(1800799024)(23010399003)(52116014)(7416014)(376014)(921020)(38350700014)(3023799007)(56012099006)(11063799006)(6133799003)(22082099003)(55112099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?VnB6ZEdVS3FQdVJ4NDVZdmx3V1VDZzlPcXdadm9kQlFDaml4cjExT0lTMFZo?= =?utf-8?B?cUFCZjRhZWRHQ0dOOEZZS2p4ai9xdk5uWG5rbytGR1VIeEIvZWg5Nk1KS3FM?= =?utf-8?B?cTA5b0VwcVAwMW1SeTZVWXI3K1hBaFErU2ZQMEl6azA1cFJXajVCS3dxR0p2?= =?utf-8?B?aGFZb3h4Q3JVeWtIbTcrMWZveFVwb0dEWEk1S1VqQ3Z1VFB6RGJnVDFrbmd0?= =?utf-8?B?UVI5YWhGZGxRUkkvMmR0VHVpN1pHdksvTXFSZzJ2dzFpdmh4NE1jYzAzVkpZ?= =?utf-8?B?UFIyVEJZL3M3YzBOcWN5eUVwdUh5RmhvakxSK1RhQVpMK2QzaklHQmRpVVFV?= =?utf-8?B?cTFudTVHSDBBcC9tc2d0QWhjNFVKTXZLNTB5QzR3N1JhUS84OGQ5NWpVRzM0?= =?utf-8?B?TmFmREpRaFBiSWI5V05BVVhVd1Mzd0F3aE5oMUR1NVhNOWlFdHVJaFBwSGY5?= =?utf-8?B?MGg5TGN0b25NY2VLcVRQTThPcXpuT1AvT2hLYVE1bFNPcU5vTHoyVUYvT2RK?= =?utf-8?B?TWNxbjRZVkwxQWhIVC9BMXk2ZHB3RWRKRWp3SlN0bHd6SWU3L0FzUFQxK256?= =?utf-8?B?V3AxVFZzemZKVnFoY3FQaVlvdVExNmhLNzFlTlV3b3hhRUZYR2NnZGE3dE9D?= =?utf-8?B?WEU3VTczM1QzbFZHcUh0Mll6dDlsdkwvTlpsakxjcGFRWVhMcG9oNUs4Sm9z?= =?utf-8?B?TURxTmFOWVB0aDFBRXlhUXpuVnowZCs4eWVUNUhlV3pYK2RYNy9LUlFSTzVl?= =?utf-8?B?bEFPemUyVmY5b3g3WVZmcHU1TExkdlFIM2xFNjk3aXd3RmVRRVFxNUpma01D?= =?utf-8?B?NGpkSlZHcFdpSEtoMy9Fd1J2eGIxQkRrZFZ4WllxbTFESWNBZzdiNG16Skg2?= =?utf-8?B?d29jK3hWTkpJNE16U0pZQUFiOTZ4eCt0dFJjWFp6eURBdjdSaXcvaVdSRHVY?= =?utf-8?B?cmtVMGNETHZDVlJ1MS9FNXZjY2dNMnZZbkwzVFVGc2FpTGhldVd4dy9Yck9p?= =?utf-8?B?M0xTUkFEYURNZm53d3FZSHVRK1daTis1NUU1Uyt1aHYwMm5YRGptKzg0QnBp?= =?utf-8?B?ckRTL3Jkc3lxdzZFQUtwdHJvOHpRc2JpR3UrZXZtSHNid1pyMUpIUUYxNHJo?= =?utf-8?B?NmsyNlZXY3NYeWwzdzZ4T05CN2xaTmlYdWxGdFFqckZ0U2o3MG5QVEtUT1lU?= =?utf-8?B?Wk1BWk52K0tIc3IzekNObTJjYTlKbzJyMEpXcnAwTTZtRHJiRWRLNEIyS2dj?= =?utf-8?B?RnN6eUNaQmsvZW9INlNWRVJrTzBWdUxYcDg3SWxWSE5sZ2tjcDdOa1huUW4r?= =?utf-8?B?WE5Wc3cxNGhNS0hHKzh2TVlsSGVMNXpPVEVBUFQ5a0pxMXppd2Jvd3p6UDBU?= =?utf-8?B?RWRHZGU1aGZUek83Yy8xYnVucE5mY1NpVzFLVW02L3c3cituV2VSbHpncVJK?= =?utf-8?B?TGw3ZHZJcTBEWC9lbDNPZzJZTWNIeEZNREM0bjBFd0lqZTRXK1RXY0FQZjBX?= =?utf-8?B?ZkMvemZCSEVwMUZ1ZDQzUTlGT213R044TTNsL2ZtbEtYTUhVVU1ZS2VJRmk1?= =?utf-8?B?c1U5SHNNVUZ3b1FXRGJDNDJPb1RnejVlU2dqS1daTmF6aGFlalNtZm41QTcy?= =?utf-8?B?ODgvSjNwcm9uYWRCUnNpU2ZMSzVvZHNON29PVUZKVmI1RmRDd1NwZ3NnZnkr?= =?utf-8?B?L211bWUyTlZ0MmExcmdnUWY2enlFTkZtSnFDdDJJRVE5aVorWVlDNE0wNWtG?= =?utf-8?B?N0dwSDJuRGVhYjhhQ1NsZW1aQjNXclN2WlAvdWlJcGR2RDJtZGhtUkkyWmlB?= =?utf-8?B?RnRBVGw2ZHZ6WG5QNi82cmdHT09rYXZpdTlZRGt1RjBoeExkUURRUkRUUHgz?= =?utf-8?B?aW5YR1hsTDhBcWlZQzRFTDl6RVJMYWVxc0REbmZsRTlNeU41Ty8vc2hUa0pQ?= =?utf-8?B?cWJxTmpGeHJQUHJIK253NXNWTlQ3b3huOGpmSlVIQzkvbGVJM2s0N2dIK3Bm?= =?utf-8?B?TWZoZ3VVemtFVnVDbnRMRE9kdTNBckdkMEsvQjNiSHlwWkVvTjFvdUVnMnV1?= =?utf-8?B?Wk5jUVhEaHlKOVorbzZSanZ3MHpSSEZlOFF4ckYzL1BBRzFqa0JTcUpjT2h5?= =?utf-8?B?eEpTbnhKa05XK0ROQWRuKzMxNGJnMXQ5VVJVYmFNZzlEcW1jaTlxWlNpcStH?= =?utf-8?B?dnFhamwvd2NPVnFyTENxRHpHNlFOanNhWEpqMUFMUE5sMGc3dUpXYXd1aHkr?= =?utf-8?B?STZjaVBmd0V1U1pwRUdXeGZMd00vSXRFbUlRaFlaL0JWNitVMUxCYWZmN2Zi?= =?utf-8?B?Qm5ZOHNZVzBwdzRpSmZkS3BML2REVW1oNlBScG9oajhCTDVOVm9DMTU4WldH?= =?utf-8?Q?HG8CtmkDk2W335YQ=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 902c1959-62ee-4668-eb4d-08dee1c0482e X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:09.5907 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: qgCaDafOCdstWhx7tW3qQd4c30aSHjkQYBVT2966nczzM5dergkWIAFXbt6X0O7bCYPmLP+0yJNGITNCzYPh7Qw3R3qNBw70CIOSPe8uDls= X-MS-Exchange-Transport-CrossTenantHeadersStamped: KL1PR02MB6189 This commit adds the driver to control the Advantech EIO I2C block, this block is included in the Advantech EIO MFD. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/i2c/busses/Kconfig | 6 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-eio.c | 1135 ++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1143 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6d727d5182e4..e228d12a2326 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -614,6 +614,7 @@ M: Ramiro Oliveira S: Maintained F: drivers/gpio/gpio-eio.c F: drivers/hwmon/eio-hwmon.c +F: drivers/i2c/busses/i2c-eio.c F: drivers/mfd/eio_core.c F: include/linux/mfd/eio.h =20 diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d7b89508311f..ba982bb00cf9 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -649,6 +649,12 @@ config I2C_DIGICOLOR This driver can also be built as a module. If so, the module will be called i2c-digicolor. =20 +config I2C_EIO + tristate "Advantech EIO I2C bus" + depends on MFD_EIO + help + Say Y or M to build support for Advantech EIO I2C block. + config I2C_EG20T tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C" depends on PCI && (X86_32 || MIPS || COMPILE_TEST) diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 3755c54b3d82..2d8d4f37838c 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_I2C_DESIGNWARE_AMDISP) +=3D i2c-designware-a= mdisp.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) +=3D i2c-designware-pci.o i2c-designware-pci-y :=3D i2c-designware-pcidrv.o obj-$(CONFIG_I2C_DIGICOLOR) +=3D i2c-digicolor.o +obj-$(CONFIG_I2C_EIO) +=3D i2c-eio.o obj-$(CONFIG_I2C_EG20T) +=3D i2c-eg20t.o obj-$(CONFIG_I2C_EMEV2) +=3D i2c-emev2.o obj-$(CONFIG_I2C_EXYNOS5) +=3D i2c-exynos5.o diff --git a/drivers/i2c/busses/i2c-eio.c b/drivers/i2c/busses/i2c-eio.c new file mode 100644 index 000000000000..56a89ca18f32 --- /dev/null +++ b/drivers/i2c/busses/i2c-eio.c @@ -0,0 +1,1135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C and SMBus driver of EIO embedded driver + * + * Copyright (C) 2025 Advantech Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUPPORTED_COMMON (I2C_FUNC_I2C | \ + I2C_FUNC_SMBUS_QUICK | \ + I2C_FUNC_SMBUS_BYTE | \ + I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | \ + I2C_FUNC_SMBUS_I2C_BLOCK) +#define SUPPORTED_SMB (SUPPORTED_COMMON | I2C_FUNC_SMBUS_BLOCK_DATA) +#define SUPPORTED_I2C (SUPPORTED_COMMON | I2C_FUNC_10BIT_ADDR) + +#define MAX_I2C_SMB 4 + +#define REG_PNP_INDEX 0x299 +#define REG_PNP_DATA 0x29A +#define REG_SUB_PNP_INDEX 0x499 +#define REG_SUB_PNP_DATA 0x49A +#define REG_EXT_MODE_ENTER 0x87 +#define REG_EXT_MODE_EXIT 0xAA +#define REG_LDN 0x07 + +#define LDN_I2C0 0x20 +#define LDN_I2C1 0x21 +#define LDN_SMBUS0 0x22 +#define LDN_SMBUS1 0x23 + +#define REG_BASE_HI 0x60 +#define REG_BASE_LO 0x61 + +#define I2C_REG_CTRL 0x00 +#define I2C_CTRL_STOP BIT(1) + +#define I2C_REG_STAT 0x01 +#define I2C_STAT_RXREADY BIT(6) +#define I2C_STAT_TXDONE BIT(5) +#define I2C_STAT_NAK_ERR BIT(4) +#define I2C_STAT_ARL_ERR BIT(3) +#define I2C_STAT_SLV_STP BIT(2) +#define I2C_STAT_BUSY BIT(1) +#define I2C_STAT_MST_SLV BIT(0) + +#define I2C_REG_MYADDR 0x02 +#define I2C_REG_ADDR 0x03 +#define I2C_REG_DATA 0x04 +#define I2C_REG_PRESCALE1 0x05 +#define I2C_REG_PRESCALE2 0x06 + +#define I2C_REG_ECTRL 0x07 +#define I2C_ECTRL_RST BIT(7) + +#define I2C_REG_SEM 0x08 +#define I2C_SEM_INUSE BIT(1) + +#define SMB_REG_HC2 0x0C + +#define SMB_REG_HS 0x00 +#define SMB_HS_BUSY BIT(0) +#define SMB_HS_FINISH BIT(1) +#define SMB_HS_ARL_ERR BIT(3) +#define SMB_HS_FAILED BIT(4) +#define SMB_HS_RX_READY BIT(5) +#define SMB_HS_INUSE BIT(6) +#define SMB_HS_TX_DONE BIT(7) + +#define SMB_REG_HS2 0x01 +#define SMB_HS2_HNOTIFY BIT(0) +#define SMB_HS2_PEC_ERR BIT(1) +#define SMB_HS2_NACK_ERR BIT(2) +#define SMB_HS2_ALERT_STS BIT(3) +#define SMB_HS2_TO_ERR BIT(4) +#define SMB_HS2_SSTOP_STS BIT(5) +#define SMB_HS2_STX_REQ BIT(6) +#define SMB_HS2_SMODE BIT(7) + +#define SMB_REG_HC 0x02 +#define SMB_HC_I2C_NACKEN BIT(0) +#define SMB_HC_KILL BIT(1) +#define SMB_HC_CMD_SHIFT 2 +#define SMB_HC_LAST_BYTE BIT(5) +#define SMB_HC_START BIT(6) +#define SMB_HC_PEC_EN BIT(7) + +#define SMB_REG_HCMD 0x03 +#define SMB_REG_HADDR 0x04 +#define SMB_REG_HD0 0x05 +#define SMB_REG_HD1 0x06 +#define SMB_REG_HBLOCK 0x07 +#define SMB_REG_HPEC 0x08 +#define SMB_REG_SADDR 0x09 +#define SMB_REG_SD0 0x0A +#define SMB_REG_SD1 0x0B + +#define SMB_REG_HC2 0x0C +#define SMB_HC2_HNOTIFY_DIS BIT(0) +#define SMB_HC2_I2C_EN BIT(1) +#define SMB_HC2_AAPEC BIT(2) +#define SMB_HC2_E32B BIT(3) +#define SMB_HC2_SRESET BIT(7) + +#define SMB_REG_HPIN 0x0D +#define SMB_REG_HC3 0x0E +#define SMB_REG_HC4 0x0F +#define SMB_REG_NOTIFY_D0 0x11 +#define SMB_REG_NOTIFY_D1 0x12 +#define SMB_REG_HPRESCALE1 0x13 +#define SMB_REG_HPRESCALE2 0x14 +#define SMB_REG_HEXTRA 0x15 + +#define I2C_TIMEOUT (10 * USEC_PER_MSEC) +#define USE_DEFAULT -1 + +#define CHIP_CLK 50000 +#define I2C_SCLH_HIGH 2500 +#define I2C_SCLH_LOW 1000 +#define I2C_SCL_FAST_MODE 0x80 +#define I2C_THRESHOLD_SPEED 100 +#define I2C_THRESHOLD_SCLH 30 +#define I2C_FREQ_MAX 400 +#define I2C_FREQ_MIN 8 + +enum eio_chan_id { + EIO_I2C0 =3D 0, + EIO_I2C1, + EIO_SMB0, + EIO_SMB1, +}; + +struct eio_i2c_dev { + struct device *dev; + struct device *mfd; + struct regmap *regmap; + struct mutex pnp_mutex; /* Mutex for PNP acces */ + struct eio_i2c_chan *chan[MAX_I2C_SMB]; +}; + +struct eio_i2c_chan { + u16 base; + enum eio_chan_id id; + struct eio_i2c_dev *parent; + struct i2c_adapter adap; + struct mutex lock; /* Mutex for regmap writes */ + int freq_override; /* kHz or USE_DEFAULT */ +}; + +static int i2c0_freq =3D USE_DEFAULT; +module_param(i2c0_freq, int, 0444); +MODULE_PARM_DESC(i2c0_freq, "Set EIO's I2C0 freq.\n"); + +static int i2c1_freq =3D USE_DEFAULT; +module_param(i2c1_freq, int, 0444); +MODULE_PARM_DESC(i2c1_freq, "Set EIO's I2C1 freq.\n"); + +static int smb0_freq =3D USE_DEFAULT; +module_param(smb0_freq, int, 0444); +MODULE_PARM_DESC(smb0_freq, "Set EIO's SMB0 freq.\n"); + +static int smb1_freq =3D USE_DEFAULT; +module_param(smb1_freq, int, 0444); +MODULE_PARM_DESC(smb1_freq, "Set EIO's SMB1 freq.\n"); + +static inline u16 eio_enc_7bit_addr(u16 x) +{ + return ((x & 0x07F) << 1); +} + +static inline u16 eio_enc_10bit_addr(u16 x) +{ + return ((x & 0xFF) | ((x & 0x0300) << 1) | 0xF000); +} + +static inline bool is_i2c(const struct eio_i2c_chan *i2c_chan) +{ + return i2c_chan->id =3D=3D EIO_I2C0 || i2c_chan->id =3D=3D EIO_I2C1; +} + +static inline struct device *eio_dev(const struct eio_i2c_chan *i2c_chan) +{ + return i2c_chan->parent->dev; +} + +static inline struct regmap *eio_map(const struct eio_i2c_chan *i2c_chan) +{ + return i2c_chan->parent->regmap; +} + +static inline int eio_reg_write(struct eio_i2c_chan *i2c_chan, + unsigned int reg_off, unsigned int val) +{ + return regmap_write(eio_map(i2c_chan), i2c_chan->base + reg_off, val); +} + +static inline int eio_reg_read(const struct eio_i2c_chan *chan, + unsigned int reg, unsigned int *val) +{ + int ret; + + ret =3D regmap_read(chan->parent->regmap, chan->base + reg, val); + return ret; +} + +static inline int eio_reg_set_bits(const struct eio_i2c_chan *chan, + unsigned int reg, unsigned int mask) +{ + return regmap_update_bits(chan->parent->regmap, reg, mask, mask); +} + +static inline int eio_reg_clear_bits(const struct eio_i2c_chan *chan, + unsigned int reg, unsigned int mask) +{ + return regmap_update_bits(chan->parent->regmap, reg, mask, 0); +} + +static inline int eio_reg_or(struct eio_i2c_chan *chan, + unsigned int reg, unsigned int mask) +{ + return eio_reg_set_bits(chan, reg, mask); +} + +static inline int eio_reg_and(struct eio_i2c_chan *chan, + unsigned int reg, unsigned int mask) +{ + return eio_reg_clear_bits(chan, reg, ~mask); +} + +static inline unsigned int eio_chan_reg(const struct eio_i2c_chan *i2c_cha= n, + unsigned int i2c_reg, + unsigned int smb_reg) +{ + return is_i2c(i2c_chan) ? i2c_reg : smb_reg; +} + +static inline int eio_trigger_read(struct eio_i2c_chan *i2c_chan, u32 *dat= a) +{ + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0); + + return eio_reg_read(i2c_chan, reg, data); +} + +static int wait_busy(struct eio_i2c_chan *i2c_chan) +{ + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_STAT_BUSY, SMB_HS_BUSY= ); + unsigned int val; + int cnt =3D 0; + + do { + fsleep(cnt++); + + if (ktime_after(ktime_get(), time_end)) { + dev_err(eio_dev(i2c_chan), "Wait I2C bus busy timeout\n"); + return -ETIME; + } + + if (eio_reg_read(i2c_chan, reg, &val)) + return -EIO; + + } while (val & target); + + return 0; +} + +static void reset_bus(struct eio_i2c_chan *i2c_chan) +{ + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRE= SET); + unsigned int val =3D 0; + unsigned int cnt =3D 0; + + dev_dbg(eio_dev(i2c_chan), "i2c[%d] bus reset\n", i2c_chan->id); + + if (is_i2c(i2c_chan)) + eio_reg_write(i2c_chan, I2C_REG_ECTRL, I2C_ECTRL_RST); + else + eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_SRESET); + + do { + fsleep(cnt++); + + if (ktime_after(ktime_get(), time_end)) { + dev_err(eio_dev(i2c_chan), "bus reset timeout\n"); + return; + } + + if (eio_reg_read(i2c_chan, reg, &val)) + return; + + } while (val & target); + + wait_busy(i2c_chan); +} + +static int wait_bus_free(struct eio_i2c_chan *i2c_chan) +{ + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int val; + int cnt =3D 1; + + /* Wait if channel is resetting */ + do { + fsleep(cnt); + + if (ktime_after(ktime_get(), time_end)) { + dev_err(eio_dev(i2c_chan), "Wait bus reset timeout\n"); + return -ETIME; + } + + if (eio_reg_read(i2c_chan, + eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2), + &val)) + return -EIO; + + } while (val & eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRESET)); + + /* Wait INUSE */ + time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + + do { + fsleep(cnt); + + if (ktime_after(ktime_get(), time_end)) { + dev_err(eio_dev(i2c_chan), "Timeout: I2C bus in use\n"); + return -ETIME; + } + + if (eio_reg_read(i2c_chan, + eio_chan_reg(i2c_chan, I2C_REG_SEM, SMB_REG_HS), + &val)) + return -EIO; + + } while (val & eio_chan_reg(i2c_chan, I2C_SEM_INUSE, SMB_HS_INUSE)); + + return 0; +} + +static int let_stop(struct eio_i2c_chan *i2c_chan) +{ + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST= _BYTE); + + return eio_reg_or(i2c_chan, reg, target); +} + +static int clr_inuse(struct eio_i2c_chan *i2c_chan) +{ + if (is_i2c(i2c_chan)) + return eio_reg_write(i2c_chan, I2C_REG_SEM, I2C_SEM_INUSE); + + return eio_reg_or(i2c_chan, SMB_REG_HS, SMB_HS_INUSE); +} + +static int bus_stop(struct eio_i2c_chan *i2c_chan) +{ + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST= _BYTE); + unsigned int val =3D 0; + int cnt =3D 0; + + /* Set STOP bit */ + eio_reg_or(i2c_chan, reg, target); + + /* Wait until STOP bit clears */ + do { + fsleep(cnt++); + + if (ktime_after(ktime_get(), time_end)) + return -ETIME; + + if (eio_reg_read(i2c_chan, reg, &val)) + return -EIO; + + } while (val & target); + + return 0; +} + +static void switch_i2c_mode(struct eio_i2c_chan *i2c_chan, bool on) +{ + u32 tmp; + + if (is_i2c(i2c_chan)) + return; + + if (eio_reg_read(i2c_chan, SMB_REG_HC2, &tmp)) + return; + + eio_reg_write(i2c_chan, SMB_REG_HC2, + on ? (tmp | SMB_HC2_I2C_EN | SMB_HC2_SRESET) + : (tmp & ~SMB_HC2_I2C_EN)); +} + +static void i2c_clear(struct eio_i2c_chan *i2c_chan) +{ + if (is_i2c(i2c_chan)) { + eio_reg_write(i2c_chan, I2C_REG_STAT, 0xFF); + } else { + eio_reg_or(i2c_chan, SMB_REG_HS, 0xA9); + eio_reg_or(i2c_chan, SMB_REG_HS2, 0x4C); + } +} + +static int wait_write_done(struct eio_i2c_chan *i2c_chan, bool no_ack) +{ + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int val =3D 0; + int cnt =3D 0; + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_STAT_TXDONE, SMB_HS_TX= _DONE); + + do { + fsleep(cnt++); + if (ktime_after(ktime_get(), time_end)) { + if (is_i2c(i2c_chan)) { + eio_reg_or(i2c_chan, I2C_REG_STAT, 0); + } else { + eio_reg_or(i2c_chan, SMB_REG_HS, 0); + eio_reg_or(i2c_chan, SMB_REG_HS2, 0); + } + dev_err(eio_dev(i2c_chan), "wait write complete timeout %X %X\n", + val, target); + return -ETIME; + } + if (eio_reg_read(i2c_chan, reg, &val)) + return -EIO; + + } while ((val & target) =3D=3D 0); + + if (no_ack) + return 0; + + if (is_i2c(i2c_chan)) { + eio_reg_or(i2c_chan, I2C_REG_STAT, 0); + return (val & I2C_STAT_NAK_ERR) ? -EIO : 0; + } + + eio_reg_or(i2c_chan, SMB_REG_HS, 0); + if (eio_reg_read(i2c_chan, SMB_REG_HS2, &val)) + return -EIO; + eio_reg_write(i2c_chan, SMB_REG_HS2, val); + + return (val & SMB_HS2_NACK_ERR) ? -EIO : 0; +} + +static int wait_ready(struct eio_i2c_chan *i2c_chan) +{ + int ret; + + ret =3D wait_bus_free(i2c_chan); + if (ret) + return ret; + + if (wait_busy(i2c_chan) =3D=3D 0) + return 0; + + reset_bus(i2c_chan); + + return wait_busy(i2c_chan); +} + +static int write_addr(struct eio_i2c_chan *i2c_chan, int addr, bool no_ack) +{ + eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_ADDR, SMB_REG_HADD= R), + addr); + + return wait_write_done(i2c_chan, no_ack); +} + +static int write_data(struct eio_i2c_chan *i2c_chan, int data, bool no_ack) +{ + eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0), + data); + + return wait_write_done(i2c_chan, no_ack); +} + +static int read_data(struct eio_i2c_chan *i2c_chan, u8 *data) +{ + unsigned int val =3D 0, tmp; + int cnt =3D 0; + ktime_t time_end =3D ktime_add_us(ktime_get(), I2C_TIMEOUT); + unsigned int stat =3D eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS); + unsigned int target =3D eio_chan_reg(i2c_chan, I2C_STAT_RXREADY, SMB_HS_R= X_READY); + unsigned int reg =3D eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0); + + do { + fsleep(cnt++); + + if (ktime_after(ktime_get(), time_end)) { + eio_reg_or(i2c_chan, stat, 0); + dev_err(eio_dev(i2c_chan), "read data timeout\n"); + return -ETIME; + } + + if (eio_reg_read(i2c_chan, stat, &val)) + return -EIO; + + } while ((val & target) !=3D target); + + /* clear status */ + eio_reg_write(i2c_chan, stat, val); + + /* Must read data after clearing status */ + if (eio_reg_read(i2c_chan, reg, &tmp)) + return -EIO; + *data =3D (u8)tmp; + + return 0; +} + +static int set_freq(struct eio_i2c_chan *i2c_chan, int freq) +{ + u8 pre1, pre2; + u16 speed; + unsigned int reg1 =3D eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_H= PRESCALE1); + unsigned int reg2 =3D eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_H= PRESCALE2); + + dev_dbg(eio_dev(i2c_chan), "set freq: %dkHz\n", freq); + if (freq > I2C_FREQ_MAX || freq < I2C_FREQ_MIN) { + dev_err(eio_dev(i2c_chan), "Invalid i2c freq: %d\n", freq); + return -EINVAL; + } + + speed =3D (freq < I2C_THRESHOLD_SCLH) ? I2C_SCLH_LOW : I2C_SCLH_HIGH; + + pre1 =3D (u8)(CHIP_CLK / speed); + pre2 =3D (u8)((speed / freq) - 1); + + if (freq > I2C_THRESHOLD_SCLH) + pre2 |=3D I2C_SCL_FAST_MODE; + + eio_reg_write(i2c_chan, reg1, pre1); + eio_reg_write(i2c_chan, reg2, pre2); + + return 0; +} + +static int get_freq(struct eio_i2c_chan *i2c_chan, int *freq) +{ + int clk; + unsigned int pre1 =3D 0, pre2 =3D 0; + unsigned int reg1 =3D eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_H= PRESCALE1); + unsigned int reg2 =3D eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_H= PRESCALE2); + + if (eio_reg_read(i2c_chan, reg1, &pre1)) + return -EIO; + if (eio_reg_read(i2c_chan, reg2, &pre2)) + return -EIO; + + clk =3D (pre2 & I2C_SCL_FAST_MODE) ? I2C_SCLH_HIGH : I2C_SCLH_LOW; + pre2 &=3D ~I2C_SCL_FAST_MODE; + + *freq =3D clk / ((int)pre2 + 1); + + return 0; +} + +static int smb_access(struct eio_i2c_chan *i2c_chan, u8 addr, bool is_read= , u8 cmd, + int size, union i2c_smbus_data *data) +{ + int i, tmp, ret =3D 0; + unsigned int st1, st2; + int len =3D 0; + + mutex_lock(&i2c_chan->lock); + + ret =3D wait_ready(i2c_chan); + if (ret) + goto exit; + + /* Force SMBus mode */ + switch_i2c_mode(i2c_chan, false); + + addr =3D eio_enc_7bit_addr(addr) | (is_read ? 1 : 0); + eio_reg_write(i2c_chan, SMB_REG_HADDR, addr); + eio_reg_write(i2c_chan, SMB_REG_HCMD, cmd); + + dev_dbg(eio_dev(i2c_chan), "SMB[%d], addr:0x%02X, cmd:0x%02X size=3D%d\n", + i2c_chan->id, addr, cmd, size); + + switch (size) { + case I2C_SMBUS_QUICK: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n"); + break; + + case I2C_SMBUS_BYTE: + if (!is_read) { + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE\n"); + eio_reg_write(i2c_chan, SMB_REG_HCMD, cmd); + } + break; + + case I2C_SMBUS_BYTE_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA\n"); + if (!is_read) { + eio_reg_write(i2c_chan, SMB_REG_HD0, data->byte); + dev_dbg(eio_dev(i2c_chan), "write %X\n", data->byte); + } + break; + + case I2C_SMBUS_WORD_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n"); + if (!is_read) { + eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]); + eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]); + } + break; + + case I2C_SMBUS_PROC_CALL: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n"); + eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]); + eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]); + break; + + case I2C_SMBUS_BLOCK_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n"); + if (is_read) + break; + + /* Program command type */ + eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp); + tmp &=3D ~(0x07 << SMB_HC_CMD_SHIFT); + tmp |=3D (size << SMB_HC_CMD_SHIFT); + eio_reg_write(i2c_chan, SMB_REG_HC, tmp); + + /* Force write for payload stage */ + eio_reg_write(i2c_chan, SMB_REG_HADDR, addr & ~0x01); + + /* Reset internal buffer index pointer */ + eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B); + eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B); + + /* Write length + data */ + eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]); + for (i =3D 1; i <=3D data->block[0]; i++) + eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]); + break; + + case I2C_SMBUS_BLOCK_PROC_CALL: + /* Set command type field */ + eio_reg_and(i2c_chan, SMB_REG_HC, (0x07 << SMB_HC_CMD_SHIFT)); + eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]); + + /* Reset buffer index */ + eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B); + eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B); + + for (i =3D 1; i <=3D data->block[0]; i++) + eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]); + break; + + default: + ret =3D -EINVAL; + goto exit; + } + + /* Launch transaction */ + eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp); + tmp &=3D ~(0x07 << SMB_HC_CMD_SHIFT); + tmp |=3D (size << SMB_HC_CMD_SHIFT) | SMB_HC_START; + tmp &=3D ~(SMB_HC_I2C_NACKEN | SMB_HC_KILL | SMB_HC_PEC_EN); + eio_reg_write(i2c_chan, SMB_REG_HC, tmp); + + ret =3D wait_busy(i2c_chan); + if (ret) + goto exit; + + eio_reg_read(i2c_chan, SMB_REG_HS, &st1); + eio_reg_read(i2c_chan, SMB_REG_HS2, &st2); + + if (st1 & SMB_HS_FAILED) { + dev_err(eio_dev(i2c_chan), "HS FAILED\n"); + ret =3D -EIO; + } else if (st1 & SMB_HS_ARL_ERR) { + dev_err(eio_dev(i2c_chan), "ARL FAILED\n"); + ret =3D -EIO; + } else if (st2 & SMB_HS2_TO_ERR) { + dev_err(eio_dev(i2c_chan), "timeout\n"); + ret =3D -ETIME; + } else if (st2 & SMB_HS2_NACK_ERR) { + dev_err(eio_dev(i2c_chan), "NACK err\n"); + ret =3D -EIO; + } else if (st2 & SMB_HS2_PEC_ERR) { + dev_err(eio_dev(i2c_chan), "PEC err\n"); + ret =3D -EIO; + } + if (ret) + goto exit; + + switch (size) { + case I2C_SMBUS_QUICK: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n"); + break; + + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + if (is_read) { + unsigned int v; + + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE/I2C_SMBUS_BYTE_DATA\n"); + eio_reg_read(i2c_chan, SMB_REG_HD0, &v); + data->block[0] =3D (u8)v; + dev_dbg(eio_dev(i2c_chan), "read %X\n", data->block[0]); + } + break; + + case I2C_SMBUS_WORD_DATA: { + unsigned int v0, v1; + + if (is_read) { + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n"); + eio_reg_read(i2c_chan, SMB_REG_HD0, &v0); + eio_reg_read(i2c_chan, SMB_REG_HD1, &v1); + data->block[0] =3D (u8)v0; + data->block[1] =3D (u8)v1; + } + break; + } + + case I2C_SMBUS_PROC_CALL: { + unsigned int v0, v1; + + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n"); + eio_reg_read(i2c_chan, SMB_REG_HD0, &v0); + eio_reg_read(i2c_chan, SMB_REG_HD1, &v1); + data->block[0] =3D (u8)v0; + data->block[1] =3D (u8)v1; + break; + } + + case I2C_SMBUS_BLOCK_DATA: + if (!is_read) + break; + + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n"); + eio_reg_read(i2c_chan, SMB_REG_HD0, (unsigned int *)&len); + len =3D min(len, I2C_SMBUS_BLOCK_MAX); + data->block[0] =3D len; + + for (i =3D 1; i <=3D len; i++) + eio_reg_read(i2c_chan, SMB_REG_HBLOCK, + (unsigned int *)&data->block[i]); + break; + + default: + ret =3D -EINVAL; + goto exit; + } + +exit: + /* Clear latched status */ + eio_reg_write(i2c_chan, SMB_REG_HS, 0xFF); + eio_reg_write(i2c_chan, SMB_REG_HS2, 0xFF); + + mutex_unlock(&i2c_chan->lock); + return ret; +} + +static int i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int nm= sgs) +{ + int msg, data; + int addr =3D 0; + int dummy; + int ret =3D 0; + struct eio_i2c_chan *i2c_chan =3D i2c_get_adapdata(adap); + + mutex_lock(&i2c_chan->lock); + + ret =3D wait_ready(i2c_chan); + if (ret) + goto exit; + + switch_i2c_mode(i2c_chan, true); + + dev_dbg(eio_dev(i2c_chan), "Transmit %d I2C messages\n", nmsgs); + for (msg =3D 0; msg < nmsgs; msg++) { + int is_read =3D msgs[msg].flags & I2C_M_RD; + bool no_ack =3D msgs[msg].flags & I2C_M_IGNORE_NAK; + + dev_dbg(eio_dev(i2c_chan), "message %d len=3D%d\n", msg, msgs[msg].len); + + if (!msgs[msg].len) + let_stop(i2c_chan); + + if (msgs[msg].flags & I2C_M_TEN) { + addr =3D eio_enc_10bit_addr(msgs[msg].addr); + addr |=3D is_read; + dev_dbg(eio_dev(i2c_chan), "10-bit addr: %X\n", addr); + + ret =3D write_addr(i2c_chan, addr >> 8, no_ack); + if (!ret) + ret =3D write_data(i2c_chan, addr & 0x7F, no_ack); + } else { + addr =3D eio_enc_7bit_addr(msgs[msg].addr); + addr |=3D is_read; + dev_dbg(eio_dev(i2c_chan), "7-bit addr: %X\n", addr); + + ret =3D write_addr(i2c_chan, addr, no_ack); + } + + if (ret) + goto exit; + + if (!msgs[msg].len) + goto exit; + + if (is_read) + ret =3D eio_trigger_read(i2c_chan, (u32 *)&dummy); + + /* Transmit all messages */ + for (data =3D 0; data < msgs[msg].len; data++) { + if (msgs[msg].flags & I2C_M_RD) { + bool last =3D (msgs[msg].len =3D=3D data + 1); + + if (last) + let_stop(i2c_chan); + + ret =3D read_data(i2c_chan, &msgs[msg].buf[data]); + dev_dbg(eio_dev(i2c_chan), "I2C read[%d] =3D %x\n", + data, msgs[msg].buf[data]); + + /* Don't stop twice */ + if (last && ret =3D=3D 0) + goto exit; + } else { + ret =3D write_data(i2c_chan, msgs[msg].buf[data], no_ack); + dev_dbg(eio_dev(i2c_chan), "I2C write[%d] =3D %x\n", + data, msgs[msg].buf[data]); + } + if (ret) + goto exit; + } + } + + if (!ret) + ret =3D bus_stop(i2c_chan); + + if (!ret) + goto exit; + +exit: + if (ret) + reset_bus(i2c_chan); + + i2c_clear(i2c_chan); + clr_inuse(i2c_chan); + + mutex_unlock(&i2c_chan->lock); + return ret ? ret : nmsgs; +} + +static int smbus_xfer(struct i2c_adapter *adap, u16 addr, + u16 flags, char is_read, u8 cmd, + int size, union i2c_smbus_data *data) +{ + int ret; + struct eio_i2c_chan *i2c_chan =3D i2c_get_adapdata(adap); + int nmsgs =3D is_read ? 2 : 1; + u8 buf[I2C_SMBUS_BLOCK_MAX + sizeof(u32)] =3D { cmd, }; + struct i2c_msg msgs[2] =3D { + { .addr =3D addr, .flags =3D flags & ~I2C_M_RD, .buf =3D buf + 0 }, + { .addr =3D addr, .flags =3D flags | I2C_M_RD, .buf =3D buf + 1 }, + }; + + /* Non-I2C channels use the SMB engine, except I2C block variants we emul= ate */ + if (!is_i2c(i2c_chan) && size !=3D I2C_SMBUS_I2C_BLOCK_DATA) + return smb_access(i2c_chan, addr, is_read, cmd, size, data); + + if (data) { + buf[0] =3D cmd; + /* FIX: preserve other flags; only toggle I2C_M_RD */ + msgs[0].flags =3D is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD); + msgs[1].buf =3D data->block; + } + + switch (size) { + case I2C_SMBUS_QUICK: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK on I2C\n"); + nmsgs =3D 1; + break; + + case I2C_SMBUS_BYTE: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE on I2C\n"); + nmsgs =3D 1; + msgs[0].len =3D 1; + msgs[0].buf =3D is_read ? data->block : buf; + msgs[0].flags =3D is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD); + break; + + case I2C_SMBUS_BYTE_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA on I2C\n"); + if (!data) + return -EINVAL; + msgs[0].len =3D is_read ? 1 : 2; + buf[1] =3D data->block[0]; + msgs[1].len =3D 1; + break; + + case I2C_SMBUS_WORD_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA on I2C\n"); + if (!data) + return -EINVAL; + msgs[0].len =3D is_read ? 1 : 3; + msgs[1].len =3D 2; + buf[1] =3D data->block[0]; + buf[2] =3D data->block[1]; + msgs[1].buf =3D data->block; + break; + + case I2C_SMBUS_I2C_BLOCK_DATA: + case I2C_SMBUS_I2C_BLOCK_BROKEN: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_I2C_BLOCK_(DATA/BROKEN) on I2C len= =3D%d\n", + data->block[0]); + if (!data) + return -EINVAL; + msgs[0].len =3D is_read ? 1 : data->block[0] + 1; + msgs[1].len =3D data->block[0]; + msgs[1].buf =3D data->block + 1; + if (msgs[0].len >=3D I2C_SMBUS_BLOCK_MAX || + msgs[1].len >=3D I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + if (!is_read) + memcpy(buf + 1, data->block + 1, msgs[0].len); + break; + + case I2C_SMBUS_PROC_CALL: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL on I2C\n"); + if (!data) + return -EINVAL; + nmsgs =3D 2; + msgs[0].flags =3D flags & ~I2C_M_RD; + msgs[0].len =3D 3; + buf[1] =3D data->block[0]; + buf[2] =3D data->block[1]; + msgs[1].len =3D 2; + break; + + case I2C_SMBUS_BLOCK_DATA: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA on I2C not supported\n"= ); + return -EINVAL; + + case I2C_SMBUS_BLOCK_PROC_CALL: + dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_PROC_CALL on I2C not support= ed\n"); + return -EINVAL; + + default: + return -EINVAL; + } + + ret =3D i2c_xfer(adap, msgs, nmsgs); + return ret < 0 ? ret : 0; +} + +static int load_i2c(struct device *dev, enum eio_chan_id id, + struct eio_i2c_chan *i2c_chan) +{ + u32 base_lo, base_hi, base; + int ldn =3D LDN_I2C0 + id; + struct eio_i2c_dev *eio_i2c =3D i2c_chan->parent; + struct regmap *map; + + if (!eio_i2c || !eio_i2c->regmap) + return dev_err_probe(dev, -ENODEV, "missing parent/regmap\n"); + + map =3D eio_i2c->regmap; + + /* Read channel I/O base via shared PNP window */ + mutex_lock(&eio_i2c->pnp_mutex); + if (regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) || + regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) || + regmap_write(map, REG_PNP_INDEX, REG_LDN) || + regmap_write(map, REG_PNP_DATA, ldn) || + regmap_write(map, REG_PNP_INDEX, REG_BASE_HI) || + regmap_read(map, REG_PNP_DATA, &base_hi) || + regmap_write(map, REG_PNP_INDEX, REG_BASE_LO) || + regmap_read(map, REG_PNP_DATA, &base_lo) || + regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_EXIT)) { + mutex_unlock(&eio_i2c->pnp_mutex); + dev_err(dev, "error read/write I2C[%d] IO port\n", id); + return -EIO; + } + mutex_unlock(&eio_i2c->pnp_mutex); + + base =3D (base_hi << 8) | base_lo; + if (base =3D=3D 0xFFFF || base =3D=3D 0) { + dev_dbg(dev, "i2c[%d] base addr=3D%#x (not in-use)\n", id, base); + return -ENODEV; + } + + dev_dbg(dev, "i2c[%d] base addr=3D%#x\n", id, base); + + /* Bind channel (no per-chan dev) */ + i2c_chan->base =3D (u16)base; + i2c_chan->id =3D id; + + /* Per-channel frequency policy */ + if (i2c_chan->freq_override !=3D USE_DEFAULT) + set_freq(i2c_chan, i2c_chan->freq_override); + + get_freq(i2c_chan, &i2c_chan->freq_override); + + return 0; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + struct eio_i2c_chan *i2c_chan =3D i2c_get_adapdata(adap); + + return is_i2c(i2c_chan) ? SUPPORTED_I2C : SUPPORTED_SMB; +} + +static const struct i2c_algorithm algo =3D { + .smbus_xfer =3D smbus_xfer, + .master_xfer =3D i2c_xfer, + .functionality =3D functionality, +}; + +static int eio_i2c_probe(struct platform_device *pdev) +{ + static const char * const names[] =3D { "i2c0", "i2c1", "smb0", "smb1" }; + struct device *dev =3D &pdev->dev; + struct eio_i2c_dev *eio_i2c; + struct eio_dev *eio_dev =3D dev_get_drvdata(dev->parent); + int ret =3D 0; + enum eio_chan_id ch; + + if (!eio_dev) { + dev_err(dev, "Error contact eio_core\n"); + return -ENODEV; + } + + eio_i2c =3D devm_kzalloc(dev, sizeof(*eio_i2c), GFP_KERNEL); + if (!eio_i2c) + return -ENOMEM; + + eio_i2c->dev =3D dev; + eio_i2c->mfd =3D dev->parent; + eio_i2c->regmap =3D dev_get_regmap(dev->parent, NULL); + if (!eio_i2c->regmap) + return dev_err_probe(dev, -ENODEV, "parent regmap not found\n"); + + mutex_init(&eio_i2c->pnp_mutex); + platform_set_drvdata(pdev, eio_i2c); + + for (ch =3D EIO_I2C0; ch < MAX_I2C_SMB; ch++) { + struct eio_i2c_chan *i2c_chan; + + i2c_chan =3D devm_kzalloc(dev, sizeof(*i2c_chan), GFP_KERNEL); + if (!i2c_chan) { + ret =3D -ENOMEM; + break; + } + + i2c_chan->parent =3D eio_i2c; + i2c_chan->freq_override =3D USE_DEFAULT; + mutex_init(&i2c_chan->lock); + + if (load_i2c(dev, ch, i2c_chan)) { + dev_info(dev, "No %s%d!\n", (ch < 2) ? "I2C" : "SMBus", ch & 1); + continue; + } + + i2c_chan->adap.owner =3D THIS_MODULE; + i2c_chan->adap.class =3D I2C_CLASS_HWMON; + i2c_chan->adap.algo =3D &algo; + i2c_chan->adap.dev.parent =3D dev; + snprintf(i2c_chan->adap.name, sizeof(i2c_chan->adap.name), "eio-%s", + names[ch]); + + i2c_set_adapdata(&i2c_chan->adap, i2c_chan); + + ret =3D i2c_add_adapter(&i2c_chan->adap); + dev_info(dev, "Add %s%d %s. %d\n", (ch < 2) ? "I2C" : "SMBus", + ch, ret ? "Error" : "Success", ret); + if (ret) + break; + + eio_i2c->chan[ch] =3D i2c_chan; + } + + if (ret) { + for (ch =3D EIO_I2C0; ch < MAX_I2C_SMB; ch++) { + if (eio_i2c->chan[ch]) { + i2c_del_adapter(&eio_i2c->chan[ch]->adap); + eio_i2c->chan[ch] =3D NULL; + } + } + } + + return ret; +} + +static void eio_i2c_remove(struct platform_device *pdev) +{ + struct eio_i2c_dev *eio_i2c =3D platform_get_drvdata(pdev); + enum eio_chan_id ch; + + for (ch =3D EIO_I2C0; ch < MAX_I2C_SMB; ch++) { + if (eio_i2c->chan[ch]) { + i2c_del_adapter(&eio_i2c->chan[ch]->adap); + eio_i2c->chan[ch] =3D NULL; + } + } +} + +static struct platform_driver eio_i2c_driver =3D { + .probe =3D eio_i2c_probe, + .remove =3D eio_i2c_remove, + .driver =3D { + .name =3D "i2c_eio", + }, +}; + +module_platform_driver(eio_i2c_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("I2C driver for Advantech EIO embedded controller"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: eio_core"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from TYDPR03CU002.outbound.protection.outlook.com (mail-japaneastazon11023110.outbound.protection.outlook.com [52.101.127.110]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 599E34963D9; Tue, 14 Jul 2026 15:55:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.127.110 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044523; cv=fail; b=t7Q198YYzpeTCofK5FoG61EQpVfR3iTC9qV0oJdCSWONdyK968aUalp6R5fWsNHaiDYQ5gWoNpSkXdKGIbgNQIRJx52WpXPBuyeMSbo3NORWU9f6aMifZL0DHLO4uCjujMPQlZfElghCB8yug7ypxIxxWSzT+iKfX+tar3xSP8k= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044523; c=relaxed/simple; bh=o3xQb+Kb4nE0n1YarbvAQPCz5oz9H0n4kTID+Qv3PbE=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=MPu9g9bpvv7jwMXejrHWhYx5RH7nWlfagPUqjLSK3M+AaNxJN69mE0F4QxjXQHEe1QyVc0IJ2kUNO48ex6jwHf/QAf9RIqTNQWiIVzVyUaqBaKEXN0++FsEoxl+thOGqOQvfdoeIt+CLQEv3W8uWUbMH02UMHUv6l+hRfUv5tp0= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=XBfLfaTS; arc=fail smtp.client-ip=52.101.127.110 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="XBfLfaTS" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=ybOy9YpxExhCxfIunpbX7eQC+WGHbVjWs+amcsokbgpewAJ7d+/gxxnihLkBKHNUzHSOXS8P8os3a2okwNiF4AGTndYKzWY2Gy5UhlaIcQBtTbmFEyPXa8a1y27ttOjlaw9F1fFBUySBuTLF3MeVtv1zqu5tXX/ClBIuNFXoTwKzV4/381INnKocD+dvC0oqu05bzYY2ivOrWO0KCm8W2cZVkkK4k7rC1Ib1upbvoI6KfExXEtorVIDLUG6bffIDfl9etffGGLlYfq7mQ5iq7QeBwZp8y55LfRJe4gcZJbesCwekFH2qiL08z5YBPO5lkNTnUAXPx2kbgFzOTRVBPQ== 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=hF92Zx0ptTPN5PMTMy0OGCj/V0J9xXzMnxxdC0MJ3+A=; b=FtwZSjR0d674qtKx1nMPFzg1vkwJZ6sPkaTs9MmYPx6nqEPUT8uP9I2/XB5qoJP6aCnoQqeKdY7iCg94IQv5WM2uvC+nQoLZYjm6C14ma5l+b6l3aY5VjlLx0tV2tgPq4RDxtw061MfcidwRB6FpEJmvFxkM34YhaHQljKAFY71RC7+h4cJW97XYdnDc9HL6zNeru6jr2Qv/GQuxwG90uYh59mixgjq8ZEbGO5VDzgJtnb1iHWKDptC3iiaSPqZPloFU8f8g6+77MH60026zZrct0TUX5InBTdqF80zVXuLUChzcGIrESvHfGRt4LPcIhCvAjT6CwjsSIe5k8wpQxg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=hF92Zx0ptTPN5PMTMy0OGCj/V0J9xXzMnxxdC0MJ3+A=; b=XBfLfaTSIK/hUtLCLg1meH3mRbA0dOYuWjwENkRSu/HLgY+L3vQLoyZgTjCuaPt+x+YduQE1VfCqUMDDjnQ9DKXwdLpQ0gxWICjRyOgXlsB96xU9zMCq1Tqd0QDKKl/QSoBrSoqjPfCzfiSFerWp3pnUvPWAy8GxZnx1JKBlwv94S1iH13hf5zNQNG/cEvyEYWti+nW5LVZmhAeHhyJVbI47YjbkhiTxc6pT/PHp3CH0c9l6wGRXp3XIsQTkYFyTEgR++UQ++5On2TR5GqBvSi4bqd4yoGQxloinkpWDwP9sqey4RcuUBgRrErtRiY9ZEJhPlRewL2R+CSAmQG/aLQ== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by JH0PR02MB6851.apcprd02.prod.outlook.com (2603:1096:990:4d::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.18; Tue, 14 Jul 2026 15:55:16 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:16 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:19 +0200 Subject: [PATCH v2 5/8] Add Advantech EIO Backlight driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-5-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=8206; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=o3xQb+Kb4nE0n1YarbvAQPCz5oz9H0n4kTID+Qv3PbE=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu4Qpw9ZQVUzW2aEdXdSLkU27l9+rR3TwCe8 +w0eqXWF5mJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CfzfDACYs7c+6KP777EuatLF71DYZOwuZH+Y//4ei5CCZjFp8gUZyWwo1YBLrqsd2MlthUE6RPl fZkRKAxAzwP6FnPUrw69M6uV4vD8j5Vvca3JrU1MioeUAi5l9XmxIPNYf8/FWYGEz9SYW0XHZdz vAA8Wk/xCTvB89YOb58ELuaisnVEZtP/9YyvS9I9DfFljtoKUVPAkXfPmANH9KTTVOf1lNdGuq7 vJe541A3f7Ojru8p3HUQOo3WyLHLOKEJOuxlZpVS7JcRQD9VeWBHVNeQ9jl5OC+GSQhVNsc6Vir D/osHmk2ssIvUdMGZzehjEUlI/ZwR8q5+mJ4drrA/2y6B6NQJGg7nxdzHkqZuRQIiFTq+FgfQDa fEk3NEE8yPHI/4a36CHqrPa7F41ziSZOWlRB81MKJHhcCE/RHmT/EAImsgsRobluwuEf+OG3oZQ td993pYlZgCbkFGwN22IbGUrQNMJaxQG+wB0KKgr/AFObtUZK6VsKXtDIO4Nhn9iFhE1s= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|JH0PR02MB6851:EE_ X-MS-Office365-Filtering-Correlation-Id: 04be9ba1-081f-425c-b009-08dee1c04c78 X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|52116014|7416014|23010399003|376014|366016|1800799024|38350700014|921020|56012099006|11063799006|55112099003|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: uiIhmfHlQy8myR2iwDyeiNrJ9KTzWlrgR6joso4QKaTUpMW/pYp/80I0i1Ubx4RRt9WOB8BJAsTaLyPE0lBvjhwGhGFfdD5p6Eof7tIyCZNMZDmCfQg2gUFkqkw2k5nJYvnqG7xoDmIebzIVgwGLpjb+CnZ+4ZSg5MwP9lymZUbm0n0c4AymuYWZN29tc8hNGwNjBh0NctMJqPwua7kE7rgrrvQFzUet3g+Kz88kILHc990RfeVEqBqM20ahxrapvCOADdvRXP6Zc6OTOhdh5mcdAsYFzi4M43xv58Uw2F7y1aNpuIXyPmGK4zrtaN50tWpORZ62XkbKiv4wTQ0wTeENlS1Wja4dnvm1y5lGq119EGvjMdN8wdiRod1n8JrZwDLY2mNQFYpE5f1lyp7NCKACa2Hc4N2FWE2VLZDFW3RWfDtm+ZKBF1XaJaVrDMtFHvxO8669TnFgDJxS4Pk08ZHEKUSiyLeDxewSVgizUeCd4mfzicxLXmVVna3PaU5LDeuh8cUSHkhjEhqTz9/YGbb4fTUvC5ndZf9u/cUSALdBCi3/l27Y3+roeGsQeAZpGlCRqIYBwAr7meCXCDX4r1Oamhk8keghXzTkw7Z8qySWUWMJx3A8eIbF7fJp+cODgO8bgN1f1b70W1Kj5LpXhu6g+H6tDWmJIqhQ0ZLOZjh25FNRRAFVp06J8SupWGzUWRDo/YtZzZcvR8XqWygRBJAHhOUg0BOufgV930AsRBs= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(52116014)(7416014)(23010399003)(376014)(366016)(1800799024)(38350700014)(921020)(56012099006)(11063799006)(55112099003)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?SnVodHhiYjJiWmc3WktlcS9wOHkrUFJhYXhvdVRyV1RPM09LcjhpNVVocklO?= =?utf-8?B?Y2N0ZnhaL0pqRDhMYW1CMThnMkk5M1FHUWZhRTZ2a3JybUJtZmwzWXF6c2to?= =?utf-8?B?N3hzY2psYk9vdkRqeW9CTy9BNXBQWjNWMS9aV3huVzRHTkFoZTc0aXk0dU9S?= =?utf-8?B?MjE0SFQrbldLSUp1TFJJQ251MTN5d1NtL0l4bDRDdHRWeWk3WE9PTURFMG1W?= =?utf-8?B?TmtYWEtESU9hZHVScmlxMUN5RWJvM2E1ellRdlAyeFZmK05nMFRCekJCdHh1?= =?utf-8?B?QzN1dFE2cDV5ZDVOTXZQcnN1L2ROVW9JMVVJU2VIeVlZTXNpY3QvS0FxR3l5?= =?utf-8?B?R3kyRVBJTHY5d1cwL1JxQXpFTG1PM3BVY2pVSVZqd0pGKzNSWkl4dC9XUmIv?= =?utf-8?B?ZmUyd1ZWL0poS1d5Y2lnalNieGxVR3hBRjk2azdxSDV2VW40VFhYcmZ5MGp2?= =?utf-8?B?V0Q0RFRIS1JTcXlmQUV2UHFEQ0ZQYWJ5NElaejA1dzZRZmwyQ2VhWkIyU2hs?= =?utf-8?B?WjNzK3k5WmtYVmpib2tzeGQwMzQzRGVOQU9LOElTR1p0d055N1FNV3FGdHd6?= =?utf-8?B?c2x2cXRlaStJbVNCUVBjNVJZM2szak9LbFNhV25vaklKMTlOQTVlR1NZbVBs?= =?utf-8?B?eEtBcG12N1dVbHZQQXFuMGR4R1MrWCtvWnVWWXQyQ1pRSVV3Z1ZpVWVLQ2ZN?= =?utf-8?B?N0pYU2VaQ2ZqWUJ1VExOTHg1dFlDaVNPbDJNUnBNT2F5bnlVTjFRd2VoVHdZ?= =?utf-8?B?ZXBJVEwzVXl6WTA4N1ZxbGc5WWoxWHB3NTJjam9RZzZkNXFFd0ZDN3pYQnp4?= =?utf-8?B?VTRMeDlsbGlrYTdyanJnclA2SWg2K0hTcjZwRXdHWlhsM1YremdlQzQ0eGoy?= =?utf-8?B?RlRmRkRHWVRPbVk1WmVBSkFZaEhyY3d4c3NVaUxvajRNVnh0ZFM2MjQ1TzY5?= =?utf-8?B?NXdyNmZSYkdNb3ZmTWcrQmJpVG5zNHpNcHdlWjFXOWRFN0xhNE4xVVVEYkNq?= =?utf-8?B?eEt6azN5U21NT1cyRFEzclAzQkx4V1VkU1BoOFlVSWZQNDdxMGVnQS80MDBS?= =?utf-8?B?M3JITmdFM2EyMkZiM2xEdVcwMjNRTjNvY1A3SVNTc0NDcGJzdTRHM2NCUy9k?= =?utf-8?B?cUcybHpiNWJFa0hzLzVvS0ZOc1Ztd29CYnNmV2tpaG5EKzBhSGpiRTFTN0Jo?= =?utf-8?B?V200UmowRWN6cE1IVUg4LzcyVWlFZ0dDMmZMdzdyMDZtOFdYU0FTYisvSTY2?= =?utf-8?B?TCtXVnp1WGFpcDBML2xqWXB6dm9qQW9SUXVNaTJobGQveWdOVFMwSVFiZDI5?= =?utf-8?B?UFNtYmdOdzRCTnZNM2ZYbnpyTEtrNUJBeTFmQmZUMnRKbFp5SGN5M2lkWmto?= =?utf-8?B?QUdLczVsYXJHT24ya1ZWcmNmT3BlckUwVmdFVU1haEd6dndRb3NteDVrT2dY?= =?utf-8?B?SVRiQXJUVlIxaDJTVEZuVlRLa0FXeS9Od2M3Wm5pUzJYeVhNaXJJL29KVWxo?= =?utf-8?B?VElFMitSUHpqOHhtSXpMbU9CQWtyL2wrUVZ1S0RtbkdZR2pXSm1YVVZacVdL?= =?utf-8?B?Wjc1VzRoL21nTzdRS2Mwci9JYzh6WmtzVUpYNms3dXgwQms3V0twU1VScklD?= =?utf-8?B?MmtDeEhsOFhFdldrYk1NVTN1cVphR0c2UjVtazV0WEE4MEV4bjhKcGFsMlY2?= =?utf-8?B?NnFPYkV2aHJpSkltYnV5UVdKNFlwcEoxTnNNSHROQ2MvWG1pLzFKOUc1cVpS?= =?utf-8?B?QlNJSXlHKzJucGJ3U0c0Y0c4ZStLWHZ4aTV5QlpuOW5KUHo2eVlkMDA1UlZ0?= =?utf-8?B?NUsxTE5SU3dFZWZvYVhpdEFrRHFYN2VnaG9TbGRnRDZyc3o5NVpxYUlNczNU?= =?utf-8?B?RTI5NlRJMklWS1Fwa3pEZG1hZHpjQlV0MitjaVRhRTRYVWFiWFdVU3dWdVdE?= =?utf-8?B?bHZtVmZHUlIxUlBybFArNFlVT3FSYkoxWXpBTVVYUTJEcDJhWjRGU3RxaDM1?= =?utf-8?B?RWZNMUlzN0w5OVpyekh4RDFidUZXK3UvS05OZGozbi8zcGZmZGJobUVVSDl2?= =?utf-8?B?Tm1DYTRMSzZOOENHSEhDRkZQYU9FRUZzOWtjb1I1UXErRXRsWTlycGZ0SzYw?= =?utf-8?B?cFNTZ0ViQ0dXSWs2UXpVOVh2SnRMbVlEb1ErT3B3akwrNHE2Y01VOXJHRVdj?= =?utf-8?B?YW9NTjlEVEg4R2JSU1NPNXN1d3dQTk1taU1sL25DVktrQ2xybkJMZWwxKytM?= =?utf-8?B?WUtTZHZIZnpnVHVaYmt3OEtrTTBZZ2NoYlJVMW9HRlcvQlNCSmkrOW0zUGM2?= =?utf-8?B?MFRvUGYwOW1nZnhiMFRDVmxwT3BVL3hGUDZoMEV1QnRjbkplaGZ5c0VMWUZK?= =?utf-8?Q?cqBNUgar3C43EyF4=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 04be9ba1-081f-425c-b009-08dee1c04c78 X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:16.7486 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: A1dEubcnqGNG76Lx5Ryv8RPcY3OI1BiP2ozTxyOyLkqfMVbA+j4UClWr+wpAYIBYhVh+LWRWbRMQF1sqaIUU90JHsSFiMdbF18bz/0ioSok= X-MS-Exchange-Transport-CrossTenantHeadersStamped: JH0PR02MB6851 This driver controls the Video Backlight block of the Advantech EIO chip. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/video/backlight/Kconfig | 6 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/eio_bl.c | 243 +++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 251 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e228d12a2326..d2d7d815ed44 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -616,6 +616,7 @@ F: drivers/gpio/gpio-eio.c F: drivers/hwmon/eio-hwmon.c F: drivers/i2c/busses/i2c-eio.c F: drivers/mfd/eio_core.c +F: drivers/video/backlight/eio_bl.c F: include/linux/mfd/eio.h =20 ADVANTECH SWBTN DRIVER diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kcon= fig index 7c66b8840d88..a5559a7db75d 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -522,6 +522,12 @@ config BACKLIGHT_RAVE_SP help Support for backlight control on RAVE SP device. =20 +config BACKLIGHT_EIO + tristate "Advantech EIO Backlight" + depends on MFD_EIO && BACKLIGHT_CLASS_DEVICE + help + Backlight driver for Advantech EIO. + config BACKLIGHT_LED tristate "Generic LED based Backlight Driver" depends on LEDS_CLASS && OF diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Mak= efile index 34469711c6cd..724246242526 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_BACKLIGHT_CGBC) +=3D cgbc_bl.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) +=3D backlight.o obj-$(CONFIG_BACKLIGHT_DA903X) +=3D da903x_bl.o obj-$(CONFIG_BACKLIGHT_DA9052) +=3D da9052_bl.o +obj-$(CONFIG_BACKLIGHT_EIO) +=3D eio_bl.o obj-$(CONFIG_BACKLIGHT_EP93XX) +=3D ep93xx_bl.o obj-$(CONFIG_BACKLIGHT_GPIO) +=3D gpio_backlight.o obj-$(CONFIG_BACKLIGHT_HP680) +=3D hp680_bl.o diff --git a/drivers/video/backlight/eio_bl.c b/drivers/video/backlight/eio= _bl.c new file mode 100644 index 000000000000..c2826400de45 --- /dev/null +++ b/drivers/video/backlight/eio_bl.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Backlight driver for Advantech EIO Embedded controller. + * + * Copyright (C) 2025 Advantech Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define PMC_BL_WRITE 0x20 +#define PMC_BL_READ 0x21 + +#define BL_CTRL_STATUS 0x00 +#define BL_CTRL_ENABLE 0x12 +#define BL_CTRL_ENABLE_INVERT 0x13 +#define BL_CTRL_DUTY 0x14 +#define BL_CTRL_INVERT 0x15 +#define BL_CTRL_FREQ 0x16 + +#define BL_MAX 2 + +#define BL_STATUS_AVAIL 0x01 +#define BL_ENABLE_OFF 0x00 +#define BL_ENABLE_ON 0x01 +#define BL_ENABLE_AUTO BIT(1) + +#define USE_DEFAULT -1 +#define THERMAL_MAX 100 + +static uint bri_freq =3D USE_DEFAULT; +module_param(bri_freq, uint, 0444); +MODULE_PARM_DESC(bri_freq, "Setup backlight PWM frequency.\n"); + +static int bri_invert =3D USE_DEFAULT; +module_param(bri_invert, int, 0444); +MODULE_PARM_DESC(bri_invert, "Setup backlight PWM polarity.\n"); + +static int bl_power_invert =3D USE_DEFAULT; +module_param(bl_power_invert, int, 0444); +MODULE_PARM_DESC(bl_power_invert, "Setup backlight enable pin polarity.\n"= ); + +struct eio_bl_dev { + struct device *mfd; + u8 id; +}; + +static int pmc_write(struct device *mfd, u8 ctrl, u8 dev_id, void *data) +{ + struct pmc_op op =3D { + .cmd =3D PMC_BL_WRITE, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + .size =3D (ctrl =3D=3D BL_CTRL_FREQ) ? 4 : 1, + }; + + return eio_core_pmc_operation(mfd, &op); +} + +static int pmc_read(struct device *mfd, u8 ctrl, u8 dev_id, void *data) +{ + struct pmc_op op =3D { + .cmd =3D PMC_BL_READ, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + .size =3D (ctrl =3D=3D BL_CTRL_FREQ) ? 4 : 1, + }; + + return eio_core_pmc_operation(mfd, &op); +} + +static int bl_update_status(struct backlight_device *bl) +{ + struct eio_bl_dev *eio_bl =3D bl_get_data(bl); + u8 duty =3D clamp_val(backlight_get_brightness(bl), 0, THERMAL_MAX); + u8 sw =3D backlight_is_blank(bl); + int ret; + + /* Setup PWM duty */ + ret =3D pmc_write(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty); + if (ret) + return ret; + + /* Setup backlight enable pin */ + return pmc_write(eio_bl->mfd, BL_CTRL_ENABLE, eio_bl->id, &sw); +} + +static int bl_get_brightness(struct backlight_device *bl) +{ + struct eio_bl_dev *eio_bl =3D bl_get_data(bl); + u8 duty =3D 0; + int ret; + + ret =3D pmc_read(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty); + + if (ret) + return ret; + + return duty; +} + +static const struct backlight_ops bl_ops =3D { + .get_brightness =3D bl_get_brightness, + .update_status =3D bl_update_status, + .options =3D BL_CORE_SUSPENDRESUME, +}; + +static int bl_init(struct device *dev, int id, + struct backlight_properties *props) +{ + int ret; + u8 enabled =3D 0; + u8 status =3D 0; + + /* Check EC-supported backlight */ + ret =3D pmc_read(dev, BL_CTRL_STATUS, id, &status); + if (ret) + return ret; + + if (!(status & BL_STATUS_AVAIL)) { + dev_dbg(dev, "eio_bl%d hardware report disabled.\n", id); + return -ENODEV; + } + + ret =3D pmc_read(dev, BL_CTRL_DUTY, id, &props->brightness); + if (ret) + return ret; + + /* Invert PWM */ + if (bri_invert > USE_DEFAULT) { + ret =3D pmc_write(dev, BL_CTRL_INVERT, id, &bri_invert); + if (ret) + return ret; + } + + ret =3D pmc_read(dev, BL_CTRL_INVERT, id, &bri_invert); + if (ret) + return ret; + + if (bri_freq !=3D USE_DEFAULT) { + ret =3D pmc_write(dev, BL_CTRL_FREQ, id, &bri_freq); + if (ret) + return ret; + } + + ret =3D pmc_read(dev, BL_CTRL_FREQ, id, &bri_freq); + if (ret) + return ret; + + if (bl_power_invert >=3D USE_DEFAULT) { + ret =3D pmc_write(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert); + if (ret) + return ret; + } + + ret =3D pmc_read(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert); + if (ret) + return ret; + + /* Read power state */ + ret =3D pmc_read(dev, BL_CTRL_ENABLE, id, &enabled); + if (ret) + return ret; + + props->power =3D enabled ? BACKLIGHT_POWER_OFF : BACKLIGHT_POWER_ON; + + return 0; +} + +static int bl_probe(struct platform_device *pdev) +{ + u8 id; + int ret; + struct device *dev =3D &pdev->dev; + struct eio_dev *eio_dev =3D dev_get_drvdata(dev->parent); + + if (!eio_dev) { + dev_err(dev, "eio_core not present\n"); + return -ENODEV; + } + + for (id =3D 0; id < BL_MAX; id++) { + char name[32]; + struct backlight_properties props; + struct eio_bl_dev *eio_bl; + struct backlight_device *bl; + + memset(&props, 0, sizeof(props)); + props.type =3D BACKLIGHT_RAW; + props.max_brightness =3D THERMAL_MAX; + props.power =3D BACKLIGHT_POWER_OFF; + props.brightness =3D props.max_brightness; + props.scale =3D BACKLIGHT_SCALE_NON_LINEAR; + + eio_bl =3D devm_kzalloc(dev, sizeof(*eio_bl), GFP_KERNEL); + if (!eio_bl) + return -ENOMEM; + + eio_bl->mfd =3D dev->parent; + eio_bl->id =3D id; + + ret =3D bl_init(eio_bl->mfd, id, &props); + if (ret) { + dev_info(dev, "%d No Backlight %u enabled!\n", ret, id); + continue; + } + + snprintf(name, sizeof(name), "%s%u", pdev->name, id); + + bl =3D devm_backlight_device_register(dev, name, dev, eio_bl, + &bl_ops, &props); + + if (IS_ERR(bl)) { + ret =3D PTR_ERR(bl); + if (ret =3D=3D -EPROBE_DEFER) + return ret; + + dev_err(dev, "register %s failed: %d\n", name, ret); + continue; + } + } + return ret; +} + +static struct platform_driver bl_driver =3D { + .probe =3D bl_probe, + .driver =3D { + .name =3D "eio_bl", + }, +}; + +module_platform_driver(bl_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Backlight driver for Advantech EIO embedded controller= "); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from SEYPR02CU001.outbound.protection.outlook.com (mail-koreacentralazon11023082.outbound.protection.outlook.com [40.107.44.82]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 33B734963D9; Tue, 14 Jul 2026 15:55:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.44.82 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044531; cv=fail; b=mlJsQp5yCm/dgxKL5W/abkrcdvrgexLJefHDHlDIWWvyTFC6m2OJXlo6T23nXaABZHUne85e7xBmX1HgYIlxVTVOM1+yTrJlimbJ7Ats/CUzhVomgJrt3xHyEd2kavkQGmjTzePHBDFziL713EEVNMjX08jUhs3WMcWFsRAMImo= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044531; c=relaxed/simple; bh=x3e7kqj2bst2UoKPib83ZPbUl4OUxQJZ3xCLzVd1xmE=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=snP2l2ncPzyJZIIsJUAAc6wQdf5ZQQb2aoDZmK/05kyvN98ISes6hTYL1QzZsDfyZ7NbaEQ9kCbMVXC86tUSDfVWd79brIcK6kvCyMTazMqLSV5ynYcE0t2Bo0wS20GBvJKy/Wda6rSnHdgi7FUGahjpyeAAl+nx61Mv/+rrJaQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=YpHrM9dG; arc=fail smtp.client-ip=40.107.44.82 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="YpHrM9dG" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=FfgWtKqgmzKESlzgyVNaf5YLgYVQU6gjljlkBpSCWhTezA437gwO47F2aEE/qov48bltXwaHjPAObdqryC0nmsz9rz/Kj1DSJ1qqx/8lf8mLjiXUTR9aIS2VLoBWzjKQkX/LrYDGDGtPtERAKZMbGFE9smwkbfZ7MUi+ELDAOxVN0HSQXu9Zy/41pn/8GoEEm468yNF+Kp/KHXKSQzqvfnQgb/cFRedPrTfcvN9t4cd4+3I4luioG+FLczR9SW011KVJjllT0AvjWgvKY85HAEn30GFdWek6z6NgBk1bVdQZa9KxNb1TWGqaUxs0+DwrmXbqdCM8BScvoPB8IuelVg== 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=yhTCLPVF1YQbZpEE0GoOdYZUCVe8GMwCq6lBLPyRr3s=; b=lb13d06Z8P2ulrflcpvrpvF0STGeL3fZUpBiGajxRU/zGnwfe5J6Egy7hqUIalzVEr6sLs4yEAm8DONtD/UvxhmAHg/BgAbyHgR7WBgSvQ4FqwbfHScfyKLnR7Dmwc0BObfVg4jmv2JTj3vKFeOpCXpvyUfvjXzXF7D+p/GSIqVjX79ttexgs6USQXk/oPtt+Jp7fsKMX/h4cedv4T9tddnmbzlcg2IgCOaNbzuckaRKZsTyDMxda3wneKygJNkw0I5sx/IBjUV88+/U0m7skqGcBYpifkMlnBLaGrVQ+jC1DdidPYBklzIzoMcLOrW35apCIUPAvintshSjfok6yQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=yhTCLPVF1YQbZpEE0GoOdYZUCVe8GMwCq6lBLPyRr3s=; b=YpHrM9dGbz48cqw/isJfEfEFJoutmFLyysQE8EQud30W4J+rWL3Nmsr1OWtlm7sNkFDmThgxKX0YTEQoaVGCCJqnZ+lIkEck7T7rEb5fLlvNa4ydzyaalGF35GUyFI/LV75HSanfBC70o61lhUcxqArVrVUbJkBBhiy6NdxxQzRC1Er1oKWi9XcWBI+SGi04Wxk3OSEcRSq8lRlsNxWLvhlYVvoJTGM1fDi+Ed6ubzOYJBLNrGaykRKL3SHUp9JHNy6Y/2pNfniHOllq/xv7uUMsAnPlEvpkfbH0RFNRmMoaJQCGqvKHHDucEiE5PZbcowcZF9RJjTLv6Dw91Zs/Rw== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by JH0PR02MB6851.apcprd02.prod.outlook.com (2603:1096:990:4d::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.18; Tue, 14 Jul 2026 15:55:24 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:23 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:20 +0200 Subject: [PATCH v2 6/8] Add Advantech EIO Watchdog driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-6-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=17129; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=x3e7kqj2bst2UoKPib83ZPbUl4OUxQJZ3xCLzVd1xmE=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu4pBwZdrsLLbYQJfkvWXvpkRBYwGKQ+wm+v BKJ9Hkt2HeJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuAAKCRDO7dCnvL2w CSkkC/42Vbvt2pbEwf0QLAseE7aEycQAJ5n/V4lxuY1kwCo/tNCiqOUxnppnbGyFTqBUcWtblr3 o5kw52pMrNbZdnVJEjGFtbCJp4NUcYrd0EH3CN/Ofhknfnl+ZOhWlxP4XF7Ei0IvzENA5ogMtEt t1ZDxmgGxv5dkqqN+wSIaC7pF/vh4rbGodCFYA3v0ziWDyohzjhX/SYrlmlh7nbLfernVqL7w4q saUk8FuaBZcW9EnEjbMmnBiTZQdNAtj0mtGWfGTcAG0NyrwQQntT5a+njHy+PX9I7ZWY/67/N/E zWlenAPJp6vr9Th3bNYmKjN4TGzqNcMgw2X7QO6o2Mp9CkyLTGMeVRjrgnOvI1xf9BHlF0oHeQd dJ2YUrcMAUxoVYWy5R8CV2CHaDon8i2uc6EBLWWlsSTCh+DZSi2aApvNn3rQ1JaHWlLNOd1wch8 5Xwq5mnKgtCb4whCOIMjjCzpHBceK/EAjXbq50c78fhJgVjWWSYo4AtIUvAZUjFCZ5BR4= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|JH0PR02MB6851:EE_ X-MS-Office365-Filtering-Correlation-Id: 51e44ada-22b0-41d2-8ccb-08dee1c050bc X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|52116014|7416014|23010399003|376014|366016|1800799024|38350700014|921020|6133799003|56012099006|11063799006|55112099003|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: l5gmk82cKdxLa/ou6JNA36J12wlJmlQQnaCoHIyFsT0DVA2j7MdWdidawO03x4IlTH4vz58I2Vtuh9AGYHFgkagz7jnO8ddQXNnumRSsoVkEM188cIVDEOIHcbx6eGOLbHmDDf2R4B2xWRMlKvbnyzoROWslizbN3KNkZy3hbDWLzWylaINyGvUE7lYhuCmhy0x2AjWDufQhwJ7MPXtcrOUrTvtuOprcwhvVH4FeXqLcU+bA6+ydiWam5JcBAz1aQcUcNlunnM/eWNDtzAevAoiJkixh7p+SVbD8WchCiOVV6jZyuJFVY/FOD++QyOX7BSqG3vCBYekMr7Lbq+MwoUsl1cMiqF9usRWauVwXRzBFC1sE8TRDgWTMSytmthEpxcNF1NQkp9vni0GhwEjOEh3DB3IAtdSXGypoaMBr+axgv35fqKwWBVXqGHInzC+rtXrMNoapc4rx/mAH4GTtZXsN6rWLRPOpG0IO5qLKrC7p0Lecqrj3To/OeMzA02nTVWIRe/oxqkRKzJkt+FjpbAgSnOrTyAITP4dRq3bnXsJ02xrs3wffU+JhU6bH3aHqYN7b7vWtn9FB+IU4vJT0P2wqS7OgPU5OiL9gqDLjCMN+onjFcWXESdV+fJgEfSZ8JETxdF+pIFTupd7EoZNozxIySIhFLEmcOSNl8jgoCecoLkobg2oBRTfRhKkOnBsvdQCwzrei/rKYyDcQXz96qdAdKOghboBTlElUYiIARWs= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(52116014)(7416014)(23010399003)(376014)(366016)(1800799024)(38350700014)(921020)(6133799003)(56012099006)(11063799006)(55112099003)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?cHlTN3NGMWlzdnMzT3E4ZjBIbldFUDhvc1Y5bE5lRngxWWJVaTlSVU9oOUh4?= =?utf-8?B?QXRVRkNwSEZRQW9YNWFrVS9za3VKQjkrS1BVdnRPdDN1ajYvbnZBbG5JK0dJ?= =?utf-8?B?MVhiUHdnbnJDc1VZVW1mNVJSekZBd0NtcFVHcVQzak5xeXlMTjBZU2xOd1RZ?= =?utf-8?B?UUY0QnhaUHFYaTF6TEt5TStVNFJRMnhqaDVzSVllOElXQ1EzbG9RWGhueHNO?= =?utf-8?B?K3B0SEJFTjI5UlBTU3RBUTBmeDRsNzRkT1JDOEl1MnlBM0hmSm90dnU2eGx4?= =?utf-8?B?aVRxQ24xMWtDODBFRDJoTW4vTjU1djkxVk1qS1BaaFhtRnAwZ3M5NXpDMzk2?= =?utf-8?B?MHhiTzVkZ1dRYk94STlVcXNORjduTXJFeUo5dVRpT004NGNqRWpDaHdRWk9S?= =?utf-8?B?UFVMQnZGTXNPS09QMlY0VnQydWIxZlNHOENrUkptamtzVTlJZXhtd3R4OVl1?= =?utf-8?B?b2Nsc0Q0Tm9uNFVIZnZMY2pnS0w1ZkN0NnVOcDNJSVJJdVMxWEYwRUN0dE92?= =?utf-8?B?aGx2YlZEcExtZzFMWkhMTDYrZDNSdkpJOWQ3TllvTTJYSFMwZEQ3WTR3VDc1?= =?utf-8?B?T2VITEdGdlgrSWJWZnNKL1RHREM4czdMbkc4a2NNOEt6YytlQmowcVVuWVFK?= =?utf-8?B?bmlKV2llODZ6U2dHQjFuQkpUK3FVYU9BRDBBQUYwdVA5bnJZSHFiU0dKM2lB?= =?utf-8?B?R2VpOHY3NFY2dWJJT2JTUVhidlBXTDB5eHhiNUUvbm1qalZBdGhNRUxhUHcr?= =?utf-8?B?MGxVTitJV0xvQkdNQlBmRGhzdHkrYkVXc05NbUcxMlRJY2txdi9pKytWMElT?= =?utf-8?B?cTRrcEczZTlUajkvODlRQXd1eEx4MkJJZGlxeDVtSzIzb3lpMXBQcUZXYmY2?= =?utf-8?B?SDQzcG5RaXdNS00zcFViTlpkTk5TNmc1Njc5Y0dPMGppUlg2SHhLZ2ZhNlNn?= =?utf-8?B?Q21SancvbTBGeVk0VXlzQ3o2c25BdnIvd0dZUlJ2a2JNNU1nS0ZlSnJYQWtI?= =?utf-8?B?YjFMdGQ3TnBQeDFnRkRMUWgwenV0MmZYdjZSTXA3Z0xmcTduWEtndE5NYk90?= =?utf-8?B?RnNjZzVHWk9jYmZjc3VrZWJZTCtidU4wdDVOOWZ0WDB3d0VaK2t6WGtqcEo0?= =?utf-8?B?MHRLODUwVlNidVV3MVRsRER0enNTOTUvMDhtcXdkdDd1b1ZpTWthNHlLcFFI?= =?utf-8?B?TDdGYVdhaHdYWWErNjZaaHI1czB3cDJJamlIUnZwNE5vRUNlU051QWFvWldx?= =?utf-8?B?WHQvZFdleDVvNmNGQlpEYU13dWhvWFhqUVIveThiejV5ME0wQ1NWazRwaTdV?= =?utf-8?B?aG95Q2l3NE05YTJINnI2VXZjeDYyZ1NURGVKNXd4NmhxTnNsL1lIOVdMajR6?= =?utf-8?B?RXp2cUVySlBsSDdCUUVTZEs1bXlNZVhTeHcyUHJHb2x6eGxpdS9mTFlveXFl?= =?utf-8?B?MkhZeDYySklSTjloQWQvbnc3NkpKWWhsUmNoUThDak85Y1hraUtET1lYWkRh?= =?utf-8?B?bHRKTWUxOUhJT0VGTDBtZFBsbEFzazNNVjMzU3hxU2JyYlZ0ZWxnaE5qOUtn?= =?utf-8?B?Z3AwaGYyem95eEVTRTZ2S09keUJ4UEVUODZ4Wml0WnQvaXBwZEIwMjF6RG9v?= =?utf-8?B?bzRZa3ZQdDdUZUF2NFQyQnZBZWQvbllSZUhSWUJCNkZYZE8xRVlJVTEwOTlG?= =?utf-8?B?SVd4eUZrdXpVc1plMkNTVW9MSVhpZGxUMXRoUU03L2JGR05YaStRbnorZ3lz?= =?utf-8?B?aVY4dGdremFBMURVWHJEeGhGemhiK1phb2dKT05HOHh1OE5xMDRXbTNEa01J?= =?utf-8?B?clIreGlPTTY4NTZ2dGV6b1MzV1hGQVhJQkFzWUlGSlZLWjkyOTRuMStrZTZ3?= =?utf-8?B?RWNRM0xRSS9kekNkd1V2RHFxeDVaVHZaWG9lM2lkZTdyOStTTDRmNEpRb2hJ?= =?utf-8?B?T1RVV3RvU3JYQ3crSGtNcGxUeXhjM1NmR1VsVzNvTjN5SmZBQVJvV3F1T1M4?= =?utf-8?B?bGt3ZEk5OHl2c3FreTJPZThONWpZSlI3cW1DS05IZHZWczlYUUpkbUNYNXdX?= =?utf-8?B?SnNtRFZXUXZRZ1I1cU9vMVJqVXpxMjN3T3B2SXZza1pPaW02NlNJUittTFJv?= =?utf-8?B?dEpIaUtnYW1Yc0JId214RVhhc0xzMXhvd2p3T0toV0x2dE1SdW1wSGVyeWFz?= =?utf-8?B?bmFZTXBsaTMwWTVzNmFEQW1FZDBqS1VwSjlLN0dCbytZU2JBZ1V6UUxXWWgy?= =?utf-8?B?YVNxMGhKdTNZL0ZsZlZ2SkRTV3c3UUsyQ2wvWXJCdHgrc29QUmQzN3JLNUJr?= =?utf-8?B?VnE5SlN6QS9FRkZqemVGQjlWMjRqc1d6cWtyWHVlNnFNYUZEOVI0MVNyRHA4?= =?utf-8?Q?Kyk6K6jf13JfUcc4=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 51e44ada-22b0-41d2-8ccb-08dee1c050bc X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:23.9166 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: pLtnVWyEXE32LKQm8jdoHUkVCPcizk0QrstmGr+f8Cy5Cw9GfSEXNSY1quB5muaaAnoh/dPZpnk1lwhNIgL4JCCFjPveIswWYQ1aBQQLG88= X-MS-Exchange-Transport-CrossTenantHeadersStamped: JH0PR02MB6851 This commit adds the driver to control the Advantech EIO Watchdog block, this block is included in the Advantech EIO Embedded Controller. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/watchdog/Kconfig | 7 + drivers/watchdog/Makefile | 1 + drivers/watchdog/eio_wdt.c | 641 +++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 650 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d2d7d815ed44..8d35fd7c5599 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -617,6 +617,7 @@ F: drivers/hwmon/eio-hwmon.c F: drivers/i2c/busses/i2c-eio.c F: drivers/mfd/eio_core.c F: drivers/video/backlight/eio_bl.c +F: drivers/watchdog/eio_wdt.c F: include/linux/mfd/eio.h =20 ADVANTECH SWBTN DRIVER diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 9f013d774897..bc0f13f4f79c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -265,6 +265,13 @@ config DA9062_WATCHDOG =20 This driver can be built as a module. The module name is da9062_wdt. =20 +config EIO_WATCHDOG + tristate "Advantech EIO Watchdog" + depends on MFD_EIO + help + Watchdog timer driver for the Advantech EIO. + If unsure, say N. + config GPIO_WATCHDOG tristate "Watchdog device controlled through GPIO-line" depends on GPIOLIB && (ACPI || OF) diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index cb78e9932eae..eee059cfe1da 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -229,6 +229,7 @@ obj-$(CONFIG_DA9052_WATCHDOG) +=3D da9052_wdt.o obj-$(CONFIG_DA9055_WATCHDOG) +=3D da9055_wdt.o obj-$(CONFIG_DA9062_WATCHDOG) +=3D da9062_wdt.o obj-$(CONFIG_DA9063_WATCHDOG) +=3D da9063_wdt.o +obj-$(CONFIG_EIO_WATCHDOG) +=3D eio_wdt.o obj-$(CONFIG_GPIO_WATCHDOG) +=3D gpio_wdt.o obj-$(CONFIG_WDAT_WDT) +=3D wdat_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) +=3D wm831x_wdt.o diff --git a/drivers/watchdog/eio_wdt.c b/drivers/watchdog/eio_wdt.c new file mode 100644 index 000000000000..b3c036a004b3 --- /dev/null +++ b/drivers/watchdog/eio_wdt.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Advantech EIO Watchdog Driver + * + * Copyright (C) 2025 Advantech Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define WATCHDOG_TIMEOUT 60 +#define WATCHDOG_PRETIMEOUT 10 + +/* Support Flags */ +#define SUPPORT_AVAILABLE BIT(0) +#define SUPPORT_PWRBTN BIT(3) +#define SUPPORT_IRQ BIT(4) +#define SUPPORT_SCI BIT(5) +#define SUPPORT_PIN BIT(6) +#define SUPPORT_RESET BIT(7) + +/* PMC registers */ +#define REG_STATUS 0x00 +#define REG_CONTROL 0x02 +#define REG_EVENT 0x10 +#define REG_PWR_EVENT_TIME 0x12 +#define REG_IRQ_EVENT_TIME 0x13 +#define REG_RESET_EVENT_TIME 0x14 +#define REG_PIN_EVENT_TIME 0x15 +#define REG_SCI_EVENT_TIME 0x16 +#define REG_IRQ_NUMBER 0x17 + +/* PMC command and control */ +#define CMD_WDT_WRITE 0x2A +#define CMD_WDT_READ 0x2B +#define CTRL_STOP 0x00 +#define CTRL_START 0x01 +#define CTRL_TRIGGER 0x02 + +/* I/O register and its flags */ +#define IOREG_UNLOCK 0x87 +#define IOREG_LOCK 0xAA +#define IOREG_LDN 0x07 +#define IOREG_LDN_PMCIO 0x0F +#define IOREG_IRQ 0x70 +#define IOREG_WDT_STATUS 0x30 + +/* Flags */ +#define FLAG_WDT_ENABLED 0x01 +#define FLAG_TRIGGER_IRQ BIT(4) + +/* Mapping event type to supported bit */ +#define EVENT_BIT(type) BIT(type + 2) + +enum event_type { + EVENT_NONE, + EVENT_PWRBTN, + EVENT_IRQ, + EVENT_SCI, + EVENT_PIN +}; + +struct eio_wdt_dev { + u32 event_type; + u32 support; + int irq; + unsigned long last_time; + struct regmap *iomap; + struct device *mfd; + struct device *dev; + struct watchdog_device wdd; + struct eio_dev *core; +}; + +static char * const type_strs[] =3D { + "NONE", + "PWRBTN", + "IRQ", + "SCI", + "PIN", +}; + +static u32 type_regs[] =3D { + REG_RESET_EVENT_TIME, + REG_PWR_EVENT_TIME, + REG_IRQ_EVENT_TIME, + REG_SCI_EVENT_TIME, + REG_PIN_EVENT_TIME, +}; + +/* Specify the pin triggered on pretimeout or timeout */ +static char *event_type =3D "NONE"; +module_param(event_type, charp, 0); +MODULE_PARM_DESC(event_type, "Watchdog timeout event type (NONE, PWRBTN, I= RQ, SCI, PIN)"); + +static int pmc_write(struct device *dev, u8 ctrl, void *data) +{ + struct pmc_op op =3D { + .cmd =3D CMD_WDT_WRITE, + .control =3D ctrl, + .payload =3D data, + .size =3D (ctrl <=3D REG_EVENT) ? 1 : + (ctrl >=3D REG_IRQ_NUMBER) ? 1 : 4, + }; + return eio_core_pmc_operation(dev, &op); +} + +static int pmc_read(struct device *dev, u8 ctrl, void *data) +{ + struct pmc_op op =3D { + .cmd =3D CMD_WDT_READ, + .control =3D ctrl, + .payload =3D data, + .size =3D (ctrl <=3D REG_EVENT) ? 1 : + (ctrl >=3D REG_IRQ_NUMBER) ? 1 : 4, + }; + return eio_core_pmc_operation(dev, &op); +} + +static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeo= ut) +{ + wdd->timeout =3D timeout; + return 0; +} + +static int wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pr= etimeout) +{ + wdd->pretimeout =3D pretimeout; + return 0; +} + +static int wdt_get_type(struct eio_wdt_dev *eio_wdt) +{ + int i; + + for (i =3D 1; i < ARRAY_SIZE(type_strs); i++) { + if (strcasecmp(event_type, type_strs[i]) =3D=3D 0) { + if ((eio_wdt->support & EVENT_BIT(i)) =3D=3D 0) { + dev_err(eio_wdt->dev, + "This board doesn't support %s trigger type\n", + event_type); + return -EINVAL; + } + eio_wdt->event_type =3D i; + return 0; + } + } + return 0; +} + +static int get_time(struct eio_wdt_dev *eio_wdt, u8 ctrl, u32 *val) +{ + int ret; + + ret =3D pmc_read(eio_wdt->mfd, ctrl, val); + if (ret) + return ret; + + /* ms to sec */ + *val /=3D 1000; + + return 0; +} + +static int set_time(struct eio_wdt_dev *eio_wdt, u8 ctrl, u32 time) +{ + /* sec to ms */ + time *=3D 1000; + + return pmc_write(eio_wdt->mfd, ctrl, &time); +} + +static int wdt_set_config(struct eio_wdt_dev *eio_wdt) +{ + int ret, type; + u32 event_time =3D 0; + u32 reset_time =3D 0; + + if (eio_wdt->event_type > EVENT_PIN) + return -EFAULT; + + /* Calculate event time and reset time */ + if (eio_wdt->wdd.pretimeout && eio_wdt->wdd.timeout) { + if (eio_wdt->wdd.timeout < eio_wdt->wdd.pretimeout) + return -EINVAL; + + reset_time =3D eio_wdt->wdd.timeout; + event_time =3D eio_wdt->wdd.timeout - eio_wdt->wdd.pretimeout; + + } else if (eio_wdt->wdd.timeout) { + reset_time =3D eio_wdt->event_type ? 0 : eio_wdt->wdd.timeout; + event_time =3D eio_wdt->event_type ? eio_wdt->wdd.timeout : 0; + } + + /* Set reset time */ + ret =3D set_time(eio_wdt, REG_RESET_EVENT_TIME, reset_time); + if (ret) + return ret; + + /* Set every other times */ + for (type =3D 1; type < ARRAY_SIZE(type_regs); type++) { + ret =3D set_time(eio_wdt, type_regs[type], + (eio_wdt->event_type =3D=3D type) ? event_time : 0); + if (ret) + return ret; + } + + dev_dbg(eio_wdt->dev, "Config wdt reset time %u\n", reset_time); + dev_dbg(eio_wdt->dev, "Config wdt event time %u\n", event_time); + dev_dbg(eio_wdt->dev, "Config wdt event type %s\n", + type_strs[eio_wdt->event_type]); + + return 0; +} + +static int wdt_get_config(struct eio_wdt_dev *eio_wdt) +{ + int ret, type; + u32 event_time =3D 0, reset_time =3D 0; + + /* Get Reset Time */ + ret =3D get_time(eio_wdt, REG_RESET_EVENT_TIME, &reset_time); + if (ret) + return ret; + + dev_dbg(eio_wdt->dev, "Timeout H/W default timeout: %u secs\n", reset_tim= e); + + /* Get every other times */ + for (type =3D 1; type < ARRAY_SIZE(type_regs); type++) { + if ((eio_wdt->support & EVENT_BIT(type)) =3D=3D 0) + continue; + + ret =3D get_time(eio_wdt, type_regs[type], &event_time); + if (ret) + return ret; + + if (event_time =3D=3D 0) + continue; + + if (reset_time) { + if (reset_time < event_time) + continue; + + eio_wdt->wdd.timeout =3D reset_time; + eio_wdt->wdd.pretimeout =3D reset_time - event_time; + + dev_dbg(eio_wdt->dev, + "Pretimeout H/W enabled with event %s of %u secs\n", + type_strs[type], eio_wdt->wdd.pretimeout); + } else { + eio_wdt->wdd.timeout =3D event_time; + eio_wdt->wdd.pretimeout =3D 0; + } + + eio_wdt->event_type =3D type; + + dev_dbg(eio_wdt->dev, "Timeout H/W enabled of %u secs\n", + eio_wdt->wdd.timeout); + return 0; + } + + eio_wdt->event_type =3D EVENT_NONE; + eio_wdt->wdd.pretimeout =3D reset_time ? 0 : WATCHDOG_PRETIMEOUT; + eio_wdt->wdd.timeout =3D reset_time ? reset_time : WATCHDOG_TIMEOU= T; + + return 0; +} + +static int set_ctrl(struct eio_wdt_dev *eio_wdt, u8 ctrl) +{ + return pmc_write(eio_wdt->mfd, REG_CONTROL, &ctrl); +} + +static int wdt_start(struct watchdog_device *wdd) +{ + struct eio_wdt_dev *eio_wdt =3D watchdog_get_drvdata(wdd); + int ret; + + ret =3D wdt_set_config(eio_wdt); + if (ret) + return ret; + + ret =3D set_ctrl(eio_wdt, CTRL_START); + if (!ret) { + eio_wdt->last_time =3D jiffies; + dev_dbg(eio_wdt->dev, "Watchdog started\n"); + } + + return ret; +} + +static int wdt_stop(struct watchdog_device *wdd) +{ + struct eio_wdt_dev *eio_wdt =3D watchdog_get_drvdata(wdd); + int ret; + + dev_dbg(eio_wdt->dev, "Watchdog stopped\n"); + eio_wdt->last_time =3D 0; + + ret =3D set_ctrl(eio_wdt, CTRL_STOP); + return ret; +} + +static int wdt_ping(struct watchdog_device *wdd) +{ + struct eio_wdt_dev *eio_wdt =3D watchdog_get_drvdata(wdd); + int ret; + + dev_dbg(eio_wdt->dev, "Watchdog ping\n"); + + ret =3D set_ctrl(eio_wdt, CTRL_TRIGGER); + if (!ret) + eio_wdt->last_time =3D jiffies; + + return ret; +} + +static int wdt_support(struct eio_wdt_dev *eio_wdt) +{ + u8 support; + + if (pmc_read(eio_wdt->mfd, REG_STATUS, &support)) + return -EIO; + + if (!(support & SUPPORT_AVAILABLE)) + return -ENODEV; + + if ((support & SUPPORT_RESET) !=3D SUPPORT_RESET) + return -ENODEV; + + eio_wdt->support =3D support; + + return 0; +} + +static int wdt_get_irq_io(struct eio_wdt_dev *eio_wdt) +{ + int ret =3D 0; + int idx =3D EIO_PNP_INDEX; + int data =3D EIO_PNP_DATA; + struct regmap *map =3D eio_wdt->iomap; + + mutex_lock(&eio_wdt->core->mutex); + + /* Unlock EC IO port */ + ret |=3D regmap_write(map, idx, IOREG_UNLOCK); + ret |=3D regmap_write(map, idx, IOREG_UNLOCK); + + /* Select logical device to PMC */ + ret |=3D regmap_write(map, idx, IOREG_LDN); + ret |=3D regmap_write(map, data, IOREG_LDN_PMCIO); + + /* Get IRQ number */ + ret |=3D regmap_write(map, idx, IOREG_IRQ); + ret |=3D regmap_read(map, data, &eio_wdt->irq); + + /* Lock back */ + ret |=3D regmap_write(map, idx, IOREG_LOCK); + + mutex_unlock(&eio_wdt->core->mutex); + + return ret ? -EIO : 0; +} + +static int wdt_get_irq_pmc(struct eio_wdt_dev *eio_wdt) +{ + return pmc_read(eio_wdt->mfd, REG_IRQ_NUMBER, &eio_wdt->irq); +} + +static int wdt_get_irq(struct eio_wdt_dev *eio_wdt) +{ + int ret; + + if (!(eio_wdt->support & BIT(EVENT_IRQ))) + return -ENODEV; + + ret =3D wdt_get_irq_pmc(eio_wdt); + if (ret) { + dev_err(eio_wdt->dev, "Error get irq by pmc\n"); + return ret; + } + + if (eio_wdt->irq) + return 0; + + /* Fallback: get IRQ number from EC IO space */ + ret =3D wdt_get_irq_io(eio_wdt); + if (ret) { + dev_err(eio_wdt->dev, "Error get irq by io\n"); + return ret; + } + + if (!eio_wdt->irq) { + dev_err(eio_wdt->dev, "Error IRQ number =3D 0\n"); + return -EIO; + } + + return 0; +} + +static int wdt_set_irq_io(struct eio_wdt_dev *eio_wdt) +{ + int ret =3D 0; + int idx =3D EIO_PNP_INDEX; + int data =3D EIO_PNP_DATA; + struct regmap *map =3D eio_wdt->iomap; + + mutex_lock(&eio_wdt->core->mutex); + + /* Unlock EC IO port */ + ret =3D regmap_write(map, idx, IOREG_UNLOCK); + if (ret) + goto unlock; + ret =3D regmap_write(map, idx, IOREG_UNLOCK); + if (ret) + goto unlock; + + /* Select logical device to PMC */ + ret =3D regmap_write(map, idx, IOREG_LDN); + if (ret) + goto unlock; + ret =3D regmap_write(map, data, IOREG_LDN_PMCIO); + if (ret) + goto unlock; + + /* Enable WDT */ + ret =3D regmap_write(map, idx, IOREG_WDT_STATUS); + if (ret) + goto unlock; + ret =3D regmap_write(map, data, FLAG_WDT_ENABLED); + if (ret) + goto unlock; + + /* Set IRQ number */ + ret =3D regmap_write(map, idx, IOREG_IRQ); + if (ret) + goto unlock; + ret =3D regmap_write(map, data, eio_wdt->irq); + if (ret) + goto unlock; + + /* Lock back */ + ret =3D regmap_write(map, idx, IOREG_LOCK); + +unlock: + mutex_unlock(&eio_wdt->core->mutex); + return ret; +} + +static int wdt_set_irq_pmc(struct eio_wdt_dev *eio_wdt) +{ + return pmc_write(eio_wdt->mfd, REG_IRQ_NUMBER, &eio_wdt->irq); +} + +static int wdt_set_irq(struct eio_wdt_dev *eio_wdt) +{ + int ret; + + if (!(eio_wdt->support & BIT(EVENT_IRQ))) + return -ENODEV; + + ret =3D wdt_set_irq_io(eio_wdt); + if (ret) { + dev_err(eio_wdt->dev, "Error set irq by io\n"); + return ret; + } + + ret =3D wdt_set_irq_pmc(eio_wdt); + if (ret) { + dev_err(eio_wdt->dev, "Error set irq by pmc\n"); + return ret; + } + + return 0; +} + +static int wdt_get_irq_event(struct eio_wdt_dev *eio_wdt) +{ + u8 status; + + if (pmc_read(eio_wdt->mfd, REG_EVENT, &status)) + return 0; + + return status; +} + +static irqreturn_t wdt_isr(int irq, void *arg) +{ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t wdt_threaded_isr(int irq, void *arg) +{ + struct eio_wdt_dev *eio_wdt =3D arg; + u8 status =3D wdt_get_irq_event(eio_wdt) & FLAG_TRIGGER_IRQ; + + if (!status) + return IRQ_NONE; + + if (eio_wdt->wdd.pretimeout) { + watchdog_notify_pretimeout(&eio_wdt->wdd); + } else { + dev_crit(eio_wdt->dev, "Watchdog expired, rebooting\n"); + emergency_restart(); + } + + return IRQ_HANDLED; +} + +static int query_irq(struct eio_wdt_dev *eio_wdt) +{ + int ret =3D 0; + + ret =3D wdt_get_irq(eio_wdt); + if (ret) + return ret; + + dev_dbg(eio_wdt->dev, "IRQ =3D %d\n", eio_wdt->irq); + + return wdt_set_irq(eio_wdt); +} + +static int wdt_init(struct eio_wdt_dev *eio_wdt) +{ + int ret; + + ret =3D wdt_support(eio_wdt); + if (ret) + return ret; + + ret =3D wdt_get_config(eio_wdt); + if (ret) + return ret; + + ret =3D wdt_get_type(eio_wdt); + if (ret) + return ret; + + if (eio_wdt->event_type =3D=3D EVENT_IRQ) + ret =3D query_irq(eio_wdt); + + return ret; +} + +static const struct watchdog_ops wdt_ops =3D { + .owner =3D THIS_MODULE, + .start =3D wdt_start, + .stop =3D wdt_stop, + .ping =3D wdt_ping, + .set_timeout =3D wdt_set_timeout, + .set_pretimeout =3D wdt_set_pretimeout, +}; + +static struct watchdog_info wdinfo =3D { + .identity =3D KBUILD_MODNAME, + .options =3D WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE, +}; + +static int eio_wdt_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct eio_wdt_dev *eio_wdt; + struct watchdog_device *wdd; + int ret =3D 0; + + eio_wdt =3D devm_kzalloc(dev, sizeof(*eio_wdt), GFP_KERNEL); + if (!eio_wdt) + return -ENOMEM; + + eio_wdt->dev =3D dev; + eio_wdt->mfd =3D dev->parent; + eio_wdt->iomap =3D dev_get_regmap(dev->parent, NULL); + if (!eio_wdt->iomap) + return dev_err_probe(dev, -ENODEV, "parent regmap missing\n"); + + eio_wdt->core =3D dev_get_drvdata(dev->parent); + if (!eio_wdt->core) + return dev_err_probe(dev, -ENODEV, "eio_core not present\n"); + + ret =3D wdt_init(eio_wdt); + if (ret) { + dev_err(dev, "wdt_init fail\n"); + return -EIO; + } + + if (eio_wdt->event_type =3D=3D EVENT_IRQ) { + ret =3D devm_request_threaded_irq(dev, eio_wdt->irq, + wdt_isr, wdt_threaded_isr, + IRQF_SHARED | IRQF_ONESHOT, pdev->name, + eio_wdt); + if (ret) { + dev_err_probe(dev, ret, "IRQ %u request fail: Disabled.\n", + eio_wdt->irq); + return ret; + } + } + + wdd =3D &eio_wdt->wdd; + wdd->info =3D &wdinfo; + wdd->ops =3D &wdt_ops; + wdd->parent =3D dev; + wdd->min_timeout =3D 1; + wdd->max_timeout =3D 0x7FFF; + + if (ret) { + dev_err(dev, "Init timeout fail\n"); + return ret; + } + + watchdog_stop_on_reboot(&eio_wdt->wdd); + watchdog_stop_on_unregister(&eio_wdt->wdd); + + watchdog_set_drvdata(&eio_wdt->wdd, eio_wdt); + platform_set_drvdata(pdev, eio_wdt); + + ret =3D devm_watchdog_register_device(dev, &eio_wdt->wdd); + if (ret) + dev_err(dev, "Cannot register watchdog device (err: %d)\n", ret); + + return ret; +} + +static struct platform_driver eio_wdt_driver =3D { + .probe =3D eio_wdt_probe, + .driver =3D { + .name =3D "eio_wdt", + }, +}; +module_platform_driver(eio_wdt_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Watchdog interface for Advantech EIO embedded controll= er"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from TYDPR03CU002.outbound.protection.outlook.com (mail-japaneastazon11023091.outbound.protection.outlook.com [52.101.127.91]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D52ED4968F1; Tue, 14 Jul 2026 15:55:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.127.91 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044536; cv=fail; b=UJ9YS5jWqziSrxinN6yS+dZNZziIi7afZLowJ/VPRixGa9Wkrd76hMXcH1xUtPwOIHiqCDtjuD3pMDeAzwpOl5WDgrVu/wk3KyZ7mam1r8a4UsYqZt9Vfsc3TvrRCcq6q5FnrI8x4u/13zAO16Z0o4lBIhCIU+iq2Kkx9BFTyns= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044536; c=relaxed/simple; bh=E4EmkEsZE4kTq05Eku5hg4KYVeZg9aGS6i3QVJguwOs=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=fXrRq6y4lJti5Zn/PLz7SW0PeoUvMQmorwB2yKkKnZNaj+LoR+SoYYCTupLKSsw3THcXHtfT2AFYqVQF91oQLrl9IROojM2XKA+/9syj5WJI2bbwLoNIEnVsAL6Brg5ZTeeyfmTsTTKzBdiaeGBz1EW1CjSPm1auhk/W/VNHBd0= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=RmERSHVn; arc=fail smtp.client-ip=52.101.127.91 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="RmERSHVn" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=bJhOOhdsbgll77mL5NEgA+8sYHIo/5QjgWWg7t+pzoufF+C0W5TvpflcSD7mHzEhxqzHp+YNJ3bA0jTEECkxz/veMIAqGdHoF3rTLb6BnPGr51rUEI0l86oZYw02MS7THLdgCdpujCBzwbTcUbcQCT/nEvfVyxKYROBGsfuiv5nNHjCOkcuMFDVwZ9JYgs+k8Z5Ag0kL+6LZbT3tQfA4FyFypcwGRb9tYLuRxb/F8xU8wldWK575kEadyaNTl2H7uFqUU6TDDpv9OrSGRkiLhjCgGijgSIvNKa69QGgcm5o271Zcggi4UYeQ5KJ8iTMkQws358eF8vgej2tfasNsjA== 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=/vUn9VKO4UJ2AzlpSWYT/A34d39+Edlti6ZneHbq5Rs=; b=uj2K7wNtffJudnO+2kjAK5JHOmFXv/LpKUhkN5WoL9ncHimiMxaxjgyORMkWuCibtsDn3Hf1iG4aYePN1eu0yYT+dGDqlB+3bplKaI6F8e/yMVv7zAGs/lner4CDNrgOa+/sLjMbVp7xkkDGYiGT2CL4i0NNR12fuXc0yZuL59qZscikt4MFffFD8jATUY9gyHHWrQ3SnVDcPLeQWCjhX94+z+3nAcsktYxyhRSLXPYw5ys3OxFV4S3VQdRWpVmc8f5KbiqcWWg0HiRJYnuYOmbg3Ma5VIK6zq/UPFQRybAMlb1HStPqViJ1zAhvP2dBChcRdX29RadZaolU1xxACg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=/vUn9VKO4UJ2AzlpSWYT/A34d39+Edlti6ZneHbq5Rs=; b=RmERSHVnH3mzkYUZuR4N26z3I0l1sR0abiPLSxpc0Y8XxNlSHQZkmlupxGPo7uumJMXLxVKI9bJtKDi8ZQxAE/mXWVRchOoOwmH3NBhqIH/Mwci20Uo+wVhe4BNfvdDLvjEcuG4iY9trec1EG9OQPhZIzR7tND+SsI0TqQfTv1pIffEnbjsnQG8JPH1TUyyuRz+o1wjWrkDbNIbLFbsC7kZtQ05US+cfgXGYqhNogLEY1TPegtGI/oU1A4GScrvh9ZcLGVP8lB/HG3rHHqKx661V3+t71bpLksYRtxxoH6OrNQxci+RdL6mrlcEPWbPzit8kJOOwWIcy0zwTvKya2w== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by JH0PR02MB6851.apcprd02.prod.outlook.com (2603:1096:990:4d::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.202.18; Tue, 14 Jul 2026 15:55:31 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:31 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:21 +0200 Subject: [PATCH v2 7/8] Add Advantech EIO Thermal driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-7-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=12827; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=E4EmkEsZE4kTq05Eku5hg4KYVeZg9aGS6i3QVJguwOs=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu5f5zFnZurUQ8uTfs7XqSwFp2jDeFc1tIby jkU3kYfMtaJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuQAKCRDO7dCnvL2w CeBzC/wPQC8ximyzVmfb9FqIPbb4XOovGYq4zYzBVuLgqKf+K/GjlI3Fo3VGGpowsoJEmP8nChs tTx4F6btPUSKbejrS6cPIknB8dSuJLtJnu0ynXJYY5jsnl6m758GulSn/M48eK5+4XBS+YInd5u neODw2uUV6/QipK30md1i65Lh5p0m6ZFkqjFlE/flAwfFD972HN0GtKSZjCGXNiJEf0PFv2lOUx LumWnmQFFeQadJkTTnbB9ayI9DVVfFW/tB9cO9OjQJt8KzUTLHSCDA/cz38KbLp5G8lQHuFaufj +s6fQPXOmeB3IqT+ba82BflcCQjvT3rQ5IqnVtk3KOgNA/Ku2dvcKlU4ynQB3P+61KfHTg4i0E9 H7TzoP2uTr4FVhNRBjt+btN3JUlocivS+4+fOy90YW6x5yhIVqEFNRghIRancHuwdmbAZBnPuBv ozPdt8WDfoiHgFOs8ibeAPQU42VnqmpyexLReH1ag06h68aMO+6WR9ihu/UWPXgXsfYgM= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|JH0PR02MB6851:EE_ X-MS-Office365-Filtering-Correlation-Id: 9f2877f9-c763-4d03-98d1-08dee1c05507 X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|52116014|7416014|23010399003|376014|366016|1800799024|38350700014|921020|6133799003|56012099006|11063799006|55112099003|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: sMjxokiSuYFtaGPCfHzeXQZPwjF3nQ4rCRhXdI5tYStQBDaHyr0WHleycAFo16CNEnOJieHAUBScmNMMzJZ5FjWIy2gJ542YoHWgRz4S4xKgnXYq0r1+WxAJ4UQYJqykmil2Z/OIBrW3M1zbASyfyjIyYpy1V2yvIhl1uLuOV0cVxJeH+ZP0MRuGTmYYlJqrP8qhHlht7F8AgxQp2pZp2ONHSBS/rDmjuUNYZJaH1YBUHmlRLPlSXZZrG1zXhRL7N7hW30pupc6tBc/VD57ia8y2SzrLYrnWnTOT8krJQNM7XImb8C10VtSJ+cumpQqf2VHSjLlvUh0yaTxA4OUSLhbIpwg7hCL0KUkaGBmbrCUiNT1jwAc3KhsJVmcdgdOdu2gvOiAzUA0HsGb6YPVRWSaAO4upB0hOBq9tHE5SmwqqDDs4BuzJ15HqFWA85qeOXLXoKhb/qCMblriL+ct3nySwfgIZnM7ZewhlItyywUaQYpTfpyifiK4chGyV91Kl9XX9Z03/7O2gcyFNHlNjCYkSsgRd7fXShya+2ShBlvxIYxNDorleGzdcGgfkj3SuHCmKqZMvouM4640JlZhGpTHsiwCGDS2ZRdgHW6CSHtYSF8lFE2/pnJ3aradTqJF6EZjprUPpFqq0r2lSA7Qf1iTH/mIHNxth7RcEKlgAeugpjh4+yios034lU1rQxo8ZK7sLhRbBbZDxbTwaXClXG0g2CGRqpGrt5ayIKFtJPerDqoKpfhjFprwX8aF7Z36T X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(52116014)(7416014)(23010399003)(376014)(366016)(1800799024)(38350700014)(921020)(6133799003)(56012099006)(11063799006)(55112099003)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?MVRUcUpaSS9Cbis0dUpKYW9QaFZKUVBKemF2Y0UyVlhaaDdSdXl5ZWNlVmY1?= =?utf-8?B?UGp3OFR4YXJ5ajBPcjZzZlVwMnJJamdxNVZXUkZzZ2VZL0dNbVpGNlkxSE4v?= =?utf-8?B?ZDdHam1Hb0NEdXQ1R0lINnhremhVbTJvTkprVk9nZWdCaFpHbTRrbVJ2dmF1?= =?utf-8?B?NG1ENWg5NzREMHJtYXlwekJNeEhidGlKVVdiVnJGTm81SHlQVUJrSTFQdHR2?= =?utf-8?B?dmNhZWtuSXllRStVajNDSzJYNWU2bldLek53cklvdEpwUjM0aHhFOHhqRWhK?= =?utf-8?B?NXFreGhBV1A4aTJyWlI1djdaWWl3TDNjS3lQTzlxbnIzWlpFcGtxVkpkd0dV?= =?utf-8?B?Z1FPQkJobmhXMGZteFBNRWpteXVsendQK2xuOE1PcnVrSzdIZXZQWDQ1WGhj?= =?utf-8?B?aWNQc2FQVk4zVmRPem5sdmlyd1FxRHIxYkpLUjg2Nmo4RTE4bzNZL0plVFMv?= =?utf-8?B?UERqVTZkSEZyRzU5NFBJTDhyK3h0S3FMWmN1M3ByYXBVN1JhSG9rMFluZzYw?= =?utf-8?B?cTRKY0NVMWFMT3Azdlk0b3ZSQkJ6REFTL2pLTnpJZUxZa1JpQWd4VXV3S083?= =?utf-8?B?ZzcxNHlIOXRERm0reTRpUVlGL2hhcFRncGw0NnFUbkNmYS9rellqOTdCVFVs?= =?utf-8?B?dml5L0VnVGljWHdFamVpamxpWlhSMDZCZzFRSlJxU1VrajRJSmdMSGJwOFlB?= =?utf-8?B?blJSRkppbjhQSzI0Q3Z3KzRhRFF6b3haSTdnR1l5ckFoWkxGRzlnSU5mWlBR?= =?utf-8?B?Nlh6d2JyMHRYU09ud0JqYVA4UjVJU2VjbXJtTTJGTDZaclBzcGh3S3dubWV5?= =?utf-8?B?c1FkMHpHeUNZSVNreVBzRWh5VVkrSGdGL3VueEEvOVZxeTJuN1hjdG1GNERv?= =?utf-8?B?OEtPL00vR2VCVjBlTnlPUmNRd05YNWk3aXZZalhNenZHRHArZWZQTjd3M0Fy?= =?utf-8?B?dDlkL2phaXZXSTJVM2FNY2lVRGJlZFd3QnRaQTJaNkdUWTNRZkxGK0M4UFMw?= =?utf-8?B?SGUweEZKQzJMVFZMRVFHZkJ5NUN1cnVqTDFLU0NYK2lJMUhLZEQvZERPVEhX?= =?utf-8?B?SDg3QjBKS1BRK0NoZ3VNdEEyV3ZteUtRZG4zcXpjZXRUNjhVcUVXcEhCUDRH?= =?utf-8?B?bGxzMmF2NndSa3RBQmxRQnJ3SUdsMmdJSmpCZnFaNUNjSnY1eUJrbXFxSk9H?= =?utf-8?B?c1MwZG9XbmJKLzV6SEw0ZDZGcGVKcE41c1Z5VTNXUFNTWE53NklDTUpSdkhG?= =?utf-8?B?dWFmOUxDbVYrVk1LWGdBTXc3MnlqUGFLSXNPekNwNHJhdkxJOC84dHNIdkFI?= =?utf-8?B?dGhvTFl2alIvR3BpT2U0eUhGVDNqTVo0U21aZklHNHRPT1VsdC82NlFsekRD?= =?utf-8?B?a1J3NmtlbE95MmZLeEs0bVZ1ZUU2VVUxN1A0eUxqVyttR0REMDcrU3hNWTZI?= =?utf-8?B?Yzc2SG5MbHNhc2cwTm9QSUFEdXR6enpJUERDRkc4Q2JUdnVaOFlxTm9nSEZq?= =?utf-8?B?ck80QTY5Q253dUtxaUhDNkk0bVNhbzRRZVJCVk9pQjJmakZXa0V0L2NoN2tZ?= =?utf-8?B?eG9wbnNucTg5S0JXeWE3cHhUUDJlaTlYZUNObE1xODlOTWhjNk1zdjJhdm8x?= =?utf-8?B?SDVKVDhBcndWSWNtMlhSZG1GcCs3UHk3K20xMFM2dTN1dU51TEI4eWl5WU1q?= =?utf-8?B?c2dTYUxpZ016SmZsbFM4K0lrWkttOGljOTB4cHp4NHZyQmZrcGxGRzJlK0p2?= =?utf-8?B?a09JM25xbEVEdGdwRnpqWHV1QmtPTTBzMHNvL0NDNWFUaTJrNTdUQzBsaUho?= =?utf-8?B?Y2xUTGRUZnFORE9lYW1QN3ZNOWptTFRoMlF2TXZTQzdmWTlmRjRRMnVYZUVZ?= =?utf-8?B?YjNYQUhGYmVOWXpwKzMycDdGcVZWNWlJUUlkMW11OHI4ZldWaERGQWFhRjY4?= =?utf-8?B?ZldURm9xMHIvcEVNaHFUWURGdERjbXVobDRQNzNOMUlkbVZLdGRtWHgzZm5w?= =?utf-8?B?UWFqY2xITTdVWWtZdHU0dUc5Tk1pV2tYbUt2NFZobmlzbG5iZllxSEhjMzhE?= =?utf-8?B?OUFIeGFFVjdrOTVsMDlWak40UTVoQjdkWWRTSG9VMUF3cXZxMjJVT3kraFhT?= =?utf-8?B?WFRxQ2pRT2ZrSk5FWmZwNTM5K1dhRFFZb3NROHVrWUE5amxHaERIZU5LKzZz?= =?utf-8?B?akE4TlJqSFlVbmliUk1wNWpUQzh6MFV1OW8vZTc1YVhnQnNNblBENzgycUhV?= =?utf-8?B?dWNrSnVzTWhpaWswdmFxODBqRk9GNE8rMERXSDJUU0xTekRBT3ltNjR0QzM0?= =?utf-8?B?a1QzeThZUnVyRHVFV0UxL0JITHZxQVBtMk1USUw2aEljTUxpVkVGdUh5bHgr?= =?utf-8?Q?gfaBoiabp/CMYlMY=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9f2877f9-c763-4d03-98d1-08dee1c05507 X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:31.1017 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 2o1cvVon5Xva5Or2AMY+idvpgoGjscU6tbZ9esSH5RznotOiLfeQcolQ7WbQwpEVJhjXqGobCu73xSO0Dx7No1oRart0E9MM7H6VTNdJJcE= X-MS-Exchange-Transport-CrossTenantHeadersStamped: JH0PR02MB6851 This commit adds the driver to control the Advantech EIO Thermal block, this block is included in the Advantech EIO Embedded Controller. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/thermal/Kconfig | 9 ++ drivers/thermal/Makefile | 1 + drivers/thermal/eio_thermal.c | 346 ++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 357 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8d35fd7c5599..ded89e70d6cc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -616,6 +616,7 @@ F: drivers/gpio/gpio-eio.c F: drivers/hwmon/eio-hwmon.c F: drivers/i2c/busses/i2c-eio.c F: drivers/mfd/eio_core.c +F: drivers/thermal/eio_thermal.c F: drivers/video/backlight/eio_bl.c F: drivers/watchdog/eio_wdt.c F: include/linux/mfd/eio.h diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 810eeccedfba..fc078012a989 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -427,6 +427,15 @@ config DA9062_THERMAL zone. Compatible with the DA9062 and DA9061 PMICs. =20 +config EIO_THERMAL + tristate "Advantech EIO Thermal zone" + depends on MFD_EIO && THERMAL + help + Thermal zone support for the Advantech EIO. This driver exposes + temperature readings, trip points and protection enable/disable via + the Linux thermal framework. It communicates with the EC through the + EIO MFD core. + menu "Mediatek thermal drivers" depends on ARCH_MEDIATEK || COMPILE_TEST source "drivers/thermal/mediatek/Kconfig" diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 3b249195c088..6abce8933e34 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_IMX91_THERMAL) +=3D imx91_thermal.o obj-$(CONFIG_MAX77620_THERMAL) +=3D max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) +=3D qoriq_thermal.o obj-$(CONFIG_DA9062_THERMAL) +=3D da9062-thermal.o +obj-$(CONFIG_EIO_THERMAL) +=3D eio_thermal.o obj-y +=3D intel/ obj-$(CONFIG_TI_SOC_THERMAL) +=3D ti-soc-thermal/ obj-y +=3D st/ diff --git a/drivers/thermal/eio_thermal.c b/drivers/thermal/eio_thermal.c new file mode 100644 index 000000000000..ad923acaf138 --- /dev/null +++ b/drivers/thermal/eio_thermal.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * eio_thermal + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Thermal zone driver for Advantech EIO embedded controller's thermal + * protect mechanism. + * + * In EIO chip. The smart fan has 3 trips. While the temperature: + * - Touch Trip0: Shutdown --> Cut off the power. + * - Touch Trip1: Poweroff --> Send the power button signal. + * - between Trip2 and Trip1: Throttle --> Intermittently hold the CPU. + * + * PowerOff Shutdown + * ^ ^ + * Throttle | | + * | | | + * +--------+------------+----------+--------- + * 0 trip2 trip1 trip0 (Temp) + * + * Copyright (C) 2025 Advantech Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_THERM_WRITE 0x10 +#define CMD_THERM_READ 0x11 +#define THERM_NUM 0x04 +#define UNIT_PER_TEMP 100 + +#define CTRL_STATE 0x00 +#define CTRL_TYPE 0x01 +#define CTRL_ERROR 0x04 +#define CTRL_VALUE 0x10 +#define CTRL_MAX 0x11 +#define CTRL_MIN 0x12 +#define CTRL_THROTTLE 0x20 +#define CTRL_THROTTLE_HI 0x21 +#define CTRL_THROTTLE_LO 0x22 +#define CTRL_THROTTLE_DEFAULT 0x28 +#define CTRL_THROTTLE_HI_DEFAULT 0x29 +#define CTRL_THROTTLE_LO_DEFAULT 0x2A +#define CTRL_POWEROFF 0x30 +#define CTRL_POWEROFF_HI 0x31 +#define CTRL_POWEROFF_LO 0x32 +#define CTRL_POWEROFF_DEFAULT 0x38 +#define CTRL_POWEROFF_HI_DEFAULT 0x39 +#define CTRL_POWEROFF_LO_DEFAULT 0x3A +#define CTRL_SHUTDOWN 0x40 +#define CTRL_SHUTDOWN_HI 0x41 +#define CTRL_SHUTDOWN_LO 0x42 +#define CTRL_SHUTDOWN_DEFAULT 0x48 +#define CTRL_SHUTDOWN_HI_DEFAULT 0x49 +#define CTRL_SHUTDOWN_LO_DEFAULT 0x4A +#define CTRL_SB_TSI_STATUS 0x80 +#define CTRL_SB_TSI_ACCESS 0x81 +#define CTRL_WARN_STATUS 0x90 +#define CTRL_WARN_BEEP 0x91 +#define CTRL_WARN_TEMP 0x92 + +#define THERM_ERR_NO 0x00 +#define THERM_ERR_CHANNEL 0x01 +#define THERM_ERR_HI 0x02 +#define THERM_ERR_LO 0x03 + +#define NAME_SIZE 5 + +#define TRIP_NUM 3 +#define TRIP_SHUTDOWN 0 +#define TRIP_POWEROFF 1 +#define TRIP_THROTTLE 2 +/* Beep mechanism no stable. Not supported, yet. */ +#define TRIP_BEEP 3 + +#define THERMAL_POLLING_DELAY 2000 /* millisecond */ +#define THERMAL_PASSIVE_DELAY 1000 + +#define DECI_KELVIN_TO_MILLI_CELSIUS(t) (((t) - 2731) * 100) +#define MILLI_CELSIUS_TO_DECI_KELVIN(t) (((t) / 100) + 2731) + +#define THERM_STS_AVAIL BIT(0) +#define THERM_STS_THROTTLE_AVAIL BIT(1) +#define THERM_STS_POWEROFF_AVAIL BIT(2) +#define THERM_STS_SHUTDOWN_AVAIL BIT(3) +#define THERM_STS_THROTTLE_EVT BIT(4) +#define THERM_STS_POWEROFF_EVT BIT(5) +#define THERM_STS_SHUTDOWN_EVT BIT(6) +/* BIT(7) reserved */ +#define THERM_STS_THROTTLE_ON BIT(8) +#define THERM_STS_POWEROFF_ON BIT(9) +#define THERM_STS_SHUTDOWN_ON BIT(10) +/* BIT(11) reserved */ +#define THERM_STS_THROTTLE_LOG BIT(12) +#define THERM_STS_POWEROFF_LOG BIT(13) +#define THERM_STS_SHUTDOWN_LOG BIT(14) + +static u8 pmc_len[] =3D { +/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */ +/* 0 */ 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 1 */ 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 2 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 3 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 4 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 5 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 6 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 7 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 */ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 9 */ 2, 1, 2, +}; + +static char therm_name[0x20][NAME_SIZE + 1] =3D { + "CPU0", "CPU1", "CPU2", "CPU3", "SYS0", "SYS1", "SYS2", "SYS3", + "AUX0", "AUX1", "AUX2", "AUX3", "DIMM0", "DIMM1", "DIMM2", "DIMM3", + "PCH", "VGA", "", "", "", "", "", "", + "", "", "", "", "OEM0", "OEM1", "OEM2", "OEM3", +}; + +static const u8 ctrl_map[] =3D { + CTRL_SHUTDOWN, CTRL_POWEROFF, CTRL_THROTTLE +}; + +struct eio_thermal_dev { + struct device *mfd; + struct device *dev; + u8 ch; + u8 name; +}; + +struct eio_trip_dev { + struct device *mfd; + u8 ch; + u8 idx; +}; + +static int pmc_write(struct device *mfd, u8 ctrl, u8 dev_id, void *data) +{ + if (ctrl >=3D ARRAY_SIZE(pmc_len)) + return -EINVAL; + + struct pmc_op op =3D { + .cmd =3D CMD_THERM_WRITE, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + .size =3D pmc_len[ctrl], + }; + + return eio_core_pmc_operation(mfd, &op); +} + +static int pmc_read(struct device *mfd, u8 ctrl, u8 dev_id, void *data) +{ + if (ctrl >=3D ARRAY_SIZE(pmc_len)) + return -EINVAL; + + struct pmc_op op =3D { + .cmd =3D CMD_THERM_READ, + .control =3D ctrl, + .device_id =3D dev_id, + .payload =3D (u8 *)data, + .size =3D pmc_len[ctrl], + }; + + return eio_core_pmc_operation(mfd, &op); +} + +static int eio_tz_get_temp(struct thermal_zone_device *tzd, int *temp) +{ + struct eio_thermal_dev *eio_thermal =3D thermal_zone_device_priv(tzd); + u16 val =3D 0; + int ret; + + ret =3D pmc_read(eio_thermal->mfd, CTRL_VALUE, eio_thermal->ch, &val); + if (ret) + return ret; + + *temp =3D DECI_KELVIN_TO_MILLI_CELSIUS(val); + return 0; +} + +static int eio_tz_set_trip_temp(struct thermal_zone_device *tzd, + const struct thermal_trip *trip, int temp) +{ + struct eio_thermal_dev *eio_thermal =3D thermal_zone_device_priv(tzd); + const u8 ctl =3D (uintptr_t)trip->priv; + u16 val; + + if (temp < 1000) + return -EINVAL; + + val =3D MILLI_CELSIUS_TO_DECI_KELVIN(temp); + return pmc_write(eio_thermal->mfd, ctl, eio_thermal->ch, &val); +} + +static int eio_tz_change_mode(struct thermal_zone_device *tzd, + enum thermal_device_mode mode) +{ + struct eio_thermal_dev *eio_thermal =3D thermal_zone_device_priv(tzd); + int trip; + int ret =3D 0; + + for (trip =3D 0; trip < TRIP_NUM; trip++) { + ret =3D pmc_write(eio_thermal->mfd, ctrl_map[trip], eio_thermal->ch, &mo= de); + if (ret) + dev_err(eio_thermal->dev, "Error when %s trip num %d\n", + mode =3D=3D THERMAL_DEVICE_ENABLED ? "enabling" : "disabling", + trip); + } + + return ret; +} + +static struct thermal_zone_device_ops zone_ops =3D { + .get_temp =3D eio_tz_get_temp, + .set_trip_temp =3D eio_tz_set_trip_temp, + .change_mode =3D eio_tz_change_mode, +}; + +static struct thermal_zone_params zone_params =3D { + .no_hwmon =3D true, +}; + +static int eio_thermal_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + int ch; + + if (!dev_get_drvdata(dev->parent)) { + dev_err(dev, "eio_core not present\n"); + return -ENODEV; + } + + for (ch =3D 0; ch < THERM_NUM; ch++) { + u16 state =3D 0; + u8 name =3D 0; + u16 hi_shutdown =3D 0, hi_poweroff =3D 0, hi_throttle =3D 0; + int t_shutdown =3D 0, t_poweroff =3D 0, t_throttle =3D 0; + struct thermal_trip trips[TRIP_NUM]; + int ntrips =3D 0; + struct eio_thermal_dev *eio_th; + struct thermal_zone_device *tzd; + + if (pmc_read(dev->parent, CTRL_STATE, (u8)ch, &state) || + pmc_read(dev->parent, CTRL_TYPE, (u8)ch, &name)) { + dev_info(dev, "thermal%d: PMC read error\n", ch); + continue; + } + + if (!(state & THERM_STS_AVAIL) || + !((state & THERM_STS_THROTTLE_AVAIL) || + (state & THERM_STS_POWEROFF_AVAIL) || + (state & THERM_STS_SHUTDOWN_AVAIL))) { + dev_info(dev, "thermal%d: firmware not activated\n", ch); + continue; + } + + if (name >=3D ARRAY_SIZE(therm_name) || !therm_name[name][0]) { + dev_info(dev, "thermal%d: unknown sensor name idx=3D%u\n", ch, name); + continue; + } + + /* Throttle starts a 1C increase it */ + int throttle_temp =3D MILLI_CELSIUS_TO_DECI_KELVIN(60000); + + pmc_write(dev->parent, CTRL_THROTTLE_HI, (u8)ch, &throttle_temp); + + pmc_read(dev->parent, CTRL_SHUTDOWN_HI, (u8)ch, &hi_shutdown); + pmc_read(dev->parent, CTRL_POWEROFF_HI, (u8)ch, &hi_poweroff); + pmc_read(dev->parent, CTRL_THROTTLE_HI, (u8)ch, &hi_throttle); + + t_shutdown =3D DECI_KELVIN_TO_MILLI_CELSIUS(hi_shutdown); + t_poweroff =3D DECI_KELVIN_TO_MILLI_CELSIUS(hi_poweroff); + t_throttle =3D DECI_KELVIN_TO_MILLI_CELSIUS(hi_throttle); + + ntrips =3D 0; + if (hi_shutdown) { + trips[ntrips].type =3D THERMAL_TRIP_CRITICAL; + trips[ntrips].temperature =3D t_shutdown; + trips[ntrips].flags =3D THERMAL_TRIP_FLAG_RW_TEMP; + trips[ntrips].priv =3D THERMAL_INT_TO_TRIP_PRIV(TRIP_SHUTDOWN), + ntrips++; + } + if (hi_poweroff) { + trips[ntrips].type =3D THERMAL_TRIP_HOT; + trips[ntrips].temperature =3D t_poweroff; + trips[ntrips].flags =3D THERMAL_TRIP_FLAG_RW_TEMP; + trips[ntrips].priv =3D THERMAL_INT_TO_TRIP_PRIV(TRIP_POWEROFF), + ntrips++; + } + if (hi_throttle) { + trips[ntrips].type =3D THERMAL_TRIP_PASSIVE; + trips[ntrips].temperature =3D t_throttle; + trips[ntrips].flags =3D THERMAL_TRIP_FLAG_RW_TEMP; + trips[ntrips].priv =3D THERMAL_INT_TO_TRIP_PRIV(TRIP_THROTTLE), + ntrips++; + } + if (!ntrips) { + dev_info(dev, "thermal%d: no valid trips\n", ch); + continue; + } + + eio_th =3D devm_kzalloc(dev, sizeof(*eio_th), GFP_KERNEL); + if (!eio_th) + return -ENOMEM; + eio_th->ch =3D (u8)ch; + eio_th->mfd =3D dev->parent; + eio_th->dev =3D dev; + + tzd =3D thermal_zone_device_register_with_trips(therm_name[name], + trips, + ntrips, + eio_th, + &zone_ops, + &zone_params, + THERMAL_PASSIVE_DELAY, + THERMAL_POLLING_DELAY); + if (IS_ERR(tzd)) + return PTR_ERR(tzd); + /* Make sure zones start disabled */ + thermal_zone_device_disable(tzd); + + dev_info(dev, "%s thermal up (ch=3D%d)\n", therm_name[name], ch); + } + + return 0; +} + +static struct platform_driver eio_thermal_driver =3D { + .probe =3D eio_thermal_probe, + .driver =3D { + .name =3D "eio_thermal", + }, +}; +module_platform_driver(eio_thermal_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Thermal driver for Advantech EIO embedded controller"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 18:53:27 2026 Received: from SEYPR02CU001.outbound.protection.outlook.com (mail-koreacentralazon11023088.outbound.protection.outlook.com [40.107.44.88]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7A6DF4968E7; Tue, 14 Jul 2026 15:55:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.44.88 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044545; cv=fail; b=nLQ9n/RbqUEKlbwf1/3uyMUORX5QCfX2Fvqw8uEx/eqyP13i8wANhgb5V35VzTwv7JyD1p0Tb1XG5cjm7/D1F50LQN0x2A6uXw65cGMZ5VzKB7DCrf8usNXWVd4iJ66Rh296TlA+amaonUFH0horiPDeiFPo22Bhi7aSyeAgI08= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784044545; c=relaxed/simple; bh=oou5EX6wko0UQV1pGcLo6KFSWBrfZXvtu2v9g4IUFYQ=; h=From:Date:Subject:Content-Type:Message-Id:References:In-Reply-To: To:Cc:MIME-Version; b=AG8Joq8DuSJHK+tQWpbKAyO2sQJ8Vp60LxNajED3v7sOJ23D2OfLWYi8n1uw/R1QXg8mDtC880eXlKypYL4es02EQpszjaLL//efH0i3BVhST8ET6APLF0ppsmkmPJHhFUGOhaCb6zZu1nua/0jRjb3dzBshmDeQH6yY1kAORLw= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com; spf=pass smtp.mailfrom=advantech.de; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b=JzLGVzwp; arc=fail smtp.client-ip=40.107.44.88 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=advantech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=advantech.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=advantech.com header.i=@advantech.com header.b="JzLGVzwp" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=E9LyEOhneifpN9qSCQTlrUwS3e+sXcx7EDF46SlO1FCZDIFC/Wgh9zDnfvuEgyBSUuiXyKqnWgvO1VnJX0S/ctHCqEc/cWlnQheRRCyF5DinifLKJu61t1FOXZuT6MCcdON+tSz2zkcrbMnR90qcABeCh9ZrqGdzei38lcLaAW614JDx/QXAxnK5m7qGOx6IyjYqH5f+WtWkUn2KYXqAeELyCsPcNeHRa/I8iN7PJre9h1IE/HTqhXycAvQt3EfHMzNh6t0gIfRef1ujRCh/Vk84MhDz+RxMzYtCLwhnWiP10bClhE+9hzWnfEXOwP6c7Fl3o7p6R/j4bqywvYdFFw== 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=fk9LJQQw4sZQVdRKE2JlB7HEHAS42YR0iXpC+U/843U=; b=XEgQzTaIc6aun6FBghvC2sLIOS1wZtE1qu5JL/l4SzZUmbQNrA2hazcebqN28Mk8LEcvIBWNHbpkHzVIyDM4zhLevv7WSkcGYzkSHIf5x1cgYfQAsjJ50puFXJ8zZdcyB4NnuTi9dyH9Lsy2qKZDf2WEU1ZLrMf3HRQp7P3Vly7OdbSaHHZGWFeaUxPDBMzswugRH3W9oQKrw2tWx8YB3cFtCBg9Gncs9CYM8S7IADbY0oQRvE2Nyd3s/rBzQ4ofxOzvWfFYt+Wr7pICnx/T976rrdu3cT9DgMASrjSCmA32WzjFFFBmdz8vymIxj1lUoDLcg9GjRCwVdHDdpBT+yw== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=advantech.de; dmarc=pass action=none header.from=advantech.com; dkim=pass header.d=advantech.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=advantech.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=fk9LJQQw4sZQVdRKE2JlB7HEHAS42YR0iXpC+U/843U=; b=JzLGVzwpP5dP/kfIbc13+tVKy7uypEpcTBdEechZpqodtuFBMCjQ15x2MD27zRJ96368cbaznLl7jVoyeIOvztd1h9HN9sRAXmrlxVgppVCFK7uCjOHKe2aibO+yfHLD5Q58c3Bm+bqKkOfkmSNAgRH92CQkoaiu5S2fQO12HQ+q5tDRXL+uzSLpG5SsPYyWJFh3pvxPG1FS3UthRHvHprg5rmILrc9AvzrtJ95buB764Q3JvQmiZef4+4s//oJKw/5kCxHPS40IEBlaKZmOk1m0LpiglN13ApCA10VpJUDBI2pXrOmNQ3b4nITUFOW58p1GMJzkbDVusVXGQd3gZw== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=advantech.com; Received: from PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) by KUXPR02MB8880.apcprd02.prod.outlook.com (2603:1096:d10:54::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.223.10; Tue, 14 Jul 2026 15:55:38 +0000 Received: from PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa]) by PSAPR02MB4502.apcprd02.prod.outlook.com ([fe80::44e6:6433:8000:35aa%3]) with mapi id 15.21.0202.018; Tue, 14 Jul 2026 15:55:38 +0000 From: Ramiro Oliveira Date: Tue, 14 Jul 2026 17:54:22 +0200 Subject: [PATCH v2 8/8] Add Advantech EIO Fan driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260714-upstream-v2-v2-8-76e5e41026db@advantech.com> References: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Guenter Roeck , Andi Shyti , Daniel Thompson , Jingoo Han , Helge Deller , Wim Van Sebroeck , "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-pm@vger.kernel.org, Wenkai Chung , Francisco Aragon-Trivino , Hongzhi Wang , Mikhail Tsukerman , Thomas Kastner , Ramiro Oliveira X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=15387; i=ramiro.oliveira@advantech.com; h=from:subject:message-id; bh=oou5EX6wko0UQV1pGcLo6KFSWBrfZXvtu2v9g4IUFYQ=; b=owEB7QES/pANAwAKAc7t0Ke8vbAJAcsmYgBqVlu53q3gUkwUYQH8HdAki/DKyAgBrtPVaKwnr oqdwmSIkIqJAbMEAAEKAB0WIQS1Nkng0ZvJmBKh6GLO7dCnvL2wCQUCalZbuQAKCRDO7dCnvL2w CZKzC/0SWurZo5szHyfqYKIFal3m4r7gGIMEbQi65aPuNmQg3Al6QMowRXtTrm10YOC94euhLWV q1B3k0COnprwuyTLfQwj0f7pcGkT5ArGz7Ry5RYfOxFecLHPCzbh1LvmUBvNgudI+Uk5grdPg0J uX0iDa9ZrFoGiSkt+sQwv8mBqJPZsDDUqVu+OiZyErhHgcLAy0MxejxP2PUuQ79b8tuqbux3q0Z EI9sIrcP+XCl7IexQrxuzuuZkFX9B6tk627uxSRVBQ8nnnpHjw792I+50o+XEKKSHNRgndQ7CtP 4+BYee7kVqVYSdmI4e+P5CMv9GobGPHy57sBTO+TooJoMxcqLEUwaCL4EVBtJOfgcv/0QBVzpXg zMsLaJPC0mVLpDNKRIymeNrFHK2vKv6/dY9G7jFLDTp8NcPvrXMgJoYenIpb+4eWBypIadl+yXV QGHNpGMLckK9YnVRZYljgy1QfaZDiQXVUHDvjPJv04v+o2NKxZL+vV4NUlarMIogTecBU= X-Developer-Key: i=ramiro.oliveira@advantech.com; a=openpgp; fpr=B53649E0D19BC99812A1E862CEEDD0A7BCBDB009 X-ClientProxiedBy: FR2P281CA0086.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:9b::14) To PSAPR02MB4502.apcprd02.prod.outlook.com (2603:1096:301:21::6) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PSAPR02MB4502:EE_|KUXPR02MB8880:EE_ X-MS-Office365-Filtering-Correlation-Id: f86e397f-9116-417a-b839-08dee1c05950 X-MS-Exchange-AtpMessageProperties: SA X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|7416014|376014|52116014|1800799024|23010399003|11063799006|6133799003|56012099006|55112099003|18002099003|22082099003|921020|38350700014; X-Microsoft-Antispam-Message-Info: DRXHT7buVyLBhWo1P0zwalYHr4ignpVJbbQn7Rjv9SqoZ5RhoHYYrz5cCKAKAAkp/tfoV4nmT9f6T31XygaBvH38i4Hp2nEJGKDwtKUs+da9tqKMNNDgWObZJeUrk7Veqqr2yMvDsxu1/9ent5Vrdip6oRqlWJu2y9yWd1hsI1AV4Tz++ZIufNiwjsaDbWnzT0Zce1io1ql1nkJl22kburR2kytH6M4v698GEf8wGJ1PwuS2MTAdSDkvhzVEBX4qK/ey9uMCpUttjG5rQFo2L3WLGuvAJpugxKl/0kODcZggNoAIRchJpYduukZQT3AhlcYx9MiF42rUd1JVquQC4OCRY0GS1cZZ9TvkXPBR08cKZIvnPLGEMe/0HTN63ehnkTbvaNH7zR5BP3F6masGRcrFFvN8fo0gTDJZR+Nnc1UIG/qZHCnNtuTvpz/Qs6FjdPlnRmcUUBXXimtoktUUPREHiovT+bj8XyUi2SE1WV9ptMnPQlyYf+O9JFjMtCHSsNeRcDOpBmEqnflW1eVnO3O+kCwTqf+ihEZoXnrQ9aXBgqLe1Fda/qSHV7XIWWJQC6bwsi6B2EMnb4CRtCImxc9he6dntsNQNGK1Y9U0GFd8DdDKBz/OVSRn4orBeHdvjElC6SFxn76DGkE2uzW+wQkaFqLvvO4hflp20ZnQINR/w6XbtqSc1jXvhCOUzk+1yhLbahSdJVeV083opHB2KaWF4SqoiH/EILXHcDKSLrE= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PSAPR02MB4502.apcprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(366016)(7416014)(376014)(52116014)(1800799024)(23010399003)(11063799006)(6133799003)(56012099006)(55112099003)(18002099003)(22082099003)(921020)(38350700014);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?MUMzWjNtbGlRekR6enUvSTVXNVJ1c25UWStGY3ZJM3FEWGFVZUUxUm5zWkJa?= =?utf-8?B?Sjh0VHQxY0MzaUNJTGs2OXM1MndMOHJBOU5XNVZWNllyTmh2NmZVcDdhNm1Y?= =?utf-8?B?bXBCbHJqSTFTOGJPbko4VDhLRXZKTWlwcXVYYTFZaWZob1BuSmlCR2FnK0Nh?= =?utf-8?B?SlNFMnV3ZUh5VXBXUStGaHQwYW1ZYzBxSHdDZGxPcmJ1Zk9rbDNKekFtZk0w?= =?utf-8?B?a3lnUHh3VWU1QXBLZFFBMTQyNUpoTWIrcXAxY0l0cmY4MlFlM0tsYW9FaG9P?= =?utf-8?B?VW1YY3p4cThnRkhqQVZQazM4ZHh3WTAvK0RSUy9CUk05clAxUnZYenRJczdm?= =?utf-8?B?YkNWclNQb2hQb2JRVEdrazZjRG9mWC9JYWgyd2twd1dXSTB4V0hLOHFCUkg4?= =?utf-8?B?R2ZDRFlWMnVwZHBZYUZYNG14NldNQlVzcTNPV0pxNjBadGJtTHFyMW1IVG1O?= =?utf-8?B?S1gvU2pkWU85bm1jcFk0WmJtZFhIWTgzUnFXQ3gyckhJYitoUHRHelAzYS9F?= =?utf-8?B?VWhWUGVDOTdVeTA5NUI5eEtFaVd0REJ4Vjg3N1hTZUdtTDFQcFpMNjZsL1FI?= =?utf-8?B?NGZhRU9BakY5OU9tTm8xZTg2LzVHcDhFZTlRQVZ6TUd4TWF0Lzg2OENpbjNR?= =?utf-8?B?VkNUQSszU2k5YUkvZndMTGFjY3RSSWRnZmFqTVhjOHZ4WS9sajRZdFRGYk9t?= =?utf-8?B?bmhrSGFBMTBYMDE2SFV0VnFEUEh1eDYreEx1WTdxM2VkcDFQVGh3QXJaT21m?= =?utf-8?B?SVA5bWNYVU9UMi9CN2xNZ1kxZDltc0tnTldoSXRoN3Q2aHpaeXZSNnh3b3dS?= =?utf-8?B?VHd5MFZTVCtlcUFiMmdsSHFwamRqWGw1cTVNWUlBVmpNVExKRDdNcy9EZ1lW?= =?utf-8?B?MFRPeFlEQVFzRnYvUzRGWCtZaFAzRnBreG1xYVMzL1JOT094eGdpREs3YjdJ?= =?utf-8?B?V3RQd3hCdXpQRGxobWVOcVByNjUzME1Uck50Tnp3MTk4dFowRVFTSVZBbGNt?= =?utf-8?B?Sm5WNk8vdzIyMU9IYTlDdmQ2TS9yb1BJa245ODVmZ25UTUhUZldDU21BMGEw?= =?utf-8?B?Z3BOaHczTVJUcXZGNisvOVJjM1laOVFBQ2preEhwampRMU1qWkxIUjI5VHhp?= =?utf-8?B?bHlMbG53anR2YnVPR3lzNFRIOExhWG5ZZXhYckNlVFZ4MUNKeER1MS9Lc0Jq?= =?utf-8?B?aFQwa3Y3MkQrSzFjS2xjUkVKa0Jzd0s2UkhEUFNLcFVIaDh1MjBrNjlHdDNQ?= =?utf-8?B?UjFqallZbG1CK3MwOU5IcDBsc3dob2E2dTRRTDlEMzR6QURqcDBEd1pyV2Np?= =?utf-8?B?Z0lFb2JBMkM3RXhLRXFuOVpYSmxzbDBSdUNSUXpLMlRvMmN4SmNtMlZBL2h5?= =?utf-8?B?SmtQa2pGMzRYUG42aEJXSkJUdEJGaEhGb20zOHhja0x4Mm0xdHpjUlNkeXNJ?= =?utf-8?B?Y1VrRHViK3RYaTJLM3U1b1p2Qm5oNzhQVGtreFg5OGZkanM5TWpzNGtzVUpZ?= =?utf-8?B?djduMmdnZWlSQk1ZYVlDSE1iemtyZXNYOEU4U3EyWnJUaDQ2Yi95VWpaampK?= =?utf-8?B?OWNPZHVsWVhTT2dVaGhmcXpIVTh4TjZzZ2Z3ZmswMWV6M1VvUTVHQ2M4SGJX?= =?utf-8?B?ZGlkNHY2eG1DK3U1KzRlY2lMTlpuQmdzejBLc05TTFBRY1gxa2pWOUh0aHE4?= =?utf-8?B?Y2hFR3hrcFZqbFFYbTBMaGgxY3FTSUxEcWpNSHE1MEVocHhrK1MrbVNteHlm?= =?utf-8?B?aXlsdVdHWWwvWjlLc2RCZ2gvTlVXNDVubVRYL2dkQkdDejBlTHJhNFNsakNl?= =?utf-8?B?L1huL3ByemZHUzhlTVlFSFo5TkVMbTZaWnhKYVNqTThwd285VGtrUmlKVVBw?= =?utf-8?B?UXZMdDgycnNtRmYrTFlCNUtxeC9hSzdRcHkvcEtNUGZpL1FXZnBCTEpqWCtB?= =?utf-8?B?NVNoRExmY05LS1dNR0JPTGlDQ0ZGekN5eGlBUWhsQTB4VHllMTZBOHZDWllD?= =?utf-8?B?d3lWWjRHc3I4VzZhMVJsaXlsaldDYUV4N0laL2JNUXU1cGx3dStDMUFvZk8y?= =?utf-8?B?NlVOZ211UHRYUFBmVFVEQUNLczNBb01JRVI4bnQxYkx0R0M1V255UWUwTkdr?= =?utf-8?B?R284MytsUnRGWC8rSlU4WHNac3RoV2I2N1c1cWFEUVJ6NGJmTzlKdHlhLy9X?= =?utf-8?B?ZmRuWGtKVmVWTWxiYU1EV05zeXVpK0ZTeFMzU1JpNnpmVm91TitSS0VHSVJ4?= =?utf-8?B?azhZMnlEVExER1JxOTVTeVJDV1huSHBnV1drNDZkNW5QMXFXcm9TWmxibCsx?= =?utf-8?B?emVmYXdGdjYvR2Y2aTMwYWlDTElHRkJJR25mVHl2U3dLUS93YXZySVFBYllE?= =?utf-8?Q?nODzA053c0YB+fjk=3D?= X-OriginatorOrg: advantech.com X-MS-Exchange-CrossTenant-Network-Message-Id: f86e397f-9116-417a-b839-08dee1c05950 X-MS-Exchange-CrossTenant-AuthSource: PSAPR02MB4502.apcprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jul 2026 15:55:38.3072 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: a77d40d9-dcba-4dda-b571-5f18e6da853f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: r4REQdZmml//hp4r7dZhElX7BuWjK0Jgo8kqKMA+9flzqbH/jr7TThyRvmIDa4WSzEMKva/is8IRHxqXor6RFH2QkcHflZc4hvDACxEm2f8= X-MS-Exchange-Transport-CrossTenantHeadersStamped: KUXPR02MB8880 This commit adds the driver to control the Advantech EIO Fan block, which is included in the Advantech EIO Embedded Controller. Signed-off-by: Ramiro Oliveira --- MAINTAINERS | 1 + drivers/thermal/Kconfig | 8 + drivers/thermal/Makefile | 1 + drivers/thermal/eio_fan.c | 478 ++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 488 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index ded89e70d6cc..5d5d4ac54030 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -616,6 +616,7 @@ F: drivers/gpio/gpio-eio.c F: drivers/hwmon/eio-hwmon.c F: drivers/i2c/busses/i2c-eio.c F: drivers/mfd/eio_core.c +F: drivers/thermal/eio_fan.c F: drivers/thermal/eio_thermal.c F: drivers/video/backlight/eio_bl.c F: drivers/watchdog/eio_wdt.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index fc078012a989..a52ec50c8cc5 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -436,6 +436,14 @@ config EIO_THERMAL the Linux thermal framework. It communicates with the EC through the EIO MFD core. =20 +config EIO_FAN + tristate "Advantech EIO Fan cooling device" + depends on MFD_EIO && THERMAL + help + Fan cooling device for the Advantech EIO. This driver exposes a + thermal cooling device with controllable states (e.g. Auto/Manua= l/PWM). + It communicates with the EC through the EIO MFD core. + menu "Mediatek thermal drivers" depends on ARCH_MEDIATEK || COMPILE_TEST source "drivers/thermal/mediatek/Kconfig" diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 6abce8933e34..872f733b6ad4 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_IMX91_THERMAL) +=3D imx91_thermal.o obj-$(CONFIG_MAX77620_THERMAL) +=3D max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) +=3D qoriq_thermal.o obj-$(CONFIG_DA9062_THERMAL) +=3D da9062-thermal.o +obj-$(CONFIG_EIO_FAN) +=3D eio_fan.o obj-$(CONFIG_EIO_THERMAL) +=3D eio_thermal.o obj-y +=3D intel/ obj-$(CONFIG_TI_SOC_THERMAL) +=3D ti-soc-thermal/ diff --git a/drivers/thermal/eio_fan.c b/drivers/thermal/eio_fan.c new file mode 100644 index 000000000000..e07b18c2ec44 --- /dev/null +++ b/drivers/thermal/eio_fan.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * eio_fan + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Thermal zone driver for Advantech EIO embedded controller's smart + * fan mechanism. + * + * We create a sysfs 'name' of the zone, point out where the fan is. Such = as + * CPU0, SYS3, etc. + * + * The sysfs 'fan_mode' can be one of 'Stop', 'Full', 'Manual' or 'Auto'. + * If 'Manual'. You can control fan speed via sysfs 'PWM'. + * If it is 'Auto'. It enables the smart fan mechanism as below. + * + * In EIO chip. The smart fan has 3 trips. When the temperature is: + * - Over Temp High(trip0), the Fan runs at the fan PWM High. + * - Between Temp Low and Temp High(trip1 - trip0), the fan PWM value slop= es + * from PWM Low to PWM High. + * - Between Temp Stop and Temp Low(trip2 - trip1), the fan PWM is PWM low. + * - Below Temp Stop, the fan stopped. + * + * (PWM)| + * | + * High |............................. ______________ + * (Max)| /: + * | / : + * | / : + * | / : + * | / : + * | / : + * | / : + * | / : + * Low |.......... __________/ : + * | | : : + * | | : : + * 0 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D+---------+--------+------------- + * 0 Stop Low High (Temp) + * + * Copyright (C) 2025 Advantech Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_FAN_WRITE 0x24 +#define CMD_FAN_READ 0x25 +#define FAN_MAX 0x04 + +#define CMD_THERM_WRITE 0x10 +#define CMD_THERM_READ 0x11 +#define THERM_MAX 0x04 +#define THERM_MULTI 100 + +#define CTRL_STATE 0x00 +#define CTRL_TYPE 0x01 +#define CTRL_CTRL 0x02 +#define CTRL_ERROR 0x04 +#define CTRL_VALUE 0x10 +#define CTRL_INVERT 0x11 +#define CTRL_FREQ 0x12 +#define CTRL_THERM_HIGH 0x13 +#define CTRL_THERM_LOW 0x14 +#define CTRL_THERM_STOP 0x15 +#define CTRL_PWM_HIGH 0x16 +#define CTRL_PWM_LOW 0x17 +#define CTRL_THERM_SRC 0x20 + +#define CTRLMODE_STOP 0x00 +#define CTRLMODE_FULL 0x01 +#define CTRLMODE_MANUAL 0x02 +#define CTRLMODE_AUTO 0x03 + +#define DUTY_MAX 100 +#define UNIT_PER_TEMP 10 +#define NAME_SIZE 4 + +#define TRIP_HIGH 0 +#define TRIP_LOW 1 +#define TRIP_STOP 2 +#define TRIP_NUM 3 + +/* Bitfields inside CTRL_CTRL */ +#define FAN_MODE_MASK GENMASK(1, 0) +#define FAN_SCM_BIT BIT(2) +#define FAN_FRAME_BIT BIT(3) +#define FAN_SRC_MASK GENMASK(7, 4) + +#define FAN_SRC(val) (((int)(val)) >> 4) + +#ifndef DECI_KELVIN_TO_MILLI_CELSIUS +#define DECI_KELVIN_TO_MILLI_CELSIUS(t) ((((t) - 2731) * 100)) +#endif + +#ifndef MILLI_CELSIUS_TO_DECI_KELVIN +#define MILLI_CELSIUS_TO_DECI_KELVIN(t) ((((t) / 100) + 2731)) +#endif + +static const u8 pmc_len[CTRL_THERM_SRC + 1] =3D { +/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */ + 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 4, 2, 2, 2, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, + 1, +}; + +static const char fan_name[0x20][NAME_SIZE + 1] =3D { + "CPU0", "CPU1", "CPU2", "CPU3", "SYS0", "SYS1", "SYS2", "SYS3", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "OEM0", "OEM1", "OEM2", "OEM3", +}; + +struct eio_fan_trip { + u8 trip_ctl; +}; + +struct eio_fan_dev { + struct device *mfd; + struct device *dev; + u8 id; + struct thermal_zone_device *tzd; + struct eio_fan_trip trip_priv[TRIP_NUM]; +}; + +static int pmc_write(struct device *mfd, u8 ctrl, u8 id, void *data) +{ + if (ctrl >=3D ARRAY_SIZE(pmc_len)) + return -EINVAL; + + struct pmc_op op =3D { + .cmd =3D CMD_FAN_WRITE, + .control =3D ctrl, + .device_id =3D id, + .size =3D pmc_len[ctrl], + .payload =3D (u8 *)data, + }; + return eio_core_pmc_operation(mfd, &op); +} + +static int pmc_read(struct device *mfd, u8 ctrl, u8 id, void *data) +{ + struct pmc_op op =3D { + .cmd =3D CMD_FAN_READ, + .control =3D ctrl, + .device_id =3D id, + .size =3D pmc_len[ctrl], + .payload =3D (u8 *)data, + }; + return eio_core_pmc_operation(mfd, &op); +} + +static int pmc_read_therm(struct device *mfd, u8 ctrl, u8 id, void *data) +{ + struct pmc_op op =3D { + .cmd =3D CMD_THERM_READ, + .control =3D ctrl, + .device_id =3D id, + .size =3D 2, + .payload =3D (u8 *)data, + }; + return eio_core_pmc_operation(mfd, &op); +} + +static int eio_fan_get_temp(struct thermal_zone_device *tzd, int *temp) +{ + struct eio_fan_dev *fan =3D thermal_zone_device_priv(tzd); + struct device *mfd =3D fan->mfd; + u8 ch =3D fan->id; + int sensor =3D 0; + u16 val =3D 0; + int ret; + + ret =3D pmc_read(mfd, CTRL_CTRL, ch, &sensor); + if (ret) + return ret; + + ret =3D pmc_read_therm(mfd, CTRL_VALUE, (u8)FAN_SRC(sensor), &val); + if (ret) + return ret; + + *temp =3D DECI_KELVIN_TO_MILLI_CELSIUS(val); + return 0; +} + +static int eio_fan_set_trip_temp(struct thermal_zone_device *tzd, + const struct thermal_trip *trip, int temp) +{ + struct eio_fan_dev *fan =3D thermal_zone_device_priv(tzd); + const struct eio_fan_trip *fan_trip =3D trip->priv; + u8 ctl =3D CTRL_THERM_HIGH + fan_trip->trip_ctl; + u16 val; + + if (temp < 1000) + return -EINVAL; + + val =3D MILLI_CELSIUS_TO_DECI_KELVIN(temp); + return pmc_write(fan->mfd, ctl, fan->id, &val); +} + +static bool eio_fan_should_bind(struct thermal_zone_device *tzd, + const struct thermal_trip *trip, + struct thermal_cooling_device *cdev, + struct cooling_spec *spec) +{ + struct eio_fan_dev *tz_fan =3D thermal_zone_device_priv(tzd); + struct eio_fan_dev *cd_fan =3D cdev->devdata; + + if (!tz_fan || !cd_fan) + return false; + + if (tz_fan->mfd !=3D cd_fan->mfd || tz_fan->id !=3D cd_fan->id) + return false; + + return true; +} + +static const struct thermal_zone_device_ops zone_ops =3D { + .get_temp =3D eio_fan_get_temp, + .set_trip_temp =3D eio_fan_set_trip_temp, + .should_bind =3D eio_fan_should_bind, +}; + +static int eio_fan_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state =3D 100; + return 0; +} + +static int eio_fan_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct eio_fan_dev *fan =3D cdev->devdata; + int fan_mode =3D 0; + u8 duty =3D 0; + int ret =3D 0; + + *state =3D 0; + ret =3D pmc_read(fan->mfd, CTRL_CTRL, fan->id, &fan_mode); + if (ret) + return ret; + + switch (fan_mode & FAN_MODE_MASK) { + case CTRLMODE_STOP: + *state =3D 0; + break; + case CTRLMODE_FULL: + *state =3D 100; + break; + case CTRLMODE_AUTO: + *state =3D 0; + ret =3D 0; + break; + case CTRLMODE_MANUAL: + ret =3D pmc_read(fan->mfd, CTRL_VALUE, fan->id, &duty); + if (ret) + return ret; + duty =3D (u8)clamp_val(duty, 0, 100); + *state =3D duty; + break; + default: + *state =3D 0; + return -EINVAL; + } + return 0; +} + +static int eio_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct eio_fan_dev *fan =3D cdev->devdata; + u8 ctrl =3D 0; + u8 duty; + int ret; + + ret =3D pmc_read(fan->mfd, CTRL_CTRL, fan->id, &ctrl); + if (ret) + return ret; + + if ((ctrl & FAN_MODE_MASK) !=3D CTRLMODE_MANUAL) + return -EOPNOTSUPP; + + duty =3D (u8)clamp_val(state, 0, 100); + + ret =3D pmc_write(fan->mfd, CTRL_VALUE, fan->id, &duty); + + return ret; +} + +static const struct thermal_cooling_device_ops cooling_ops =3D { + .get_max_state =3D eio_fan_get_max_state, + .get_cur_state =3D eio_fan_get_cur_state, + .set_cur_state =3D eio_fan_set_cur_state, +}; + +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + static const char * const names[] =3D { "Stop", "Full", "Manual", "Auto" = }; + struct thermal_zone_device *tzd =3D dev_get_drvdata(dev); + struct eio_fan_dev *fan =3D thermal_zone_device_priv(tzd); + u8 mode =3D 0; + + int ret =3D pmc_read(fan->mfd, CTRL_CTRL, fan->id, &mode); + + if (ret) + return ret; + + return sysfs_emit(buf, "%s\n", names[mode & 0x03]); +} + +static ssize_t fan_mode_store(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + static const char * const names[] =3D { "Stop", "Full", "Manual", "Auto" = }; + struct thermal_zone_device *tzd =3D dev_get_drvdata(dev); + struct eio_fan_dev *fan =3D thermal_zone_device_priv(tzd); + u8 ctrl, newc; + int mode_idx, ret; + + for (mode_idx =3D 0; mode_idx < ARRAY_SIZE(names); mode_idx++) { + if (strncasecmp(buf, names[mode_idx], strlen(names[mode_idx]))) + continue; + + ret =3D pmc_read(fan->mfd, CTRL_CTRL, fan->id, &ctrl); + if (ret) + return -EIO; + + newc =3D ctrl & FAN_SRC_MASK; + + switch (mode_idx) { + case CTRLMODE_AUTO: + newc |=3D FAN_FRAME_BIT; + newc &=3D ~FAN_SCM_BIT; + newc |=3D CTRLMODE_AUTO; + break; + case CTRLMODE_MANUAL: + newc &=3D ~FAN_FRAME_BIT; + newc &=3D ~FAN_SCM_BIT; + newc |=3D CTRLMODE_MANUAL; + break; + case CTRLMODE_FULL: + newc &=3D ~FAN_FRAME_BIT; + newc &=3D ~FAN_SCM_BIT; + newc |=3D CTRLMODE_FULL; + break; + case CTRLMODE_STOP: + default: + newc &=3D ~FAN_FRAME_BIT; + newc &=3D ~FAN_SCM_BIT; + newc |=3D CTRLMODE_STOP; + break; + } + + ret =3D pmc_write(fan->mfd, CTRL_CTRL, fan->id, &newc); + return ret ? ret : count; + } + + return -EINVAL; +} + +static DEVICE_ATTR_RW(fan_mode); + +static int eio_fan_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + unsigned int fan_id; + int ret; + + if (!dev_get_drvdata(dev->parent)) { + dev_err(dev, "eio_core not present\n"); + return -ENODEV; + } + + for (fan_id =3D 0; fan_id < FAN_MAX; fan_id++) { + u8 state =3D 0, name =3D 0; + int trip_hi =3D 0, trip_lo =3D 0, trip_stop =3D 0; + int pwm_hi =3D 0, pwm_lo =3D 0; + struct eio_fan_dev *fan; + struct thermal_zone_device *tzd; + struct thermal_cooling_device *cdev; + + if (pmc_read(dev->parent, CTRL_STATE, fan_id, &state) || + pmc_read(dev->parent, CTRL_TYPE, fan_id, &name) || + pmc_read(dev->parent, CTRL_THERM_HIGH, fan_id, &trip_hi) || + pmc_read(dev->parent, CTRL_THERM_LOW, fan_id, &trip_lo) || + pmc_read(dev->parent, CTRL_THERM_STOP, fan_id, &trip_stop) || + pmc_read(dev->parent, CTRL_PWM_HIGH, fan_id, &pwm_hi) || + pmc_read(dev->parent, CTRL_PWM_LOW, fan_id, &pwm_lo)) { + dev_info(dev, "fan%u: pmc read error, skipping\n", fan_id); + continue; + } + + if (!(state & 0x1)) { + dev_info(dev, "fan%u: firmware reports disabled\n", fan_id); + continue; + } + + if (!fan_name[name][0]) { + dev_info(dev, "fan%u: unknown name index %u\n", fan_id, name); + continue; + } + + fan =3D devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL); + if (!fan) + return -ENOMEM; + + fan->mfd =3D dev->parent; + fan->id =3D (u8)fan_id; + + fan->trip_priv[TRIP_HIGH].trip_ctl =3D CTRL_THERM_HIGH; + fan->trip_priv[TRIP_LOW].trip_ctl =3D CTRL_THERM_LOW; + fan->trip_priv[TRIP_STOP].trip_ctl =3D CTRL_THERM_STOP; + + struct thermal_trip trips[TRIP_NUM] =3D { + [TRIP_HIGH] =3D { + .type =3D THERMAL_TRIP_ACTIVE, + .temperature =3D DECI_KELVIN_TO_MILLI_CELSIUS(trip_hi), + .flags =3D THERMAL_TRIP_FLAG_RW_TEMP, + .priv =3D &fan->trip_priv[TRIP_HIGH], + }, + [TRIP_LOW] =3D { + .type =3D THERMAL_TRIP_ACTIVE, + .temperature =3D DECI_KELVIN_TO_MILLI_CELSIUS(trip_lo), + .flags =3D THERMAL_TRIP_FLAG_RW_TEMP, + .priv =3D &fan->trip_priv[TRIP_LOW], + }, + [TRIP_STOP] =3D { + .type =3D THERMAL_TRIP_ACTIVE, + .temperature =3D DECI_KELVIN_TO_MILLI_CELSIUS(trip_stop), + .flags =3D THERMAL_TRIP_FLAG_RW_TEMP, + .priv =3D &fan->trip_priv[TRIP_STOP], + }, + }; + + tzd =3D thermal_zone_device_register_with_trips(fan_name[name], + trips, TRIP_NUM, + fan, + &zone_ops, + NULL, + 0, 0); + if (IS_ERR(tzd)) + return PTR_ERR(tzd); + + cdev =3D thermal_cooling_device_register(fan_name[name], fan, &cooling_o= ps); + if (IS_ERR(cdev)) { + thermal_zone_device_unregister(tzd); + dev_err(dev, "fan%u: cdev register failed: %ld\n", + fan_id, PTR_ERR(cdev)); + return PTR_ERR(cdev); + } + + dev_set_drvdata(thermal_zone_device(tzd), tzd); + ret =3D device_create_file(thermal_zone_device(tzd), &dev_attr_fan_mode); + if (ret) + dev_warn(dev, "Error create thermal zone fan_mode sysfs\n"); + } + return 0; +} + +static struct platform_driver eio_fan_driver =3D { + .probe =3D eio_fan_probe, + .driver =3D { + .name =3D "eio_fan", + }, +}; + +module_platform_driver(eio_fan_driver); + +MODULE_AUTHOR("Wenkai Chung "); +MODULE_AUTHOR("Ramiro Oliveira "); +MODULE_DESCRIPTION("Fan driver for Advantech EIO embedded controller"); +MODULE_LICENSE("GPL"); --=20 2.43.0