From patchwork Mon May 3 21:44:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Anderson X-Patchwork-Id: 12237093 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-17.4 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,MSGID_FROM_MTA_HEADER,SPF_HELO_NONE, SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EE94AC433ED for ; Mon, 3 May 2021 21:47:08 +0000 (UTC) Received: from desiato.infradead.org (desiato.infradead.org [90.155.92.199]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 5584361283 for ; Mon, 3 May 2021 21:47:08 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 5584361283 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=seco.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding :Content-Type:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=BvF17kldPXo4QTlKuEjnGN2IR50i8uNwsO7bVxI69iw=; b=defSAtg6rlMFZZjAnnw6LkY8M goLPGDyWZgQ1jjYA5TJWcAy6fUbw01DK29G5K2l0VQiH9yzMEL0NsQgcYyc1fMV1zPxjfsf6Bxgze As2CCzrKEl0yQLzjSaN6MG5x9a7R1Dqk1Mdtw9d6/6/69WH6EoXoiPiT2w8oqFAXqVU51LsUIkXAH +GT7QYC5oj1dHW0EcQBEqpUKsh3J1exGeBWOSPMJyLXD38QRlC115Fk+DTLL/mDC6Ur/MXDgcOgwL g2j3zBRCsYTPklmfK6Wa0c7+tAN3RlKR334FDem9ZQmQZPimps9+A3LsUvXENDVRlw62iRIUsTFyW cOYKkwplw==; Received: from localhost ([::1] helo=desiato.infradead.org) by desiato.infradead.org with esmtp (Exim 4.94 #2 (Red Hat Linux)) id 1ldgN2-00Erpo-PD; Mon, 03 May 2021 21:44:53 +0000 Received: from bombadil.infradead.org ([2607:7c80:54:e::133]) by desiato.infradead.org with esmtps (Exim 4.94 #2 (Red Hat Linux)) id 1ldgMp-00Ero8-Ma for linux-arm-kernel@desiato.infradead.org; Mon, 03 May 2021 21:44:40 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=bombadil.20210309; h=MIME-Version: Content-Transfer-Encoding:Content-Type:References:In-Reply-To:Message-Id:Date :Subject:Cc:To:From:Sender:Reply-To:Content-ID:Content-Description; bh=SXCVqm/S7dCuak2dS+LiOG2n7JZRATSsDVG6QKy2BX8=; b=RPBKfPW0ZttjwbIpILvDlllbuy TW2BFi+dOf+l1tiPpb4nbcV1lPq6QOrF13RLjpUiCqB+tuu8uzWbAZz3hxP6HwSiNBdx7KM4iSfHT lLwT+fWiVpg6/xDUB9mjDW5M4YAXSaKxddjQTlazIC+kg9Bmj9p8TB3xUpxR9vFKj+rDTeJE3NDCl I1zb4Gj6/gfi+tj02KcZWJJPuJfpnsmLOu4Q2CpTva5ZickKCXiKVf0KG8P5zB//9fgoriXn+boTV DUvUK8YgRT2TJznyodTpNF1QpSGkBc5o55n1j3gtt+Rtgd35kWjQMX61dAlhYaLp14J00gTj1GSO0 9voT+XiQ==; Received: from mail-db8eur05on2086.outbound.protection.outlook.com ([40.107.20.86] helo=EUR05-DB8-obe.outbound.protection.outlook.com) by bombadil.infradead.org with esmtps (Exim 4.94 #2 (Red Hat Linux)) id 1ldgMm-003VCA-AO for linux-arm-kernel@lists.infradead.org; Mon, 03 May 2021 21:44:38 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=bW2WqkDkoLklIJ11SM0XB0dKVhnq7jIHAuzyFdMkdKZZbdOXhlhSyHZFO46kJJPkKwRNmqoIO9+M23JMKyAKWtlPKa+0kHp/zzVDtJVIWDu7dZ7r/tMjcZWwkOWd/VOzlDqc8zlbxQ5wZr20qEmRmqDIMDweHyooPmOQDFDGha7bW2+DWy+SsfkgJrMkpcjc73glqa3T51qMUvODZ1cQb/3Ahn894f1CGX7GiZaLF8qCtZLUtgLx5GTySWKawbO7GbbC9wjhZjasiXjqiSqtPsM2n5ZyAtsMaTzbpFri5eqk3GMm7Gh+O7r4l52eyRK8ekHaBhe0dL4P92QUCKnefQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=SXCVqm/S7dCuak2dS+LiOG2n7JZRATSsDVG6QKy2BX8=; b=goOIFpATof17Y5/D+3izTshPwuW1v/3rHkk+rcIGTq7thp5fOb12cweOlGeDJYguNOTd4W0WBqs+49UJxo1TJQtmQVZArXCWtFs/ECoiskXSfuw7BdeJdmVbV0cgNHYVM5VJjZF8Adr91a0bOt9kRDbSdIz5BguFTOacR+byS9vvjoJUXNhwNk8iiAEqZQlFlXbTD0sFxDNKL0p+lXwgvcz30YCAjZoMUQoaLt8IKuwapnzlPxrWaZZBY9Iy1nyOuMBqezEeVkwRgNpvyZiioLNp4orrS0LuInj+Oq5r4sQjdA3hSU+qDicH+lK04PqZlEXNlwzG1FLilRvS/16sdg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=seco.com; dmarc=pass action=none header.from=seco.com; dkim=pass header.d=seco.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=secospa.onmicrosoft.com; s=selector2-secospa-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=SXCVqm/S7dCuak2dS+LiOG2n7JZRATSsDVG6QKy2BX8=; b=h/a9/0BrcyqkTgVr9gEnOdfk830ZEtmUcvT4NfgGOJdT0xAvGhnTTfi0KceGbp0R6lvoNlRisb0/YwK9aizQtawXZc/fBMl3HUDQSvgIOH0VAYNZxMf3ZesaaZosGL+hxBGq5cZ8CQx7SHxTsWLq+DB3IcYTrzo4dNC+oU+CuFc= Authentication-Results: vger.kernel.org; dkim=none (message not signed) header.d=none;vger.kernel.org; dmarc=none action=none header.from=seco.com; Received: from DB7PR03MB4523.eurprd03.prod.outlook.com (2603:10a6:10:19::27) by DB8PR03MB6201.eurprd03.prod.outlook.com (2603:10a6:10:13f::22) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.4087.39; Mon, 3 May 2021 21:44:30 +0000 Received: from DB7PR03MB4523.eurprd03.prod.outlook.com ([fe80::40d5:3554:c709:6b1b]) by DB7PR03MB4523.eurprd03.prod.outlook.com ([fe80::40d5:3554:c709:6b1b%5]) with mapi id 15.20.4087.044; Mon, 3 May 2021 21:44:30 +0000 From: Sean Anderson To: linux-pwm@vger.kernel.org, devicetree@vger.kernel.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, michal.simek@xilinx.com, Sean Anderson , Lee Jones , Thierry Reding , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= Subject: [PATCH 2/2] pwm: Add support for Xilinx AXI Timer Date: Mon, 3 May 2021 17:44:13 -0400 Message-Id: <20210503214413.3145015-2-sean.anderson@seco.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210503214413.3145015-1-sean.anderson@seco.com> References: <20210503214413.3145015-1-sean.anderson@seco.com> X-Originating-IP: [50.195.82.171] X-ClientProxiedBy: MN2PR15CA0023.namprd15.prod.outlook.com (2603:10b6:208:1b4::36) To DB7PR03MB4523.eurprd03.prod.outlook.com (2603:10a6:10:19::27) MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 Received: from plantagenet.inhand.com (50.195.82.171) by MN2PR15CA0023.namprd15.prod.outlook.com (2603:10b6:208:1b4::36) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.4087.27 via Frontend Transport; Mon, 3 May 2021 21:44:29 +0000 X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: 84109644-c88d-4a5a-ec39-08d90e7ca1fc X-MS-TrafficTypeDiagnostic: DB8PR03MB6201: X-MS-Exchange-Transport-Forked: True X-Microsoft-Antispam-PRVS: X-MS-Oob-TLC-OOBClassifiers: OLM:6108; X-MS-Exchange-SenderADCheck: 1 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: zPZeQjjETR/lXOzGb8ry58hJim4FzXCQDBfOpUQWKK5cfX3uU+JV5rpyNxTAAHzPdqctpoUq9P12kp3l4r2kZ6yrrfnUIi05Ojs9wgkrbDbiBlDfcFEHL9ULlkxvcGzBV5M0G813Lb0jE0n0mngxaIXecS569/JKRZD85xKEeMb8nQkQnAJX8o98NojQei6yASc6hc1s26v/KIep13y9LdflzDoOgBcd54olH/+1LrvdCXFuDUT0K+rEqzR4MMNIYpXw28sjy0EV/Fy5VxH9QtwgLfT/k465mG0e195Q2MMGzoZqMCnFS3gLRIOI+VSNn4voRjR9UtwwBRLiV8EPOLmBNNT6DsFy72LF7E96imJVN8dqlwuF2h/rVZaylRKtBL/5PEevzeF6OQCW7UxqrLmiOOccV0ztBzte7yZUfkMPEjho42oQD4ivR7ONFYO4d2tRmtwQaVedyPUPEhs344kaSFbOXUwKxUpaHFCWVQmmzYK6s/dxRishUHwQzapyPd79bJmDYnz7BcIj5znnwcz5YMfbev8Hh6PwWKRbzNNb/Owof9TST7pLS2XaMZWXi2b9UNMGZ3n/PcpX+YN6mtYjUIZoXIqGD6ppld2mub3n4oQXcbP1L0M6sMHMDfqwLcm7MOI04E8i1QXLzeSZB4D1DKmAOCmo9YBj+PWIcsWBkc/a1OCDOZRrm9+Q7SXdMb3BtPANAh6ZnQSGbOSz7dRjC/bWe8kfsm3r51YdEKQ= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:DB7PR03MB4523.eurprd03.prod.outlook.com; PTR:; CAT:NONE; SFS:(346002)(366004)(376002)(136003)(8676002)(44832011)(1076003)(66556008)(66946007)(2906002)(54906003)(186003)(38350700002)(66476007)(38100700002)(83380400001)(6512007)(36756003)(30864003)(26005)(16526019)(52116002)(5660300002)(4326008)(6506007)(6486002)(8936002)(508600001)(6666004)(956004)(86362001)(2616005)(966005); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData: =?utf-8?q?W3nXzy2pMT6oYP9XCmGyfheVN2bGoV?= =?utf-8?q?F1e3v5E+8rDx70LBrIYlUEjeV6Y9o44FFVtQdus/AmMjHalVBTHePhMXAWQpUiWIJ?= =?utf-8?q?RHY8sZuTjcUgMFQKZL+3REfwWeqZDcruIyYRTJxRerhYQHZmiQ/ka33OyqXBAGJtS?= =?utf-8?q?ZhQsqtef41IR7u2y0A3/kAp0av0BA/LmyWBaQXAyM/2TFvNc3nqUPZ5KFaDptkzPr?= =?utf-8?q?bpAgTRO0lRlk1KN4CIDmOg4wrMlPbhifI3Br1hEMmfaDGJSpj7uGVoBqG6oNfoDxx?= =?utf-8?q?WLJN+PSBGDHRhWkIV+kdtkNlRMUZlVtTS/VhTsjxCs62vOISotd/JcPdrhky1aIaT?= =?utf-8?q?NZyBNv4hwE6SNyDgBF4LJPtT6jKbl2h9ZhppLYJ2/qppGAsM18uP5Uj78Zc2Yo6UX?= =?utf-8?q?Bp83kchEEpmpH+6WhXXDGUU9gpL9mAFFwQ5RQMorntSsdoe1MtQKusgrTL4yhJRqm?= =?utf-8?q?Ims9f+ENxkdn3YQ6Y4tbOw7bwG7D6Wy+4Cl2AZ2RX+i2E4LoiczmJwLezLfWutcOK?= =?utf-8?q?oMQHu2n0ZA57DYDyOpFU8Ra/7O7JHUop3ru2gZEhQgrRCit1YUmrVSo6F209CwA/3?= =?utf-8?q?nDt2IogRAR5WxIQbj6SsaQ+5m9WXg3Y9ixwfIQnJrLpWONBcuYJv7kU01JHqnrFvX?= =?utf-8?q?lNdjdlO//zuXkNFK45bnCjIuqVCgKDIYKbhsEJ/JGKUhQK7AanH8il8jD7AbaH0hI?= =?utf-8?q?1ASHqRn+qFXE7s2/VtkBrcuaDTm5Fh+xUUdb6WJkCj9VVqdodeIrpmjjkv8t9Exck?= =?utf-8?q?43mVrw9vFjjjb44roNHEivFbqnWMB/k5kuL+rnvT2wE/iXjYL0N8cSrII70NV2mU1?= =?utf-8?q?nyA84aqNdUWEHEq/niBB7azB/AWgZ7FjpDro8ZGglMu49VulTedTqGzxtlUImV8H1?= =?utf-8?q?rDgLrpo4pSo6voErANUqrzsHo9oFV2/EkeDFMg/86Q4PsOJPSjFYOuBfF64WJ4jen?= =?utf-8?q?YMLAxJWxfM8xZDPQgEH1c9IavZIb1fpCnbYBrVuGtJoq5eGpN4Cho10kqPzs0WkjI?= =?utf-8?q?xIDfgdImVw0f0Sstkq76dUn8JkocrOBanXZVAGR1ZB2wbIxv3urzkFhvC4zLQkmwf?= =?utf-8?q?LqxsSrkp2peonfDCfcCn/LPIBl3W+IB7+F//e5mNo1qfLzIjb5BHguF3QWsK2Fv4u?= =?utf-8?q?BGYPXvS+59mwRFGcQYx/1FegkrHY14zBkxapYY2XZAC/O4wg6NnGfd/BFXnjYd2tA?= =?utf-8?q?rjcq177+njlWL1WhjFjSAg9gMG7TNW2CQjOwwLhgAfgJbgFZB0nHHmdAH2HtEBMC/?= =?utf-8?q?XHvw/smKJeAbGQ?= X-OriginatorOrg: seco.com X-MS-Exchange-CrossTenant-Network-Message-Id: 84109644-c88d-4a5a-ec39-08d90e7ca1fc X-MS-Exchange-CrossTenant-AuthSource: DB7PR03MB4523.eurprd03.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 03 May 2021 21:44:30.8395 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: bebe97c3-6438-442e-ade3-ff17aa50e733 X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: a0g6cU2NBgNqRIGK14KdsGcrWWbcZlZ3yJLTeGuMdJgpTrakaKuPKdXgXaftbquVFOhBa9VKP4TXaukMChijJg== X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB8PR03MB6201 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210503_144436_393478_2CF28654 X-CRM114-Status: GOOD ( 24.87 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org This adds PWM support for Xilinx LogiCORE IP AXI soft timers commonly found on Xilinx FPGAs. There is another driver for this device located at arch/microblaze/kernel/timer.c, but it is only used for timekeeping. This driver was written with reference to Xilinx DS764 for v1.03.a [1]. [1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf Signed-off-by: Sean Anderson Reported-by: kernel test robot --- arch/arm64/configs/defconfig | 1 + drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-xilinx.c | 322 +++++++++++++++++++++++++++++++++++ 4 files changed, 335 insertions(+) create mode 100644 drivers/pwm/pwm-xilinx.c diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 08c6f769df9a..81794209f287 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1083,6 +1083,7 @@ CONFIG_PWM_SAMSUNG=y CONFIG_PWM_SL28CPLD=m CONFIG_PWM_SUN4I=m CONFIG_PWM_TEGRA=m +CONFIG_PWM_XILINX=m CONFIG_SL28CPLD_INTC=y CONFIG_QCOM_PDC=y CONFIG_RESET_IMX7=y diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d3371ac7b871..01e62928f4bf 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -628,4 +628,15 @@ config PWM_VT8500 To compile this driver as a module, choose M here: the module will be called pwm-vt8500. +config PWM_XILINX + tristate "Xilinx AXI Timer PWM support" + depends on !MICROBLAZE + help + PWM framework driver for Xilinx LogiCORE IP AXI Timers. This + timer is typically a soft core which may be present in Xilinx + FPGAs. If you don't have this IP in your design, choose N. + + To compile this driver as a module, choose M here: the module + will be called pwm-xilinx. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index d3879619bd76..fc1bd6ccc9ed 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -59,3 +59,4 @@ obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o obj-$(CONFIG_PWM_TWL) += pwm-twl.o obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o +obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c new file mode 100644 index 000000000000..240bd2993f97 --- /dev/null +++ b/drivers/pwm/pwm-xilinx.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Sean Anderson + */ +#include +#include +#include +#include +#include +#include +#include + +#define TCSR0 0x00 +#define TLR0 0x04 +#define TCR0 0x08 +#define TCSR1 0x10 +#define TLR1 0x14 +#define TCR1 0x18 + +#define TCSR_MDT BIT(0) +#define TCSR_UDT BIT(1) +#define TCSR_GENT BIT(2) +#define TCSR_CAPT BIT(3) +#define TCSR_ARHT BIT(4) +#define TCSR_LOAD BIT(5) +#define TCSR_ENIT BIT(6) +#define TCSR_ENT BIT(7) +#define TCSR_TINT BIT(8) +#define TCSR_PWMA BIT(9) +#define TCSR_ENALL BIT(10) +#define TCSR_CASC BIT(11) + +/* Bits we need to set/clear to enable PWM mode, not including CASC */ +#define TCSR_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA) +#define TCSR_CLEAR (TCSR_MDT | TCSR_LOAD) + +#define NSEC_PER_SEC_ULL 1000000000ULL + +/** + * struct xilinx_pwm_device - Driver data for Xilinx AXI timer PWM driver + * @chip: PWM controller chip + * @clk: Parent clock + * @regs: Base address of this device + * @width: Width of the counters, in bits + */ +struct xilinx_pwm_device { + struct pwm_chip chip; + struct clk *clk; + void __iomem *regs; + unsigned int width; +}; + +static inline struct xilinx_pwm_device *xilinx_pwm_chip_to_device(struct pwm_chip *chip) +{ + return container_of(chip, struct xilinx_pwm_device, chip); +} + +static bool xilinx_pwm_is_enabled(u32 tcsr0, u32 tcsr1) +{ + return !(~tcsr0 & TCSR_SET || tcsr0 & (TCSR_CLEAR | TCSR_CASC) || + ~tcsr1 & TCSR_SET || tcsr1 & TCSR_CLEAR); +} + +static u32 xilinx_pwm_calc_tlr(struct xilinx_pwm_device *pwm, u32 tcsr, + unsigned int period) +{ + u64 cycles = DIV_ROUND_DOWN_ULL(period * clk_get_rate(pwm->clk), + NSEC_PER_SEC_ULL); + + if (tcsr & TCSR_UDT) + return cycles - 2; + else + return (BIT_ULL(pwm->width) - 1) - cycles + 2; +} + +static unsigned int xilinx_pwm_get_period(struct xilinx_pwm_device *pwm, + u32 tlr, u32 tcsr) +{ + u64 cycles; + + if (tcsr & TCSR_UDT) + cycles = tlr + 2; + else + cycles = (BIT_ULL(pwm->width) - 1) - tlr + 2; + + return DIV_ROUND_DOWN_ULL(cycles * NSEC_PER_SEC_ULL, + clk_get_rate(pwm->clk)); +} + +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused, + const struct pwm_state *state) +{ + struct xilinx_pwm_device *pwm = xilinx_pwm_chip_to_device(chip); + u32 tlr0, tlr1; + u32 tcsr0 = readl(pwm->regs + TCSR0); + u32 tcsr1 = readl(pwm->regs + TCSR1); + bool enabled = xilinx_pwm_is_enabled(tcsr0, tcsr1); + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + if (!enabled && state->enabled) + clk_rate_exclusive_get(pwm->clk); + + tlr0 = xilinx_pwm_calc_tlr(pwm, tcsr0, state->period); + tlr1 = xilinx_pwm_calc_tlr(pwm, tcsr1, state->duty_cycle); + writel(tlr0, pwm->regs + TLR0); + writel(tlr1, pwm->regs + TLR1); + + if (state->enabled) { + /* Only touch the TCSRs if we aren't already running */ + if (!enabled) { + /* Load TLR into TCR */ + writel(tcsr0 | TCSR_LOAD, pwm->regs + TCSR0); + writel(tcsr1 | TCSR_LOAD, pwm->regs + TCSR1); + /* Enable timers all at once with ENALL */ + tcsr0 = (TCSR_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT); + tcsr1 = TCSR_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT); + writel(tcsr0, pwm->regs + TCSR0); + writel(tcsr1, pwm->regs + TCSR1); + } + } else { + writel(tcsr0 & ~TCSR_ENT, pwm->regs + TCSR0); + writel(tcsr1 & ~TCSR_ENT, pwm->regs + TCSR1); + } + + if (enabled && !state->enabled) + clk_rate_exclusive_put(pwm->clk); + + return 0; +} + +static void xilinx_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *unused, + struct pwm_state *state) +{ + struct xilinx_pwm_device *pwm = xilinx_pwm_chip_to_device(chip); + u32 tlr0, tlr1, tcsr0, tcsr1; + + tlr0 = readl(pwm->regs + TLR0); + tlr1 = readl(pwm->regs + TLR1); + tcsr0 = readl(pwm->regs + TCSR0); + tcsr1 = readl(pwm->regs + TCSR1); + + state->period = xilinx_pwm_get_period(pwm, tlr0, tcsr0); + state->duty_cycle = xilinx_pwm_get_period(pwm, tlr1, tcsr1); + state->enabled = xilinx_pwm_is_enabled(tcsr0, tcsr1); + state->polarity = PWM_POLARITY_NORMAL; +} + +static const struct pwm_ops xilinx_pwm_ops = { + .apply = xilinx_pwm_apply, + .get_state = xilinx_pwm_get_state, +}; + +static struct dentry *debug_dir; + +#define DEBUG_REG(reg) { .name = #reg, .offset = (reg), } +static struct debugfs_reg32 xilinx_pwm_reg32[] = { + DEBUG_REG(TCSR0), + DEBUG_REG(TLR0), + DEBUG_REG(TCR0), + DEBUG_REG(TCSR1), + DEBUG_REG(TLR1), + DEBUG_REG(TCR1), +}; + +static int xilinx_pwm_debug_create(struct xilinx_pwm_device *pwm) +{ + struct debugfs_regset32 *regset; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return 0; + + regset = devm_kzalloc(pwm->chip.dev, sizeof(*regset), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + regset->regs = xilinx_pwm_reg32; + regset->nregs = ARRAY_SIZE(xilinx_pwm_reg32); + regset->base = pwm->regs; + + debugfs_create_regset32(dev_name(pwm->chip.dev), 0400, debug_dir, + regset); + return 0; +} + +static int xilinx_pwm_probe(struct platform_device *pdev) +{ + bool enabled; + int i, ret; + struct device *dev = &pdev->dev; + struct xilinx_pwm_device *pwm; + u32 one_timer; + + ret = of_property_read_u32(dev->of_node, "xlnx,one-timer-only", + &one_timer); + if (ret || one_timer) { + dev_err(dev, "two timers are needed for PWM mode\n"); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + char fmt[] = "xlnx,gen%u-assert"; + char buf[sizeof(fmt)]; + u32 gen; + + snprintf(buf, sizeof(buf), fmt, i); + ret = of_property_read_u32(dev->of_node, buf, &gen); + if (ret || !gen) { + dev_err(dev, "generateout%u must be active high\n", i); + return -EINVAL; + } + } + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + platform_set_drvdata(pdev, pwm); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &xilinx_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 1; + + pwm->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pwm->regs)) + return PTR_ERR(pwm->regs); + + ret = of_property_read_u32(dev->of_node, "xlnx,count-width", &pwm->width); + if (ret) { + dev_err(dev, "missing counter width\n"); + return -EINVAL; + } + + pwm->clk = devm_clk_get(dev, NULL); + if (IS_ERR(pwm->clk)) + return dev_err_probe(dev, PTR_ERR(pwm->clk), "missing clock\n"); + + ret = clk_prepare_enable(pwm->clk); + if (ret) { + dev_err(dev, "clock enable failed\n"); + return ret; + } + + ret = xilinx_pwm_debug_create(pwm); + if (ret) { + clk_disable_unprepare(pwm->clk); + return ret; + } + + ret = pwmchip_add(&pwm->chip); + if (ret) { + clk_disable_unprepare(pwm->clk); + return ret; + } + + enabled = xilinx_pwm_is_enabled(readl(pwm->regs + TCSR0), + readl(pwm->regs + TCSR1)); + if (enabled) + clk_rate_exclusive_get(pwm->clk); + + return ret; +} + +static int xilinx_pwm_remove(struct platform_device *pdev) +{ + struct xilinx_pwm_device *pwm = platform_get_drvdata(pdev); + bool enabled = xilinx_pwm_is_enabled(readl(pwm->regs + TCSR0), + readl(pwm->regs + TCSR1)); + + if (enabled) + clk_rate_exclusive_put(pwm->clk); + clk_disable_unprepare(pwm->clk); + return pwmchip_remove(&pwm->chip); +} + +static const struct of_device_id xilinx_pwm_of_match[] = { + { .compatible = "xlnx,xps-timer-1.00.a" }, + { .compatible = "xlnx,axi-timer-2.0" }, + {}, +}; +MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match); + +static struct platform_driver xilinx_pwm_driver = { + .probe = xilinx_pwm_probe, + .remove = xilinx_pwm_remove, + .driver = { + .name = "xilinx-pwm", + .of_match_table = of_match_ptr(xilinx_pwm_of_match), + }, +}; + +static int __init xilinx_pwm_init(void) +{ + int ret = platform_driver_register(&xilinx_pwm_driver); + + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_DEBUG_FS)) { + debug_dir = debugfs_create_dir(xilinx_pwm_driver.driver.name, NULL); + if (IS_ERR(debug_dir)) { + platform_driver_unregister(&xilinx_pwm_driver); + return PTR_ERR(debug_dir); + } + } + return 0; +} +module_init(xilinx_pwm_init); + +static void __exit xilinx_pwm_exit(void) +{ + platform_driver_unregister(&xilinx_pwm_driver); + if (IS_ENABLED(CONFIG_DEBUG_FS)) + debugfs_remove_recursive(debug_dir); +} +module_exit(xilinx_pwm_exit); + +MODULE_ALIAS("platform:xilinx-pwm"); +MODULE_DESCRIPTION("Xilinx LogiCORE IP AXI Timer PWM driver"); +MODULE_LICENSE("GPL v2");