From patchwork Mon Jul 8 10:52:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957886 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=pihhcRLN; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=2604:1380:45d1:ec00::1; helo=ny.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2721-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from ny.mirrors.kernel.org (ny.mirrors.kernel.org [IPv6:2604:1380:45d1:ec00::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvP2wHYz1xrP for ; Mon, 8 Jul 2024 20:53:05 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ny.mirrors.kernel.org (Postfix) with ESMTPS id 4913B1C20F9A for ; Mon, 8 Jul 2024 10:53:03 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 6131878276; Mon, 8 Jul 2024 10:53:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="pihhcRLN" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9A2B578C7F for ; Mon, 8 Jul 2024 10:52:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435981; cv=none; b=YS4CawsbvXT/aTFT3PBD8fGa7SRg0699Is7tYaTlX00HMaNvN/rq7fQHUBpMB1ky4ki6jEsRLTiIzSuK09XRNb7on3OTDEuFHU/DakcBYvOt8RPHp24BCjYq+Syn43+l2IEVWzBlASEpzyiPhEzJKYsumzDCcKjnRtl/RGGt/a0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435981; c=relaxed/simple; bh=GGCCgLkO274+XDpQcgYgeQzKCK43CBf3srjmHwgoVz4=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Lq0o5e9SwMjCQlphohFUjuaYsYOCoRtyB6zCg+MlNJn1UnUEjEQohviE0k7qTZXOx9BjQcUFqMA+BFc3Ff8O/xSdW2raPLnUmgJvCh2pDFJWeCegfnI3WyaqXqXnzvXl/aDD4Wj+kZ3RhbSTG2Ar3cZGNmunz1ycq6ULyZcl5To= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=pihhcRLN; arc=none smtp.client-ip=209.85.221.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-367ab50a07aso1846381f8f.0 for ; Mon, 08 Jul 2024 03:52:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435977; x=1721040777; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=ENr9C6wqLScYtM/ACpfJbtdpg2MMvtMfkrf/bCmK5uo=; b=pihhcRLNHv5wlWg/h20sfFaLUJ/Builg2AfQ9dVKx7yEzy49ieN54yU4pRFfio6cmp 7n1usQRmv/qSXvXWI0zCBXm/QH5oB/zE00+DbHNxzGD+3AEPBk7g+yjPI2dgcJfI6kuh bXZh8w6lYSiKRu+PF4oKgnZVW31nKgaOJq8yerNPp0/MTsHv658TCbYndhMjaeKLHIGO DOVFbDzBVz0ynAh7w9ZuCBw2eO3szgLmY52omFIXYQuQDSwWXaOCtc9XgWbcd6dQ027Y A11XRtCLKRZ6ykBH9xZLzt6IjTh4+BsHy6kzGRgLe/kGw7VCL2TLvcAvJ86OtFkKdt3+ qkLw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435977; x=1721040777; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ENr9C6wqLScYtM/ACpfJbtdpg2MMvtMfkrf/bCmK5uo=; b=lRFrJb9ABk1bjCF6IjK9Rgsy42TZKhMtQATfuG7eg1MW6nJj4RoHKB1rv8vZdIyO0X MB3cmfLj0ciSpC/t1ydH8fQ6XVH5c7hBoIqq0/g4v6clEH35u9gUwAlJ55xXiuCf1w0F Qo0xKeCQ3sATfRjDuIJsLGp2f9+s705Jb5G494qBtjAEucKXDtjvRQeIoCjZ3XAuufVE qEuL4M1fykgCpe7zBYW3JZvlDKLtf6MEOtoAO3kwEGDAE3mhmHmd24h6sPKI6GkavzRs OJFSHsN8iWp759y3ZniZweMB9EbLoK1sJwNTTPoy1HfdVYaf7m58Q2FzszxvZnMDuONX 3mXA== X-Gm-Message-State: AOJu0YyqQzZGQV406VDhhQgypFhFE8RWPQQc0DEwfc37j1N9J2kDmJgT kIyfM4+tTE8Vzj5meu41djQGqxuYzrCPdI+Sic6d0y1bRKgn8wLmm33Nfo+Hm34kkzYin9QDJIZ i X-Google-Smtp-Source: AGHT+IE3XZYNK/otDFabY0D3eQVUP+Cv9yOwc4qG8Hwmy+sRItsFqJrh3eAZzXttYUP89mj0NxUqJg== X-Received: by 2002:a5d:5184:0:b0:367:8a2b:734e with SMTP id ffacd0b85a97d-3679dd15b85mr7272537f8f.12.1720435977086; Mon, 08 Jul 2024 03:52:57 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3678e68a622sm14852116f8f.106.2024.07.08.03.52.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:52:56 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Subject: [PATCH 1/6] pwm: Add more locking Date: Mon, 8 Jul 2024 12:52:27 +0200 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=7925; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=GGCCgLkO274+XDpQcgYgeQzKCK43CBf3srjmHwgoVz4=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8Tu9ZyBSyVaUpMvWXKbfRcG+ujGgzL1EVJbA qdZ9LATbnaJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE7gAKCRCPgPtYfRL+ TvCbCACJXbnpg+uU7RDnForMsnpQWvQYxhNRLhgQPrACJD2tvZFsnsvK9LxSaRc1VYL1uNHSWu4 InW5q/nEa9bro6+gS/JmAMaZmhWCAYCynFdqfmVZIf8X0WB38Zb/t4gW0C9X1c6EzVO4kOlLsdi 7fy35XGWUMzrKmkLNHjUgLXnDKXMKRazny2BUPDrJ33B5niitJ4oQIzwvVMSmzY3kGCKiB/diGG 9B8Wnzaa+I/oi06kg0uDtFSU5ieloSnhGgWoSRts+6nxS18PwcvKgz5alHalYigPhRBmrvskAtB +72fYdLBUw+cs3ktFRJb2vTfAtTMLKvPzaq00t12iOzwVF94 X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 This ensures that a pwm_chip that has no corresponding driver isn't used and that a driver doesn't go away while a callback is still running. In the presence of device links this isn't necessary yet (so this is no fix) but for pwm character device support this is needed. To not serialize all pwm_apply_state() calls, this introduces a per chip lock. An additional complication is that for atomic chips a mutex cannot be used (as pwm_apply_atomic() must not sleep) and a spinlock cannot be held while calling an operation for a sleeping chip. So depending on the chip being atomic or not a spinlock or a mutex is used. Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 103 +++++++++++++++++++++++++++++++++++++++----- include/linux/pwm.h | 13 ++++++ 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 8acbcf5b6673..c31e12e76495 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -31,6 +31,24 @@ static DEFINE_MUTEX(pwm_lock); static DEFINE_IDR(pwm_chips); +static void pwmchip_lock(struct pwm_chip *chip) +{ + if (chip->atomic) + spin_lock(&chip->atomic_lock); + else + mutex_lock(&chip->nonatomic_lock); +} + +static void pwmchip_unlock(struct pwm_chip *chip) +{ + if (chip->atomic) + spin_unlock(&chip->atomic_lock); + else + mutex_unlock(&chip->nonatomic_lock); +} + +DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T)) + static void pwm_apply_debug(struct pwm_device *pwm, const struct pwm_state *state) { @@ -220,6 +238,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) { int err; + struct pwm_chip *chip = pwm->chip; /* * Some lowlevel driver's implementations of .apply() make use of @@ -230,7 +249,12 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) */ might_sleep(); - if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) { + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) { /* * Catch any drivers that have been marked as atomic but * that will sleep anyway. @@ -254,9 +278,16 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep); */ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) { - WARN_ONCE(!pwm->chip->atomic, + struct pwm_chip *chip = pwm->chip; + + WARN_ONCE(!chip->atomic, "sleeping PWM driver used in atomic context\n"); + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + return __pwm_apply(pwm, state); } EXPORT_SYMBOL_GPL(pwm_apply_atomic); @@ -328,15 +359,22 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config); int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout) { - if (!pwm || !pwm->chip->ops) + struct pwm_chip *chip; + + if (!pwm || !(chip = pwm->chip)->ops) return -EINVAL; - if (!pwm->chip->ops->capture) + if (!chip->ops->capture) return -ENOSYS; guard(mutex)(&pwm_lock); - return pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + return chip->ops->capture(pwm->chip, pwm, result, timeout); } EXPORT_SYMBOL_GPL(pwm_capture); @@ -369,6 +407,14 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) if (test_bit(PWMF_REQUESTED, &pwm->flags)) return -EBUSY; + /* + * This function is called while holding pwm_lock. As .operational only + * changes while holding this lock, checking it here without holding the + * chip lock is fine. + */ + if (!chip->operational) + return -ENODEV; + if (!try_module_get(chip->owner)) return -ENODEV; @@ -397,7 +443,9 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) */ struct pwm_state state = { 0, }; - err = ops->get_state(chip, pwm, &state); + scoped_guard(pwmchip, chip) + err = ops->get_state(chip, pwm, &state); + trace_pwm_get(pwm, &state, err); if (!err) @@ -1021,6 +1069,7 @@ struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t chip->npwm = npwm; chip->uses_pwmchip_alloc = true; + chip->operational = false; pwmchip_dev = &chip->dev; device_initialize(pwmchip_dev); @@ -1126,6 +1175,11 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) chip->owner = owner; + if (chip->atomic) + spin_lock_init(&chip->atomic_lock); + else + mutex_init(&chip->nonatomic_lock); + guard(mutex)(&pwm_lock); ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); @@ -1139,6 +1193,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) if (IS_ENABLED(CONFIG_OF)) of_pwmchip_add(chip); + scoped_guard(pwmchip, chip) + chip->operational = true; + ret = device_add(&chip->dev); if (ret) goto err_device_add; @@ -1146,6 +1203,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) return 0; err_device_add: + scoped_guard(pwmchip, chip) + chip->operational = false; + if (IS_ENABLED(CONFIG_OF)) of_pwmchip_remove(chip); @@ -1165,11 +1225,27 @@ void pwmchip_remove(struct pwm_chip *chip) { pwmchip_sysfs_unexport(chip); - if (IS_ENABLED(CONFIG_OF)) - of_pwmchip_remove(chip); + scoped_guard(mutex, &pwm_lock) { + unsigned int i; + + scoped_guard(pwmchip, chip) + chip->operational = false; + + for (i = 0; i < chip->npwm; ++i) { + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + dev_alert(&chip->dev, "Freeing requested PWM #%u\n", i); + if (pwm->chip->ops->free) + pwm->chip->ops->free(pwm->chip, pwm); + } + } + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); - scoped_guard(mutex, &pwm_lock) idr_remove(&pwm_chips, chip->id); + } device_del(&chip->dev); } @@ -1539,12 +1615,17 @@ void pwm_put(struct pwm_device *pwm) guard(mutex)(&pwm_lock); - if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + /* + * If the chip isn't operational, PWMF_REQUESTED was already cleared. So + * don't warn in this case. This can only happen if a consumer called + * pwm_put() twice. + */ + if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { pr_warn("PWM device already freed\n"); return; } - if (chip->ops->free) + if (chip->operational && chip->ops->free) pwm->chip->ops->free(pwm->chip, pwm); pwm->label = NULL; diff --git a/include/linux/pwm.h b/include/linux/pwm.h index f8c2dc12dbd3..5176dfebfbfd 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -275,6 +275,9 @@ struct pwm_ops { * @of_xlate: request a PWM device given a device tree PWM specifier * @atomic: can the driver's ->apply() be called in atomic context * @uses_pwmchip_alloc: signals if pwmchip_allow was used to allocate this chip + * @operational: signals if the chip can be used (or is already deregistered) + * @nonatomic_lock: mutex for nonatomic chips + * @atomic_lock: mutex for atomic chips * @pwms: array of PWM devices allocated by the framework */ struct pwm_chip { @@ -290,6 +293,16 @@ struct pwm_chip { /* only used internally by the PWM framework */ bool uses_pwmchip_alloc; + bool operational; + union { + /* + * depending on the chip being atomic or not either the mutex or + * the spinlock is used. It protects .operational and + * synchronizes calls to the .ops->apply and .ops->get_state() + */ + struct mutex nonatomic_lock; + struct spinlock atomic_lock; + }; struct pwm_device pwms[] __counted_by(npwm); }; From patchwork Mon Jul 8 10:52:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957889 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=TgxJKoZp; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=147.75.80.249; helo=am.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2722-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from am.mirrors.kernel.org (am.mirrors.kernel.org [147.75.80.249]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvS1zwfz1xrP for ; Mon, 8 Jul 2024 20:53:08 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id 7574C1F21913 for ; Mon, 8 Jul 2024 10:53:04 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id F34CB78C6B; Mon, 8 Jul 2024 10:53:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="TgxJKoZp" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-lj1-f180.google.com (mail-lj1-f180.google.com [209.85.208.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3EF1F7603A for ; Mon, 8 Jul 2024 10:53:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435982; cv=none; b=Q9/9k97rynVm4L215vJuGjtpzcsJcjf14lcLm+gB796s5h/zM6f8ie4U1AW3qnaMS+97NNRdHfNov6IdwVhJb6IxfWGc7OfyzwFDaluXowwurPjYxeX8cx50P8lcCX2hWRACWZt6zkRzQ5otOXP7/xGsZIjLDb1+W5gnGT73Ksk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435982; c=relaxed/simple; bh=WQ1tdQJ8LGKBiWT+pmlsAYiB2OzWVFeNGMvJEuabIak=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OxMqWlBGvHxDbivld8xQOEhOAGaVpOf9Vd8/4mMKdQgUQLlzrBommLLU/27zLPr/lGaQXCxjh6bMojK2+2vSj7LEky0xhulWhilZriVMHDXO7G5Fo1+YKDetS3goS3dWnaIALywVwwMXb6NeuUl52LYcBx5rFFd+1PnuUPsW4ZI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=TgxJKoZp; arc=none smtp.client-ip=209.85.208.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-lj1-f180.google.com with SMTP id 38308e7fff4ca-2eeb1051360so1737041fa.0 for ; Mon, 08 Jul 2024 03:52:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435978; x=1721040778; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Mpt6NFsPkisaY1MwVr6kXrTsatrXRavGiwZl9130ebI=; b=TgxJKoZp4CGjrXvHS5M3I8XO1J6gJRbiLXdUIeo/oJ0fo539DUWhgYD1GrWFu+Um3A mQ9fYWq8K1tcJ0CXgD0zy2bCOH/KvSWb5RHJAw7R+UKyUbK/aFrFQwcijJn0sjGaL8Fl 0EuX0MhGZULcWyDrWOy8kzupmAdMXqrqzPffFCvDYWyqBE0f+KUQFUaiteHMkzzhzxSX LDcraWv92Uqa3tdbGv98E9Zxwdd27MMqUkh7U3OT8mRm2HaGjqO9qeJ8b2Lo9h+9GZxI 78NHsBawpOErHgHvBLDnsPr+nLy6i08HuRMhJS9zsDXU+In9lM80+yl6klZt/nZIYKSa Q24A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435978; x=1721040778; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Mpt6NFsPkisaY1MwVr6kXrTsatrXRavGiwZl9130ebI=; b=ts+gJzpmn8bu/62Rbjrf7b6iFXc2o/sLdhWNKhbIrIN1+MUFBV87uNsdI4/i93IsO7 qjVuxoCqwKG8wKad5g3tcoFzWMR7m54BYsrkBJPb3OgeoBDz3WWyjokBvjbBX/jv0Chs o3tgbo03qvCgFbFACbCxCo1wHC2sz6ef+3qV0UNe8D5Xc8xPlv05kY6jGPTIhxMrAVF4 iliuLpqoUSluKpJR7yEG6duVzrvoqGM2HPpa6w+c3XTgnUEpd3nPszOjRobRhICR0nqB aqNomueCUgpKxaxgL1LGGyr5JY1wRlSdJ398VOk/sk0cf+nWQ/ZanQKwzSh9Me3ioXsT byzQ== X-Gm-Message-State: AOJu0Ywb0tQyv/oFOsmsq1yXsowtI2cFK+iPfOqR46Cedej3RUbYp4Y5 hX3BkB4MgPY+SKvy2jb1hz1LVsB+UDaRiyfou5GOC0CKC/dmvGcW1031o8qHmHisUqmHqj0GHpj t X-Google-Smtp-Source: AGHT+IG1Kyf6+PqZfCJhkCxwpP2yCSENJwwTLW1fympL37tJ+rPZDfY6sUvPMiFk+1HfICML4Msv1A== X-Received: by 2002:a2e:3c03:0:b0:2ee:4ccf:ca4f with SMTP id 38308e7fff4ca-2ee8eda42e4mr71360861fa.31.1720435978299; Mon, 08 Jul 2024 03:52:58 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264d6d41cesm147133775e9.16.2024.07.08.03.52.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:52:57 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Cc: Michael Hennerich , =?utf-8?q?Nuno_S=C3=A1?= , Fabrice Gasnier , Maxime Coquelin , Alexandre Torgue , linux-stm32@st-md-mailman.stormreply.com, linux-arm-kernel@lists.infradead.org Subject: [PATCH 2/6] pwm: New abstraction for PWM waveforms Date: Mon, 8 Jul 2024 12:52:28 +0200 Message-ID: <6f7deef208bf25baad7daa8ff35621fb68e7aeb4.1720435656.git.u.kleine-koenig@baylibre.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=11779; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=WQ1tdQJ8LGKBiWT+pmlsAYiB2OzWVFeNGMvJEuabIak=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8TwFCxuEN1LF0Bi0vED9Q+cPd9ZskZQXcF55 oMB2TlxhimJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE8AAKCRCPgPtYfRL+ TljMB/wICzCrg5w7KNoMUuNqsfdJbLNBEN08ijU878THwEjfdMqEEiyf/2EvosUQqhrN8SIJ8FD C3mBTuykHKykLLd8CFhWEPxUars3SV2v4WMgGHtOlrwTXylxkkIs4V/lh4U+/75Sz4ZAmG9yYw8 eOSPW+BO1adTvvQBxfQrIjoZkIZVhutjyZzC1H0iDPjIoae3Hk2QZSrdVHGXapkYr40R+ejemv0 9N6rbGa6SmnSAzYrHn8ONV8DCiC5hbLOQKPwNWyydYaVuX4H84Kit07laJFpsNMaarvsAMZF7tz uaN4kHZmyxnFsObDq+QrMgh2hKFTFpRVvpEMvaZ50E/mx3wM X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 Up to now the configuration of a PWM setting is decribed exclusively by a struct pwm_state which contains information about period, duty_cycle, polarity and if the PWM is enabled. (There is another member usage_power which doesn't completely fit into pwm_state, I ignore it here for simplicity.) Instead of a polarity the new abstraction has a member duty_offset that defines when the rising edge happens after the period start. This is more general, as with a pwm_state the rising edge can only happen at the period's start or such that the falling edge is at the end of the period (i.e. duty_offset == 0 or duty_offset == period_lengh - duty_length). A disabled PWM is modeled by .period_length = 0. In my eyes this is a nice usage of that otherwise unusable setting, as it doesn't define anything about the future which matches the fact that consumers should consider the state of the output as undefined and it's just there to say "No further requirements about the output, you can save some power.". Further I renamed period and duty_cycle to period_length and duty_length. In the past there was confusion from time to time about duty_cycle being measured in nanoseconds because people expected a percentage of period instead. With "length" as suffix the semantic should be more obvious to people unfamiliar with the pwm subsystem. period is renamed period_length for consistency. The API for consumers doesn't change yet, but lowlevel drivers can implement callbacks that work with pwm_waveforms instead of pwm_states. A new thing about these callbacks is that the calculation of hardware settings needed to implement a certain waveform is separated from actually writing these settings. The motivation for that is that this allows a consumer to query the hardware capabilities without actually modifying the hardware state. The rounding rules that are expected to be implemented in the round_waveform_tohw() are: First pick the biggest possible period not bigger than wf->period_length. For that period pick the biggest possible duty setting not bigger than wf->duty_length. Third pick the biggest possible offset not bigger than wf->duty_offset. If the requested period is too small for the hardware, it's expected that a setting with the minimal period and duty_length = duty_offset = 0 is returned and this fact is signaled by a return value of 1. Signed-off-by: Uwe Kleine-König Reviewed-by: Trevor Gamblin --- drivers/pwm/core.c | 194 +++++++++++++++++++++++++++++++++++++++----- include/linux/pwm.h | 35 ++++++++ 2 files changed, 208 insertions(+), 21 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index c31e12e76495..8e68481a7b33 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -49,6 +49,72 @@ static void pwmchip_unlock(struct pwm_chip *chip) DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T)) +static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state) +{ + if (wf->period_length) { + if (wf->duty_length + wf->duty_offset < wf->period_length) + *state = (struct pwm_state){ + .enabled = true, + .polarity = PWM_POLARITY_NORMAL, + .period = wf->period_length, + .duty_cycle = wf->duty_length, + }; + else + *state = (struct pwm_state){ + .enabled = true, + .polarity = PWM_POLARITY_INVERSED, + .period = wf->period_length, + .duty_cycle = wf->period_length - wf->duty_length, + }; + } else { + *state = (struct pwm_state){ + .enabled = false, + }; + } +} + +static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf) +{ + if (state->enabled) { + if (state->polarity == PWM_POLARITY_NORMAL) + *wf = (struct pwm_waveform){ + .period_length = state->period, + .duty_length = state->duty_cycle, + .duty_offset = 0, + }; + else + *wf = (struct pwm_waveform){ + .period_length = state->period, + .duty_length = state->period - state->duty_cycle, + .duty_offset = state->duty_cycle, + }; + } else { + *wf = (struct pwm_waveform){ + .period_length = 0, + }; + } +} + +static int pwm_check_rounding(const struct pwm_waveform *wf, + const struct pwm_waveform *wf_rounded) +{ + if (!wf->period_length) + return 0; + + if (wf->period_length < wf_rounded->period_length) + return 1; + + if (wf->duty_length < wf_rounded->duty_length) + return 1; + + if (wf->duty_offset < wf_rounded->duty_offset) + return 1; + + return 0; +} + +#define WFHWSIZE 20 + static void pwm_apply_debug(struct pwm_device *pwm, const struct pwm_state *state) { @@ -182,6 +248,7 @@ static bool pwm_state_valid(const struct pwm_state *state) static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) { struct pwm_chip *chip; + const struct pwm_ops *ops; int err; if (!pwm || !state) @@ -205,6 +272,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) } chip = pwm->chip; + ops = chip->ops; if (state->period == pwm->state.period && state->duty_cycle == pwm->state.duty_cycle && @@ -213,18 +281,59 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) state->usage_power == pwm->state.usage_power) return 0; - err = chip->ops->apply(chip, pwm, state); - trace_pwm_apply(pwm, state, err); - if (err) - return err; + if (ops->write_waveform) { + struct pwm_waveform wf; + char wfhw[WFHWSIZE]; - pwm->state = *state; + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); - /* - * only do this after pwm->state was applied as some - * implementations of .get_state depend on this - */ - pwm_apply_debug(pwm, state); + pwm_state2wf(state, &wf); + + /* + * XXX The rounding is wrong here for states with inverted + * polarity. While .apply() rounds down duty_cycle (which + * represents the time from the start of the period to the inner + * edge), .round_waveform_tohw() rounds down the time the PWM is + * high. + */ + + err = ops->round_waveform_tohw(chip, pwm, &wf, &wfhw); + if (err) + return err; + + if (IS_ENABLED(PWM_DEBUG)) { + struct pwm_waveform wf_rounded; + + err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + if (err) + return err; + + if (pwm_check_rounding(&wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + wf.duty_length, wf.period_length, wf.duty_offset, + wf_rounded.duty_length, wf_rounded.period_length, wf_rounded.duty_offset); + } + + err = ops->write_waveform(chip, pwm, &wfhw); + if (err) + return err; + + pwm->state = *state; + + } else { + err = ops->apply(chip, pwm, state); + trace_pwm_apply(pwm, state, err); + if (err) + return err; + + pwm->state = *state; + + /* + * only do this after pwm->state was applied as some + * implementations of .get_state depend on this + */ + pwm_apply_debug(pwm, state); + } return 0; } @@ -292,6 +401,41 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) } EXPORT_SYMBOL_GPL(pwm_apply_atomic); +static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + int ret = -EOPNOTSUPP; + + if (ops->read_waveform) { + char wfhw[WFHWSIZE]; + struct pwm_waveform wf; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + scoped_guard(pwmchip, chip) { + + ret = ops->read_waveform(chip, pwm, &wfhw); + if (ret) + return ret; + + ret = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf); + if (ret) + return ret; + } + + pwm_wf2state(&wf, state); + + } else if (ops->get_state) { + scoped_guard(pwmchip, chip) + ret = ops->get_state(chip, pwm, state); + + trace_pwm_get(pwm, state, ret); + } + + return ret; +} + /** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device @@ -433,7 +577,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) } } - if (ops->get_state) { + if (ops->read_waveform || ops->get_state) { /* * Zero-initialize state because most drivers are unaware of * .usage_power. The other members of state are supposed to be @@ -443,11 +587,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) */ struct pwm_state state = { 0, }; - scoped_guard(pwmchip, chip) - err = ops->get_state(chip, pwm, &state); - - trace_pwm_get(pwm, &state, err); - + err = pwm_get_state_hw(pwm, &state); if (!err) pwm->state = state; @@ -1134,12 +1274,24 @@ static bool pwm_ops_check(const struct pwm_chip *chip) { const struct pwm_ops *ops = chip->ops; - if (!ops->apply) - return false; + if (ops->write_waveform) { + if (!ops->round_waveform_tohw || + !ops->round_waveform_fromhw || + !ops->write_waveform) + return false; - if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state) - dev_warn(pwmchip_parent(chip), - "Please implement the .get_state() callback\n"); + if (WFHWSIZE < ops->sizeof_wfhw) { + dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw); + return false; + } + } else { + if (!ops->apply) + return false; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state) + dev_warn(pwmchip_parent(chip), + "Please implement the .get_state() callback\n"); + } return true; } diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 5176dfebfbfd..b5dff2a99038 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -49,6 +49,30 @@ enum { PWMF_EXPORTED = 1, }; +/* + * struct pwm_waveform - description of a PWM waveform + * @period_length: PWM period + * @duty_length: PWM duty cycle + * @duty_offset: offset of the rising edge from the period's start + * + * This is a representation of a PWM waveform alternative to struct pwm_state + * below. It's more expressive than struct pwm_state as it contains a + * duty_offset and so can represent offsets other than $period - $duty_cycle + * which is done using .polarity = PWM_POLARITY_INVERSED. Note there is no + * explicit bool for enabled. A "disabled" PWM is represented by .period = 0. + * + * Note that the behaviour of a "disabled" PWM is undefined. Depending on the + * hardware's capabilities it might drive the active or inactive level, go + * high-z or even continue to toggle. + * + * The unit for all three members is nanoseconds. + */ +struct pwm_waveform { + u64 period_length; + u64 duty_length; + u64 duty_offset; +}; + /* * struct pwm_state - state of a PWM channel * @period: PWM period (in nanoseconds) @@ -259,6 +283,17 @@ struct pwm_ops { void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout); + + size_t sizeof_wfhw; + int (*round_waveform_tohw)(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_waveform *wf, void *wfhw); + int (*round_waveform_fromhw)(struct pwm_chip *chip, struct pwm_device *pwm, + const void *wfhw, struct pwm_waveform *wf); + int (*read_waveform)(struct pwm_chip *chip, struct pwm_device *pwm, + void *wfhw); + int (*write_waveform)(struct pwm_chip *chip, struct pwm_device *pwm, + const void *wfhw); + int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state); int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, From patchwork Mon Jul 8 10:52:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957887 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=TCm9txWG; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=2604:1380:45d1:ec00::1; helo=ny.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2723-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from ny.mirrors.kernel.org (ny.mirrors.kernel.org [IPv6:2604:1380:45d1:ec00::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvP5frdz1xpd for ; Mon, 8 Jul 2024 20:53:05 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ny.mirrors.kernel.org (Postfix) with ESMTPS id E9F681C20FA5 for ; Mon, 8 Jul 2024 10:53:03 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 627187603A; Mon, 8 Jul 2024 10:53:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="TCm9txWG" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-wr1-f46.google.com (mail-wr1-f46.google.com [209.85.221.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23E0977F2F for ; Mon, 8 Jul 2024 10:53:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435983; cv=none; b=iOnSV3hw5/CY6ueWHpNBXYxLpD4hGvTcJ34MKL5phLb5/vQMDmVYf42vd19PQ/UzM7T1qIHAKy9NAClBHw7E3q2mucQHcYf34vA9+s53ookWyZA7yw9iLhTOkw+kIR5Vmtc/x6xSdSZGOPTGGsSc+zRjxOobNS/m85O4aweq19M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435983; c=relaxed/simple; bh=vAjtirpm0fIC21TuPksPdz4VyyIIfSUSAw59/NpCLGQ=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=XxLdsVAjiTEm7zy5CYMkzqcR3WTnqKbN9FJlMQok83tlq+xbCGxdYp6mbZ9bp6B7+p1ebI3Tf3a1vbauJ4IL8Secu9MCGpVoQoHGMxDOS1O/Xq78GscQrJHg6llpCs7SeN91RW8tNR82YgOgmHBwIb+fyrRbBfRDPh3j338FegI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=TCm9txWG; arc=none smtp.client-ip=209.85.221.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-wr1-f46.google.com with SMTP id ffacd0b85a97d-3678aa359b7so2977709f8f.1 for ; Mon, 08 Jul 2024 03:53:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435979; x=1721040779; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=ftNW5xrF5gjDjc1qKCd9tZDdz/xw5+OkVsLhAu2Y8rA=; b=TCm9txWGPX5E8xEwRSoWxCcVY/xXb3oOm/YaSaFf6wXilEWNoQZWVjcmQHev5XtMBZ 1E9tjsSg54YYx0bzWshGYHkH0bdjtbIOcChflKDQciOoWgmosd7qtBpgtS845io5ZX5O pfkuzHi30Eifp+x3rRQoyNAtg1qPtQQ5waxWds8kDNKyFaGC+DXFcza2zcxgJ3cS5gxo 5JOEVubnHStgXMZwqlsT8zHbnxT5FJ1AG5rMgYjr4gftoLyZ+2rzvpXXMrk6SFBwu+mO dZKrrbJN9GuSavef7w/Rwk8ISu5EFPVKym7BXCOldeDXWNuNlXn7gG2swDxg+OnGWIhF q3tA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435979; x=1721040779; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ftNW5xrF5gjDjc1qKCd9tZDdz/xw5+OkVsLhAu2Y8rA=; b=S9k4JFQiEP2SuSl9JnfY+ZQUw2CP2TFZZ9dxn68iHhLof3B+5PEbpSes9VPelyQTHR RcsBSL3QTNoUQOp3Kn4qh0JXiLiMAx1om5Y7x88HZMJnMLczkF6F9+Dt8S9OlapgMnE8 5JAfsXCnrQuKa//N6Rphzm6Mk8MsCJLTwG+/3eUSjF4eKXwHgW1eCRmyH9wPI8K6f+rQ thEItH5VDBLKmVRJfFOm1xluz/06XzCpqxOPIuGrRejfCxPzySM0VmDDZ76nYnyoe1ek C9cDc9Ye1WHDj8AYHcScrY4op7BDHkPvJNcdSHmx39sdcQSkpXprfnoNytZJxKJqXF70 Yo1A== X-Gm-Message-State: AOJu0Yz3TkA75NQO8SHSAIPnUVAjPRg0IwSA3L3w46nZBl8ISSWtiSw3 wj3qVOTmATqvhUA7syOJOcd0gOGmE+tneSnXy3mLDZmfWflewjFvsyR16WYWRGNU3a8Ozc36IQ1 E X-Google-Smtp-Source: AGHT+IFjKas8N0vNhEYaQxxRYkLsUWuOCvxDEnBDHd3zdWj/viRvhm3KFNWeDkAz8Lw0fYD+lyoxng== X-Received: by 2002:a5d:4392:0:b0:360:9d2a:fe05 with SMTP id ffacd0b85a97d-3679f6f0ddbmr10710412f8f.13.1720435979527; Mon, 08 Jul 2024 03:52:59 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-367a1540508sm9991544f8f.66.2024.07.08.03.52.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:52:59 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Subject: [PATCH 3/6] pwm: Add support for pwmchip devices for faster and easier userspace access Date: Mon, 8 Jul 2024 12:52:29 +0200 Message-ID: <7490e64bbe12e2046d92716dadef7070881592e6.1720435656.git.u.kleine-koenig@baylibre.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=13207; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=vAjtirpm0fIC21TuPksPdz4VyyIIfSUSAw59/NpCLGQ=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8TzwQ6faZpE8fGJzI3j3caTxRf4iPUJ2LcJW 4zeeZfNe1OJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE8wAKCRCPgPtYfRL+ TjlWCAC7frr3EErxOamfX96tlEoZ7LSteRaollnbO0DO5d/9+e7YwgnYMGtA4Z/1qTcFS5+0++7 tBQANYLqZsiQhEONGctgUb7z+YF15zMynPVPNjiaDvu/nxwdx06+mQi5jzWRuGm84fEy/rzR9io kI2mL/GPrjk8VYSxBD5VuCMEFxlacZo7DRku53WS6dDTLjWNBpOLyDzNVUjOFNT/jzqdnyT3srK Sz9Gql7xkdTGpZ6SIXUxmdK7vPaU/OpzlxX4Qv7jKZGlhyyQ0tH7pdhFWMXFUKI1UTRSoak7Iic YMNF8PRqaz/L4xScS9czgDIx2kCR9aFIyTI79dY/TWCxVsUA X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 With this change each pwmchip can be accessed from userspace via a character device. Compared to the sysfs-API this is faster (on a stm32mp157 applying a new configuration takes approx 25% only) and allows to pass the whole configuration in a single ioctl allowing atomic application. Thanks to Randy Dunlap for pointing out a missing kernel-doc description. Signed-off-by: Uwe Kleine-König Reviewed-by: Trevor Gamblin --- drivers/pwm/core.c | 367 +++++++++++++++++++++++++++++++++++++-- include/linux/pwm.h | 3 + include/uapi/linux/pwm.h | 24 +++ 3 files changed, 379 insertions(+), 15 deletions(-) create mode 100644 include/uapi/linux/pwm.h diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 8e68481a7b33..d64c033c4cb2 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -23,6 +23,8 @@ #include +#include + #define CREATE_TRACE_POINTS #include @@ -95,6 +97,29 @@ static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf) } } +static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b) +{ + if (a->period_length > b->period_length) + return 1; + + if (a->period_length < b->period_length) + return -1; + + if (a->duty_length > b->duty_length) + return 1; + + if (a->duty_length < b->duty_length) + return -1; + + if (a->duty_offset > b->duty_offset) + return 1; + + if (a->duty_offset < b->duty_offset) + return -1; + + return 0; +} + static int pwm_check_rounding(const struct pwm_waveform *wf, const struct pwm_waveform *wf_rounded) { @@ -115,6 +140,127 @@ static int pwm_check_rounding(const struct pwm_waveform *wf, #define WFHWSIZE 20 +static int pwm_get_waveform(struct pwm_device *pwm, + struct pwm_waveform *wf) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + char wfhw[WFHWSIZE]; + int err; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + err = ops->read_waveform(chip, pwm, &wfhw); + if (err) + return err; + + return ops->round_waveform_fromhw(chip, pwm, &wfhw, wf); +} + +/* Called with the pwmchip lock held */ +static int __pwm_set_waveform(struct pwm_device *pwm, + const struct pwm_waveform *wf, + bool exact) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + char wfhw[WFHWSIZE]; + struct pwm_waveform wf_rounded; + int err; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + if (wf->period_length && + (wf->duty_length > wf->period_length || + wf->duty_offset >= wf->period_length)) + return -EINVAL; + + err = ops->round_waveform_tohw(chip, pwm, wf, &wfhw); + if (err) + return err; + + if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length) { + err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + if (err) + return err; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm_check_rounding(wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + wf->duty_length, wf->period_length, wf->duty_offset, + wf_rounded.duty_length, wf_rounded.period_length, wf_rounded.duty_offset); + + if (exact && pwmwfcmp(wf, &wf_rounded)) { + dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n", + wf->duty_length, wf->period_length, wf->duty_offset, + wf_rounded.duty_length, wf_rounded.period_length, wf_rounded.duty_offset); + + return 1; + } + } + + err = ops->write_waveform(chip, pwm, &wfhw); + if (err) + return err; + + /* update .state */ + pwm_wf2state(wf, &pwm->state); + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length) { + struct pwm_waveform wf_set; + + err = ops->read_waveform(chip, pwm, &wfhw); + if (err) + /* maybe ignore? */ + return err; + + err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_set); + if (err) + /* maybe ignore? */ + return err; + + if (pwmwfcmp(&wf_set, &wf_rounded) != 0) + dev_err(&chip->dev, + "Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n", + wf->duty_length, wf->period_length, wf->duty_offset, + wf_rounded.duty_length, wf_rounded.period_length, wf_rounded.duty_offset, + wf_set.duty_length, wf_set.period_length, wf_set.duty_offset); + } + return 0; +} + +static int pwm_set_waveform_might_sleep(struct pwm_device *pwm, + struct pwm_waveform *wf, bool exact) +{ + struct pwm_chip *chip = pwm->chip; + int err; + + might_sleep(); + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) { + /* + * Catch any drivers that have been marked as atomic but + * that will sleep anyway. + */ + non_block_start(); + err = __pwm_set_waveform(pwm, wf, exact); + non_block_end(); + } else { + err = __pwm_set_waveform(pwm, wf, exact); + } + + return err; +} + static void pwm_apply_debug(struct pwm_device *pwm, const struct pwm_state *state) { @@ -301,19 +447,6 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) if (err) return err; - if (IS_ENABLED(PWM_DEBUG)) { - struct pwm_waveform wf_rounded; - - err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); - if (err) - return err; - - if (pwm_check_rounding(&wf, &wf_rounded)) - dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", - wf.duty_length, wf.period_length, wf.duty_offset, - wf_rounded.duty_length, wf_rounded.period_length, wf_rounded.duty_offset); - } - err = ops->write_waveform(chip, pwm, &wfhw); if (err) return err; @@ -1296,6 +1429,197 @@ static bool pwm_ops_check(const struct pwm_chip *chip) return true; } +struct pwm_cdev_data { + struct pwm_chip *chip; + struct pwm_device *pwm[]; +}; + +static int pwm_cdev_open(struct inode *inode, struct file *file) +{ + struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev); + struct pwm_cdev_data *cdata; + + guard(mutex)(&pwm_lock); + + if (!chip->operational) + return -ENXIO; + + cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->chip = chip; + + file->private_data = cdata; + + return nonseekable_open(inode, file); +} + +static int pwm_cdev_release(struct inode *inode, struct file *file) +{ + struct pwm_cdev_data *cdata = file->private_data; + unsigned int i; + + for (i = 0; i < cdata->chip->npwm; ++i) { + if (cdata->pwm[i]) + pwm_put(cdata->pwm[i]); + } + kfree(cdata); + + return 0; +} + +static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm) +{ + struct pwm_chip *chip = cdata->chip; + + if (hwpwm >= chip->npwm) + return -EINVAL; + + if (!cdata->pwm[hwpwm]) { + struct pwm_device *pwm = &chip->pwms[hwpwm]; + int ret; + + ret = pwm_device_request(pwm, "pwm-cdev"); + if (ret < 0) + return ret; + + cdata->pwm[hwpwm] = pwm; + } + + return 0; +} + +static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm) +{ + struct pwm_chip *chip = cdata->chip; + + if (hwpwm >= chip->npwm) + return -EINVAL; + + if (cdata->pwm[hwpwm]) { + struct pwm_device *pwm = cdata->pwm[hwpwm]; + + pwm_put(pwm); + + cdata->pwm[hwpwm] = NULL; + } + + return 0; +} + +static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + struct pwm_cdev_data *cdata = file->private_data; + struct pwm_chip *chip = cdata->chip; + + guard(mutex)(&pwm_lock); + + if (!chip->operational) + return -ENODEV; + + switch (cmd) { + case PWM_IOCTL_GET_NUM_PWMS: + return chip->npwm; + + case PWM_IOCTL_REQUEST: + { + unsigned int hwpwm; + + ret = get_user(hwpwm, (unsigned int __user *)arg); + if (ret) + return ret; + + return pwm_cdev_request(cdata, hwpwm); + } + + case PWM_IOCTL_FREE: + { + unsigned int hwpwm; + + ret = get_user(hwpwm, (unsigned int __user *)arg); + if (ret) + return ret; + + return pwm_cdev_free(cdata, hwpwm); + } + + case PWM_IOCTL_GETWF: + { + struct pwmchip_waveform cwf; + struct pwm_waveform wf; + struct pwm_device *pwm; + + ret = copy_from_user(&cwf, (struct pwmchip_waveform __user *)arg, sizeof(cwf)); + if (ret) + return -EFAULT; + + ret = pwm_cdev_request(cdata, cwf.hwpwm); + if (ret) + return ret; + + pwm = cdata->pwm[cwf.hwpwm]; + + ret = pwm_get_waveform(pwm, &wf); + if (ret) + return ret; + + cwf.period_length = wf.period_length; + cwf.duty_length = wf.duty_length; + cwf.duty_offset = wf.duty_offset; + + return copy_to_user((struct pwmchip_waveform __user *)arg, &cwf, sizeof(cwf)); + } + break; + + case PWM_IOCTL_SETROUNDEDWF: + case PWM_IOCTL_SETEXACTWF: + { + struct pwmchip_waveform cwf; + struct pwm_waveform wf; + struct pwm_device *pwm; + + ret = copy_from_user(&cwf, (struct pwmchip_waveform __user *)arg, sizeof(cwf)); + if (ret) + return -EFAULT; + + if (cwf.period_length > 0 && + (cwf.duty_length > cwf.period_length || + cwf.duty_offset >= cwf.period_length)) + return -EINVAL; + + ret = pwm_cdev_request(cdata, cwf.hwpwm); + if (ret) + return ret; + + pwm = cdata->pwm[cwf.hwpwm]; + + wf = (struct pwm_waveform){ + .period_length = cwf.period_length, + .duty_length = cwf.duty_length, + .duty_offset = cwf.duty_offset, + }; + + return pwm_set_waveform_might_sleep(pwm, &wf, cmd == PWM_IOCTL_SETEXACTWF); + } + break; + + default: + return -ENOTTY; + } +} + +static const struct file_operations pwm_cdev_fileops = { + .open = pwm_cdev_open, + .release = pwm_cdev_release, + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = pwm_cdev_ioctl, +}; + +static dev_t pwm_devt; + /** * __pwmchip_add() - register a new PWM chip * @chip: the PWM chip to add @@ -1348,7 +1672,13 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) scoped_guard(pwmchip, chip) chip->operational = true; - ret = device_add(&chip->dev); + if (chip->id < 256 && chip->ops->write_waveform) + chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id); + + cdev_init(&chip->cdev, &pwm_cdev_fileops); + chip->cdev.owner = owner; + + ret = cdev_device_add(&chip->cdev, &chip->dev); if (ret) goto err_device_add; @@ -1399,7 +1729,7 @@ void pwmchip_remove(struct pwm_chip *chip) idr_remove(&pwm_chips, chip->id); } - device_del(&chip->dev); + cdev_device_del(&chip->cdev, &chip->dev); } EXPORT_SYMBOL_GPL(pwmchip_remove); @@ -1943,9 +2273,16 @@ static int __init pwm_init(void) { int ret; + ret = alloc_chrdev_region(&pwm_devt, 0, 256, "pwm"); + if (ret) { + pr_warn("Failed to initialize chrdev region for PWM usage\n"); + return ret; + } + ret = class_register(&pwm_class); if (ret) { pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret)); + unregister_chrdev_region(pwm_devt, 256); return ret; } diff --git a/include/linux/pwm.h b/include/linux/pwm.h index b5dff2a99038..3e503a28f5f7 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -2,6 +2,7 @@ #ifndef __LINUX_PWM_H #define __LINUX_PWM_H +#include #include #include #include @@ -303,6 +304,7 @@ struct pwm_ops { /** * struct pwm_chip - abstract a PWM controller * @dev: device providing the PWMs + * @cdev: &struct cdev for this device * @ops: callbacks for this PWM controller * @owner: module providing this chip * @id: unique number of this PWM chip @@ -317,6 +319,7 @@ struct pwm_ops { */ struct pwm_chip { struct device dev; + struct cdev cdev; const struct pwm_ops *ops; struct module *owner; unsigned int id; diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h new file mode 100644 index 000000000000..1ecf2e033b62 --- /dev/null +++ b/include/uapi/linux/pwm.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ + +#ifndef _UAPI_PWM_H_ +#define _UAPI_PWM_H_ + +#include +#include + +struct pwmchip_waveform { + unsigned int hwpwm; + __u64 period_length; + __u64 duty_length; + __u64 duty_offset; +}; + +#define PWM_IOCTL_GET_NUM_PWMS _IO(0x75, 0) +#define PWM_IOCTL_REQUEST _IOW(0x75, 1, unsigned int) +#define PWM_IOCTL_FREE _IOW(0x75, 2, unsigned int) +#define PWM_IOCTL_ROUNDWF _IOWR(0x75, 3, struct pwmchip_waveform) +#define PWM_IOCTL_GETWF _IOWR(0x75, 4, struct pwmchip_waveform) +#define PWM_IOCTL_SETROUNDEDWF _IOW(0x75, 5, struct pwmchip_waveform) +#define PWM_IOCTL_SETEXACTWF _IOW(0x75, 6, struct pwmchip_waveform) + +#endif /* _UAPI_PWM_H_ */ From patchwork Mon Jul 8 10:52:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957888 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=EpsIzj9I; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=139.178.88.99; helo=sv.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2725-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from sv.mirrors.kernel.org (sv.mirrors.kernel.org [139.178.88.99]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvS14B7z1xpd for ; Mon, 8 Jul 2024 20:53:08 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by sv.mirrors.kernel.org (Postfix) with ESMTPS id 84B82282755 for ; Mon, 8 Jul 2024 10:53:06 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 41BC077F2F; Mon, 8 Jul 2024 10:53:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="EpsIzj9I" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9763A78285 for ; Mon, 8 Jul 2024 10:53:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435986; cv=none; b=GZsSRjPZS7O6+RCyVNJAV5HmCK/N13MEI2ajw2LkXk/lVq4lnP8N9ezeprFRSuJ/oPI3NS33GnnUeyD6KH1u+pllJsJLz0uK8ZRADyBlT+ts1bcAB3MLO0qfiMA26/ls8ON5FavM+mcMgTHkPXSiityJvUq9rD4xHa9APxVCX1s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435986; c=relaxed/simple; bh=E4du6S52fRbYsHamOTmJy4P2wwGTiQOfVTfLt80msyI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tx2fRxn4Qa2EbC4tMyj1Vn1A7NKAa9b/83iO8P3jb4kLj1J/YPCNQ4NzGUxQEvxjsx186lFNH+SVcVnrhVXYrIyT9yc5fN0WvA6xExkB7Mdd8ER3s0GL+MNVTyTS6MOL8RkKAaU4QPnZRjGid2KZJDw7tlD0F6Xo5iZgGm66dkw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=EpsIzj9I; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-4257d5fc9b7so32398445e9.2 for ; Mon, 08 Jul 2024 03:53:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435981; x=1721040781; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=D4D/brPtHccM1xEdoPbrKHPZtT4177lAMIyHBRhH8rg=; b=EpsIzj9I3BFPyEhGNQwnI1of2gzB2jadRRNSfDgXoAGPbDviUai9uNS10WaSsGDgG5 Sl396N+VYSyrbr/y8aSAKhrgsoBQkTHSpU70rUNjuoVZ9nxKCVv1cT7zS2PMY7F6boaY Cj1Eu8zFuwLKQHJ7Vacg+tOoSeFY4b+scxSbZ7wyq9rNR/PUEH9Rw3tPXfB/MaYZLjT3 Ki9TlioDVwGEq9vVNyYHRSbK4z8cHgH76JGYyChIiCnyXsyBVZZROIImkEqmzrHk4Tv/ vI+iOKNhvSC+20GNu4utsDwhsae88NCl1iqGBwkrMKaoRBCZJYs0demm+5Qyo8d/p4ZK R/zQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435981; x=1721040781; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=D4D/brPtHccM1xEdoPbrKHPZtT4177lAMIyHBRhH8rg=; b=sfV+p4Rw8as3RIiKhmjOdmT9FvoGvoYnPi4Hp4fzuMk8eigqxpFNngO05czbF/SbeH 3PRcfJaYBYC3rGEyy9iiZHyBeEhO/wM38f+X77m2KW93AKyZp5JVYZ+5ins9qGRNp435 LLlVdFImayu869SIxsx6yI/v0UkwHgwOF+yxgiBRECtHYOP9ITj1dgSXDKDEzjpeuQ1J zoKzfl11qOJeoz0vvSsJOh673no+90pifmN4BFSueami0OXQN5iBIxBNV0ZUnHdqay9a CgS91AHFaRHsKYbPWLq+Q5NF2ENjWAXfAS9WT4396+9nmtCtzwgZkrLVhBLYqe7xGfmA T6GA== X-Gm-Message-State: AOJu0Yy5FG8Bjtq1RHpB6qw0Sf67qe9gYTIB09/y020vkJqP9I1nUy8W xjncXfc6Jknf7ggy8GHMvtvX+JcHnJIJPPX5O4pIya1HndDBML+cfSyn1FqX5ui3cTqKcZRUHnB i X-Google-Smtp-Source: AGHT+IFFsSoiSvxcSbUAHayafMd+68AqCP0R7LoEBZomzgrICTjVKAqqWfgdDIoyTMMsmvnfESX2LA== X-Received: by 2002:a05:600c:4a24:b0:426:59d3:8cae with SMTP id 5b1f17b1804b1-42659d38d8amr68737915e9.13.1720435980681; Mon, 08 Jul 2024 03:53:00 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-36796bae9ecsm12493683f8f.38.2024.07.08.03.53.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:53:00 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Cc: Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , linux-trace-kernel@vger.kernel.org Subject: [PATCH 4/6] pwm: Add tracing for waveform callbacks Date: Mon, 8 Jul 2024 12:52:30 +0200 Message-ID: <7f7ce46efc76e02a472ed52ab1fc06c3575a14e8.1720435656.git.u.kleine-koenig@baylibre.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=8499; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=E4du6S52fRbYsHamOTmJy4P2wwGTiQOfVTfLt80msyI=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8T1RWmi7qeHrGKBYS6lDIT6PDW/n5kMHXOzM 0NxS3rQ1UeJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE9QAKCRCPgPtYfRL+ TuCrB/wKT0q/JFCs8fx2iUyr42UZ8UFC6LJHw8B18+5I00kTj1P6lY4j6/XLzcL3+Sj1pzsTE1K eNh9IMF5nFRy02hNwmdPZYsiKDHK+37xhNw3mIqGa53C0xrOArf4gXy15j10mIWU0w4jN2y6SNF kaXNSxdj8S9FHSDP8vjd+W5EY3LdB0YgShX2uD+Dz6c4SRf09vOu/fmrkBndD0P8hIzEu8grqVU Fck0AwKkJ68Ab/mHVxm276DcmWCpnjXwBN/Em8FQn4mGyI7rMZ97OQDklTO2oFtrAXMgDGe8SIq hG+efSVB75zZvdGATT+FwQ8u52cUI7Q1IITqpnFjaXCAXGp0 X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 --- drivers/pwm/core.c | 68 ++++++++++++++++--- include/trace/events/pwm.h | 134 ++++++++++++++++++++++++++++++++++--- 2 files changed, 183 insertions(+), 19 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index d64c033c4cb2..a2320ae77220 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -138,6 +138,52 @@ static int pwm_check_rounding(const struct pwm_waveform *wf, return 0; } +static int pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_waveform *wf, void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw); + trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret); + + return ret; +} + +static int pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, + const void *wfhw, struct pwm_waveform *wf) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf); + trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret); + + return ret; +} + +static int pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->read_waveform(chip, pwm, wfhw); + trace_pwm_read_waveform(pwm, wfhw, ret); + + return ret; +} + +static int pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->write_waveform(chip, pwm, wfhw); + trace_pwm_write_waveform(pwm, wfhw, ret); + + return ret; +} + #define WFHWSIZE 20 static int pwm_get_waveform(struct pwm_device *pwm, @@ -155,11 +201,11 @@ static int pwm_get_waveform(struct pwm_device *pwm, if (!chip->operational) return -ENODEV; - err = ops->read_waveform(chip, pwm, &wfhw); + err = pwm_read_waveform(chip, pwm, &wfhw); if (err) return err; - return ops->round_waveform_fromhw(chip, pwm, &wfhw, wf); + return pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf); } /* Called with the pwmchip lock held */ @@ -180,12 +226,12 @@ static int __pwm_set_waveform(struct pwm_device *pwm, wf->duty_offset >= wf->period_length)) return -EINVAL; - err = ops->round_waveform_tohw(chip, pwm, wf, &wfhw); + err = pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); if (err) return err; if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length) { - err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + err = pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); if (err) return err; @@ -203,7 +249,7 @@ static int __pwm_set_waveform(struct pwm_device *pwm, } } - err = ops->write_waveform(chip, pwm, &wfhw); + err = pwm_write_waveform(chip, pwm, &wfhw); if (err) return err; @@ -213,12 +259,12 @@ static int __pwm_set_waveform(struct pwm_device *pwm, if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length) { struct pwm_waveform wf_set; - err = ops->read_waveform(chip, pwm, &wfhw); + err = pwm_read_waveform(chip, pwm, &wfhw); if (err) /* maybe ignore? */ return err; - err = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf_set); + err = pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set); if (err) /* maybe ignore? */ return err; @@ -443,11 +489,11 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) * high. */ - err = ops->round_waveform_tohw(chip, pwm, &wf, &wfhw); + err = pwm_round_waveform_tohw(chip, pwm, &wf, &wfhw); if (err) return err; - err = ops->write_waveform(chip, pwm, &wfhw); + err = pwm_write_waveform(chip, pwm, &wfhw); if (err) return err; @@ -548,11 +594,11 @@ static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state) scoped_guard(pwmchip, chip) { - ret = ops->read_waveform(chip, pwm, &wfhw); + ret = pwm_read_waveform(chip, pwm, &wfhw); if (ret) return ret; - ret = ops->round_waveform_fromhw(chip, pwm, &wfhw, &wf); + ret = pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf); if (ret) return ret; } diff --git a/include/trace/events/pwm.h b/include/trace/events/pwm.h index 8022701c446d..3b5b20d2aff0 100644 --- a/include/trace/events/pwm.h +++ b/include/trace/events/pwm.h @@ -8,15 +8,135 @@ #include #include +#define TP_PROTO_pwm(args...) \ + TP_PROTO(struct pwm_device *pwm, args) + +#define TP_ARGS_pwm(args...) \ + TP_ARGS(pwm, args) + +#define TP_STRUCT__entry_pwm(args...) \ + TP_STRUCT__entry( \ + __field(unsigned int, chipid) \ + __field(unsigned int, hwpwm) \ + args) + +#define TP_fast_assign_pwm(args...) \ + TP_fast_assign( \ + __entry->chipid = pwm->chip->id; \ + __entry->hwpwm = pwm->hwpwm; \ + args) + +#define TP_printk_pwm(fmt, args...) \ + TP_printk("pwmchip%u.%u: " fmt, __entry->chipid, __entry->hwpwm, args) + +#define __field_pwmwf(wf) \ + __field(u64, wf ## _period_length) \ + __field(u64, wf ## _duty_length) \ + __field(u64, wf ## _duty_offset) \ + +#define fast_assign_pwmwf(wf) \ + __entry->wf ## _period_length = wf->period_length; \ + __entry->wf ## _duty_length = wf->duty_length; \ + __entry->wf ## _duty_offset = wf->duty_offset + +#define printk_pwmwf_format(wf) \ + "%lld/%lld [+%lld]" + +#define printk_pwmwf_formatargs(wf) \ + __entry->wf ## _duty_length, __entry->wf ## _period_length, __entry->wf ## _duty_offset + +TRACE_EVENT(pwm_round_waveform_tohw, + + TP_PROTO_pwm(const struct pwm_waveform *wf, void *wfhw, int err), + + TP_ARGS_pwm(wf, wfhw, err), + + TP_STRUCT__entry_pwm( + __field_pwmwf(wf) + __field(void *, wfhw) + __field(int, err) + ), + + TP_fast_assign_pwm( + fast_assign_pwmwf(wf); + __entry->wfhw = wfhw; + __entry->err = err; + ), + + TP_printk_pwm(printk_pwmwf_format(wf) " > %p err=%d", + printk_pwmwf_formatargs(wf), __entry->wfhw, __entry->err) +); + +TRACE_EVENT(pwm_round_waveform_fromhw, + + TP_PROTO_pwm(const void *wfhw, struct pwm_waveform *wf, int err), + + TP_ARGS_pwm(wfhw, wf, err), + + TP_STRUCT__entry_pwm( + __field(const void *, wfhw) + __field_pwmwf(wf) + __field(int, err) + ), + + TP_fast_assign_pwm( + __entry->wfhw = wfhw; + fast_assign_pwmwf(wf); + __entry->err = err; + ), + + TP_printk_pwm("%p > " printk_pwmwf_format(wf) " err=%d", + __entry->wfhw, printk_pwmwf_formatargs(wf), __entry->err) +); + +TRACE_EVENT(pwm_read_waveform, + + TP_PROTO_pwm(void *wfhw, int err), + + TP_ARGS_pwm(wfhw, err), + + TP_STRUCT__entry_pwm( + __field(void *, wfhw) + __field(int, err) + ), + + TP_fast_assign_pwm( + __entry->wfhw = wfhw; + __entry->err = err; + ), + + TP_printk_pwm("%p err=%d", + __entry->wfhw, __entry->err) +); + +TRACE_EVENT(pwm_write_waveform, + + TP_PROTO_pwm(const void *wfhw, int err), + + TP_ARGS_pwm(wfhw, err), + + TP_STRUCT__entry_pwm( + __field(const void *, wfhw) + __field(int, err) + ), + + TP_fast_assign_pwm( + __entry->wfhw = wfhw; + __entry->err = err; + ), + + TP_printk_pwm("%p err=%d", + __entry->wfhw, __entry->err) +); + + DECLARE_EVENT_CLASS(pwm, TP_PROTO(struct pwm_device *pwm, const struct pwm_state *state, int err), TP_ARGS(pwm, state, err), - TP_STRUCT__entry( - __field(unsigned int, chipid) - __field(unsigned int, hwpwm) + TP_STRUCT__entry_pwm( __field(u64, period) __field(u64, duty_cycle) __field(enum pwm_polarity, polarity) @@ -24,9 +144,7 @@ DECLARE_EVENT_CLASS(pwm, __field(int, err) ), - TP_fast_assign( - __entry->chipid = pwm->chip->id; - __entry->hwpwm = pwm->hwpwm; + TP_fast_assign_pwm( __entry->period = state->period; __entry->duty_cycle = state->duty_cycle; __entry->polarity = state->polarity; @@ -34,8 +152,8 @@ DECLARE_EVENT_CLASS(pwm, __entry->err = err; ), - TP_printk("pwmchip%u.%u: period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", - __entry->chipid, __entry->hwpwm, __entry->period, __entry->duty_cycle, + TP_printk_pwm("period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", + __entry->period, __entry->duty_cycle, __entry->polarity, __entry->enabled, __entry->err) ); From patchwork Mon Jul 8 10:52:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957891 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=R1Uc+3HV; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=147.75.80.249; helo=am.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2724-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from am.mirrors.kernel.org (am.mirrors.kernel.org [147.75.80.249]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvT217gz1xpd for ; Mon, 8 Jul 2024 20:53:09 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id A7FD21F21877 for ; Mon, 8 Jul 2024 10:53:06 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 560C278C6B; Mon, 8 Jul 2024 10:53:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="R1Uc+3HV" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 796FF17721 for ; Mon, 8 Jul 2024 10:53:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435985; cv=none; b=lU01Fy315dKcSGfiK7r73uToCci9zx30hI7m5HTRScqtHnaTDFjtrst1kkvOQ9cUfp0OVmQ8VI6ruRKFozoYQUS5AohzeNmYobt7x1400Ubj0iMRqLTdcRnoydmn+D/jLuufmcO7lACbA/prIbyvHCmKDOfm6aQqagN6Q3w4xQ4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435985; c=relaxed/simple; bh=Z0Wou9UJ/BhBczHjo/WbUSvrV4JMkFQL3FRoqJbCglY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=QsHOTZgNG0BrZP757m/CIEGX2LlQenkc2VhPkQgDX3QnDjXGMXgrFNsiWbZ51+k4W1aNYEePUhgBvux3MGx6uUa3hXAWHj5exAw8DWunBli7dkqsWxm0tZeaDO9Z7LzosOaJhUL81v9MPSa7YVahg5/9nF4VYh8U5cvbCF2+WYw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=R1Uc+3HV; arc=none smtp.client-ip=209.85.221.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-36798e62aeeso2519876f8f.1 for ; Mon, 08 Jul 2024 03:53:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435982; x=1721040782; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=wqW0uZCqH5HVLtMjGDVx2xadsk75pptCe8ArkLxW0BI=; b=R1Uc+3HV4wX8fwG+DaNXKDEF+3mI7XOUJmYIK83aDWKlCRViFLJrNLC0opRUAXkwQU 9wSOs3eMV/Y0N3+V5DVsq2WiCP4ZZijY5ZrUi7avXSmpzRvmM6FQIrRRkngUq7xKBw1s RpgEaWHtO3qB5Y/J+ma1i8a/lbrq5Yf5Dt1C2grVsvSl/F81lSy/FHKhgVKBYPc+DL3x Wdkf6eVvaKv+ZmJpd2PubGmGPz1gnfcFE57wgqAXi+3tBLuf9nAUkeN/wBZ7KWLt1t1m iSlbwYuiZMcRhhW3+uoVAuKDQ8++cCQOsdDlhnJhUsUDBhTX1FFnG/VIR/F8ONmVC8Wp TjaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435982; x=1721040782; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wqW0uZCqH5HVLtMjGDVx2xadsk75pptCe8ArkLxW0BI=; b=VxWiwOQSY8rJaxQVnX8ebSkHIgXqS3NyaTgcpW84rXcyqJ28FvfXOlgsKLpK7N4eIb /ka/U7eWXqokSab+rE94v0yHAdmavp+aPo+oiPYdVxgV85F8C4HoLBH5OS9bY6j9Gc7d NhEXY07nKS4MloBJEeoymzZSCd4wODK+IbtUDHxf71+Pd+0g3DlnI0T89IBxsCpNRyS7 vONyn3hB1N/j0gVGa8sTn1KOfVAEayed77F2nPm8Y8KhKbGt3b3Jqij69VEIxU2om8oD tE4qr/KeIQuPNBHMsSz21fdl5Rc7Zr7Of0Gp6ppVLnEY3RgbVex1ebMtVOkfHhwvXJQB zcDQ== X-Gm-Message-State: AOJu0Yx5iXAPaW9sIQYAtHYw/eZXeX6IaK8FYxBGLUZB8MchQpJwwOpP 2ZIEgY5+LMV3xhCo06PAtCdULQ5PDMITv/iRm+LUjpAE4UMarg3YPeJvgbVvYRCeWAmOrczqfw0 v X-Google-Smtp-Source: AGHT+IHMxb8Pkz6tOMDQA4LhfpVe7ohHWfwfKVMKlry1DWQTxambt1D+1NABi6c8oKAreMF9U/B32A== X-Received: by 2002:a05:6000:12d1:b0:35f:122e:bd8c with SMTP id ffacd0b85a97d-3679dd3cbd6mr7851916f8f.17.1720435982030; Mon, 08 Jul 2024 03:53:02 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-367c4eec908sm1850913f8f.88.2024.07.08.03.53.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:53:01 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Cc: Michael Hennerich , =?utf-8?q?Nuno_S=C3=A1?= Subject: [PATCH 5/6] pwm: axi-pwmgen: Implementation of the waveform callbacks Date: Mon, 8 Jul 2024 12:52:31 +0200 Message-ID: <2c54a25eecc6f4745861568c49b98ca5b34a2f73.1720435656.git.u.kleine-koenig@baylibre.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=6777; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=Z0Wou9UJ/BhBczHjo/WbUSvrV4JMkFQL3FRoqJbCglY=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8T30PbP/6I88a+orp0W6GRXs33Va+7ZkeSop DrEH7cH0mGJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE9wAKCRCPgPtYfRL+ Tvf7B/0aIz+8dBfoduuVn8jKUxkoU6ZnLZfrcdaACHPVqkIqRF8Rj+8E9fGaMDdTXkHsLYQS5tB 5aV4tMx/wQM3/niFNOxV23tGcZZKu/eDvBDMumHKDa4s98ga9ybeTRRE+cOCSkbow8vuQK3X9It LusebiNkZngz9r/TEKuPjVplwG3s5jponoZFbtvxzzWG3cVzGvxy1fh14namn3TfLAOZV0SF9y1 XxjSNq2zdU8hI5xnoW72KaktYJqr6DPJRiV7fLrl1/Yeo4crH8l1PtJtV5WOZgYnt+aosvbZxKs QIXFywNggCS7v7uHx537DqahGjyVNc9lbbKcE4Mpm6I5pzF8 X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 Convert the axi-pwmgen driver to use the new callbacks for hardware programming. Signed-off-by: Uwe Kleine-König Tested-by: Trevor Gamblin --- drivers/pwm/pwm-axi-pwmgen.c | 148 ++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 46 deletions(-) diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c index aac4f395497b..757ae037d5d6 100644 --- a/drivers/pwm/pwm-axi-pwmgen.c +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -53,81 +54,136 @@ static const struct regmap_config axi_pwmgen_regmap_config = { .val_bits = 32, }; -static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) +/* This represents a hardware configuration for one channel */ +struct axi_pwmgen_waveform { + u32 period_cnt; + u32 duty_cycle_cnt; + u32 duty_offset_cnt; +}; + +static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) { + struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip); + + if (wf->period_length == 0) { + *wfhw = (struct axi_pwmgen_waveform){ + .period_cnt = 0, + .duty_cycle_cnt = 0, + .duty_offset_cnt = 0, + }; + } else { + /* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */ + wfhw->period_cnt = min_t(u64, mul_u64_u32_div(wf->period_length, ddata->clk_rate_hz, NSEC_PER_SEC), U32_MAX); + + if (wfhw->period_cnt == 0) { + /* + * The specified period is too short for the hardware. + * Let's round .duty_cycle down to 0 to get a (somewhat) + * valid result. + */ + wfhw->period_cnt = 1; + wfhw->duty_cycle_cnt = 0; + wfhw->duty_offset_cnt = 0; + } else { + wfhw->duty_cycle_cnt = min_t(u64, mul_u64_u32_div(wf->duty_length, ddata->clk_rate_hz, NSEC_PER_SEC), U32_MAX); + wfhw->duty_offset_cnt = min_t(u64, mul_u64_u32_div(wf->duty_offset, ddata->clk_rate_hz, NSEC_PER_SEC), U32_MAX); + } + } + + dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n", + pwm->hwpwm, wf->duty_length, wf->period_length, wf->duty_offset, + ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt); + + return 0; +} + +static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, + const void *_wfhw, struct pwm_waveform *wf) +{ + const struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip); + + wf->period_length = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + wf->duty_length = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + wf->duty_offset = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + return 0; +} + +static int axi_pwmgen_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + const struct axi_pwmgen_waveform *wfhw = _wfhw; struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip); - unsigned int ch = pwm->hwpwm; struct regmap *regmap = ddata->regmap; - u64 period_cnt, duty_cnt; + unsigned int ch = pwm->hwpwm; int ret; - if (state->polarity != PWM_POLARITY_NORMAL) - return -EINVAL; + ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt); + if (ret) + return ret; - if (state->enabled) { - period_cnt = mul_u64_u64_div_u64(state->period, ddata->clk_rate_hz, NSEC_PER_SEC); - if (period_cnt > UINT_MAX) - period_cnt = UINT_MAX; + ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt); + if (ret) + return ret; - if (period_cnt == 0) - return -EINVAL; - - ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt); - if (ret) - return ret; - - duty_cnt = mul_u64_u64_div_u64(state->duty_cycle, ddata->clk_rate_hz, NSEC_PER_SEC); - if (duty_cnt > UINT_MAX) - duty_cnt = UINT_MAX; - - ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt); - if (ret) - return ret; - } else { - ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0); - if (ret) - return ret; - - ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0); - if (ret) - return ret; - } + ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt); + if (ret) + return ret; return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG); } -static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm, - struct pwm_state *state) +static int axi_pwmgen_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) { + struct axi_pwmgen_waveform *wfhw = _wfhw; struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip); struct regmap *regmap = ddata->regmap; unsigned int ch = pwm->hwpwm; - u32 cnt; int ret; - ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &cnt); + ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt); if (ret) return ret; - state->enabled = cnt != 0; - - state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz); - - ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt); + ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt); if (ret) return ret; - state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz); + ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt); + if (ret) + return ret; - state->polarity = PWM_POLARITY_NORMAL; + if (wfhw->duty_cycle_cnt > wfhw->period_cnt) + wfhw->duty_cycle_cnt = wfhw->period_cnt; + + /* XXX: is this the actual behaviour of the hardware? */ + if (wfhw->duty_offset_cnt >= wfhw->period_cnt) { + wfhw->duty_cycle_cnt = 0; + wfhw->duty_offset_cnt = 0; + } return 0; } static const struct pwm_ops axi_pwmgen_pwm_ops = { - .apply = axi_pwmgen_apply, - .get_state = axi_pwmgen_get_state, + .sizeof_wfhw = sizeof(struct axi_pwmgen_waveform), + .round_waveform_tohw = axi_pwmgen_round_waveform_tohw, + .round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw, + .read_waveform = axi_pwmgen_read_waveform, + .write_waveform = axi_pwmgen_write_waveform, }; static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev) From patchwork Mon Jul 8 10:52:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= X-Patchwork-Id: 1957890 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=mjK4tF+8; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=2604:1380:45e3:2400::1; helo=sv.mirrors.kernel.org; envelope-from=linux-pwm+bounces-2726-incoming=patchwork.ozlabs.org@vger.kernel.org; receiver=patchwork.ozlabs.org) Received: from sv.mirrors.kernel.org (sv.mirrors.kernel.org [IPv6:2604:1380:45e3:2400::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WHgvS6kbsz1yNs for ; Mon, 8 Jul 2024 20:53:08 +1000 (AEST) Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by sv.mirrors.kernel.org (Postfix) with ESMTPS id 9FC702826DE for ; Mon, 8 Jul 2024 10:53:07 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 59D5C78285; Mon, 8 Jul 2024 10:53:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b="mjK4tF+8" X-Original-To: linux-pwm@vger.kernel.org Received: from mail-lf1-f53.google.com (mail-lf1-f53.google.com [209.85.167.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 01493770ED for ; Mon, 8 Jul 2024 10:53:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435987; cv=none; b=DKVwNKrOf2CqKRw4WBXQs9B39ZwO6+vEeLRHif0zQ6oWBLQNjXAJiI3sXsDGYpSU15aD3vQiF16xfuU/BE8bfJcbDgLGVB5idhgcDCxMg33+rqXaDTgjbi9PW8fM78lgr6LsXnjdDs3/rKptzZHT7x2qf+jluH3hfqe+VgLllT4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720435987; c=relaxed/simple; bh=yhsRxTmP+cPE+8kUf176lTv2yrrTFVO2vxSx26zHd0I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OL+ZhWG6uIJXc+ixaC8PO9SCDIwHivW+FuiDPmQHiGujGppp+LNjp0zGuyirJAQFsFrwh6h7D/sN9/lGpPKa3wup6dG0IQkyUxeu5DqGMb2lb9krCEH1qaHG9kKiXs6m6nmN8NmKPL1yU+Q5hhznDwATb/LRQM7pzTxHm95eLP4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com; spf=pass smtp.mailfrom=baylibre.com; dkim=pass (2048-bit key) header.d=baylibre-com.20230601.gappssmtp.com header.i=@baylibre-com.20230601.gappssmtp.com header.b=mjK4tF+8; arc=none smtp.client-ip=209.85.167.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=baylibre.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=baylibre.com Received: by mail-lf1-f53.google.com with SMTP id 2adb3069b0e04-52ea2b6a9f5so3888711e87.0 for ; Mon, 08 Jul 2024 03:53:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20230601.gappssmtp.com; s=20230601; t=1720435983; x=1721040783; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=yzQ12MBO5tx4hCgekxUxY/qWko2QtfYvPcuwIh1kocA=; b=mjK4tF+8OSYzVuXsqIyTy4KqGXvOf+QDZ/1cb2wbq9Fg2Rm6mu/PKeD+Dj9onVp6BP PYbZWCcQPIh0cIOefzu7uRgQa8lQoPc96VIIQ5Z5TdIDyr3q6iE4Ijb/TJnjjPuiCSay dhqVpNuEPen54gdz/z3OLUqJnP6WsdCVGmnzBZLv+9ExK9Kmrp+YITypzuQaZvS4rwoy c13m/qbejIXuxqwh/AoxOruUiSLmjtZweTs/DuF73IDmpklyNIbqV1msx/PwYcKXgMk2 4DyJ7RG4SKq9fwNQKFtCEbhmK3yxiNfMmP8iQuwPAhHBu5qynMyQf4/ejMylD4EqqO26 cm+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720435983; x=1721040783; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=yzQ12MBO5tx4hCgekxUxY/qWko2QtfYvPcuwIh1kocA=; b=Jeed6dFl1p+84Q/ynY1c/9B9FMCzZZ13E1vY0phVgb/I7kmtnn+wSpmSdNYuI3CVXF yCT4mo6RJP7ou3qbLnrYh000IMZJScAjfLTQ9CDneB5rK84SYew2SYLdKjafzSHJl12R NQuh+zJToWs2m6qziVHaMP0Uk84zyBDWNK5yE+TQa+SnKS0XUBWJj9OxQKHXKYtdUIwh cJ5cKM3BX24oV+KmAqpsnwKcn4EW2U8LkZL9IQE11Bclb4RCzikimj6FfQNQc8yjhx/s /FuxkofKfBitiH9puB6hbis5afWmXsPE6yhkka97M+SIx999WhLMZgirbnlk93AsDh+M 5vJA== X-Gm-Message-State: AOJu0YwlzJC4TbnWBzoKXXhUuMHEFJcZOXkCL1MiENMe5mVXpgsL1lNc 7zd6Gw6CODxa6NDUr6er1jH/YZWaQlYzULYbHfk7eFH6Dda6wdO//bmRMlKzjun9OB+7MGTZ0Qt I X-Google-Smtp-Source: AGHT+IHYHbku7YKCTGpHkQcjNl3Wy4XpBMGPUhc+THoxMvdm6tXgsUYD9cCVpSLXZH0OO8CQc1kJSQ== X-Received: by 2002:a05:6512:312a:b0:52c:ccb4:ec70 with SMTP id 2adb3069b0e04-52ea0632f20mr7976815e87.22.1720435983217; Mon, 08 Jul 2024 03:53:03 -0700 (PDT) Received: from localhost (p50915e7b.dip0.t-ipconnect.de. [80.145.94.123]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4264a2ca5casm161986155e9.32.2024.07.08.03.53.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jul 2024 03:53:02 -0700 (PDT) From: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= To: linux-pwm@vger.kernel.org Cc: Fabrice Gasnier , Maxime Coquelin , Alexandre Torgue , linux-stm32@st-md-mailman.stormreply.com, linux-arm-kernel@lists.infradead.org Subject: [PATCH 6/6] pwm: stm32: Implementation of the waveform callbacks Date: Mon, 8 Jul 2024 12:52:32 +0200 Message-ID: <69eb4b894ec82a0d53bc14450006a977d82551ec.1720435656.git.u.kleine-koenig@baylibre.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-pwm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=17011; i=u.kleine-koenig@baylibre.com; h=from:subject:message-id; bh=yhsRxTmP+cPE+8kUf176lTv2yrrTFVO2vxSx26zHd0I=; b=owEBbQGS/pANAwAKAY+A+1h9Ev5OAcsmYgBmi8T68q3i4AsqKv/3eD7+PVUgJ1AuvSqy1csHu PUxhL7FqsGJATMEAAEKAB0WIQQ/gaxpOnoeWYmt/tOPgPtYfRL+TgUCZovE+gAKCRCPgPtYfRL+ TnmxB/48DbmWoYO+leRSz4W37BtI4ysuXyR392jO9nkgRve/c9SnkG3uBLhg/6QE7spnYoYbe0M KPJeGj09jNTwvCcgbISAZvMNyXPkGUgE2JsuLlVKXGCqG1VET81iKxdlGFxFU0xq7VuLvUTfbTB 7VIAsOEXTvRjUz0Le9XvPqijxpbO0i+C3A7/dU8wvTLPnaU0LZLv5RsOjmQHue9A1BCwa79jmtN huhmVxha7Swm270SkzMaSjPNK3buIzx8no5wXG29nuZBFw86qWDWhbUByM1wEap5NPNWiy9azjc pz/3FFzKMMprILwMegbBznAx5C8xxK9cb0BscThbWYiT1iKa X-Developer-Key: i=u.kleine-koenig@baylibre.com; a=openpgp; fpr=0D2511F322BFAB1C1580266BE2DCDD9132669BD6 Convert the stm32 pwm driver to use the new callbacks for hardware programming. Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 605 +++++++++++++++++++++++++--------------- 1 file changed, 384 insertions(+), 221 deletions(-) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index fd754a99cf2e..4dedfabce63b 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -51,6 +51,384 @@ static u32 active_channels(struct stm32_pwm *dev) return ccer & TIM_CCER_CCXE; } +struct stm32_pwm_waveform { + u32 ccer; + u32 psc; + u32 arr; + u32 ccr; +}; + +static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + unsigned long rate; + u64 ccr, duty; + int ret; + + if (wf->period_length == 0) { + *wfhw = (struct stm32_pwm_waveform){ + .ccer = 0, + }; + + return 0; + } + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + wfhw->ccer = TIM_CCER_CCxE(ch + 1); + if (priv->have_complementary_output) + wfhw->ccer = TIM_CCER_CCxNE(ch); + + rate = clk_get_rate(priv->clk); + + if (active_channels(priv) & ~(1 << ch * 4)) { + u64 arr; + + /* + * Other channels are already enabled, so the configured PSC and + * ARR must be used for this channel, too. + */ + ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr); + if (ret) + goto out; + + /* + * calculate the best value for ARR for the given PSC, refuse if + * the resulting period gets bigger than the requested one. + */ + arr = mul_u64_u64_div_u64(wf->period_length, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + if (arr <= wfhw->arr) { + /* + * requested period is small than the currently + * configured and unchangable period, report back the smallest + * possible period, i.e. the current state; Initialize + * ccr to anything valid. + */ + wfhw->ccr = 0; + ret = 1; + goto out; + } + + } else { + /* + * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so + * the calculations here won't overflow. + * First we need to find the minimal value for prescaler such that + * + * period_ns * clkrate + * ------------------------------ < max_arr + 1 + * NSEC_PER_SEC * (prescaler + 1) + * + * This equation is equivalent to + * + * period_ns * clkrate + * ---------------------------- < prescaler + 1 + * NSEC_PER_SEC * (max_arr + 1) + * + * Using integer division and knowing that the right hand side is + * integer, this is further equivalent to + * + * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler + */ + u64 psc = mul_u64_u64_div_u64(wf->period_length, rate, + (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1)); + u64 arr; + + wfhw->psc = min_t(u64, psc, MAX_TIM_PSC); + + arr = mul_u64_u64_div_u64(wf->period_length, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + if (!arr) { + /* + * requested period is too small, report back the smallest + * possible period, i.e. ARR = 0. The only valid CCR + * value is then zero, too. + */ + wfhw->arr = 0; + wfhw->ccr = 0; + ret = 1; + goto out; + } + + /* + * ARR is limited intentionally to values less than + * priv->max_arr to allow 100% duty cycle. + */ + wfhw->arr = min_t(u64, arr, priv->max_arr) - 1; + } + + duty = mul_u64_u64_div_u64(wf->duty_length, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + duty = min_t(u64, duty, wfhw->arr + 1); + + if (wf->duty_length && wf->duty_offset && + wf->duty_length + wf->duty_offset >= wf->period_length) { + wfhw->ccer |= TIM_CCER_CCxP(ch + 1); + if (priv->have_complementary_output) + wfhw->ccer |= TIM_CCER_CCxNP(ch + 1); + + ccr = wfhw->arr + 1 - duty; + } else { + ccr = duty; + } + + wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1); + + dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n", + pwm->hwpwm, wf->duty_length, wf->period_length, wf->duty_offset, + rate, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr); + +out: + clk_disable(priv->clk); + + return ret; +} + +/* + * This should be moved to lib/math/div64.c. Currently there are some changes + * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles. + */ +static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c) +{ + u64 res = mul_u64_u64_div_u64(a, b, c); + /* Those multiplications might overflow but it doesn't matter */ + u64 rem = a * b - c * res; + + if (rem) + res += 1; + + return res; +} + +static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + unsigned long rate = clk_get_rate(priv->clk); + u64 ccr_ns; + + /* The result doesn't overflow for rate >= 15259 */ + wf->period_length = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), NSEC_PER_SEC, rate); + + ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, NSEC_PER_SEC, rate); + + if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) { + wf->duty_length = + stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), + NSEC_PER_SEC, rate); + + wf->duty_offset = ccr_ns; + } else { + wf->duty_length = ccr_ns; + wf->duty_offset = 0; + } + } else { + *wf = (struct pwm_waveform){ + .period_length = 0, + }; + } + + return 0; +} + +static int stm32_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + int ret; + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, TIM_CCER, &wfhw->ccer); + if (ret) + goto out; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr); + if (ret) + goto out; + + if (wfhw->arr == U32_MAX) + wfhw->arr -= 1; + + ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &wfhw->ccr); + if (ret) + goto out; + + if (wfhw->ccr > wfhw->arr + 1) + wfhw->ccr = wfhw->arr + 1; + } + +out: + clk_disable(priv->clk); + + return ret; +} + +static int stm32_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + const struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + int ret; + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + u32 ccer, mask; + unsigned shift; + u32 ccmr; + + ret = regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ret) + goto out; + + /* If there are other channels enabled, don't update PSC and ARR */ + if (ccer & ~TIM_CCER_CCxE(ch + 1) & TIM_CCER_CCXE) { + u32 psc, arr; + + ret = regmap_read(priv->regmap, TIM_PSC, &psc); + if (ret) + goto out; + + if (psc != wfhw->psc) { + ret = -EBUSY; + goto out; + } + + regmap_read(priv->regmap, TIM_ARR, &arr); + if (ret) + goto out; + + if (arr != wfhw->arr) { + ret = -EBUSY; + goto out; + } + } else { + ret = regmap_write(priv->regmap, TIM_PSC, wfhw->psc); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, TIM_ARR, wfhw->arr); + if (ret) + goto out; + + ret = regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + if (ret) + goto out; + + } + + /* set polarity */ + mask = TIM_CCER_CCxP(ch + 1) | TIM_CCER_CCxNP(ch + 1); + ret = regmap_update_bits(priv->regmap, TIM_CCER, mask, wfhw->ccer); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, TIM_CCRx(ch + 1), wfhw->ccr); + if (ret) + goto out; + + /* Configure output mode */ + shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT; + ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift; + mask = CCMR_CHANNEL_MASK << shift; + + if (ch < 2) + ret = regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr); + else + ret = regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr); + if (ret) + goto out; + + ret = regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE); + if (ret) + goto out; + + if (!(ccer & TIM_CCER_CCxE(ch + 1))) { + mask = TIM_CCER_CCxE(ch + 1) | TIM_CCER_CCxNE(ch + 1); + + ret = clk_enable(priv->clk); + if (ret) + goto out; + + ccer = (ccer & ~mask) | (wfhw->ccer & mask); + regmap_write(priv->regmap, TIM_CCER, ccer); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + /* Enable controller */ + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + } + + } else { + /* disable channel */ + u32 mask, ccer; + + mask = TIM_CCER_CCxE(ch + 1); + if (priv->have_complementary_output) + mask |= TIM_CCER_CCxNE(ch + 1); + + ret = regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ret) + goto out; + + if (ccer & mask) { + ccer = ccer & ~mask; + + ret = regmap_write(priv->regmap, TIM_CCER, ccer); + if (ret) + goto out; + + if (!(ccer & TIM_CCER_CCXE)) { + /* When all channels are disabled, we can disable the controller */ + ret = regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + if (ret) + goto out; + } + + clk_disable(priv->clk); + } + } + +out: + clk_disable(priv->clk); + + return ret; +} + #define TIM_CCER_CC12P (TIM_CCER_CC1P | TIM_CCER_CC2P) #define TIM_CCER_CC12E (TIM_CCER_CC1E | TIM_CCER_CC2E) #define TIM_CCER_CC34P (TIM_CCER_CC3P | TIM_CCER_CC4P) @@ -308,228 +686,13 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm, return ret; } -static int stm32_pwm_config(struct stm32_pwm *priv, unsigned int ch, - u64 duty_ns, u64 period_ns) -{ - unsigned long long prd, dty; - unsigned long long prescaler; - u32 ccmr, mask, shift; - - /* - * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so - * the calculations here won't overflow. - * First we need to find the minimal value for prescaler such that - * - * period_ns * clkrate - * ------------------------------ < max_arr + 1 - * NSEC_PER_SEC * (prescaler + 1) - * - * This equation is equivalent to - * - * period_ns * clkrate - * ---------------------------- < prescaler + 1 - * NSEC_PER_SEC * (max_arr + 1) - * - * Using integer division and knowing that the right hand side is - * integer, this is further equivalent to - * - * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler - */ - - prescaler = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk), - (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1)); - if (prescaler > MAX_TIM_PSC) - return -EINVAL; - - prd = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk), - (u64)NSEC_PER_SEC * (prescaler + 1)); - if (!prd) - return -EINVAL; - - /* - * All channels share the same prescaler and counter so when two - * channels are active at the same time we can't change them - */ - if (active_channels(priv) & ~(1 << ch * 4)) { - u32 psc, arr; - - regmap_read(priv->regmap, TIM_PSC, &psc); - regmap_read(priv->regmap, TIM_ARR, &arr); - - if ((psc != prescaler) || (arr != prd - 1)) - return -EBUSY; - } - - regmap_write(priv->regmap, TIM_PSC, prescaler); - regmap_write(priv->regmap, TIM_ARR, prd - 1); - regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); - - /* Calculate the duty cycles */ - dty = mul_u64_u64_div_u64(duty_ns, clk_get_rate(priv->clk), - (u64)NSEC_PER_SEC * (prescaler + 1)); - - regmap_write(priv->regmap, TIM_CCRx(ch + 1), dty); - - /* Configure output mode */ - shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT; - ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift; - mask = CCMR_CHANNEL_MASK << shift; - - if (ch < 2) - regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr); - else - regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr); - - regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE); - - return 0; -} - -static int stm32_pwm_set_polarity(struct stm32_pwm *priv, unsigned int ch, - enum pwm_polarity polarity) -{ - u32 mask; - - mask = TIM_CCER_CCxP(ch + 1); - if (priv->have_complementary_output) - mask |= TIM_CCER_CCxNP(ch + 1); - - regmap_update_bits(priv->regmap, TIM_CCER, mask, - polarity == PWM_POLARITY_NORMAL ? 0 : mask); - - return 0; -} - -static int stm32_pwm_enable(struct stm32_pwm *priv, unsigned int ch) -{ - u32 mask; - int ret; - - ret = clk_enable(priv->clk); - if (ret) - return ret; - - /* Enable channel */ - mask = TIM_CCER_CCxE(ch + 1); - if (priv->have_complementary_output) - mask |= TIM_CCER_CCxNE(ch); - - regmap_set_bits(priv->regmap, TIM_CCER, mask); - - /* Make sure that registers are updated */ - regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); - - /* Enable controller */ - regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); - - return 0; -} - -static void stm32_pwm_disable(struct stm32_pwm *priv, unsigned int ch) -{ - u32 mask; - - /* Disable channel */ - mask = TIM_CCER_CCxE(ch + 1); - if (priv->have_complementary_output) - mask |= TIM_CCER_CCxNE(ch + 1); - - regmap_clear_bits(priv->regmap, TIM_CCER, mask); - - /* When all channels are disabled, we can disable the controller */ - if (!active_channels(priv)) - regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); - - clk_disable(priv->clk); -} - -static int stm32_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) -{ - bool enabled; - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ret; - - enabled = pwm->state.enabled; - - if (!state->enabled) { - if (enabled) - stm32_pwm_disable(priv, pwm->hwpwm); - return 0; - } - - if (state->polarity != pwm->state.polarity) - stm32_pwm_set_polarity(priv, pwm->hwpwm, state->polarity); - - ret = stm32_pwm_config(priv, pwm->hwpwm, - state->duty_cycle, state->period); - if (ret) - return ret; - - if (!enabled && state->enabled) - ret = stm32_pwm_enable(priv, pwm->hwpwm); - - return ret; -} - -static int stm32_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) -{ - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ret; - - /* protect common prescaler for all active channels */ - mutex_lock(&priv->lock); - ret = stm32_pwm_apply(chip, pwm, state); - mutex_unlock(&priv->lock); - - return ret; -} - -static int stm32_pwm_get_state(struct pwm_chip *chip, - struct pwm_device *pwm, struct pwm_state *state) -{ - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ch = pwm->hwpwm; - unsigned long rate; - u32 ccer, psc, arr, ccr; - u64 dty, prd; - int ret; - - mutex_lock(&priv->lock); - - ret = regmap_read(priv->regmap, TIM_CCER, &ccer); - if (ret) - goto out; - - state->enabled = ccer & TIM_CCER_CCxE(ch + 1); - state->polarity = (ccer & TIM_CCER_CCxP(ch + 1)) ? - PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; - ret = regmap_read(priv->regmap, TIM_PSC, &psc); - if (ret) - goto out; - ret = regmap_read(priv->regmap, TIM_ARR, &arr); - if (ret) - goto out; - ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &ccr); - if (ret) - goto out; - - rate = clk_get_rate(priv->clk); - - prd = (u64)NSEC_PER_SEC * (psc + 1) * (arr + 1); - state->period = DIV_ROUND_UP_ULL(prd, rate); - dty = (u64)NSEC_PER_SEC * (psc + 1) * ccr; - state->duty_cycle = DIV_ROUND_UP_ULL(dty, rate); - -out: - mutex_unlock(&priv->lock); - return ret; -} - static const struct pwm_ops stm32pwm_ops = { - .apply = stm32_pwm_apply_locked, - .get_state = stm32_pwm_get_state, + .sizeof_wfhw = sizeof(struct stm32_pwm_waveform), + .round_waveform_tohw = stm32_pwm_round_waveform_tohw, + .round_waveform_fromhw = stm32_pwm_round_waveform_fromhw, + .read_waveform = stm32_pwm_read_waveform, + .write_waveform = stm32_pwm_write_waveform, + .capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL, };