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: 13726408 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id D4F53C3DA42 for ; Mon, 8 Jul 2024 10:53:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=Mpt6NFsPkisaY1MwVr6kXrTsatrXRavGiwZl9130ebI=; b=1z0MKeQ18CHIEGnzccttwH5eH/ kxRyuGp24EjLwTCwElV5iB7XSPkbRjmLxvsAWoLrhBAVgEn/NyP7NocucugyruAzwI2dni7dzw281 hvJ1E0trr1CNeFJ+QTDdd4ESgKOl60D5DSjRaT4NcsPZnosQ/dE93PR2QXNJ/VNrFUNBh+FfFHu+d wdqtvsXgUJthLSBZpqLDA/DLnMx+RAWQzH9jiDF3Hl/qdi6GBw8uuXTO6qCooZJBYFoU6og8Sbx3e htDQwpi01JbaNmhCoAXI58SFNe00eI87a9Fh/wHX9pP+NV2D2ODB8a317cCHa9sA3VgD7sZkhe9NA xNBsfY1g==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sQlzq-00000003VFl-0l07; Mon, 08 Jul 2024 10:53:26 +0000 Received: from mail-lj1-x236.google.com ([2a00:1450:4864:20::236]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sQlzQ-00000003VCq-2tOS for linux-arm-kernel@lists.infradead.org; Mon, 08 Jul 2024 10:53:02 +0000 Received: by mail-lj1-x236.google.com with SMTP id 38308e7fff4ca-2ee910d6a9dso34438981fa.1 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=lists.infradead.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=D2iDvO8AqVPdXYersH1Ni8nQC8IXw1/x4XGBxqB439ZUjBeiIg09CBv1Wo0JyYxnb8 ZCfPnD36DRAuJydkHXraBnn0n/NBZs3w6R+PelL8Ze4wTVNfFxvKqRPwDT52V5yUQyzF aTx8Kb+vmBneYiurzmJuDCS0T8zxVy6K42VGOtkz7PitQHJTdmibQvfij9Vg4cjzEAiY wKclpubeLGK83YfgPoroVWtu6g/xWkhVlRyyyiLG2ET620uBu5XfzpoY4/OB+qw0d9py oKPtLAJdetW6PJVcqSBNPxdEFs6BaSQGFNb+pW+vSoIGa5gKZNAA9i/BViIw1mgqB62/ uuMg== 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=mbG7vorYiXJaPiUYoZIsVMfkccTZ3J9JLCnZGkcX81Ol7tiTDzvDjlJn1LHeXPjK5/ fY8p73HJEHdhWPXTXjvOGeshY1CGpwQz8LEUIPF+jmoSZwXRhuO99/fQk8LM4KlLgN1b QfExCrVjBpJdyLOLZhH7h2L4KPNNcgUvOD7n7n6noSYk3DszpmLROsl39FRRdypPzFoL wmBVDEw8Sedbi8n87TSSWRQchvl0XI82W6mKz+aRi/qrAieQmngJkJqi7SGgJ6Ttp8IN Uwr/S8age6ZIL6jruYt4Wfobk9hUlbCaK/Qu1P43IOLXvo8rLiDrGJrzIoNeipc/5DES XDYw== X-Forwarded-Encrypted: i=1; AJvYcCXAF1/CYPHfbjMlPyj4c98uhUUQCihhRTJPOdOd5FNYbEUsKDA09EBgxEer9JNKge8iGWh5BSYUpM5QYHrnN3gXr2/3pvKGsHk5LtaAmlyjnHQkP/o= X-Gm-Message-State: AOJu0YzzCJ57PqpZYQyBn3FcgQ4DsNipCGe733JSAB9Bjfh0xF6ccIl9 AlzY2Ydfnyc6Xzz79yWpc2v08f2eIXhmtb6qfW1nqU2vBcUONOUjt2ephfXWog4= 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: 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 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240708_035300_763164_BF8C9485 X-CRM114-Status: GOOD ( 36.52 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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: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: 13726409 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 334AEC3271E for ; Mon, 8 Jul 2024 10:53:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=yzQ12MBO5tx4hCgekxUxY/qWko2QtfYvPcuwIh1kocA=; b=YC6e4PlQmGw8cYG9ZAZOk78PEf /gU727pbdsv005Ty6Mo603ppHUte8MU3uj6AL6j1tWrZB3Rm+uLLsi+PWQAQJbSqJS6qPfTzsCX87 vmaKXOgKecu+SsL9Rv5MgrsoK/fhBd2Bx+rRs5Xqfj27TQQPuANAmMkKnE2LZrUmjh/bWlIyg8i5H 2HOX2JY+DkAa5F/KhVq5i/4butZeP5+L/j6VFRnCmjUodECqe1P7L25zEBnrWc3tDCGjYAuiSDWJv /f7ulC1SeDjTX7NUB+QuT9xF7gEB/Z8aFO4I4CpWpbpJmvTKGY8H2mkKqRgsNXu7uftuH0gDShWpM US9JBAPA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sQm02-00000003VIB-10Dw; Mon, 08 Jul 2024 10:53:38 +0000 Received: from mail-lf1-x131.google.com ([2a00:1450:4864:20::131]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sQlzV-00000003VDM-1wzg for linux-arm-kernel@lists.infradead.org; Mon, 08 Jul 2024 10:53:07 +0000 Received: by mail-lf1-x131.google.com with SMTP id 2adb3069b0e04-52ea2b6a9f5so3888712e87.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=lists.infradead.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=fYLjDGMCTm6qIxNSDH/M6lnAV+5A/vsBqzhPMxlmTB6H6Fbpk2wvpF8B+TzbDLZOcg gbfAK7x9NKNw5Ii159hGN2MkC1UrI3dmbaHfyyiQQIma98ivmX4C/y4WF/uL0r9nL7XW BGK+lqrOOGnX+DtSQqtPl5rXh1sYpN6hUJJm1Thsqgwq69A9vCPt5zSuM9U1YtE6Tkuv 7HRETjikPsjBfuy9/WNd7mcRQb3fZUctTz+19GKsaWO39+BvGluyapTJoLSMNzp3Q5rc eGULzTISv3xoSKzmyfdSJ17GawFYwrWxlYLcZXVzR1Hvgh/HPCczZXqA/Q7yuEsEWFVR A47w== 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=ELuVWhQFFb4oZOH5Co4KDYnCR5KthRUAGCI8SLM/ZFN7nZ8pGeQeQ/KoPevmWskhpJ AbRT46bBvza22coKEvgJE0Pk5UBqH1UpOeL48iQQsY5wBatRRB4dd78EW2h9D1U1URDn An35QRlPmC4A5+d2b9OoW0yhx4IeV4NRlBzgY2ztL8h53LSmmVlP/Y4Vw/UqDpJsRUCD kk2UagrKRF1/rFXO9AqQCXi+7B0k+KjRaCOxZrY2Ua+exOJGjeI3Jggj5zfxfR1vCeWZ GS6mhZLuxJJK/G3qTEtxaaNTbxyympVeQIBPCXcVfoQ395mByVbLmXH9DA8COeooSVSa aGhA== X-Forwarded-Encrypted: i=1; AJvYcCUWfyE5dy+aFfnjKi46gxfZtPYsVCdy9ILgiaiA1q9GR8NDEW0XJ9MqviRbdBkxdb/fGxreYbj4OFWrxLSL4cUmIxduc6kTMORXo42U40LlhuoBdQY= X-Gm-Message-State: AOJu0YyKcE0FnXPThql+jcdZpS/9c/tjEADMZZQLPTxe/xXCA6GZKGKa YU4KKpur8wx0Ul9ShPdgkhwCisd6rfKsD8zvHBfCdNYm0pH6ZA9E5KtcF6vFUZ0= 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: 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 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240708_035305_540914_6B81D952 X-CRM114-Status: GOOD ( 27.68 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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, };