From patchwork Thu Feb 2 16:57:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126555 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A68F1C636D6 for ; Thu, 2 Feb 2023 16:58:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231977AbjBBQ6F (ORCPT ); Thu, 2 Feb 2023 11:58:05 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33726 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232318AbjBBQ5r (ORCPT ); Thu, 2 Feb 2023 11:57:47 -0500 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 9D0A26F227; Thu, 2 Feb 2023 08:57:45 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,267,1669042800"; d="scan'208";a="148266715" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie5.idc.renesas.com with ESMTP; 03 Feb 2023 01:57:45 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id A2928402DBB2; Fri, 3 Feb 2023 01:57:40 +0900 (JST) From: Biju Das To: Rob Herring , Krzysztof Kozlowski Cc: Biju Das , Daniel Lezcano , Thomas Gleixner , Lee Jones , "William Breathitt Gray" , "Thierry Reding" , =?utf-8?q?Uwe_Kleine-K=C3=B6ni?= =?utf-8?q?g?= , devicetree@vger.kernel.org, Geert Uytterhoeven , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org, Krzysztof Kozlowski , Rob Herring Subject: [PATCH v12 1/6] dt-bindings: timer: Document RZ/G2L MTU3a bindings Date: Thu, 2 Feb 2023 16:57:27 +0000 Message-Id: <20230202165732.305650-2-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org The RZ/G2L multi-function timer pulse unit 3 (MTU3a) is embedded in the Renesas RZ/G2L family SoC's. It consists of eight 16-bit timer channels and one 32-bit timer channel. It supports the following functions - Counter - Timer - PWM Signed-off-by: Biju Das Reviewed-by: Krzysztof Kozlowski Reviewed-by: Rob Herring --- Ref: https://patchwork.kernel.org/project/linux-renesas-soc/patch/20221010145222.1047748-2-biju.das.jz@bp.renesas.com/ v11->V12: * No change. v10->v11: * No change v9->v10: * No change v8->v9: * No change v7->v8: * No change v6->v7: * No change v5->v6: * Added Rb tag from Rob and Krzysztof. v4->v5: * Modelled as timer bindings. * Fixed the typo. v3->v4: * Dropped counter and pwm compatibeles as they don't have any resources. * Made rz-mtu3 as pwm provider. * Updated the example and description. v2->v3: * Dropped counter bindings and integrated with mfd as it has only one property. * Removed "#address-cells" and "#size-cells" as it do not have children with unit addresses. * Removed quotes from counter and pwm. * Provided full path for pwm bindings. * Updated the example. v1->v2: * Modelled counter and pwm as a single device that handles multiple channels. * Moved counter and pwm bindings to respective subsystems * Dropped 'bindings' from MFD binding title. * Updated the example * Changed the compatible names. --- .../bindings/timer/renesas,rz-mtu3.yaml | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml diff --git a/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml new file mode 100644 index 000000000000..bffdab0b0185 --- /dev/null +++ b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml @@ -0,0 +1,302 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/timer/renesas,rz-mtu3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G2L Multi-Function Timer Pulse Unit 3 (MTU3a) + +maintainers: + - Biju Das + +description: | + This hardware block consists of eight 16-bit timer channels and one + 32- bit timer channel. It supports the following specifications: + - Pulse input/output: 28 lines max. + - Pulse input 3 lines + - Count clock 11 clocks for each channel (14 clocks for MTU0, 12 clocks + for MTU2, and 10 clocks for MTU5, four clocks for MTU1-MTU2 combination + (when LWA = 1)) + - Operating frequency Up to 100 MHz + - Available operations [MTU0 to MTU4, MTU6, MTU7, and MTU8] + - Waveform output on compare match + - Input capture function (noise filter setting available) + - Counter-clearing operation + - Simultaneous writing to multiple timer counters (TCNT) + (excluding MTU8). + - Simultaneous clearing on compare match or input capture + (excluding MTU8). + - Simultaneous input and output to registers in synchronization with + counter operations (excluding MTU8). + - Up to 12-phase PWM output in combination with synchronous operation + (excluding MTU8) + - [MTU0 MTU3, MTU4, MTU6, MTU7, and MTU8] + - Buffer operation specifiable + - [MTU1, MTU2] + - Phase counting mode can be specified independently + - 32-bit phase counting mode can be specified for interlocked operation + of MTU1 and MTU2 (when TMDR3.LWA = 1) + - Cascade connection operation available + - [MTU3, MTU4, MTU6, and MTU7] + - Through interlocked operation of MTU3/4 and MTU6/7, the positive and + negative signals in six phases (12 phases in total) can be output in + complementary PWM and reset-synchronized PWM operation. + - In complementary PWM mode, values can be transferred from buffer + registers to temporary registers at crests and troughs of the timer- + counter values or when the buffer registers (TGRD registers in MTU4 + and MTU7) are written to. + - Double-buffering selectable in complementary PWM mode. + - [MTU3 and MTU4] + - Through interlocking with MTU0, a mode for driving AC synchronous + motors (brushless DC motors) by using complementary PWM output and + reset-synchronized PWM output is settable and allows the selection + of two types of waveform output (chopping or level). + - [MTU5] + - Capable of operation as a dead-time compensation counter. + - [MTU0/MTU5, MTU1, MTU2, and MTU8] + - 32-bit phase counting mode specifiable by combining MTU1 and MTU2 and + through interlocked operation with MTU0/MTU5 and MTU8. + - Interrupt-skipping function + - In complementary PWM mode, interrupts on crests and troughs of counter + values and triggers to start conversion by the A/D converter can be + skipped. + - Interrupt sources: 43 sources. + - Buffer operation: + - Automatic transfer of register data (transfer from the buffer + register to the timer register). + - Trigger generation + - A/D converter start triggers can be generated + - A/D converter start request delaying function enables A/D converter + to be started with any desired timing and to be synchronized with + PWM output. + - Low power consumption function + - The MTU3a can be placed in the module-stop state. + + There are two phase counting modes. 16-bit phase counting mode in which + MTU1 and MTU2 operate independently, and cascade connection 32-bit phase + counting mode in which MTU1 and MTU2 are cascaded. + + In phase counting mode, the phase difference between two external input + clocks is detected and the corresponding TCNT is incremented or + decremented. + The below counters are supported + count0 - MTU1 16-bit phase counting + count1 - MTU2 16-bit phase counting + count2 - MTU1+ MTU2 32-bit phase counting + + The module supports PWM mode{1,2}, Reset-synchronized PWM mode and + complementary PWM mode{1,2,3}. + + In complementary PWM mode, six positive-phase and six negative-phase PWM + waveforms (12 phases in total) with dead time can be output by + combining MTU{3,4} and MTU{6,7}. + + The below pwm channels are supported in pwm mode 1. + pwm0 - MTU0.MTIOC0A PWM mode 1 + pwm1 - MTU0.MTIOC0C PWM mode 1 + pwm2 - MTU1.MTIOC1A PWM mode 1 + pwm3 - MTU2.MTIOC2A PWM mode 1 + pwm4 - MTU3.MTIOC3A PWM mode 1 + pwm5 - MTU3.MTIOC3C PWM mode 1 + pwm6 - MTU4.MTIOC4A PWM mode 1 + pwm7 - MTU4.MTIOC4C PWM mode 1 + pwm8 - MTU6.MTIOC6A PWM mode 1 + pwm9 - MTU6.MTIOC6C PWM mode 1 + pwm10 - MTU7.MTIOC7A PWM mode 1 + pwm11 - MTU7.MTIOC7C PWM mode 1 + +properties: + compatible: + items: + - enum: + - renesas,r9a07g044-mtu3 # RZ/G2{L,LC} + - renesas,r9a07g054-mtu3 # RZ/V2L + - const: renesas,rz-mtu3 + + reg: + maxItems: 1 + + interrupts: + items: + - description: MTU0.TGRA input capture/compare match + - description: MTU0.TGRB input capture/compare match + - description: MTU0.TGRC input capture/compare match + - description: MTU0.TGRD input capture/compare match + - description: MTU0.TCNT overflow + - description: MTU0.TGRE compare match + - description: MTU0.TGRF compare match + - description: MTU1.TGRA input capture/compare match + - description: MTU1.TGRB input capture/compare match + - description: MTU1.TCNT overflow + - description: MTU1.TCNT underflow + - description: MTU2.TGRA input capture/compare match + - description: MTU2.TGRB input capture/compare match + - description: MTU2.TCNT overflow + - description: MTU2.TCNT underflow + - description: MTU3.TGRA input capture/compare match + - description: MTU3.TGRB input capture/compare match + - description: MTU3.TGRC input capture/compare match + - description: MTU3.TGRD input capture/compare match + - description: MTU3.TCNT overflow + - description: MTU4.TGRA input capture/compare match + - description: MTU4.TGRB input capture/compare match + - description: MTU4.TGRC input capture/compare match + - description: MTU4.TGRD input capture/compare match + - description: MTU4.TCNT overflow/underflow + - description: MTU5.TGRU input capture/compare match + - description: MTU5.TGRV input capture/compare match + - description: MTU5.TGRW input capture/compare match + - description: MTU6.TGRA input capture/compare match + - description: MTU6.TGRB input capture/compare match + - description: MTU6.TGRC input capture/compare match + - description: MTU6.TGRD input capture/compare match + - description: MTU6.TCNT overflow + - description: MTU7.TGRA input capture/compare match + - description: MTU7.TGRB input capture/compare match + - description: MTU7.TGRC input capture/compare match + - description: MTU7.TGRD input capture/compare match + - description: MTU7.TCNT overflow/underflow + - description: MTU8.TGRA input capture/compare match + - description: MTU8.TGRB input capture/compare match + - description: MTU8.TGRC input capture/compare match + - description: MTU8.TGRD input capture/compare match + - description: MTU8.TCNT overflow + - description: MTU8.TCNT underflow + + interrupt-names: + items: + - const: tgia0 + - const: tgib0 + - const: tgic0 + - const: tgid0 + - const: tgiv0 + - const: tgie0 + - const: tgif0 + - const: tgia1 + - const: tgib1 + - const: tgiv1 + - const: tgiu1 + - const: tgia2 + - const: tgib2 + - const: tgiv2 + - const: tgiu2 + - const: tgia3 + - const: tgib3 + - const: tgic3 + - const: tgid3 + - const: tgiv3 + - const: tgia4 + - const: tgib4 + - const: tgic4 + - const: tgid4 + - const: tgiv4 + - const: tgiu5 + - const: tgiv5 + - const: tgiw5 + - const: tgia6 + - const: tgib6 + - const: tgic6 + - const: tgid6 + - const: tgiv6 + - const: tgia7 + - const: tgib7 + - const: tgic7 + - const: tgid7 + - const: tgiv7 + - const: tgia8 + - const: tgib8 + - const: tgic8 + - const: tgid8 + - const: tgiv8 + - const: tgiu8 + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + "#pwm-cells": + const: 2 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - power-domains + - resets + +additionalProperties: false + +examples: + - | + #include + #include + + mtu3: timer@10001200 { + compatible = "renesas,r9a07g044-mtu3", "renesas,rz-mtu3"; + reg = <0x10001200 0xb00>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "tgia0", "tgib0", "tgic0", "tgid0", "tgiv0", "tgie0", + "tgif0", + "tgia1", "tgib1", "tgiv1", "tgiu1", + "tgia2", "tgib2", "tgiv2", "tgiu2", + "tgia3", "tgib3", "tgic3", "tgid3", "tgiv3", + "tgia4", "tgib4", "tgic4", "tgid4", "tgiv4", + "tgiu5", "tgiv5", "tgiw5", + "tgia6", "tgib6", "tgic6", "tgid6", "tgiv6", + "tgia7", "tgib7", "tgic7", "tgid7", "tgiv7", + "tgia8", "tgib8", "tgic8", "tgid8", "tgiv8", "tgiu8"; + clocks = <&cpg CPG_MOD R9A07G044_MTU_X_MCK_MTU3>; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_MTU_X_PRESET_MTU3>; + #pwm-cells = <2>; + }; From patchwork Thu Feb 2 16:57:28 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126557 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 36C12C05027 for ; Thu, 2 Feb 2023 16:58:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231883AbjBBQ6G (ORCPT ); Thu, 2 Feb 2023 11:58:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33742 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232432AbjBBQ5x (ORCPT ); Thu, 2 Feb 2023 11:57:53 -0500 Received: from relmlie6.idc.renesas.com (relmlor2.renesas.com [210.160.252.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 5C9696F227 for ; Thu, 2 Feb 2023 08:57:50 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,267,1669042800"; d="scan'208";a="151446204" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie6.idc.renesas.com with ESMTP; 03 Feb 2023 01:57:49 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id E7C7D402DBB2; Fri, 3 Feb 2023 01:57:45 +0900 (JST) From: Biju Das To: Lee Jones , Philipp Zabel Cc: Biju Das , "Daniel Lezcano" , "William Breathitt Gray" , "Thierry Reding" , =?utf-8?q?Uwe_Kleine-K=C3=B6ni?= =?utf-8?q?g?= , Geert Uytterhoeven , Fabrizio Castro , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org Subject: [PATCH v12 2/6] mfd: Add Renesas RZ/G2L MTU3a core driver Date: Thu, 2 Feb 2023 16:57:28 +0000 Message-Id: <20230202165732.305650-3-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org The RZ/G2L multi-function timer pulse unit 3 (MTU3a) is embedded in the Renesas RZ/G2L family SoCs. It consists of eight 16-bit timer channels and one 32-bit timer channel. It supports the following functions - Counter - Timer - PWM The 8/16/32 bit registers are mixed in each channel. Add MTU3a core driver for RZ/G2L SoC. The core driver shares the clk and channel register access for the other child devices like Counter, PWM and Clock event. Signed-off-by: Biju Das --- Ref: https://patchwork.kernel.org/project/linux-renesas-soc/patch/20230113161753.1073706-3-biju.das.jz@bp.renesas.com/ v11->v2: * Moved the core driver from timer to MFD. * Moved header fine from clocksource/rz-mtu3.h->linux/mfd/rz-mtu3.h * Removed Select MFD_CORE option from config. v10->v11: * No change. v9->v10: * No change. v8->v9: * No change. v7->v8: * Add locking for RMW on rz_mtu3_shared_reg_update_bit() * Replaced enum rz_mtu3_functions with channel busy flag * Added API for request and release a channel. v6->v7: * Added channel specific mutex to avoid races between child devices (for eg: pwm and counter) * Added rz_mtu3_shared_reg_update_bit() to update bit. v5->v6: * Updated commit and KConfig description * Selected MFD_CORE to avoid build error if CONFIG_MFD_CORE not set. * Improved error handling in probe(). * Updated MODULE_DESCRIPTION and title. v4->v5: * Moved core driver from MFD to timer * Child devices instatiated using mfd_add_devices() v3->v4: * A single driver that registers both the counter and the pwm functionalities that binds against "renesas,rz-mtu3". * Moved PM handling from child devices to here. * replaced include/linux/mfd/rz-mtu3.h->drivers/mfd/rz-mtu3.h * Removed "remove" callback v2->v3: * removed unwanted header files * Added LUT for 32 bit registers as it needed for 32-bit cascade counting. * Exported 32 bit read/write functions. v1->v2: * Changed the compatible name * Replaced devm_reset_control_get->devm_reset_control_get_exclusive * Renamed function names rzg2l_mtu3->rz_mtu3 as this is generic IP in RZ family SoC's. --- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/rz-mtu3.c | 458 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/rz-mtu3.h | 237 +++++++++++++++++++ 4 files changed, 706 insertions(+) create mode 100644 drivers/mfd/rz-mtu3.c create mode 100644 include/linux/mfd/rz-mtu3.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fcc141e067b9..e16c550c5b05 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1308,6 +1308,16 @@ config MFD_SC27XX_PMIC This driver provides common support for accessing the SC27xx PMICs, and it also adds the irq_chip parts for handling the PMIC chip events. +config RZ_MTU3 + bool "Renesas RZ/G2L MTU3a core driver" + depends on (ARCH_RZG2L && OF) || COMPILE_TEST + help + Select this option to enable Renesas RZ/G2L MTU3a core driver for + the Multi-Function Timer Pulse Unit 3 (MTU3a) hardware available + on SoCs from Renesas. The core driver shares the clk and channel + register access for the other child devices like Counter, PWM, + Clock Source, and Clock event. + config ABX500_CORE bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" depends on ARCH_U8500 || COMPILE_TEST diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 2f6c89d1e277..1d2392f06f78 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -174,6 +174,7 @@ pcf50633-objs := pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_MFD_PCF50633) += pcf50633.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o +obj-$(CONFIG_RZ_MTU3) += rz-mtu3.o obj-$(CONFIG_ABX500_CORE) += abx500-core.o obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o # ab8500-core need to come after db8500-prcmu (which provides the channel) diff --git a/drivers/mfd/rz-mtu3.c b/drivers/mfd/rz-mtu3.c new file mode 100644 index 000000000000..ad6bb6b28694 --- /dev/null +++ b/drivers/mfd/rz-mtu3.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L Multi-Function Timer Pulse Unit 3(MTU3a) Core driver + * + * Copyright (C) 2023 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned long rz_mtu3_8bit_ch_reg_offs[][13] = { + { + [RZ_MTU3_TIER] = 0x4, [RZ_MTU3_NFCR] = 0x70, + [RZ_MTU3_TCR] = 0x0, [RZ_MTU3_TCR2] = 0x28, + [RZ_MTU3_TMDR1] = 0x1, [RZ_MTU3_TIORH] = 0x2, + [RZ_MTU3_TIORL] = 0x3 + }, + { + [RZ_MTU3_TIER] = 0x4, [RZ_MTU3_NFCR] = 0xef, + [RZ_MTU3_TSR] = 0x5, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0x14, [RZ_MTU3_TMDR1] = 0x1, + [RZ_MTU3_TIOR] = 0x2 + }, + { + [RZ_MTU3_TIER] = 0x4, [RZ_MTU3_NFCR] = 0x16e, + [RZ_MTU3_TSR] = 0x5, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0xc, [RZ_MTU3_TMDR1] = 0x1, + [RZ_MTU3_TIOR] = 0x2 + }, + { + [RZ_MTU3_TIER] = 0x8, [RZ_MTU3_NFCR] = 0x93, + [RZ_MTU3_TSR] = 0x2c, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0x4c, [RZ_MTU3_TMDR1] = 0x2, + [RZ_MTU3_TIORH] = 0x4, [RZ_MTU3_TIORL] = 0x5, + [RZ_MTU3_TBTM] = 0x38 + }, + { + [RZ_MTU3_TIER] = 0x8, [RZ_MTU3_NFCR] = 0x93, + [RZ_MTU3_TSR] = 0x2c, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0x4c, [RZ_MTU3_TMDR1] = 0x2, + [RZ_MTU3_TIORH] = 0x5, [RZ_MTU3_TIORL] = 0x6, + [RZ_MTU3_TBTM] = 0x38 + }, + { + [RZ_MTU3_TIER] = 0x32, [RZ_MTU3_NFCR] = 0x1eb, + [RZ_MTU3_TSTR] = 0x34, [RZ_MTU3_TCNTCMPCLR] = 0x36, + [RZ_MTU3_TCRU] = 0x4, [RZ_MTU3_TCR2U] = 0x5, + [RZ_MTU3_TIORU] = 0x6, [RZ_MTU3_TCRV] = 0x14, + [RZ_MTU3_TCR2V] = 0x15, [RZ_MTU3_TIORV] = 0x16, + [RZ_MTU3_TCRW] = 0x24, [RZ_MTU3_TCR2W] = 0x25, + [RZ_MTU3_TIORW] = 0x26 + }, + { + [RZ_MTU3_TIER] = 0x8, [RZ_MTU3_NFCR] = 0x93, + [RZ_MTU3_TSR] = 0x2c, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0x4c, [RZ_MTU3_TMDR1] = 0x2, + [RZ_MTU3_TIORH] = 0x4, [RZ_MTU3_TIORL] = 0x5, + [RZ_MTU3_TBTM] = 0x38 + }, + { + [RZ_MTU3_TIER] = 0x8, [RZ_MTU3_NFCR] = 0x93, + [RZ_MTU3_TSR] = 0x2c, [RZ_MTU3_TCR] = 0x0, + [RZ_MTU3_TCR2] = 0x4c, [RZ_MTU3_TMDR1] = 0x2, + [RZ_MTU3_TIORH] = 0x5, [RZ_MTU3_TIORL] = 0x6, + [RZ_MTU3_TBTM] = 0x38 + }, + { + [RZ_MTU3_TIER] = 0x4, [RZ_MTU3_NFCR] = 0x368, + [RZ_MTU3_TCR] = 0x0, [RZ_MTU3_TCR2] = 0x6, + [RZ_MTU3_TMDR1] = 0x1, [RZ_MTU3_TIORH] = 0x2, + [RZ_MTU3_TIORL] = 0x3 + } +}; + +static const unsigned long rz_mtu3_16bit_ch_reg_offs[][12] = { + { + [RZ_MTU3_TCNT] = 0x6, [RZ_MTU3_TGRA] = 0x8, + [RZ_MTU3_TGRB] = 0xa, [RZ_MTU3_TGRC] = 0xc, + [RZ_MTU3_TGRD] = 0xe, [RZ_MTU3_TGRE] = 0x20, + [RZ_MTU3_TGRF] = 0x22 + }, + { + [RZ_MTU3_TCNT] = 0x6, [RZ_MTU3_TGRA] = 0x8, + [RZ_MTU3_TGRB] = 0xa + }, + { + [RZ_MTU3_TCNT] = 0x6, [RZ_MTU3_TGRA] = 0x8, + [RZ_MTU3_TGRB] = 0xa + }, + { + [RZ_MTU3_TCNT] = 0x10, [RZ_MTU3_TGRA] = 0x18, + [RZ_MTU3_TGRB] = 0x1a, [RZ_MTU3_TGRC] = 0x24, + [RZ_MTU3_TGRD] = 0x26, [RZ_MTU3_TGRE] = 0x72 + }, + { + [RZ_MTU3_TCNT] = 0x11, [RZ_MTU3_TGRA] = 0x1b, + [RZ_MTU3_TGRB] = 0x1d, [RZ_MTU3_TGRC] = 0x27, + [RZ_MTU3_TGRD] = 0x29, [RZ_MTU3_TGRE] = 0x73, + [RZ_MTU3_TGRF] = 0x75, [RZ_MTU3_TADCR] = 0x3f, + [RZ_MTU3_TADCORA] = 0x43, [RZ_MTU3_TADCORB] = 0x45, + [RZ_MTU3_TADCOBRA] = 0x47, + [RZ_MTU3_TADCOBRB] = 0x49 + }, + { + [RZ_MTU3_TCNTU] = 0x0, [RZ_MTU3_TGRU] = 0x2, + [RZ_MTU3_TCNTV] = 0x10, [RZ_MTU3_TGRV] = 0x12, + [RZ_MTU3_TCNTW] = 0x20, [RZ_MTU3_TGRW] = 0x22 + }, + { + [RZ_MTU3_TCNT] = 0x10, [RZ_MTU3_TGRA] = 0x18, + [RZ_MTU3_TGRB] = 0x1a, [RZ_MTU3_TGRC] = 0x24, + [RZ_MTU3_TGRD] = 0x26, [RZ_MTU3_TGRE] = 0x72 + }, + { + [RZ_MTU3_TCNT] = 0x11, [RZ_MTU3_TGRA] = 0x1b, + [RZ_MTU3_TGRB] = 0x1d, [RZ_MTU3_TGRC] = 0x27, + [RZ_MTU3_TGRD] = 0x29, [RZ_MTU3_TGRE] = 0x73, + [RZ_MTU3_TGRF] = 0x75, [RZ_MTU3_TADCR] = 0x3f, + [RZ_MTU3_TADCORA] = 0x43, [RZ_MTU3_TADCORB] = 0x45, + [RZ_MTU3_TADCOBRA] = 0x47, + [RZ_MTU3_TADCOBRB] = 0x49 + }, +}; + +static const unsigned long rz_mtu3_32bit_ch_reg_offs[][5] = { + { + [RZ_MTU3_TCNTLW] = 0x20, [RZ_MTU3_TGRALW] = 0x24, + [RZ_MTU3_TGRBLW] = 0x28 + }, + { [RZ_MTU3_TCNT] = 0x8, [RZ_MTU3_TGRA] = 0xc, + [RZ_MTU3_TGRB] = 0x10, [RZ_MTU3_TGRC] = 0x14, + [RZ_MTU3_TGRD] = 0x18 + } +}; + +static bool rz_mtu3_is_16bit_shared_reg(u16 off) +{ + return (off == RZ_MTU3_TDDRA || off == RZ_MTU3_TDDRB || + off == RZ_MTU3_TCDRA || off == RZ_MTU3_TCDRB || + off == RZ_MTU3_TCBRA || off == RZ_MTU3_TCBRB || + off == RZ_MTU3_TCNTSA || off == RZ_MTU3_TCNTSB); +} + +u16 rz_mtu3_shared_reg_read(struct rz_mtu3_channel *ch, u16 off) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent); + + if (rz_mtu3_is_16bit_shared_reg(off)) + return readw(mtu->mmio + off); + else + return readb(mtu->mmio + off); +} +EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_read); + +u8 rz_mtu3_8bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + u16 ch_offs; + + ch_offs = rz_mtu3_8bit_ch_reg_offs[ch->index][off]; + if (off != RZ_MTU3_TCR && ch_offs == 0) + return -EINVAL; + + /* + * NFCR register addresses on MTU{0,1,2,5,8} channels are smaller than + * channel's base address. + */ + if (off == RZ_MTU3_NFCR && (ch->index <= RZ_MTU2 || + ch->index == RZ_MTU5 || + ch->index == RZ_MTU8)) + return readb(ch->base - ch_offs); + else + return readb(ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_8bit_ch_read); + +u16 rz_mtu3_16bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + u16 ch_offs; + + /* MTU8 doesn't have 16-bit registers */ + if (ch->index == RZ_MTU8) + return 0; + + ch_offs = rz_mtu3_16bit_ch_reg_offs[ch->index][off]; + if (ch->index != RZ_MTU5 && off != RZ_MTU3_TCNTU && ch_offs == 0) + return 0; + + return readw(ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_16bit_ch_read); + +u32 rz_mtu3_32bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + u16 ch_offs; + + if (ch->index == RZ_MTU1) + ch_offs = rz_mtu3_32bit_ch_reg_offs[0][off]; + else if (ch->index == RZ_MTU8) + ch_offs = rz_mtu3_32bit_ch_reg_offs[1][off]; + + if (!ch_offs) + return -EINVAL; + + return readl(ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_32bit_ch_read); + +void rz_mtu3_8bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u8 val) +{ + u16 ch_offs; + + ch_offs = rz_mtu3_8bit_ch_reg_offs[ch->index][off]; + if (ch->index != RZ_MTU5 && off != RZ_MTU3_TCR && ch_offs == 0) + return; + + /* + * NFCR register addresses on MTU{0,1,2,5,8} channels are smaller than + * channel's base address. + */ + if (off == RZ_MTU3_NFCR && (ch->index <= RZ_MTU2 || + ch->index == RZ_MTU5 || + ch->index == RZ_MTU8)) + writeb(val, ch->base - ch_offs); + else + writeb(val, ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_8bit_ch_write); + +void rz_mtu3_16bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u16 val) +{ + u16 ch_offs; + + /* MTU8 doesn't have 16-bit registers */ + if (ch->index == RZ_MTU8) + return; + + ch_offs = rz_mtu3_16bit_ch_reg_offs[ch->index][off]; + if (ch->index != RZ_MTU5 && off != RZ_MTU3_TCNTU && ch_offs == 0) + return; + + writew(val, ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_16bit_ch_write); + +void rz_mtu3_32bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u32 val) +{ + u16 ch_offs; + + if (ch->index == RZ_MTU1) + ch_offs = rz_mtu3_32bit_ch_reg_offs[0][off]; + else if (ch->index == RZ_MTU8) + ch_offs = rz_mtu3_32bit_ch_reg_offs[1][off]; + + if (!ch_offs) + return; + + writel(val, ch->base + ch_offs); +} +EXPORT_SYMBOL_GPL(rz_mtu3_32bit_ch_write); + +void rz_mtu3_shared_reg_write(struct rz_mtu3_channel *ch, u16 off, u16 value) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent); + + if (rz_mtu3_is_16bit_shared_reg(off)) + writew(value, mtu->mmio + off); + else + writeb((u8)value, mtu->mmio + off); +} +EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_write); + +void rz_mtu3_shared_reg_update_bit(struct rz_mtu3_channel *ch, u16 off, + u16 pos, u8 val) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent); + unsigned long tmdr, flags; + + raw_spin_lock_irqsave(&mtu->lock, flags); + tmdr = rz_mtu3_shared_reg_read(ch, off); + __assign_bit(pos, &tmdr, !!val); + rz_mtu3_shared_reg_write(ch, off, tmdr); + raw_spin_unlock_irqrestore(&mtu->lock, flags); +} +EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_update_bit); + +static void rz_mtu3_start_stop_ch(struct rz_mtu3_channel *ch, bool start) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent); + unsigned long flags, value; + u8 offs; + + /* start stop register shared by multiple timer channels */ + raw_spin_lock_irqsave(&mtu->lock, flags); + + if (ch->index == RZ_MTU6 || ch->index == RZ_MTU7) { + value = rz_mtu3_shared_reg_read(ch, RZ_MTU3_TSTRB); + if (start) + value |= 1 << ch->index; + else + value &= ~(1 << ch->index); + rz_mtu3_shared_reg_write(ch, RZ_MTU3_TSTRB, value); + } else if (ch->index != RZ_MTU5) { + value = rz_mtu3_shared_reg_read(ch, RZ_MTU3_TSTRA); + if (ch->index == RZ_MTU8) + offs = 0x08; + else if (ch->index < RZ_MTU3) + offs = 1 << ch->index; + else + offs = 1 << (ch->index + 3); + if (start) + value |= offs; + else + value &= ~offs; + rz_mtu3_shared_reg_write(ch, RZ_MTU3_TSTRA, value); + } + + raw_spin_unlock_irqrestore(&mtu->lock, flags); +} + +bool rz_mtu3_is_enabled(struct rz_mtu3_channel *ch) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent); + unsigned long flags, value; + bool ret = false; + u8 offs; + + /* start stop register shared by multiple timer channels */ + raw_spin_lock_irqsave(&mtu->lock, flags); + + if (ch->index == RZ_MTU6 || ch->index == RZ_MTU7) { + value = rz_mtu3_shared_reg_read(ch, RZ_MTU3_TSTRB); + ret = value & (1 << ch->index); + } else if (ch->index != RZ_MTU5) { + value = rz_mtu3_shared_reg_read(ch, RZ_MTU3_TSTRA); + if (ch->index == RZ_MTU8) + offs = 0x08; + else if (ch->index < RZ_MTU3) + offs = 1 << ch->index; + else + offs = 1 << (ch->index + 3); + + ret = value & offs; + } + + raw_spin_unlock_irqrestore(&mtu->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(rz_mtu3_is_enabled); + +int rz_mtu3_enable(struct rz_mtu3_channel *ch) +{ + /* enable channel */ + rz_mtu3_start_stop_ch(ch, true); + + return 0; +} +EXPORT_SYMBOL_GPL(rz_mtu3_enable); + +void rz_mtu3_disable(struct rz_mtu3_channel *ch) +{ + /* disable channel */ + rz_mtu3_start_stop_ch(ch, false); +} +EXPORT_SYMBOL_GPL(rz_mtu3_disable); + +static const unsigned int ch_reg_offsets[] = { + 0x100, 0x180, 0x200, 0x000, 0x001, 0xa80, 0x800, 0x801, 0x400 +}; + +static void rz_mtu3_reset_assert(void *data) +{ + struct rz_mtu3 *mtu = dev_get_drvdata(data); + + mfd_remove_devices(data); + reset_control_assert(mtu->rstc); +} + +static const struct mfd_cell rz_mtu3_devs[] = { + { + .name = "rz-mtu3-counter", + }, + { + .name = "pwm-rz-mtu3", + }, +}; + +static int rz_mtu3_probe(struct platform_device *pdev) +{ + struct rz_mtu3 *ddata; + unsigned int i; + int ret; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->mmio)) + return PTR_ERR(ddata->mmio); + + ddata->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(ddata->rstc)) + return PTR_ERR(ddata->rstc); + + ddata->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ddata->clk)) + return PTR_ERR(ddata->clk); + + reset_control_deassert(ddata->rstc); + raw_spin_lock_init(&ddata->lock); + platform_set_drvdata(pdev, ddata); + + for (i = 0; i < RZ_MTU_NUM_CHANNELS; i++) { + ddata->channels[i].index = i; + ddata->channels[i].is_busy = false; + ddata->channels[i].base = ddata->mmio + ch_reg_offsets[i]; + mutex_init(&ddata->channels[i].lock); + } + + ret = mfd_add_devices(&pdev->dev, 0, rz_mtu3_devs, + ARRAY_SIZE(rz_mtu3_devs), NULL, 0, NULL); + if (ret < 0) + goto err_assert; + + return devm_add_action_or_reset(&pdev->dev, rz_mtu3_reset_assert, + &pdev->dev); + +err_assert: + reset_control_assert(ddata->rstc); + return ret; +} + +static const struct of_device_id rz_mtu3_of_match[] = { + { .compatible = "renesas,rz-mtu3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rz_mtu3_of_match); + +static struct platform_driver rz_mtu3_driver = { + .probe = rz_mtu3_probe, + .driver = { + .name = "rz-mtu3", + .of_match_table = rz_mtu3_of_match, + }, +}; +module_platform_driver(rz_mtu3_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a Core Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/rz-mtu3.h b/include/linux/mfd/rz-mtu3.h new file mode 100644 index 000000000000..43b87186348b --- /dev/null +++ b/include/linux/mfd/rz-mtu3.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Renesas Electronics Corporation + */ +#ifndef __LINUX_RZ_MTU3_H__ +#define __LINUX_RZ_MTU3_H__ + +#include + +/* 8-bit shared register offsets macros */ +#define RZ_MTU3_TSTRA 0x080 /* Timer start register A */ +#define RZ_MTU3_TSTRB 0x880 /* Timer start register B */ + +/* 16-bit shared register offset macros */ +#define RZ_MTU3_TDDRA 0x016 /* Timer dead time data register A */ +#define RZ_MTU3_TDDRB 0x816 /* Timer dead time data register B */ +#define RZ_MTU3_TCDRA 0x014 /* Timer cycle data register A */ +#define RZ_MTU3_TCDRB 0x814 /* Timer cycle data register B */ +#define RZ_MTU3_TCBRA 0x022 /* Timer cycle buffer register A */ +#define RZ_MTU3_TCBRB 0x822 /* Timer cycle buffer register B */ +#define RZ_MTU3_TCNTSA 0x020 /* Timer subcounter A */ +#define RZ_MTU3_TCNTSB 0x820 /* Timer subcounter B */ + +/* + * MTU5 contains 3 timer counter registers and is totaly different + * from other channels, so we must separate its offset + */ + +/* 8-bit register offset macros of MTU3 channels except MTU5 */ +#define RZ_MTU3_TIER 0 /* Timer interrupt register */ +#define RZ_MTU3_NFCR 1 /* Noise filter control register */ +#define RZ_MTU3_TSR 2 /* Timer status register */ +#define RZ_MTU3_TCR 3 /* Timer control register */ +#define RZ_MTU3_TCR2 4 /* Timer control register 2 */ +#define RZ_MTU3_TMDR1 5 /* Timer mode register 1 */ +#define RZ_MTU3_TIOR 6 /* Timer I/O control register */ +#define RZ_MTU3_TIORH 6 /* Timer I/O control register H */ +#define RZ_MTU3_TIORL 7 /* Timer I/O control register L */ +/* Only MTU3/4/6/7 have TBTM registers */ +#define RZ_MTU3_TBTM 8 /* Timer buffer operation transfer mode register */ + +/* 8-bit MTU5 register offset macros */ +#define RZ_MTU3_TSTR 2 /* MTU5 Timer start register */ +#define RZ_MTU3_TCNTCMPCLR 3 /* MTU5 Timer compare match clear register */ +#define RZ_MTU3_TCRU 4 /* Timer control register U */ +#define RZ_MTU3_TCR2U 5 /* Timer control register 2U */ +#define RZ_MTU3_TIORU 6 /* Timer I/O control register U */ +#define RZ_MTU3_TCRV 7 /* Timer control register V */ +#define RZ_MTU3_TCR2V 8 /* Timer control register 2V */ +#define RZ_MTU3_TIORV 9 /* Timer I/O control register V */ +#define RZ_MTU3_TCRW 10 /* Timer control register W */ +#define RZ_MTU3_TCR2W 11 /* Timer control register 2W */ +#define RZ_MTU3_TIORW 12 /* Timer I/O control register W */ + +/* 16-bit register offset macros of MTU3 channels except MTU5 */ +#define RZ_MTU3_TCNT 0 /* Timer counter */ +#define RZ_MTU3_TGRA 1 /* Timer general register A */ +#define RZ_MTU3_TGRB 2 /* Timer general register B */ +#define RZ_MTU3_TGRC 3 /* Timer general register C */ +#define RZ_MTU3_TGRD 4 /* Timer general register D */ +#define RZ_MTU3_TGRE 5 /* Timer general register E */ +#define RZ_MTU3_TGRF 6 /* Timer general register F */ +/* Timer A/D converter start request registers */ +#define RZ_MTU3_TADCR 7 /* control register */ +#define RZ_MTU3_TADCORA 8 /* cycle set register A */ +#define RZ_MTU3_TADCORB 9 /* cycle set register B */ +#define RZ_MTU3_TADCOBRA 10 /* cycle set buffer register A */ +#define RZ_MTU3_TADCOBRB 11 /* cycle set buffer register B */ + +/* 16-bit MTU5 register offset macros */ +#define RZ_MTU3_TCNTU 0 /* MTU5 Timer counter U */ +#define RZ_MTU3_TGRU 1 /* MTU5 Timer general register U */ +#define RZ_MTU3_TCNTV 2 /* MTU5 Timer counter V */ +#define RZ_MTU3_TGRV 3 /* MTU5 Timer general register V */ +#define RZ_MTU3_TCNTW 4 /* MTU5 Timer counter W */ +#define RZ_MTU3_TGRW 5 /* MTU5 Timer general register W */ + +/* 32-bit register offset */ +#define RZ_MTU3_TCNTLW 0 /* Timer longword counter */ +#define RZ_MTU3_TGRALW 1 /* Timer longword general register A */ +#define RZ_MTU3_TGRBLW 2 /* Timer longowrd general register B */ + +#define RZ_MTU3_TMDR3 0x191 /* MTU1 Timer Mode Register 3 */ + +/* Macros for setting registers */ +#define RZ_MTU3_TCR_CCLR_TGRA BIT(5) + +enum rz_mtu3_channels { + RZ_MTU0, + RZ_MTU1, + RZ_MTU2, + RZ_MTU3, + RZ_MTU4, + RZ_MTU5, + RZ_MTU6, + RZ_MTU7, + RZ_MTU8, + RZ_MTU_NUM_CHANNELS +}; + +/** + * struct rz_mtu3_channel - MTU3 channel private data + * + * @dev: device handle + * @index: channel index + * @base: channel base address + * @lock: Lock to protect channel state + * @is_busy: channel state + */ +struct rz_mtu3_channel { + struct device *dev; + unsigned int index; + void __iomem *base; + struct mutex lock; /* Protect channel state */ + bool is_busy; +}; + +/** + * struct rz_mtu3 - MTU3 core private data + * + * @clk: MTU3 module clock + * @mmio: MTU3 module clock + * @lock: Lock to protect shared register access + * @rz_mtu3_channel: HW channels + */ +struct rz_mtu3 { + void *priv_rz_mtu3; + void __iomem *mmio; + struct clk *clk; + struct reset_control *rstc; + raw_spinlock_t lock; /* Protect the shared registers */ + struct rz_mtu3_channel channels[RZ_MTU_NUM_CHANNELS]; +}; + +#if IS_ENABLED(CONFIG_RZ_MTU3) +static inline bool rz_mtu3_request_channel(struct rz_mtu3_channel *ch) +{ + bool is_idle; + + mutex_lock(&ch->lock); + is_idle = !ch->is_busy; + if (is_idle) + ch->is_busy = true; + mutex_unlock(&ch->lock); + + return is_idle; +} + +static inline void rz_mtu3_release_channel(struct rz_mtu3_channel *ch) +{ + mutex_lock(&ch->lock); + ch->is_busy = false; + mutex_unlock(&ch->lock); +} + +bool rz_mtu3_is_enabled(struct rz_mtu3_channel *ch); +void rz_mtu3_disable(struct rz_mtu3_channel *ch); +int rz_mtu3_enable(struct rz_mtu3_channel *ch); + +u8 rz_mtu3_8bit_ch_read(struct rz_mtu3_channel *ch, u16 off); +u16 rz_mtu3_16bit_ch_read(struct rz_mtu3_channel *ch, u16 off); +u32 rz_mtu3_32bit_ch_read(struct rz_mtu3_channel *ch, u16 off); +u16 rz_mtu3_shared_reg_read(struct rz_mtu3_channel *ch, u16 off); + +void rz_mtu3_8bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u8 val); +void rz_mtu3_16bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u16 val); +void rz_mtu3_32bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u32 val); +void rz_mtu3_shared_reg_write(struct rz_mtu3_channel *ch, u16 off, u16 val); +void rz_mtu3_shared_reg_update_bit(struct rz_mtu3_channel *ch, u16 off, + u16 pos, u8 val); +#else +static inline bool rz_mtu3_request_channel(struct rz_mtu3_channel *ch) +{ + return false; +} + +static inline void rz_mtu3_release_channel(struct rz_mtu3_channel *ch) +{ +} + +static inline bool rz_mtu3_is_enabled(struct rz_mtu3_channel *ch) +{ + return false; +} + +static inline void rz_mtu3_disable(struct rz_mtu3_channel *ch) +{ +} + +static inline int rz_mtu3_enable(struct rz_mtu3_channel *ch) +{ + return 0; +} + +static inline u8 rz_mtu3_8bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + return 0; +} + +static inline u16 rz_mtu3_16bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + return 0; +} + +static inline u32 rz_mtu3_32bit_ch_read(struct rz_mtu3_channel *ch, u16 off) +{ + return 0; +} + +static inline u16 rz_mtu3_shared_reg_read(struct rz_mtu3_channel *ch, u16 off) +{ + return 0; +} + +static inline void rz_mtu3_8bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u8 val) +{ +} + +static inline void rz_mtu3_16bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u16 val) +{ +} + +static inline void rz_mtu3_32bit_ch_write(struct rz_mtu3_channel *ch, u16 off, u32 val) +{ +} + +static inline void rz_mtu3_shared_reg_write(struct rz_mtu3_channel *ch, u16 off, u16 val) +{ +} + +static inline void rz_mtu3_shared_reg_update_bit(struct rz_mtu3_channel *ch, + u16 off, u16 pos, u8 val) +{ +} +#endif + +#endif /* __LINUX_RZ_MTU3_H__ */ From patchwork Thu Feb 2 16:57:29 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126559 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BCE5EC64EC4 for ; Thu, 2 Feb 2023 16:58:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232218AbjBBQ6i (ORCPT ); Thu, 2 Feb 2023 11:58:38 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34330 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232439AbjBBQ6K (ORCPT ); Thu, 2 Feb 2023 11:58:10 -0500 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id A564627998; Thu, 2 Feb 2023 08:58:08 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,268,1669042800"; d="scan'208";a="148266718" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie5.idc.renesas.com with ESMTP; 03 Feb 2023 01:57:53 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id 1AE7D402DBB5; Fri, 3 Feb 2023 01:57:49 +0900 (JST) From: Biju Das To: William Breathitt Gray Cc: Biju Das , linux-iio@vger.kernel.org, Lee Jones , "Daniel Lezcano" , "Thierry Reding" , =?utf-8?q?Uwe_Kleine-K=C3=B6ni?= =?utf-8?q?g?= , Geert Uytterhoeven , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org Subject: [PATCH v12 3/6] Documentation: ABI: sysfs-bus-counter: add cascade_counts_enable and external_input_phase_clock_select Date: Thu, 2 Feb 2023 16:57:29 +0000 Message-Id: <20230202165732.305650-4-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org This commit adds cascade_counts_enable and external_input_phase_ clock_select items to counter ABI file. (e.g. for Renesas MTU3 hardware used for phase counting). Signed-off-by: Biju Das Reviewed-by: William Breathitt Gray --- v11->v12: * No change v10->v11: * No change. v9->v10: * Added Rb tag from William Breathitt Gray v8->v9: * Added available blocks for external_input_phase_clock_select_available * Removed the "This attribute" from the external_input_phase_clock_select description, and capitalize the word "counter" from description. * Removed the "This attribute" from the cascade_counts_enable description, and capitalize "counts" and "counter" * Moved these device-level configuration blocks to top of the file. v7->v8: * Replaced cascade_enable->cascade_counts_enable * Updated commit header and description * Added external_input_phase_clock_select_available entry for driver- specific enum attribute and created a new entry block for it. * Add a line stating cascade_counts_enable is a boolean attribute. * Added missing 'component_id' suffix. v6->v7: * Replaced long_word_access_ctrl_mode->cascade_enable * Updated Kernel version v5->v6: * No change v5: * New patch --- Documentation/ABI/testing/sysfs-bus-counter | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter index ff83320b4255..97c1bd7a5df2 100644 --- a/Documentation/ABI/testing/sysfs-bus-counter +++ b/Documentation/ABI/testing/sysfs-bus-counter @@ -1,3 +1,33 @@ +What: /sys/bus/counter/devices/counterX/cascade_counts_enable +KernelVersion: 6.3 +Contact: linux-iio@vger.kernel.org +Description: + Indicates the cascading of Counts on Counter X. + + Valid attribute values are boolean. + +What: /sys/bus/counter/devices/counterX/external_input_phase_clock_select +KernelVersion: 6.3 +Contact: linux-iio@vger.kernel.org +Description: + Selects the external clock pin for phase counting mode of + Counter X. + + MTCLKA-MTCLKB: + MTCLKA and MTCLKB pins are selected for the external + phase clock. + + MTCLKC-MTCLKD: + MTCLKC and MTCLKD pins are selected for the external + phase clock. + +What: /sys/bus/counter/devices/counterX/external_input_phase_clock_select_available +KernelVersion: 6.3 +Contact: linux-iio@vger.kernel.org +Description: + Discrete set of available values for the respective device + configuration are listed in this file. + What: /sys/bus/counter/devices/counterX/countY/count KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org @@ -215,6 +245,8 @@ Contact: linux-iio@vger.kernel.org Description: This attribute indicates the number of overflows of count Y. +What: /sys/bus/counter/devices/counterX/cascade_counts_enable_component_id +What: /sys/bus/counter/devices/counterX/external_input_phase_clock_select_component_id What: /sys/bus/counter/devices/counterX/countY/capture_component_id What: /sys/bus/counter/devices/counterX/countY/ceiling_component_id What: /sys/bus/counter/devices/counterX/countY/floor_component_id From patchwork Thu Feb 2 16:57:30 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126560 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 519BCC64EC6 for ; Thu, 2 Feb 2023 16:58:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231644AbjBBQ6i (ORCPT ); Thu, 2 Feb 2023 11:58:38 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34372 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232465AbjBBQ6O (ORCPT ); Thu, 2 Feb 2023 11:58:14 -0500 Received: from relmlie5.idc.renesas.com (relmlor1.renesas.com [210.160.252.171]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id C7D4D1F91B; Thu, 2 Feb 2023 08:58:10 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,268,1669042800"; d="scan'208";a="148266721" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie5.idc.renesas.com with ESMTP; 03 Feb 2023 01:57:57 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id EFDAF402DBB5; Fri, 3 Feb 2023 01:57:53 +0900 (JST) From: Biju Das To: William Breathitt Gray Cc: Biju Das , linux-iio@vger.kernel.org, Lee Jones , "Daniel Lezcano" , "Thierry Reding" , =?utf-8?q?Uwe_Kleine-K=C3=B6ni?= =?utf-8?q?g?= , Geert Uytterhoeven , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org Subject: [PATCH v12 4/6] counter: Add Renesas RZ/G2L MTU3a counter driver Date: Thu, 2 Feb 2023 16:57:30 +0000 Message-Id: <20230202165732.305650-5-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org Add RZ/G2L MTU3a counter driver. This IP supports the following phase counting modes on MTU1 and MTU2 channels 1) 16-bit phase counting modes on MTU1 and MTU2 channels. 2) 32-bit phase counting mode by cascading MTU1 and MTU2 channels. This patch adds 3 counter value channels. count0: 16-bit phase counter value channel on MTU1 count1: 16-bit phase counter value channel on MTU2 count2: 32-bit phase counter value channel by cascading MTU1 and MTU2 channels. The external input phase clock pin for the counter value channels are as follows: count0: "MTCLKA-MTCLKB" count1: "MTCLKA-MTCLKB" or "MTCLKC-MTCLKD" count2: "MTCLKA-MTCLKB" or "MTCLKC-MTCLKD" Use the sysfs variable "external_input_phase_clock_select" to select the external input phase clock pin and "cascade_counts_enable" to enable/ disable cascading of channels. Signed-off-by: Biju Das Reviewed-by: William Breathitt Gray --- v11->v12: * Updated header file to as core driver is moved to MFD. v10->v11: * Added Rb tag from William Breathitt Gray * Replaced count2 channel name from "combined"->"cascaded", as channels are cascaded * Simplified the locking by adding the helper functions rz_mtu3_lock_if_counter_is_valid, rz_mtu3_lock_if_count_is_enabled, and rz_mtu3_lock_if_ch0_is_enabled. v9->v10: * Added helper function for rz_mtu3_count_{action,function}_read * Added priv->lock in rz_mtu3_count_function_read and rz_mtu3_count_ direction_read. * Added ch->is_busy check for rz_mtu3_action_read() * Added rz_mtu3_is_ch0_enabled() for device specific sysfs variables. * Added ch->is_busy check for device specific sysfs variables. v8->v9: * Added count_is_enabled variable to struct rz_mtu3_cnt * Added check for ch->is_busy and count_is_enabled before every Counter callback to ensure we do not try to access a busy channel used by other subsystem(eg: pwm). * Removed id parameter from rz_mtu3_32bit_cnt_setting() * Made definition of rz_mtu3_get_ch() in single line. * Replaced break->return in rz_mtu3_32bit_cnt_setting(), rz_mtu3_count_function_read() and rz_mtu3_initialize_counter() and removed redundant return 0. * Simplified synapse signal check for rz_mtu3_action_read(). v7->v8: * Simplified rz_mtu3_initialize_counter by calling rz_mtu3_request_ channel() and release the acquired sibling channel in case of error. * Simplified rz_mtu3_terminate_counter by calling rz_mtu3_release_ channel(). * Removed unused ceiling and ch_id from rz_mtu3_count_write() * Replaced the error -EINVAL->-EBUSY for rz_mtu3_is_counter_invalid() * Avoided race between rz_mtu3_count_{read, write} with rz_mtu3_ cascade_counts_enable_set() by adding locks and moved the lock before rz_mtu3_is_counter_invalid() * Protected the rz_mtu3_count_ceiling_read() function with a lock to make sure the cascade operation mode doesn't not change and that the priv data structure accesses don't race when they are changed in the ceiling_write() callback. * Added lock in rz_mtu3_cascade_enable_set() to make sure the other callbacks don't try to read the LWA state while updating LWA. * Added lock in rz_mtu3_ext_input_phase_clock_select_set() to ensure the other callbacks don't try to read the PHCKSEL state while updating PHCKSEL. * Added lock to avoid race between rz_mtu3_count_function_write() and rz_mtu3_action_read() * Updated rz_mtu3_action_read to return 0, if Synapse is in COUNTER_SYNAPSE _ACTION_NONE state. * Replaced sysfs variable cascade_enable->cascade_counts_enable * Renamed rz_mtu3_cascade_enable_get->rz_mtu3_cascade_counts_enable_get * Renamed rz_mtu3_cascade_enable_set->rz_mtu3_cascade_counts_enable_set * Removed redundent ceiling assignment from rz_mtu3_count_ceiling_read() * Removed unused ceiling and ch_id from rz_mtu3_count_write(). v6->v7: * Updated commit description * Added Register descriptions * Opimized size of cache variable by using union * Used test_bit() in rz_mtu3_is_counter_invalid() * Replaced val->timer_mode in rz_mtu3_count_function_{read,write} * Added TODO comment phase3 and phase5 modes. * replaced if-else with ternary expression in rz_mtu3_count_direction_read() * Used switch statement in rz_mtu3_count_ceiling_read to consistent with write * Provided default case for all switch statement. * Add mutex lock for avoiding races with other devices * Updated comments in rz_mtu3_action_read * Replaced COUNTER_COMP_DEVICE_BOOL->COUNTER_COMP_DEVICE_BOOL for cascade_enable * Replaced RZ_MTU3_GET_HW_CH->rz_mtu3_get_hw_ch * Added rz_mtu3_get_ch() to get channels * used rz_mtu3_shared_reg_update_bit for cascade_enable and selecting phase input clock. * Added rz_mtu3_is_counter_invalid() check in rz_mtu3_count_ceiling_read() v5->v6: * Updated KConfig and commit description * Sorted header * Fixed RZ_MTU3_GET_HW_CH Macro for argument reuse 'id' - possible side-effects? * Replaced SET_RUNTIME_PM_OPS->DEFINE_RUNTIME_DEV_PM_OPS and removed __maybe_unused from suspend/resume() v4->v5: * Updated the Kconfig with SoC vendor name * Introduced rz_mtu3_is_counter_invalid() * replaced pointer to an array of struct rz_mtu3_channel with a simple pointer to struct rz_mtu3_channel. * Added long_word_access_ctrl_mode sysfs entry for 16-bit and 32-bit access * Added external_input_phase_clock_select sysfs entry for selecting input clocks. * used preprocessor defines represent SIGNAL_{A,B,C,D}_ID instead of signal ids. v3->v4: * There is no resource associated with "rz-mtu3-counter" compatible and moved the code to mfd subsystem as it binds against "rz-mtu". * Removed struct platform_driver rz_mtu3_cnt_driver. * Updated commit description * Updated Kconfig description * Added macros RZ_MTU3_16_BIT_MTU{1,2}_CH for MTU1 and MTU2 channels * Added RZ_MTU3_GET_HW_CH macro for getting channel ID. * replaced priv->ch[id]->priv->ch[0] in rz_mtu3_count_read() * Cached counter max values * replaced cnt->tsr in rz_mtu3_count_direction_read() * Added comments for RZ_MTU3_TCR_CCLR_NONE * Replaced if with switch in rz_mtu3_initialize_counter() and rz_mtu3_count_ceiling_write() * Added locks in initialize, terminate and enable_read to prevent races. * Updated rz_mtu3_action_read to take care of MTU2 signals. * Added separate distinct array for each group of Synapse. * Moved pm handling to parent. v1->v3: * Modelled as a counter device supporting 3 counters(2 16-bit and 32-bit) * Add kernel-doc comments to document struct rz_mtu3_cnt * Removed mmio variable from struct rz_mtu3_cnt * Removed cnt local variable from rz_mtu3_count_read() * Replaced -EINVAL->-ERANGE for out of range error conditions. * Removed explicit cast from write functions. * Removed local variable val from rz_mtu3_count_ceiling_read() * Added lock for RMW for counter/ceiling updates. * Added different synapses for counter0 and counter{1,2} * Used ARRAY for assigning num_counts. * Added PM runtime for managing clocks. * Add MODULE_IMPORT_NS(COUNTER) to import the COUNTER namespace. --- drivers/counter/Kconfig | 11 + drivers/counter/Makefile | 1 + drivers/counter/rz-mtu3-cnt.c | 902 ++++++++++++++++++++++++++++++++++ 3 files changed, 914 insertions(+) create mode 100644 drivers/counter/rz-mtu3-cnt.c diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index b5ba8fb02cf7..4228be917038 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -73,6 +73,17 @@ config MICROCHIP_TCB_CAPTURE To compile this driver as a module, choose M here: the module will be called microchip-tcb-capture. +config RZ_MTU3_CNT + tristate "Renesas RZ/G2L MTU3a counter driver" + depends on RZ_MTU3 || COMPILE_TEST + help + Enable support for MTU3a counter driver found on Renesas RZ/G2L alike + SoCs. This IP supports both 16-bit and 32-bit phase counting mode + support. + + To compile this driver as a module, choose M here: the + module will be called rz-mtu3-cnt. + config STM32_LPTIMER_CNT tristate "STM32 LP Timer encoder counter driver" depends on MFD_STM32_LPTIMER || COMPILE_TEST diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index b9a369e0d4fc..933fdd50b3e4 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -8,6 +8,7 @@ counter-y := counter-core.o counter-sysfs.o counter-chrdev.o obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o obj-$(CONFIG_INTERRUPT_CNT) += interrupt-cnt.o +obj-$(CONFIG_RZ_MTU3_CNT) += rz-mtu3-cnt.o obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o obj-$(CONFIG_TI_EQEP) += ti-eqep.o diff --git a/drivers/counter/rz-mtu3-cnt.c b/drivers/counter/rz-mtu3-cnt.c new file mode 100644 index 000000000000..c4b5440da878 --- /dev/null +++ b/drivers/counter/rz-mtu3-cnt.c @@ -0,0 +1,902 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L MTU3a Counter driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * Register descriptions + * TSR: Timer Status Register + * TMDR1: Timer Mode Register 1 + * TMDR3: Timer Mode Register 3 + * TIOR: Timer I/O Control Register + * TCR: Timer Control Register + * TCNT: Timer Counter + * TGRA: Timer general register A + * TCNTLW: Timer Longword Counter + * TGRALW: Timer longword general register A + */ + +#define RZ_MTU3_TSR_TCFD BIT(7) /* Count Direction Flag */ + +#define RZ_MTU3_TMDR1_PH_CNT_MODE_1 (4) /* Phase counting mode 1 */ +#define RZ_MTU3_TMDR1_PH_CNT_MODE_2 (5) /* Phase counting mode 2 */ +#define RZ_MTU3_TMDR1_PH_CNT_MODE_3 (6) /* Phase counting mode 3 */ +#define RZ_MTU3_TMDR1_PH_CNT_MODE_4 (7) /* Phase counting mode 4 */ +#define RZ_MTU3_TMDR1_PH_CNT_MODE_5 (9) /* Phase counting mode 5 */ +#define RZ_MTU3_TMDR1_PH_CNT_MODE_MASK (0xf) + +/* + * LWA: MTU1/MTU2 Combination Longword Access Control + * 0: 16-bit, 1: 32-bit + */ +#define RZ_MTU3_TMDR3_LWA (0) + +/* + * PHCKSEL: External Input Phase Clock Select + * 0: MTCLKA and MTCLKB, 1: MTCLKC and MTCLKD + */ +#define RZ_MTU3_TMDR3_PHCKSEL (1) + +#define RZ_MTU3_16_BIT_MTU1_CH (0) +#define RZ_MTU3_16_BIT_MTU2_CH (1) +#define RZ_MTU3_32_BIT_CH (2) + +#define RZ_MTU3_TIOR_NO_OUTPUT (0) /* Output prohibited */ +#define RZ_MTU3_TIOR_IC_BOTH (10) /* Input capture at both edges */ + +#define SIGNAL_A_ID (0) +#define SIGNAL_B_ID (1) +#define SIGNAL_C_ID (2) +#define SIGNAL_D_ID (3) + +#define RZ_MTU3_MAX_HW_CNTR_CHANNELS (2) +#define RZ_MTU3_MAX_LOGICAL_CNTR_CHANNELS (3) + +/** + * struct rz_mtu3_cnt - MTU3 counter private data + * + * @clk: MTU3 module clock + * @lock: Lock to prevent concurrent access for ceiling and count + * @ch: HW channels for the counters + * @count_is_enabled: Enabled state of Counter value channel + * @mtu_16bit_max: Cache for 16-bit counters + * @mtu_32bit_max: Cache for 32-bit counters + */ +struct rz_mtu3_cnt { + struct clk *clk; + struct mutex lock; + struct rz_mtu3_channel *ch; + bool count_is_enabled[RZ_MTU3_MAX_LOGICAL_CNTR_CHANNELS]; + union { + u16 mtu_16bit_max[RZ_MTU3_MAX_HW_CNTR_CHANNELS]; + u32 mtu_32bit_max; + }; +}; + +static const enum counter_function rz_mtu3_count_functions[] = { + COUNTER_FUNCTION_QUADRATURE_X4, + COUNTER_FUNCTION_PULSE_DIRECTION, + COUNTER_FUNCTION_QUADRATURE_X2_B, +}; + +static inline size_t rz_mtu3_get_hw_ch(const size_t id) +{ + return (id == RZ_MTU3_32_BIT_CH) ? 0 : id; +} + +static inline struct rz_mtu3_channel *rz_mtu3_get_ch(struct counter_device *counter, int id) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + const size_t ch_id = rz_mtu3_get_hw_ch(id); + + return &priv->ch[ch_id]; +} + +static bool rz_mtu3_is_counter_invalid(struct counter_device *counter, int id) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + unsigned long tmdr; + + pm_runtime_get_sync(priv->ch->dev); + tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3); + pm_runtime_put(priv->ch->dev); + + if (id == RZ_MTU3_32_BIT_CH && test_bit(RZ_MTU3_TMDR3_LWA, &tmdr)) + return false; + + if (id != RZ_MTU3_32_BIT_CH && !test_bit(RZ_MTU3_TMDR3_LWA, &tmdr)) + return false; + + return true; +} + +static int rz_mtu3_lock_if_counter_is_valid(struct counter_device *counter, + struct rz_mtu3_channel *const ch, + struct rz_mtu3_cnt *const priv, + int id) +{ + mutex_lock(&priv->lock); + + if (ch->is_busy && !priv->count_is_enabled[id]) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + if (rz_mtu3_is_counter_invalid(counter, id)) { + mutex_unlock(&priv->lock); + return -EBUSY; + } + + return 0; +} + +static int rz_mtu3_lock_if_count_is_enabled(struct rz_mtu3_channel *const ch, + struct rz_mtu3_cnt *const priv, + int id) +{ + mutex_lock(&priv->lock); + + if (ch->is_busy && !priv->count_is_enabled[id]) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + return 0; +} + +static int rz_mtu3_count_read(struct counter_device *counter, + struct counter_count *count, u64 *val) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id); + if (ret) + return ret; + + pm_runtime_get_sync(ch->dev); + if (count->id == RZ_MTU3_32_BIT_CH) + *val = rz_mtu3_32bit_ch_read(ch, RZ_MTU3_TCNTLW); + else + *val = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TCNT); + pm_runtime_put(ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_count_write(struct counter_device *counter, + struct counter_count *count, const u64 val) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id); + if (ret) + return ret; + + pm_runtime_get_sync(ch->dev); + if (count->id == RZ_MTU3_32_BIT_CH) + rz_mtu3_32bit_ch_write(ch, RZ_MTU3_TCNTLW, val); + else + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TCNT, val); + pm_runtime_put(ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_count_function_read_helper(struct rz_mtu3_channel *const ch, + struct rz_mtu3_cnt *const priv, + enum counter_function *function) +{ + u8 timer_mode; + + pm_runtime_get_sync(ch->dev); + timer_mode = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TMDR1); + pm_runtime_put(ch->dev); + + switch (timer_mode & RZ_MTU3_TMDR1_PH_CNT_MODE_MASK) { + case RZ_MTU3_TMDR1_PH_CNT_MODE_1: + *function = COUNTER_FUNCTION_QUADRATURE_X4; + return 0; + case RZ_MTU3_TMDR1_PH_CNT_MODE_2: + *function = COUNTER_FUNCTION_PULSE_DIRECTION; + return 0; + case RZ_MTU3_TMDR1_PH_CNT_MODE_4: + *function = COUNTER_FUNCTION_QUADRATURE_X2_B; + return 0; + default: + /* + * TODO: + * - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_3 + * - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_5 + */ + return -EINVAL; + } +} + +static int rz_mtu3_count_function_read(struct counter_device *counter, + struct counter_count *count, + enum counter_function *function) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id); + if (ret) + return ret; + + ret = rz_mtu3_count_function_read_helper(ch, priv, function); + mutex_unlock(&priv->lock); + + return ret; +} + +static int rz_mtu3_count_function_write(struct counter_device *counter, + struct counter_count *count, + enum counter_function function) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + u8 timer_mode; + int ret; + + ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id); + if (ret) + return ret; + + switch (function) { + case COUNTER_FUNCTION_QUADRATURE_X4: + timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_1; + break; + case COUNTER_FUNCTION_PULSE_DIRECTION: + timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_2; + break; + case COUNTER_FUNCTION_QUADRATURE_X2_B: + timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_4; + break; + default: + /* + * TODO: + * - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_3 + * - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_5 + */ + mutex_unlock(&priv->lock); + return -EINVAL; + } + + pm_runtime_get_sync(ch->dev); + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, timer_mode); + pm_runtime_put(ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_count_direction_read(struct counter_device *counter, + struct counter_count *count, + enum counter_count_direction *direction) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + u8 tsr; + + ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id); + if (ret) + return ret; + + pm_runtime_get_sync(ch->dev); + tsr = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TSR); + pm_runtime_put(ch->dev); + + *direction = (tsr & RZ_MTU3_TSR_TCFD) ? + COUNTER_COUNT_DIRECTION_FORWARD : COUNTER_COUNT_DIRECTION_BACKWARD; + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_count_ceiling_read(struct counter_device *counter, + struct counter_count *count, + u64 *ceiling) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + const size_t ch_id = rz_mtu3_get_hw_ch(count->id); + int ret; + + ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id); + if (ret) + return ret; + + switch (count->id) { + case RZ_MTU3_16_BIT_MTU1_CH: + case RZ_MTU3_16_BIT_MTU2_CH: + *ceiling = priv->mtu_16bit_max[ch_id]; + break; + case RZ_MTU3_32_BIT_CH: + *ceiling = priv->mtu_32bit_max; + break; + default: + /* should never reach this path */ + mutex_unlock(&priv->lock); + return -EINVAL; + } + + mutex_unlock(&priv->lock); + return 0; +} + +static int rz_mtu3_count_ceiling_write(struct counter_device *counter, + struct counter_count *count, + u64 ceiling) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + const size_t ch_id = rz_mtu3_get_hw_ch(count->id); + int ret; + + ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id); + if (ret) + return ret; + + switch (count->id) { + case RZ_MTU3_16_BIT_MTU1_CH: + case RZ_MTU3_16_BIT_MTU2_CH: + if (ceiling > U16_MAX) + return -ERANGE; + priv->mtu_16bit_max[ch_id] = ceiling; + break; + case RZ_MTU3_32_BIT_CH: + if (ceiling > U32_MAX) + return -ERANGE; + priv->mtu_32bit_max = ceiling; + break; + default: + /* should never reach this path */ + mutex_unlock(&priv->lock); + return -EINVAL; + } + + pm_runtime_get_sync(ch->dev); + if (count->id == RZ_MTU3_32_BIT_CH) + rz_mtu3_32bit_ch_write(ch, RZ_MTU3_TGRALW, ceiling); + else + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRA, ceiling); + + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA); + pm_runtime_put(ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static void rz_mtu3_32bit_cnt_setting(struct counter_device *counter) +{ + struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0); + struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1); + + /* Phase counting mode 1 is used as default in initialization. */ + rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_PH_CNT_MODE_1); + + rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA); + rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TIOR, RZ_MTU3_TIOR_IC_BOTH); + + rz_mtu3_enable(ch1); + rz_mtu3_enable(ch2); +} + +static void rz_mtu3_16bit_cnt_setting(struct counter_device *counter, int id) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id); + + /* Phase counting mode 1 is used as default in initialization. */ + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_PH_CNT_MODE_1); + + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA); + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIOR, RZ_MTU3_TIOR_NO_OUTPUT); + rz_mtu3_enable(ch); +} + +static int rz_mtu3_initialize_counter(struct counter_device *counter, int id) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id); + struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0); + struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1); + + switch (id) { + case RZ_MTU3_16_BIT_MTU1_CH: + case RZ_MTU3_16_BIT_MTU2_CH: + if (!rz_mtu3_request_channel(ch)) + return -EBUSY; + + rz_mtu3_16bit_cnt_setting(counter, id); + return 0; + case RZ_MTU3_32_BIT_CH: + /* + * 32-bit phase counting need MTU1 and MTU2 to create 32-bit + * cascade counter. + */ + if (!rz_mtu3_request_channel(ch1)) + return -EBUSY; + + if (!rz_mtu3_request_channel(ch2)) { + rz_mtu3_release_channel(ch1); + return -EBUSY; + } + + rz_mtu3_32bit_cnt_setting(counter); + return 0; + default: + /* should never reach this path */ + return -EINVAL; + } +} + +static void rz_mtu3_terminate_counter(struct counter_device *counter, int id) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id); + struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0); + struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1); + + if (id == RZ_MTU3_32_BIT_CH) { + rz_mtu3_release_channel(ch2); + rz_mtu3_release_channel(ch1); + rz_mtu3_disable(ch2); + rz_mtu3_disable(ch1); + } else { + rz_mtu3_release_channel(ch); + rz_mtu3_disable(ch); + } +} + +static int rz_mtu3_count_enable_read(struct counter_device *counter, + struct counter_count *count, u8 *enable) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0); + struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id); + if (ret) + return ret; + + if (count->id == RZ_MTU3_32_BIT_CH) + *enable = rz_mtu3_is_enabled(ch1) && rz_mtu3_is_enabled(ch2); + else + *enable = rz_mtu3_is_enabled(ch); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_count_enable_write(struct counter_device *counter, + struct counter_count *count, u8 enable) +{ + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret = 0; + + if (enable) { + pm_runtime_get_sync(ch->dev); + mutex_lock(&priv->lock); + ret = rz_mtu3_initialize_counter(counter, count->id); + if (ret == 0) + priv->count_is_enabled[count->id] = true; + mutex_unlock(&priv->lock); + } else { + mutex_lock(&priv->lock); + rz_mtu3_terminate_counter(counter, count->id); + priv->count_is_enabled[count->id] = false; + mutex_unlock(&priv->lock); + pm_runtime_put(ch->dev); + } + + return ret; +} + +static int rz_mtu3_lock_if_ch0_is_enabled(struct rz_mtu3_cnt *const priv) +{ + mutex_lock(&priv->lock); + if (priv->ch->is_busy && !(priv->count_is_enabled[RZ_MTU3_16_BIT_MTU1_CH] || + priv->count_is_enabled[RZ_MTU3_32_BIT_CH])) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + return 0; +} + +static int rz_mtu3_cascade_counts_enable_get(struct counter_device *counter, + u8 *cascade_enable) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + unsigned long tmdr; + int ret; + + ret = rz_mtu3_lock_if_ch0_is_enabled(priv); + if (ret) + return ret; + + pm_runtime_get_sync(priv->ch->dev); + tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3); + pm_runtime_put(priv->ch->dev); + *cascade_enable = test_bit(RZ_MTU3_TMDR3_LWA, &tmdr); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_cascade_counts_enable_set(struct counter_device *counter, + u8 cascade_enable) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_ch0_is_enabled(priv); + if (ret) + return ret; + + pm_runtime_get_sync(priv->ch->dev); + rz_mtu3_shared_reg_update_bit(priv->ch, RZ_MTU3_TMDR3, + RZ_MTU3_TMDR3_LWA, cascade_enable); + pm_runtime_put(priv->ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_ext_input_phase_clock_select_get(struct counter_device *counter, + u32 *ext_input_phase_clock_select) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + unsigned long tmdr; + int ret; + + ret = rz_mtu3_lock_if_ch0_is_enabled(priv); + if (ret) + return ret; + + pm_runtime_get_sync(priv->ch->dev); + tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3); + pm_runtime_put(priv->ch->dev); + *ext_input_phase_clock_select = test_bit(RZ_MTU3_TMDR3_PHCKSEL, &tmdr); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rz_mtu3_ext_input_phase_clock_select_set(struct counter_device *counter, + u32 ext_input_phase_clock_select) +{ + struct rz_mtu3_cnt *const priv = counter_priv(counter); + int ret; + + ret = rz_mtu3_lock_if_ch0_is_enabled(priv); + if (ret) + return ret; + + pm_runtime_get_sync(priv->ch->dev); + rz_mtu3_shared_reg_update_bit(priv->ch, RZ_MTU3_TMDR3, + RZ_MTU3_TMDR3_PHCKSEL, + ext_input_phase_clock_select); + pm_runtime_put(priv->ch->dev); + mutex_unlock(&priv->lock); + + return 0; +} + +static struct counter_comp rz_mtu3_count_ext[] = { + COUNTER_COMP_DIRECTION(rz_mtu3_count_direction_read), + COUNTER_COMP_ENABLE(rz_mtu3_count_enable_read, + rz_mtu3_count_enable_write), + COUNTER_COMP_CEILING(rz_mtu3_count_ceiling_read, + rz_mtu3_count_ceiling_write), +}; + +static const enum counter_synapse_action rz_mtu3_synapse_actions[] = { + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, + COUNTER_SYNAPSE_ACTION_RISING_EDGE, + COUNTER_SYNAPSE_ACTION_NONE, +}; + +static int rz_mtu3_action_read(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + enum counter_synapse_action *action) +{ + const bool is_signal_ab = (synapse->signal->id == SIGNAL_A_ID) || + (synapse->signal->id == SIGNAL_B_ID); + struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id); + struct rz_mtu3_cnt *const priv = counter_priv(counter); + enum counter_function function; + bool mtclkc_mtclkd; + unsigned long tmdr; + int ret; + + ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id); + if (ret) + return ret; + + ret = rz_mtu3_count_function_read_helper(ch, priv, &function); + if (ret) { + mutex_unlock(&priv->lock); + return ret; + } + + /* Default action mode */ + *action = COUNTER_SYNAPSE_ACTION_NONE; + + if (count->id != RZ_MTU3_16_BIT_MTU1_CH) { + tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3); + mtclkc_mtclkd = test_bit(RZ_MTU3_TMDR3_PHCKSEL, &tmdr); + if ((mtclkc_mtclkd && is_signal_ab) || + (!mtclkc_mtclkd && !is_signal_ab)) { + mutex_unlock(&priv->lock); + return 0; + } + } + + switch (function) { + case COUNTER_FUNCTION_PULSE_DIRECTION: + /* + * Rising edges on signal A (signal C) updates the respective + * count. The input level of signal B (signal D) determines + * direction. + */ + if (synapse->signal->id == SIGNAL_A_ID || + synapse->signal->id == SIGNAL_C_ID) + *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE; + break; + case COUNTER_FUNCTION_QUADRATURE_X2_B: + /* + * Any state transition on quadrature pair signal B (signal D) + * updates the respective count. + */ + if (synapse->signal->id == SIGNAL_B_ID || + synapse->signal->id == SIGNAL_D_ID) + *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + break; + case COUNTER_FUNCTION_QUADRATURE_X4: + /* counts up/down on both edges of A (C) and B (D) signal */ + *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + break; + default: + /* should never reach this path */ + mutex_unlock(&priv->lock); + return -EINVAL; + } + + mutex_unlock(&priv->lock); + + return 0; +} + +static const struct counter_ops rz_mtu3_cnt_ops = { + .count_read = rz_mtu3_count_read, + .count_write = rz_mtu3_count_write, + .function_read = rz_mtu3_count_function_read, + .function_write = rz_mtu3_count_function_write, + .action_read = rz_mtu3_action_read, +}; + +#define RZ_MTU3_PHASE_SIGNAL(_id, _name) { \ + .id = (_id), \ + .name = (_name), \ +} + +static struct counter_signal rz_mtu3_signals[] = { + RZ_MTU3_PHASE_SIGNAL(SIGNAL_A_ID, "MTU1 MTCLKA"), + RZ_MTU3_PHASE_SIGNAL(SIGNAL_B_ID, "MTU1 MTCLKB"), + RZ_MTU3_PHASE_SIGNAL(SIGNAL_C_ID, "MTU2 MTCLKC"), + RZ_MTU3_PHASE_SIGNAL(SIGNAL_D_ID, "MTU2 MTCLKD"), +}; + +static struct counter_synapse rz_mtu3_mtu1_count_synapses[] = { + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals, + }, + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals + 1, + } +}; + +static struct counter_synapse rz_mtu3_mtu2_count_synapses[] = { + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals, + }, + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals + 1, + }, + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals + 2, + }, + { + .actions_list = rz_mtu3_synapse_actions, + .num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions), + .signal = rz_mtu3_signals + 3, + } +}; + +static struct counter_count rz_mtu3_counts[] = { + { + .id = RZ_MTU3_16_BIT_MTU1_CH, + .name = "Channel 1 Count", + .functions_list = rz_mtu3_count_functions, + .num_functions = ARRAY_SIZE(rz_mtu3_count_functions), + .synapses = rz_mtu3_mtu1_count_synapses, + .num_synapses = ARRAY_SIZE(rz_mtu3_mtu1_count_synapses), + .ext = rz_mtu3_count_ext, + .num_ext = ARRAY_SIZE(rz_mtu3_count_ext), + }, + { + .id = RZ_MTU3_16_BIT_MTU2_CH, + .name = "Channel 2 Count", + .functions_list = rz_mtu3_count_functions, + .num_functions = ARRAY_SIZE(rz_mtu3_count_functions), + .synapses = rz_mtu3_mtu2_count_synapses, + .num_synapses = ARRAY_SIZE(rz_mtu3_mtu2_count_synapses), + .ext = rz_mtu3_count_ext, + .num_ext = ARRAY_SIZE(rz_mtu3_count_ext), + }, + { + .id = RZ_MTU3_32_BIT_CH, + .name = "Channel 1 and 2 (cascaded) Count", + .functions_list = rz_mtu3_count_functions, + .num_functions = ARRAY_SIZE(rz_mtu3_count_functions), + .synapses = rz_mtu3_mtu2_count_synapses, + .num_synapses = ARRAY_SIZE(rz_mtu3_mtu2_count_synapses), + .ext = rz_mtu3_count_ext, + .num_ext = ARRAY_SIZE(rz_mtu3_count_ext), + } +}; + +static const char *const rz_mtu3_ext_input_phase_clock_select[] = { + "MTCLKA-MTCLKB", + "MTCLKC-MTCLKD", +}; + +static DEFINE_COUNTER_ENUM(rz_mtu3_ext_input_phase_clock_select_enum, + rz_mtu3_ext_input_phase_clock_select); + +static struct counter_comp rz_mtu3_device_ext[] = { + COUNTER_COMP_DEVICE_BOOL("cascade_counts_enable", + rz_mtu3_cascade_counts_enable_get, + rz_mtu3_cascade_counts_enable_set), + COUNTER_COMP_DEVICE_ENUM("external_input_phase_clock_select", + rz_mtu3_ext_input_phase_clock_select_get, + rz_mtu3_ext_input_phase_clock_select_set, + rz_mtu3_ext_input_phase_clock_select_enum), +}; + +static int rz_mtu3_cnt_pm_runtime_suspend(struct device *dev) +{ + struct clk *const clk = dev_get_drvdata(dev); + + clk_disable_unprepare(clk); + + return 0; +} + +static int rz_mtu3_cnt_pm_runtime_resume(struct device *dev) +{ + struct clk *const clk = dev_get_drvdata(dev); + + clk_prepare_enable(clk); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_cnt_pm_ops, + rz_mtu3_cnt_pm_runtime_suspend, + rz_mtu3_cnt_pm_runtime_resume, NULL); + +static void rz_mtu3_cnt_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); +} + +static int rz_mtu3_cnt_probe(struct platform_device *pdev) +{ + struct rz_mtu3 *ddata = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct counter_device *counter; + struct rz_mtu3_channel *ch; + struct rz_mtu3_cnt *priv; + unsigned int i; + int ret; + + counter = devm_counter_alloc(dev, sizeof(*priv)); + if (!counter) + return -ENOMEM; + + priv = counter_priv(counter); + priv->clk = ddata->clk; + priv->mtu_32bit_max = U32_MAX; + priv->ch = &ddata->channels[RZ_MTU1]; + ch = &priv->ch[0]; + for (i = 0; i < RZ_MTU3_MAX_HW_CNTR_CHANNELS; i++) { + ch->dev = dev; + priv->mtu_16bit_max[i] = U16_MAX; + ch++; + } + + mutex_init(&priv->lock); + platform_set_drvdata(pdev, priv->clk); + clk_prepare_enable(priv->clk); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + ret = devm_add_action_or_reset(&pdev->dev, rz_mtu3_cnt_pm_disable, dev); + if (ret < 0) + goto disable_clock; + + counter->name = dev_name(dev); + counter->parent = dev; + counter->ops = &rz_mtu3_cnt_ops; + counter->counts = rz_mtu3_counts; + counter->num_counts = ARRAY_SIZE(rz_mtu3_counts); + counter->signals = rz_mtu3_signals; + counter->num_signals = ARRAY_SIZE(rz_mtu3_signals); + counter->ext = rz_mtu3_device_ext; + counter->num_ext = ARRAY_SIZE(rz_mtu3_device_ext); + + /* Register Counter device */ + ret = devm_counter_add(dev, counter); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to add counter\n"); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(priv->clk); + + return ret; +} + +static struct platform_driver rz_mtu3_cnt_driver = { + .probe = rz_mtu3_cnt_probe, + .driver = { + .name = "rz-mtu3-counter", + .pm = pm_ptr(&rz_mtu3_cnt_pm_ops), + }, +}; +module_platform_driver(rz_mtu3_cnt_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_ALIAS("platform:rz-mtu3-counter"); +MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a counter driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(COUNTER); From patchwork Thu Feb 2 16:57:31 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126556 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4D33CC636D4 for ; Thu, 2 Feb 2023 16:58:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232280AbjBBQ6H (ORCPT ); Thu, 2 Feb 2023 11:58:07 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34190 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232494AbjBBQ6D (ORCPT ); Thu, 2 Feb 2023 11:58:03 -0500 Received: from relmlie6.idc.renesas.com (relmlor2.renesas.com [210.160.252.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 1C4AFB76F; Thu, 2 Feb 2023 08:58:02 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,267,1669042800"; d="scan'208";a="151446210" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie6.idc.renesas.com with ESMTP; 03 Feb 2023 01:58:01 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id 027FD402DBB2; Fri, 3 Feb 2023 01:57:57 +0900 (JST) From: Biju Das To: William Breathitt Gray Cc: Biju Das , linux-iio@vger.kernel.org, Lee Jones , "Daniel Lezcano" , "Thierry Reding" , =?utf-8?q?Uwe_Kleine-K=C3=B6ni?= =?utf-8?q?g?= , Geert Uytterhoeven , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org Subject: [PATCH v12 5/6] MAINTAINERS: Add entries for Renesas RZ/G2L MTU3a counter driver Date: Thu, 2 Feb 2023 16:57:31 +0000 Message-Id: <20230202165732.305650-6-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org Add the MAINTAINERS entries for the Renesas RZ/G2L MTU3a counter driver. Signed-off-by: Biju Das --- v11->v12: * No change. --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 590bcd047a7f..f55ad30fcb23 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17773,6 +17773,14 @@ S: Supported F: Documentation/devicetree/bindings/iio/adc/renesas,rzg2l-adc.yaml F: drivers/iio/adc/rzg2l_adc.c +RENESAS RZ/G2L MTU3a COUNTER DRIVER +M: Biju Das +L: linux-iio@vger.kernel.org +L: linux-renesas-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml +F: drivers/counter/rz-mtu3-cnt.c + RENESAS RZ/N1 A5PSW SWITCH DRIVER M: Clément Léger L: linux-renesas-soc@vger.kernel.org From patchwork Thu Feb 2 16:57:32 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Biju Das X-Patchwork-Id: 13126558 X-Patchwork-Delegate: geert@linux-m68k.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 34434C636D6 for ; Thu, 2 Feb 2023 16:58:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231216AbjBBQ6h (ORCPT ); Thu, 2 Feb 2023 11:58:37 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34314 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232418AbjBBQ6I (ORCPT ); Thu, 2 Feb 2023 11:58:08 -0500 Received: from relmlie6.idc.renesas.com (relmlor2.renesas.com [210.160.252.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id A0E491D936; Thu, 2 Feb 2023 08:58:05 -0800 (PST) X-IronPort-AV: E=Sophos;i="5.97,267,1669042800"; d="scan'208";a="151446223" Received: from unknown (HELO relmlir6.idc.renesas.com) ([10.200.68.152]) by relmlie6.idc.renesas.com with ESMTP; 03 Feb 2023 01:58:05 +0900 Received: from localhost.localdomain (unknown [10.226.92.118]) by relmlir6.idc.renesas.com (Postfix) with ESMTP id 14D0A402DBB9; Fri, 3 Feb 2023 01:58:01 +0900 (JST) From: Biju Das To: Thierry Reding Cc: Biju Das , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , linux-pwm@vger.kernel.org, Lee Jones , "Daniel Lezcano" , "William Breathitt Gray" , Geert Uytterhoeven , Chris Paterson , Prabhakar Mahadev Lad , linux-renesas-soc@vger.kernel.org Subject: [PATCH v12 6/6] pwm: Add Renesas RZ/G2L MTU3a PWM driver Date: Thu, 2 Feb 2023 16:57:32 +0000 Message-Id: <20230202165732.305650-7-biju.das.jz@bp.renesas.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> References: <20230202165732.305650-1-biju.das.jz@bp.renesas.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-renesas-soc@vger.kernel.org Add support for RZ/G2L MTU3a PWM driver. The IP supports following PWM modes 1) PWM mode{1,2} 2) Reset-synchronized PWM mode 3) Complementary PWM mode{1,2,3} This patch adds basic pwm mode 1 support for RZ/G2L MTU3a pwm driver by creating separate logical channels for each IOs. Signed-off-by: Biju Das --- v11->v12: * Updated header file to as core driver is in MFD. * Reordered get_state() v10->v11: * No change. v9->v10: * No change. v8->v9: * Added prescale/duty_cycle variables to struct rz_mtu3_pwm_chip and cached this values in rz_mtu3_pwm_config and used this cached values in get_state(), if PWM is disabled. * Added return code for get_state() v7->v8: * Simplified rz_mtu3_pwm_request by calling rz_mtu3_request_channel() * Simplified rz_mtu3_pwm_free by calling rz_mtu3_release_channel() v6->v7: * Added channel specific mutex lock to avoid race between counter device and rz_mtu3_pwm_{request,free} * Added pm_runtime_resume_and_get in rz_mtu3_pwm_enable() * Added pm_runtime_put_sync in rz_mtu3_pwm_disable() * Updated rz_mtu3_pwm_config() * Updated rz_mtu3_pwm_apply() v5->v6: * Updated commit and Kconfig description * Sorted the header * Replaced dev_get_drvdata from rz_mtu3_pwm_pm_disable() * Replaced SET_RUNTIME_PM_OPS->DEFINE_RUNTIME_DEV_PM_OPS and removed __maybe_unused from suspend/resume() v4->v5: * pwm device is instantiated by mtu3a core driver. v3->v4: * There is no resource associated with "rz-mtu3-pwm" compatible and moved the code to mfd subsystem as it binds against "rz-mtu". * Removed struct platform_driver rz_mtu3_pwm_driver. v2->v3: * No change. v1->v2: * Modelled as a single PWM device handling multiple channles. * Used PM framework to manage the clocks. --- drivers/pwm/Kconfig | 11 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-rz-mtu3.c | 485 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 497 insertions(+) create mode 100644 drivers/pwm/pwm-rz-mtu3.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 31cdc9dae3c5..c54cbeabe093 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -492,6 +492,17 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_RZ_MTU3 + tristate "Renesas RZ/G2L MTU3a PWM Timer support" + depends on RZ_MTU3 || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the MTU3a PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rz-mtu3. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index a95aabae9115..6b75c0145336 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_RZV2M) += pwm-rzv2m.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o diff --git a/drivers/pwm/pwm-rz-mtu3.c b/drivers/pwm/pwm-rz-mtu3.c new file mode 100644 index 000000000000..d94e3fc36dfb --- /dev/null +++ b/drivers/pwm/pwm-rz-mtu3.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L MTU3a PWM Timer driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + * + * Hardware manual for this IP can be found here + * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en + * + * Limitations: + * - When PWM is disabled, the output is driven to Hi-Z. + * - While the hardware supports both polarities, the driver (for now) + * only handles normal polarity. + * - While the hardware supports pwm mode{1,2}, reset-synchronized pwm and + * complementary pwm modes, the driver (for now) only handles pwm mode1. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZ_MTU3_TMDR1_MD_NORMAL (0) +#define RZ_MTU3_TMDR1_MD_PWM_MODE_1 (2) + +#define RZ_MTU3_TIOR_OC_RETAIN (0) +#define RZ_MTU3_TIOR_OC_0_H_COMP_MATCH (2) +#define RZ_MTU3_TIOR_OC_1_TOGGLE (7) +#define RZ_MTU3_TIOR_OC_IOA GENMASK(3, 0) + +#define RZ_MTU3_TCR_CCLR_TGRC (5 << 5) +#define RZ_MTU3_TCR_CKEG_RISING (0 << 3) + +#define RZ_MTU3_TCR_TPCS GENMASK(2, 0) + +#define RZ_MTU3_MAX_PWM_MODE1_CHANNELS (12) + +#define RZ_MTU3_MAX_HW_PWM_CHANNELS (7) + +static const u8 rz_mtu3_pwm_mode1_num_ios[] = { 2, 1, 1, 2, 2, 2, 2 }; + +/** + * struct rz_mtu3_pwm_chip - MTU3 pwm private data + * + * @chip: MTU3 pwm chip data + * @clk: MTU3 module clock + * @lock: Lock to prevent concurrent access for usage count + * @rate: MTU3 clock rate + * @user_count: MTU3 usage count + * @rz_mtu3_channel: HW channels for the PWM + */ + +struct rz_mtu3_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + struct mutex lock; + unsigned long rate; + u32 user_count[RZ_MTU3_MAX_HW_PWM_CHANNELS]; + struct rz_mtu3_channel *ch[RZ_MTU3_MAX_HW_PWM_CHANNELS]; + + /* + * The driver cannot read the current duty cycle/prescale from the + * hardware if the hardware is disabled. Cache the last programmed + * duty cycle/prescale value to return in that case. + */ + u8 prescale[RZ_MTU3_MAX_HW_PWM_CHANNELS]; + unsigned int duty_cycle[RZ_MTU3_MAX_PWM_MODE1_CHANNELS]; +}; + +static inline struct rz_mtu3_pwm_chip *to_rz_mtu3_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct rz_mtu3_pwm_chip, chip); +} + +static u8 rz_mtu3_pwm_calculate_prescale(struct rz_mtu3_pwm_chip *rz_mtu3, + u64 period_cycles) +{ + u32 prescaled_period_cycles; + u8 prescale; + + prescaled_period_cycles = period_cycles >> 16; + if (prescaled_period_cycles >= 16) + prescale = 3; + else + prescale = (fls(prescaled_period_cycles) + 1) / 2; + + return prescale; +} + +static struct rz_mtu3_channel * +rz_mtu3_get_hw_channel(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, u32 channel) +{ + unsigned int i, ch_index = 0; + + for (i = 0; i < ARRAY_SIZE(rz_mtu3_pwm_mode1_num_ios); i++) { + ch_index += rz_mtu3_pwm_mode1_num_ios[i]; + + if (ch_index > channel) + break; + } + + return rz_mtu3_pwm->ch[i]; +} + +static u32 rz_mtu3_get_hw_channel_index(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + struct rz_mtu3_channel *ch) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(rz_mtu3_pwm_mode1_num_ios); i++) { + if (ch == rz_mtu3_pwm->ch[i]) + break; + } + + return i; +} + +static bool rz_mtu3_pwm_is_second_channel(u32 ch_index, u32 hwpwm) +{ + u32 i, pwm_ch_index = 0; + + for (i = 0; i < ch_index; i++) + pwm_ch_index += rz_mtu3_pwm_mode1_num_ios[i]; + + return pwm_ch_index != hwpwm; +} + +static bool rz_mtu3_pwm_is_ch_enabled(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + u32 hwpwm) +{ + struct rz_mtu3_channel *ch; + bool is_channel_en; + u32 ch_index; + u8 val; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + is_channel_en = rz_mtu3_is_enabled(ch); + + if (rz_mtu3_pwm_is_second_channel(ch_index, hwpwm)) + val = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TIORL); + else + val = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TIORH); + + return (is_channel_en && (val & RZ_MTU3_TIOR_OC_IOA)); +} + +static int rz_mtu3_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_channel *ch; + u32 ch_index; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + if (!rz_mtu3_pwm->user_count[ch_index] && !rz_mtu3_request_channel(ch)) + return -EBUSY; + + mutex_lock(&rz_mtu3_pwm->lock); + rz_mtu3_pwm->user_count[ch_index]++; + mutex_unlock(&rz_mtu3_pwm->lock); + + return 0; +} + +static void rz_mtu3_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_channel *ch; + u32 ch_index; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + + mutex_lock(&rz_mtu3_pwm->lock); + rz_mtu3_pwm->user_count[ch_index]--; + mutex_unlock(&rz_mtu3_pwm->lock); + + if (!rz_mtu3_pwm->user_count[ch_index]) + rz_mtu3_release_channel(ch); +} + +static int rz_mtu3_pwm_enable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + struct pwm_device *pwm) +{ + struct rz_mtu3_channel *ch; + u32 ch_index; + u8 val; + int rc; + + rc = pm_runtime_resume_and_get(rz_mtu3_pwm->chip.dev); + if (rc) + return rc; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + val = (RZ_MTU3_TIOR_OC_1_TOGGLE << 4) | RZ_MTU3_TIOR_OC_0_H_COMP_MATCH; + + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_MD_PWM_MODE_1); + if (rz_mtu3_pwm_is_second_channel(ch_index, pwm->hwpwm)) + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIORL, val); + else + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIORH, val); + + if (rz_mtu3_pwm->user_count[ch_index] <= 1) + rz_mtu3_enable(ch); + + return 0; +} + +static void rz_mtu3_pwm_disable(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, + struct pwm_device *pwm) +{ + struct rz_mtu3_channel *ch; + u32 ch_index; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + + /* Return to normal mode and disable output pins of MTU3 channel */ + if (rz_mtu3_pwm->user_count[ch_index] <= 1) + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_MD_NORMAL); + + if (rz_mtu3_pwm_is_second_channel(ch_index, pwm->hwpwm)) + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIORL, RZ_MTU3_TIOR_OC_RETAIN); + else + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIORH, RZ_MTU3_TIOR_OC_RETAIN); + + if (rz_mtu3_pwm->user_count[ch_index] <= 1) + rz_mtu3_disable(ch); + + pm_runtime_put_sync(rz_mtu3_pwm->chip.dev); +} + +static int rz_mtu3_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_channel *ch; + u8 prescale, val; + u32 ch_index; + u16 dc, pv; + u64 tmp; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + pm_runtime_get_sync(chip->dev); + state->enabled = rz_mtu3_pwm_is_ch_enabled(rz_mtu3_pwm, pwm->hwpwm); + if (state->enabled) { + val = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TCR); + prescale = FIELD_GET(RZ_MTU3_TCR_TPCS, val); + + if (rz_mtu3_pwm_is_second_channel(ch_index, pwm->hwpwm)) { + dc = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TGRD); + pv = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TGRC); + } else { + dc = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TGRB); + pv = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TGRA); + } + + tmp = NSEC_PER_SEC * (u64)pv << (2 * prescale); + state->period = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate); + } else { + /* If the PWM is disabled, use the cached value. */ + prescale = rz_mtu3_pwm->prescale[ch_index]; + dc = rz_mtu3_pwm->duty_cycle[pwm->hwpwm]; + } + + tmp = NSEC_PER_SEC * (u64)dc << (2 * prescale); + state->duty_cycle = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate); + state->polarity = PWM_POLARITY_NORMAL; + pm_runtime_put(chip->dev); + + return 0; +} + +static int rz_mtu3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + struct rz_mtu3_channel *ch; + unsigned long pv, dc; + u64 period_cycles; + u64 duty_cycles; + u32 ch_index; + u8 prescale; + int err; + u8 val; + + /* + * Refuse clk rates > 1 GHz to prevent overflowing the following + * calculation. + */ + if (rz_mtu3_pwm->rate > NSEC_PER_SEC) + return -EINVAL; + + ch = rz_mtu3_get_hw_channel(rz_mtu3_pwm, pwm->hwpwm); + ch_index = rz_mtu3_get_hw_channel_index(rz_mtu3_pwm, ch); + period_cycles = mul_u64_u32_div(state->period, rz_mtu3_pwm->rate, + NSEC_PER_SEC); + prescale = rz_mtu3_pwm_calculate_prescale(rz_mtu3_pwm, period_cycles); + + if (period_cycles >> (2 * prescale) <= U16_MAX) + pv = period_cycles >> (2 * prescale); + else + pv = U16_MAX; + + duty_cycles = mul_u64_u32_div(state->duty_cycle, rz_mtu3_pwm->rate, + NSEC_PER_SEC); + if (duty_cycles >> (2 * prescale) <= U16_MAX) + dc = duty_cycles >> (2 * prescale); + else + dc = U16_MAX; + + /* + * Store the duty cycle/prescale for future reference in cases where the + * corresponding registers can't be read (i.e. when the PWM is disabled). + */ + rz_mtu3_pwm->prescale[ch_index] = prescale; + rz_mtu3_pwm->duty_cycle[pwm->hwpwm] = dc; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. + */ + if (!pwm->state.enabled) { + err = pm_runtime_resume_and_get(chip->dev); + if (err) + return err; + } + + val = RZ_MTU3_TCR_CKEG_RISING | prescale; + if (rz_mtu3_pwm_is_second_channel(ch_index, pwm->hwpwm)) { + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, + RZ_MTU3_TCR_CCLR_TGRC | val); + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRD, dc); + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRC, pv); + } else { + rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, + RZ_MTU3_TCR_CCLR_TGRA | val); + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRB, dc); + rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRA, pv); + } + + /* If the PWM is not enabled, turn the clock off again to save power. */ + if (!pwm->state.enabled) + pm_runtime_put(chip->dev); + + return 0; +} + +static int rz_mtu3_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip); + bool enabled = pwm->state.enabled; + int ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + if (!state->enabled) { + if (enabled) + rz_mtu3_pwm_disable(rz_mtu3_pwm, pwm); + + return 0; + } + + ret = rz_mtu3_pwm_config(chip, pwm, state); + if (ret) + return ret; + + if (!enabled) + ret = rz_mtu3_pwm_enable(rz_mtu3_pwm, pwm); + + return ret; +} + +static const struct pwm_ops rz_mtu3_pwm_ops = { + .request = rz_mtu3_pwm_request, + .free = rz_mtu3_pwm_free, + .get_state = rz_mtu3_pwm_get_state, + .apply = rz_mtu3_pwm_apply, + .owner = THIS_MODULE, +}; + +static int rz_mtu3_pwm_pm_runtime_suspend(struct device *dev) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev); + + clk_disable_unprepare(rz_mtu3_pwm->clk); + + return 0; +} + +static int rz_mtu3_pwm_pm_runtime_resume(struct device *dev) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = dev_get_drvdata(dev); + + clk_prepare_enable(rz_mtu3_pwm->clk); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_pwm_pm_ops, + rz_mtu3_pwm_pm_runtime_suspend, + rz_mtu3_pwm_pm_runtime_resume, NULL); + +static void rz_mtu3_pwm_pm_disable(void *data) +{ + struct rz_mtu3_pwm_chip *rz_mtu3_pwm = data; + + pm_runtime_disable(rz_mtu3_pwm->chip.dev); + pm_runtime_set_suspended(rz_mtu3_pwm->chip.dev); +} + +static int rz_mtu3_pwm_probe(struct platform_device *pdev) +{ + struct rz_mtu3 *ddata = dev_get_drvdata(pdev->dev.parent); + struct rz_mtu3_pwm_chip *rz_mtu3_pwm; + struct device *dev = &pdev->dev; + int num_pwm_hw_ch = 0; + unsigned int i; + int ret; + + rz_mtu3_pwm = devm_kzalloc(&pdev->dev, sizeof(*rz_mtu3_pwm), GFP_KERNEL); + if (!rz_mtu3_pwm) + return -ENOMEM; + + rz_mtu3_pwm->clk = ddata->clk; + rz_mtu3_pwm->rate = clk_get_rate(rz_mtu3_pwm->clk); + for (i = 0; i < RZ_MTU_NUM_CHANNELS; i++) { + if (i == RZ_MTU5 || i == RZ_MTU8) + continue; + + rz_mtu3_pwm->ch[num_pwm_hw_ch] = &ddata->channels[i]; + rz_mtu3_pwm->ch[num_pwm_hw_ch]->dev = dev; + num_pwm_hw_ch++; + } + + mutex_init(&rz_mtu3_pwm->lock); + platform_set_drvdata(pdev, rz_mtu3_pwm); + clk_prepare_enable(rz_mtu3_pwm->clk); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + ret = devm_add_action_or_reset(&pdev->dev, + rz_mtu3_pwm_pm_disable, + rz_mtu3_pwm); + if (ret < 0) + goto disable_clock; + + rz_mtu3_pwm->chip.dev = &pdev->dev; + rz_mtu3_pwm->chip.ops = &rz_mtu3_pwm_ops; + rz_mtu3_pwm->chip.npwm = RZ_MTU3_MAX_PWM_MODE1_CHANNELS; + ret = devm_pwmchip_add(&pdev->dev, &rz_mtu3_pwm->chip); + if (ret) { + dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n"); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(rz_mtu3_pwm->clk); + return ret; +} + +static struct platform_driver rz_mtu3_pwm_driver = { + .driver = { + .name = "pwm-rz-mtu3", + .pm = pm_ptr(&rz_mtu3_pwm_pm_ops), + }, + .probe = rz_mtu3_pwm_probe, +}; +module_platform_driver(rz_mtu3_pwm_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_ALIAS("platform:pwm-rz-mtu3"); +MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a PWM Timer Driver"); +MODULE_LICENSE("GPL");