From patchwork Fri Sep 13 08:29:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Andrei Stefanescu X-Patchwork-Id: 828722 Received: from EUR05-VI1-obe.outbound.protection.outlook.com (mail-vi1eur05on2059.outbound.protection.outlook.com [40.107.21.59]) (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 B62B61D6DD5; Fri, 13 Sep 2024 08:30:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.21.59 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726216241; cv=fail; b=l683YCVehHPai/ew6pZXR0bw6AUZkOn781LON1ufULdMUKOisI3RvzHf33Pd0L1H6SLy2be6sYCFfMSBuo3IE3JvP/Nxn4IM3ixWkFnQBV1Ibg7m5DeTMFMqLZQrYg5HT3xISG4EIwc7qL55RvVYmRcDR4pkxUmW1iAawSNxbms= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726216241; c=relaxed/simple; bh=30wtJJ7kOFYriOJWYVSJoPQZUthCTzHwyvuCYNlL+3k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=aSRD7SGvP/xNL7DHvyvse+wgxodqAtQGpB9CJ9YjFfnDSuCSPHxmMVoKyo0AJkPpKgM99tHink+0ejsa8GsVb9QXs64LPGbkAdTkURJU/0Z4H+bELoLDE35f/eK9TJr7sPrgdlDaQaROUfdqb8MaGKCnFR+Lli3Z5lamQceel5c= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.nxp.com; spf=pass smtp.mailfrom=oss.nxp.com; dkim=pass (2048-bit key) header.d=NXP1.onmicrosoft.com header.i=@NXP1.onmicrosoft.com header.b=iIs0TzH9; arc=fail smtp.client-ip=40.107.21.59 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.nxp.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=NXP1.onmicrosoft.com header.i=@NXP1.onmicrosoft.com header.b="iIs0TzH9" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=H26lUhUpEjAcT+SNbfm5CZxgOVV9OdMX0wJBzB1rUT6iWUG+5dkF6yaFxMG3m7WEox+OKluUP+ITKgHXDqsh+ztZLHsm4z7ZPQnsdBrISlRTaTf8sAZJva0xKl9xlXwNi3H1daaCDZhrZ5PoVflu5S/TK0+3HzBkEU1NzwBlDAB8MeTqmQufsk4YKT9oWzc8tozC0rl0hwVJLeU6mZOnHoS96hoV+6bAWEOb0mCiwjzG8atsFThOl4DBXj8N5Yt1ZLxjzba+rfBbp0E4vfiH9+gdMDur5kOJqGUC9pb8tMDr/vt0gcsKr3cvM4TtqzZKg4gxTYu6wYxU+3oBdrZqIg== 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=/T1rLla+D6DfujtTtzBu7C0IpGMDpAsMtcKH5kh9sUw=; b=mJSsncikqNzTc5XMyhzV6S8BJZ1WrKuc+MrW8Rcbr4KEo8dcqqKlK9NIk5sGWDQeHRxnAOID6NMqFAhm+2hoadGZeoJ5OIqL+jmyhiX8iWLWHRqNdyT2x6pdpYavyHjttwWQQeqMln/Dyx3WtmpeNFpLgZSy7Dp3XAxFyLDulDqj+uJ1eSx4rMC+VNFMScj3Fa0quNIgprOgpBsLC0D0FnXrzcykr+Wg5Acv+NToXIC4HQsmBYjVymcGF23mL2xW331sem9ALJ+nfb251m1nhxUud8lDMrRDAFRPNsAMeVRKtbCF3c85/M8nPVjBXve2nAdNEGCSTfaFquqwsAWFLg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=oss.nxp.com; dmarc=pass action=none header.from=oss.nxp.com; dkim=pass header.d=oss.nxp.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=NXP1.onmicrosoft.com; s=selector1-NXP1-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=/T1rLla+D6DfujtTtzBu7C0IpGMDpAsMtcKH5kh9sUw=; b=iIs0TzH9SPUcMeofEUlzkIojbFcALGu8s++5J6ce1xyFHfqGO/ghbtspD228DSBEkT/RHDK4/WarisXVxSxAw7ZNYUM2rDhdP7JpTsVT5oW/YSDKv5s3OTs2qEB6DVe7hd4BeJrOqz6eJWl/JYGl23qLnmIbmc8T5StaBTgoYfLP78JkoECKxX3fsgr4YL9eKq42lQ7fTvSqPR+DDdSfHPgdEMphRkHaWzxuFun602iqR2Dde6fRvOoDWJczWDIRVJxUUNZm7Npzlv6wgfzX0dFr2ox8sDPXszcwYSPfVPOoMLQxWp4knMfco2iMxvY+FbT3qguG4gwTDbLvRBheQg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=oss.nxp.com; Received: from AM9PR04MB8487.eurprd04.prod.outlook.com (2603:10a6:20b:41a::6) by AS8PR04MB8403.eurprd04.prod.outlook.com (2603:10a6:20b:3f7::16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7962.17; Fri, 13 Sep 2024 08:30:35 +0000 Received: from AM9PR04MB8487.eurprd04.prod.outlook.com ([fe80::6d7a:8d2:f020:455]) by AM9PR04MB8487.eurprd04.prod.outlook.com ([fe80::6d7a:8d2:f020:455%5]) with mapi id 15.20.7962.018; Fri, 13 Sep 2024 08:30:35 +0000 From: Andrei Stefanescu To: Linus Walleij , Bartosz Golaszewski , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chester Lin , Matthias Brugger , Greg Kroah-Hartman , "Rafael J. Wysocki" Cc: linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, NXP S32 Linux Team , Andrei Stefanescu Subject: [PATCH v2 3/4] gpio: siul2-s32g2: add NXP S32G2/S32G3 SoCs support Date: Fri, 13 Sep 2024 11:29:34 +0300 Message-ID: <20240913082937.444367-4-andrei.stefanescu@oss.nxp.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240913082937.444367-1-andrei.stefanescu@oss.nxp.com> References: <20240913082937.444367-1-andrei.stefanescu@oss.nxp.com> X-ClientProxiedBy: AM0PR02CA0134.eurprd02.prod.outlook.com (2603:10a6:20b:28c::31) To AM9PR04MB8487.eurprd04.prod.outlook.com (2603:10a6:20b:41a::6) Precedence: bulk X-Mailing-List: linux-gpio@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AM9PR04MB8487:EE_|AS8PR04MB8403:EE_ X-MS-Office365-Filtering-Correlation-Id: 96f6c686-1b68-4887-60e2-08dcd3ce56f0 X-MS-Exchange-SharedMailbox-RoutingAgent-Processed: True X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; ARA:13230040|52116014|376014|7416014|366016|1800799024|38350700014; X-Microsoft-Antispam-Message-Info: =?utf-8?q?jXjUSz/pD4EEDxkroKFfD2bVEX4cQPO?= =?utf-8?q?fKPIthZU/XOX2TXy2gDjrIv039jDE53Mv/4/oOvpgmmjW4q1IjfqRKdBofypKkTjO?= =?utf-8?q?KAJl8f+9g1A/0zVKYI5on5qbh8zqCEI8iOHFSezJdsj71CabpxCF6Neht0Ki8KAGR?= =?utf-8?q?GrMXPXzLoI1fWvQHtpjiMmhCKi8UM4QLoLOl0NqWdrYKfB9yLdY0xVevdsdS+nSpC?= =?utf-8?q?uL3XWBKVxuUPypmQCQOZKypzbEFq+psl4zK/LbIyZi0MPLBKq9mg7YLsSuEy7n3vu?= =?utf-8?q?mvlMstRzxUxYSGfd4U2zOEd5G3+5eVgNFyUsgSlbdl0tuDAzRuyS/JgjlZeJ5Zbbw?= =?utf-8?q?3SR0gx9r55fCMQnOghsljBSeaNTuQLwaPmoAqp2PI1hSYch7eK4NJuM3GZrTOyrcc?= =?utf-8?q?xC5p/fcrzqd5if48sy9SpCwj6O6Ubg2I0H8Hznf/wNHPZzZooptIScZ6bex5ToROk?= =?utf-8?q?lndGqD28pROjoWih51xRBXgGblMXahLUXFfeS7a/tM6HAxoxv/WOflpCUvHEn83Gz?= =?utf-8?q?ngYAifSHRhqmg01gbT6CjgagFHT2TC79WioupkMxWkPeeL3baHqBF8wUCKaby2bqy?= =?utf-8?q?+NIAWW8lV3In2GEvX76YYKbxXykfUw/b3K+5E2hE3VJ+re43Dx2StXkPLV8hqkeV/?= =?utf-8?q?0mdDsl1oMK9YEvg0mX0LQp+Pm0psOqYRj0skR+zDUceb+u4jk0ZyEp9xQ2/YFCCu+?= =?utf-8?q?Os6XF4J/XeYPSOumqjFES/pbk5qs6MaE6fcziWF8gpbg3swWlXi09OlsarOsDk0z6?= =?utf-8?q?5Z0HUwpoF2ocsl9GCeRTqZCu9M9HEsC8T3jXtHdwc0zyLzSDcj98QCFkwh2Jyt2xT?= =?utf-8?q?uVw/Y1zLuWT92bye6XLJObO0vUj49dLPm2zTrPVoQXjyyRt8MpV+aMfwG4hM2TWoT?= =?utf-8?q?Opa9ZibafKEDU7kgwwP1raJQuLUjIX7OGaL4K9ITpNlbeyCsBU/A2oDPIWR2sLdYc?= =?utf-8?q?U4ozG28FsElT4bqg6NnltSsy0s5oFS+qK39rQ4U3Wt9/Qi/asf1Oxe0Z0W5p3z33b?= =?utf-8?q?yHnU55oYi3b2bpv6Xs8EI1DfKO2Ms3wbzn5YhQGEy1WrVNYKJl6tjtdY/l34yDWtn?= =?utf-8?q?2NisQ8q1MwRJq9J6rLm7JhnAP3xOIykY52IAPBOot46MOrbLoevG/9vxgXqjGbMq1?= =?utf-8?q?rOSofjhD198XcHXBhNMhW4vVIguT479x2eBa5Ul8KqQjtxNAlKB49BrNy/XK3diS3?= =?utf-8?q?gOOmPnPQIm9aPnAJucSYJD8ZCNmCGun6+NkFFKmu6RhJltpwlCTBHcAaROJoDI5z4?= =?utf-8?q?2VqW++3NNlheZlSs7ruKbSHh568ErD6d24ZkDUzwMK5cWTSVLFNZ0kdMD/qJb439v?= =?utf-8?q?PgC1WnmMoTsPMeMmwg/rzbGvsmXGgo6ygg=3D=3D?= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AM9PR04MB8487.eurprd04.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230040)(52116014)(376014)(7416014)(366016)(1800799024)(38350700014); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?q?lvMr5GjpKWJ2NBILacqgpiwzI3kV?= =?utf-8?q?p3E91IA0pI3fIhYRH1rAdb7DsnW7NMkyM4nBnnrZP5X7N/1jfEtibMro3NSDCigQS?= =?utf-8?q?jvff8n+ELgiV9H+KIyI8DPVmnjxkYQF8jTtjEO4yueha0FzVx56eWjJBA0yxpmVER?= =?utf-8?q?yyIA0p6nNMKVZT+NPnz9TFYr1O4Cn+KacJZtdc/OqDv6HaR4wPplXOhkXNdhpfAqg?= =?utf-8?q?dT3Uw8owt4KedJB2KW/7KlJe7kT8ax8oQzXoARBaQ/lfX3LAFSuLaBkP7VhZs6AJk?= =?utf-8?q?rFUjz9zkDzHbiy7cpi/5VmY9GKNwvNHgpIoAHuPmTifAKxcQjFQYIpbTMwEvpjGHx?= =?utf-8?q?CiagINoBCCiKc0Bs+1xRWMjTPQqk0meg8n7GlwpEkYvVl8AeQHGeeq/R8aXjB+aCW?= =?utf-8?q?9aYOVBkZ63D53FtKWzXVvbTbu52bsPwNa+6JkVPr2RV9OOmIRamX6T6kNyTFG6CLu?= =?utf-8?q?mCBUVBvq0wbfBn2gWB155NDeAi8IGIMObvCTMEPkCN8dgHJLXpP4DAs3PZ11buaxz?= =?utf-8?q?Rzgll96MegwO0xHoYdErycQCx44PvimFcicPZEkPIutmZUoqCFFcwvEhFOdHPS2Uw?= =?utf-8?q?54l5DWWcW+hwTsQ3iFSSo7kHMYuQTbEuduorTqThapWii16iuyaK5zloSGxk/gQrI?= =?utf-8?q?xA9Au7ELnvkf4TTf/eH88Qmn9V+31vVDl3BkQ0apmdOfX5HnNFoT/+QRfTMN5MTgF?= =?utf-8?q?/CQxKevl/qNbJKhwy74ilQqglIw1OgX+WikhCSyYb0tl9Iu9rYgVQhw/XZZzHQw2C?= =?utf-8?q?hHXEjVtZpj4FypFY2Vu/ZmBsCeoFn1ukIhJmsdJ/GRQGjJ1vxKLE/zUgtmG+HE08n?= =?utf-8?q?zGK+DGzjAiVwQZaD9rNuGBh7YnhxEKjTiu5fBPmyJuha9RDkVNvqFuyY0bAxJysWx?= =?utf-8?q?rwCSwkt6DjGT2q3l8ilAW9B6H6kH164dUNW0QfyOxucb23oSXjvPEm4kTBkYeGauG?= =?utf-8?q?ZtzT+++FzuMkLJD1lTErvRPJXr5S0xHE+8n+2yjp2BmJ0ublUZ7OLT3pwmdq/VxNH?= =?utf-8?q?QRH9EmGEPTbxCSgwxBJgCpyIvpLEEh47Qowblmps6Bds2VSiVzLdARBLXe0sa7JmP?= =?utf-8?q?4KgeHH8ax/iaZ+deGafoZKAkMqLlFiUJIk2u+kwIYJ0qqOBBIq4DZtp94lgkH1Qvc?= =?utf-8?q?206PulS9I27t99YCq/5/OiTreVIXpO2hiBwTzqC2jxdpAgc/5l4Sj+jLILHV+oyzF?= =?utf-8?q?uH8Bxcm++/EZvmdZYwdpYqA2NZB3iRtwTjTsEF1shUX63eGTljTajJo3UVJhM44Me?= =?utf-8?q?NXlhDnO/0kqhAojLdStbIqKqehuiNt4RasqjPS2YYQw11YcJWIIV/ncevNigXWHqi?= =?utf-8?q?c+TvFkOUEGTGrEdyRs0+tlsYTE3ozEh9UKBVx7mlAZTThDlJmQ14lIgTTYy7bD81P?= =?utf-8?q?1fzsqp2UU664aySHdqCywOb7ueJJKwmThbhP9o1vUEsr++dJC5KsM64zuiFXaRF/W?= =?utf-8?q?IZZLZD7eZPV5hZNhh0a6QUBiBsvZqJZZBz5NLK2O57bGXqj/LHSvvQu3lGOG1RD8B?= =?utf-8?q?HR80+NgNJgeL7dROPBPRiElzhdjmBRM7WA=3D=3D?= X-OriginatorOrg: oss.nxp.com X-MS-Exchange-CrossTenant-Network-Message-Id: 96f6c686-1b68-4887-60e2-08dcd3ce56f0 X-MS-Exchange-CrossTenant-AuthSource: AM9PR04MB8487.eurprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 13 Sep 2024 08:30:35.6788 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 686ea1d3-bc2b-4c6f-a92c-d99c5c301635 X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: AHWbQwxUvfaD2rNI4Uz17mlEXeQ3+i4igBP4AdmMh1fs31qV6oryhMYcWk/p6psjwW0bEyaEH8Mg7zHLcUvZCD2eqKdebDZcPqvCNtxLBLI= X-MS-Exchange-Transport-CrossTenantHeadersStamped: AS8PR04MB8403 Add the GPIO driver for S32G2/S32G3 SoCs. This driver uses the SIUL2 (System Integration Unit Lite2) hardware module. There are two SIUL2 hardware modules present, SIUL2_0(controlling GPIOs 0-101) and SIUL2_1 for the rest. The GPIOs are not fully contiguous, there are some gaps: - GPIO102 up to GPIO111(inclusive) are invalid - GPIO123 up to GPIO143(inclusive) are invalid Some GPIOs are input only(i.e. GPI182) though this restriction is not yet enforced in code. This patch adds basic GPIO functionality(no interrupts, no suspend/resume functions). Signed-off-by: Ghennadi Procopciuc Signed-off-by: Larisa Grigore Signed-off-by: Phu Luu An Signed-off-by: Andrei Stefanescu --- drivers/gpio/Kconfig | 10 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-siul2-s32g2.c | 581 ++++++++++++++++++++++++++++++++ 3 files changed, 592 insertions(+) create mode 100644 drivers/gpio/gpio-siul2-s32g2.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 58f43bcced7c..75a6ca60ebc7 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -643,6 +643,16 @@ config GPIO_SIOX Say yes here to support SIOX I/O devices. These are units connected via a SIOX bus and have a number of fixed-direction I/O lines. +config GPIO_SIUL2_S32G2 + tristate "GPIO driver for S32G2/S32G3" + depends on ARCH_S32 || COMPILE_TEST + depends on OF_GPIO + select REGMAP_MMIO + help + This enables support for the SIUL2 GPIOs found on the S32G2/S32G3 + chips. Say yes here to enable the SIUL2 to be used as an GPIO + controller for S32G2/S32G3 platforms. + config GPIO_SNPS_CREG bool "Synopsys GPIO via CREG (Control REGisters) driver" depends on ARC || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 64dd6d9d730d..fb6e770a64b9 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -149,6 +149,7 @@ obj-$(CONFIG_GPIO_SCH) += gpio-sch.o obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o obj-$(CONFIG_GPIO_SIM) += gpio-sim.o obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o +obj-$(CONFIG_GPIO_SIUL2_S32G2) += gpio-siul2-s32g2.o obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o diff --git a/drivers/gpio/gpio-siul2-s32g2.c b/drivers/gpio/gpio-siul2-s32g2.c new file mode 100644 index 000000000000..205a1a4d216d --- /dev/null +++ b/drivers/gpio/gpio-siul2-s32g2.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SIUL2 GPIO support. + * + * Copyright (c) 2016 Freescale Semiconductor, Inc. + * Copyright 2018-2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PGPDOs are 16bit registers that come in big endian + * order if they are grouped in pairs of two. + * + * For example, the order is PGPDO1, PGPDO0, PGPDO3, PGPDO2... + */ +#define SIUL2_PGPDO(N) (((N) ^ 1) * 2) +#define S32G2_SIUL2_NUM 2 +#define S32G2_PADS_DTS_TAG_LEN (7) + +#define SIUL2_GPIO_16_PAD_SIZE 16 + +/** + * struct siul2_device_data - platform data attached to the compatible. + * @pad_access: access table for I/O pads, consists of S32G2_SIUL2_NUM tables. + * @reset_cnt: reset the pin name counter to zero when switching to SIUL2_1. + */ +struct siul2_device_data { + const struct regmap_access_table **pad_access; + const bool reset_cnt; +}; + +/** + * struct siul2_desc - describes a SIUL2 hw module. + * @pad_access: array of valid I/O pads. + * @opadmap: the regmap of the Parallel GPIO Pad Data Out Register. + * @ipadmap: the regmap of the Parallel GPIO Pad Data In Register. + * @gpio_base: the first GPIO pin. + * @gpio_num: the number of GPIO pins. + */ +struct siul2_desc { + const struct regmap_access_table *pad_access; + struct regmap *opadmap; + struct regmap *ipadmap; + u32 gpio_base; + u32 gpio_num; +}; + +/** + * struct siul2_gpio_dev - describes a group of GPIO pins. + * @platdata: the platform data. + * @siul2: SIUL2_0 and SIUL2_1 modules information. + * @pin_dir_bitmap: the bitmap with pin directions. + * @gc: the GPIO chip. + * @lock: mutual access to bitmaps. + */ +struct siul2_gpio_dev { + const struct siul2_device_data *platdata; + struct siul2_desc siul2[S32G2_SIUL2_NUM]; + unsigned long *pin_dir_bitmap; + struct gpio_chip gc; + raw_spinlock_t lock; +}; + +static const struct regmap_range s32g2_siul20_pad_yes_ranges[] = { + regmap_reg_range(SIUL2_PGPDO(0), SIUL2_PGPDO(0)), + regmap_reg_range(SIUL2_PGPDO(1), SIUL2_PGPDO(1)), + regmap_reg_range(SIUL2_PGPDO(2), SIUL2_PGPDO(2)), + regmap_reg_range(SIUL2_PGPDO(3), SIUL2_PGPDO(3)), + regmap_reg_range(SIUL2_PGPDO(4), SIUL2_PGPDO(4)), + regmap_reg_range(SIUL2_PGPDO(5), SIUL2_PGPDO(5)), + regmap_reg_range(SIUL2_PGPDO(6), SIUL2_PGPDO(6)), +}; + +static const struct regmap_access_table s32g2_siul20_pad_access_table = { + .yes_ranges = s32g2_siul20_pad_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(s32g2_siul20_pad_yes_ranges), +}; + +static const struct regmap_range s32g2_siul21_pad_yes_ranges[] = { + regmap_reg_range(SIUL2_PGPDO(7), SIUL2_PGPDO(7)), + regmap_reg_range(SIUL2_PGPDO(9), SIUL2_PGPDO(9)), + regmap_reg_range(SIUL2_PGPDO(10), SIUL2_PGPDO(10)), + regmap_reg_range(SIUL2_PGPDO(11), SIUL2_PGPDO(11)), +}; + +static const struct regmap_access_table s32g2_siul21_pad_access_table = { + .yes_ranges = s32g2_siul21_pad_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(s32g2_siul21_pad_yes_ranges), +}; + +static const struct regmap_access_table *s32g2_pad_access_table[] = { + &s32g2_siul20_pad_access_table, + &s32g2_siul21_pad_access_table +}; + +static_assert(ARRAY_SIZE(s32g2_pad_access_table) == S32G2_SIUL2_NUM); + +static const struct siul2_device_data s32g2_device_data = { + .pad_access = s32g2_pad_access_table, + .reset_cnt = true, +}; + +static int siul2_get_gpio_pinspec(struct platform_device *pdev, + struct of_phandle_args *pinspec, + unsigned int range_index) +{ + struct device_node *np = pdev->dev.of_node; + + return of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, + range_index, pinspec); +} + +static struct regmap *siul2_offset_to_regmap(struct siul2_gpio_dev *dev, + unsigned int offset, + bool input) +{ + struct siul2_desc *siul2; + size_t i; + + for (i = 0; i < ARRAY_SIZE(dev->siul2); i++) { + siul2 = &dev->siul2[i]; + if (offset >= siul2->gpio_base && + offset - siul2->gpio_base < siul2->gpio_num) + return input ? siul2->ipadmap : siul2->opadmap; + } + + return NULL; +} + +static void siul2_gpio_set_direction(struct siul2_gpio_dev *dev, + unsigned int gpio, int dir) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&dev->lock, flags); + + if (dir == GPIO_LINE_DIRECTION_IN) + __clear_bit(gpio, dev->pin_dir_bitmap); + else + __set_bit(gpio, dev->pin_dir_bitmap); + + raw_spin_unlock_irqrestore(&dev->lock, flags); +} + + +static int siul2_get_direction(struct siul2_gpio_dev *dev, + unsigned int gpio) +{ + return test_bit(gpio, dev->pin_dir_bitmap) ? GPIO_LINE_DIRECTION_OUT : + GPIO_LINE_DIRECTION_IN; +} + +static struct siul2_gpio_dev *to_siul2_gpio_dev(struct gpio_chip *chip) +{ + return container_of(chip, struct siul2_gpio_dev, gc); +} + +static int siul2_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio) +{ + struct siul2_gpio_dev *gpio_dev; + int ret = 0; + + ret = pinctrl_gpio_direction_input(chip, gpio); + if (ret) + return ret; + + gpio_dev = to_siul2_gpio_dev(chip); + siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_IN); + + return 0; +} + +static int siul2_gpio_get_dir(struct gpio_chip *chip, unsigned int gpio) +{ + return siul2_get_direction(to_siul2_gpio_dev(chip), gpio); +} + +static unsigned int siul2_pin2pad(unsigned int pin) +{ + return pin / SIUL2_GPIO_16_PAD_SIZE; +} + +static u16 siul2_pin2mask(unsigned int pin) +{ + /** + * From Reference manual : + * PGPDOx[PPDOy] = GPDO(x × 16) + (15 - y)[PDO_(x × 16) + (15 - y)] + */ + return BIT(SIUL2_GPIO_16_PAD_SIZE - 1 - pin % SIUL2_GPIO_16_PAD_SIZE); +} + +static void siul2_gpio_set_val(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip); + unsigned int pad, reg_offset; + struct regmap *regmap; + u16 mask; + + mask = siul2_pin2mask(offset); + pad = siul2_pin2pad(offset); + + reg_offset = SIUL2_PGPDO(pad); + regmap = siul2_offset_to_regmap(gpio_dev, offset, false); + if (!regmap) + return; + + value = value ? mask : 0; + + regmap_update_bits(regmap, reg_offset, mask, value); +} + +static int siul2_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio, + int val) +{ + struct siul2_gpio_dev *gpio_dev; + int ret = 0; + + gpio_dev = to_siul2_gpio_dev(chip); + siul2_gpio_set_val(chip, gpio, val); + + ret = pinctrl_gpio_direction_output(chip, gpio); + if (ret) + return ret; + + siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_OUT); + + return 0; +} + +static void siul2_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip); + + if (!gpio_dev) + return; + + if (siul2_get_direction(gpio_dev, offset) == GPIO_LINE_DIRECTION_IN) + return; + + siul2_gpio_set_val(chip, offset, value); +} + +static int siul2_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip); + unsigned int mask, pad, reg_offset, data = 0; + struct regmap *regmap; + + mask = siul2_pin2mask(offset); + pad = siul2_pin2pad(offset); + + reg_offset = SIUL2_PGPDO(pad); + regmap = siul2_offset_to_regmap(gpio_dev, offset, true); + if (!regmap) + return -EINVAL; + + regmap_read(regmap, reg_offset, &data); + + return !!(data & mask); +} + +static const struct regmap_config siul2_regmap_conf = { + .val_bits = 32, + .reg_bits = 32, + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, +}; + +static struct regmap *common_regmap_init(struct platform_device *pdev, + struct regmap_config *conf, + const char *name) +{ + struct device *dev = &pdev->dev; + struct resource *res; + resource_size_t size; + void __iomem *base; + + base = devm_platform_get_and_ioremap_resource_byname(pdev, name, &res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to get MEM resource: %s\n", name); + return ERR_PTR(-EINVAL); + } + + size = resource_size(res); + conf->val_bits = conf->reg_stride * 8; + conf->max_register = size - conf->reg_stride; + conf->name = name; + conf->use_raw_spinlock = true; + + if (conf->cache_type != REGCACHE_NONE) + conf->num_reg_defaults_raw = size / conf->reg_stride; + + return devm_regmap_init_mmio(dev, base, conf); +} + +static bool not_writable(__always_unused struct device *dev, + __always_unused unsigned int reg) +{ + return false; +} + +static struct regmap *init_padregmap(struct platform_device *pdev, + struct siul2_gpio_dev *gpio_dev, + int selector, bool input) +{ + const struct siul2_device_data *platdata = gpio_dev->platdata; + struct regmap_config regmap_conf = siul2_regmap_conf; + char dts_tag[S32G2_PADS_DTS_TAG_LEN]; + int err; + + regmap_conf.reg_stride = 2; + + if (selector != 0 && selector != 1) + return ERR_PTR(-EINVAL); + + regmap_conf.rd_table = platdata->pad_access[selector]; + + err = snprintf(dts_tag, ARRAY_SIZE(dts_tag), "%cpads%d", + input ? 'i' : 'o', selector); + if (err < 0) + return ERR_PTR(-EINVAL); + + if (input) { + regmap_conf.writeable_reg = not_writable; + regmap_conf.cache_type = REGCACHE_NONE; + } else { + regmap_conf.wr_table = platdata->pad_access[selector]; + } + + return common_regmap_init(pdev, ®map_conf, dts_tag); +} + +static int siul2_gpio_pads_init(struct platform_device *pdev, + struct siul2_gpio_dev *gpio_dev) +{ + struct device *dev = &pdev->dev; + size_t i; + + for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) { + gpio_dev->siul2[i].opadmap = init_padregmap(pdev, gpio_dev, i, + false); + if (IS_ERR(gpio_dev->siul2[i].opadmap)) { + dev_err(dev, + "Failed to initialize opad2%zu regmap config\n", + i); + return PTR_ERR(gpio_dev->siul2[i].opadmap); + } + + gpio_dev->siul2[i].ipadmap = init_padregmap(pdev, gpio_dev, i, + true); + if (IS_ERR(gpio_dev->siul2[i].ipadmap)) { + dev_err(dev, + "Failed to initialize ipad2%zu regmap config\n", + i); + return PTR_ERR(gpio_dev->siul2[i].ipadmap); + } + } + + return 0; +} + +static int siul2_gen_names(struct device *dev, unsigned int cnt, char **names, + char *ch_index, unsigned int *num_index) +{ + unsigned int i; + + for (i = 0; i < cnt; i++) { + if (i != 0 && !(*num_index % 16)) + (*ch_index)++; + + names[i] = devm_kasprintf(dev, GFP_KERNEL, "P%c_%02d", + *ch_index, 0xFU & (*num_index)++); + if (!names[i]) + return -ENOMEM; + } + + return 0; +} + +static int siul2_gpio_remove_reserved_names(struct device *dev, + struct siul2_gpio_dev *gpio_dev, + char **names) +{ + struct device_node *np = dev->of_node; + int num_ranges, i, j, ret; + u32 base_gpio, num_gpio; + + /* Parse the gpio-reserved-ranges to know which GPIOs to exclude. */ + + num_ranges = of_property_count_u32_elems(dev->of_node, + "gpio-reserved-ranges"); + + /* The "gpio-reserved-ranges" is optional. */ + if (num_ranges < 0) + return 0; + num_ranges /= 2; + + for (i = 0; i < num_ranges; i++) { + ret = of_property_read_u32_index(np, "gpio-reserved-ranges", + i * 2, &base_gpio); + if (ret) { + dev_err(dev, "Could not parse the start GPIO: %d\n", + ret); + return ret; + } + + ret = of_property_read_u32_index(np, "gpio-reserved-ranges", + i * 2 + 1, &num_gpio); + if (ret) { + dev_err(dev, "Could not parse num. GPIOs: %d\n", ret); + return ret; + } + + if (base_gpio + num_gpio > gpio_dev->gc.ngpio) { + dev_err(dev, "Reserved GPIOs outside of GPIO range\n"); + return -EINVAL; + } + + /* Remove names set for reserved GPIOs. */ + for (j = base_gpio; j < base_gpio + num_gpio; j++) { + devm_kfree(dev, names[j]); + names[j] = NULL; + } + } + + return 0; +} + +static int siul2_gpio_populate_names(struct device *dev, + struct siul2_gpio_dev *gpio_dev) +{ + unsigned int num_index = 0; + char ch_index = 'A'; + char **names; + int i, ret; + + names = devm_kcalloc(dev, gpio_dev->gc.ngpio, sizeof(*names), + GFP_KERNEL); + if (!names) + return -ENOMEM; + + for (i = 0; i < S32G2_SIUL2_NUM; i++) { + ret = siul2_gen_names(dev, gpio_dev->siul2[i].gpio_num, + names + gpio_dev->siul2[i].gpio_base, + &ch_index, &num_index); + if (ret) { + dev_err(dev, "Could not set names for SIUL2_%d GPIOs\n", + i); + return ret; + } + + if (gpio_dev->platdata->reset_cnt) + num_index = 0; + + ch_index++; + } + + ret = siul2_gpio_remove_reserved_names(dev, gpio_dev, names); + if (ret) + return ret; + + gpio_dev->gc.names = (const char *const *)names; + + return 0; +} + +static int siul2_gpio_probe(struct platform_device *pdev) +{ + struct siul2_gpio_dev *gpio_dev; + struct device *dev = &pdev->dev; + struct of_phandle_args pinspec; + size_t i, bitmap_size; + struct gpio_chip *gc; + int ret = 0; + + gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL); + if (!gpio_dev) + return -ENOMEM; + + gpio_dev->platdata = &s32g2_device_data; + + for (i = 0; i < S32G2_SIUL2_NUM; i++) + gpio_dev->siul2[i].pad_access = + gpio_dev->platdata->pad_access[i]; + + ret = siul2_gpio_pads_init(pdev, gpio_dev); + if (ret) + return ret; + + gc = &gpio_dev->gc; + + platform_set_drvdata(pdev, gpio_dev); + + raw_spin_lock_init(&gpio_dev->lock); + + for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) { + ret = siul2_get_gpio_pinspec(pdev, &pinspec, i); + if (ret) { + dev_err(dev, + "unable to get pinspec %zu from device tree\n", + i); + return -EINVAL; + } + + of_node_put(pinspec.np); + + if (pinspec.args_count != 3) { + dev_err(dev, "Invalid pinspec count: %d\n", + pinspec.args_count); + return -EINVAL; + } + + gpio_dev->siul2[i].gpio_base = pinspec.args[1]; + gpio_dev->siul2[i].gpio_num = pinspec.args[2]; + } + + gc->base = -1; + + /* In some cases, there is a gap between the SIUL GPIOs. */ + gc->ngpio = gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_base + + gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_num; + + ret = siul2_gpio_populate_names(&pdev->dev, gpio_dev); + if (ret) + return ret; + + bitmap_size = BITS_TO_LONGS(gc->ngpio) * + sizeof(*gpio_dev->pin_dir_bitmap); + gpio_dev->pin_dir_bitmap = devm_kzalloc(dev, bitmap_size, GFP_KERNEL); + if (!gpio_dev->pin_dir_bitmap) + return -ENOMEM; + + gc->parent = dev; + gc->label = dev_name(dev); + + gc->set = siul2_gpio_set; + gc->get = siul2_gpio_get; + gc->set_config = gpiochip_generic_config; + gc->request = gpiochip_generic_request; + gc->free = gpiochip_generic_free; + gc->direction_output = siul2_gpio_dir_out; + gc->direction_input = siul2_gpio_dir_in; + gc->get_direction = siul2_gpio_get_dir; + gc->owner = THIS_MODULE; + + ret = devm_gpiochip_add_data(dev, gc, gpio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to add gpiochip\n"); + + return 0; +} + +static const struct of_device_id siul2_gpio_dt_ids[] = { + { .compatible = "nxp,s32g2-siul2-gpio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, siul2_gpio_dt_ids); + +static struct platform_driver siul2_gpio_driver = { + .driver = { + .name = "s32g2-siul2-gpio", + .of_match_table = siul2_gpio_dt_ids, + }, + .probe = siul2_gpio_probe, +}; + +module_platform_driver(siul2_gpio_driver); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP SIUL2 GPIO"); +MODULE_LICENSE("GPL");