From patchwork Fri May 31 14:11:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Hironori KIKUCHI X-Patchwork-Id: 13681740 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 92334C25B75 for ; Fri, 31 May 2024 14:12:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=c+Uaaxk9QvmaWsUWViuORQ22zY4iq91WilN32e+935k=; b=YqiDaFG5f9YO7j HcFHNpdQ3UHL7qziW2VfYgTzkvMu6K14ug7AODkFwQ2CVzysTpyFLZvQMlzPeVdW2mHJ6vniJMvdQ SmHj99hxTpeh5Gr4clAuK0nNSe2oa+FmOBQXfpXFMBshH9ETpTC7wxhntpTueY+0QewzCcShNQTLo fkIIEjOogLsqxfTOMds2WyneMXKEO+BwLvXFrqyWWG3j5x4EVnWosOSfqotnNVF4f4wvRP6hcnFyM SlN1A+E+WY9ngcKwa35LeITuyx8V5SfzEE3bpEJUXyV9hmViu1PE+BXn5XqYBCvF3qeU61T+gCHnb JeWJiK68eCfES1qPBxAw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sD2zp-0000000ASwc-1M54; Fri, 31 May 2024 14:12:41 +0000 Received: from mail-pf1-x42a.google.com ([2607:f8b0:4864:20::42a]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sD2zh-0000000ASrk-3ozJ for linux-arm-kernel@lists.infradead.org; Fri, 31 May 2024 14:12:35 +0000 Received: by mail-pf1-x42a.google.com with SMTP id d2e1a72fcca58-7024d560b32so324779b3a.1 for ; Fri, 31 May 2024 07:12:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1717164752; x=1717769552; 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=iRKfuq7tJP6RRlShAcOHog3cbaDmp6QUmMlJFE3QvoU=; b=OuzfTssYa8QI8x2sm9y495Y2UD5mLKNvtXXj4YhDOMaXlvlR8VCFBSbl+mJHY9oM5S tUDlS4vsHRJ85X0iksXI7Yz4if4rrX4EwlhGxl92vlVwuMLg2jzSMqDWOC70gFxwtUAI kHEq7wWbo6HwsqfWYOV8kPvxEecm/MHOU8O9kycP66bIUsTmsVIe5O2tW55gyDwwoME5 uT7JBV8MSO6s1U/RffJze9fxajO+p0JXrWv+y81y3CUnNrJztQnknh7dWh/30HQOgLdM itpXcOyjMXqzIaZfNclzbjFWodpqmXkFv5K4VF19YjU6I05e0OqNKY9E/wjp7xw1Ufwd uRDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1717164752; x=1717769552; 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=iRKfuq7tJP6RRlShAcOHog3cbaDmp6QUmMlJFE3QvoU=; b=ciIdTvXmP5gaKB+hcJxXrlNiA7Agk7wM/sMj0jgdyUAQblJSCqgUIGXPezGHdZC+xo Bro9kd7jVqfb0yGfXD/UF6JuSLNUMBs+W7ztxMun4UHsepvhJHGvaiSNJAxsWepOmDIe c07nQhj7p5h+9e8GK3eCaSGo/bLydMDErCGrDgDJh711jZxq1P1Sz4+Zg58OgTl1aZiO luO3xlFpCM4k4/+JvUUZWOeY8iInr4BKd+9bS+1Z5W1/DGpbveUKlc8vNZ8cwWYd1y8L c7TCy3WE1f2iA5GX2Kk9icZzv2p6m1zWVDe0rNxln+jWul/SOJCfQhsAgpvd37mrPw+Y cthw== X-Forwarded-Encrypted: i=1; AJvYcCX5hmD9Zd6JLJ3xT2EbGtr+xmywW6p0BOXgjjzhSLzzY0Z+pWbnLtiBCHoUYdfucbnSOAJY4KumXmkP57VnhlrvWnUr8pN2bACTDO8FIwyhUyOEyCI= X-Gm-Message-State: AOJu0Ywww5qV5yxg8xw0/VyIrVAxWggHCRbnX2fFllDkOm9m/i85HSbb sf0yUMO2T6v1wRwXnVraZWhmpEewN51tLY4m7x/c1UbS4v39rFyl X-Google-Smtp-Source: AGHT+IEDId5bGd2rtdv1MJoyfA1gkmv24Qhvy26tVItCuDcbxaF94h7HM+TLn21fO32yRuaYw68WQw== X-Received: by 2002:a05:6a00:2d1c:b0:6f8:b8d7:1123 with SMTP id d2e1a72fcca58-7024569eae9mr3142578b3a.1.1717164751842; Fri, 31 May 2024 07:12:31 -0700 (PDT) Received: from noel.flets-west.jp ([2405:6586:4480:a10:167:9818:d778:5c14]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-70242b057besm1418103b3a.162.2024.05.31.07.12.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 31 May 2024 07:12:31 -0700 (PDT) From: Hironori KIKUCHI To: linux-kernel@vger.kernel.org Cc: Hironori KIKUCHI , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Aleksandr Shubin , Cheo Fusi , linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev Subject: [PATCH 4/5] pwm: sun20i: Delegating the clock source and DIV_M to the Device Tree Date: Fri, 31 May 2024 23:11:36 +0900 Message-ID: <20240531141152.327592-5-kikuchan98@gmail.com> X-Mailer: git-send-email 2.45.1 In-Reply-To: <20240531141152.327592-1-kikuchan98@gmail.com> References: <20240531141152.327592-1-kikuchan98@gmail.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240531_071234_088153_6AC7908C X-CRM114-Status: GOOD ( 26.83 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org This patch removes the SUN20I_PWM_MAGIC macro by delegating the clock source and DIV_M selection to the Device Tree. This change addresses the issue of resolution discrepancies that arise from the enabling order of PWM channels which are coupled. Additionally, this patch clarifies and corrects the calculations for the period and duty cycle. By using DIV_ROUND_CLOSEST(), it minimizes the errors between the configured and actual values. Signed-off-by: Hironori KIKUCHI --- drivers/pwm/pwm-sun20i.c | 190 ++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 111 deletions(-) diff --git a/drivers/pwm/pwm-sun20i.c b/drivers/pwm/pwm-sun20i.c index d07ce0ebd2a..4bf8a67df38 100644 --- a/drivers/pwm/pwm-sun20i.c +++ b/drivers/pwm/pwm-sun20i.c @@ -52,53 +52,13 @@ #define SUN20I_PWM_PCNTR_SIZE BIT(16) -/* - * SUN20I_PWM_MAGIC is used to quickly compute the values of the clock dividers - * div_m (SUN20I_PWM_CLK_CFG_DIV_M) & prescale_k (SUN20I_PWM_CTL_PRESCAL_K) - * without using a loop. These dividers limit the # of cycles in a period - * to SUN20I_PWM_PCNTR_SIZE by applying a scaling factor of - * 1/(div_m * (prescale_k + 1)) to the clock source. - * - * SUN20I_PWM_MAGIC is derived by solving for div_m and prescale_k - * such that for a given requested period, - * - * i) div_m is minimized for any prescale_k ≤ SUN20I_PWM_CTL_PRESCAL_K_MAX, - * ii) prescale_k is minimized. - * - * The derivation proceeds as follows, with val = # of cycles for requested - * period: - * - * for a given value of div_m we want the smallest prescale_k such that - * - * (val >> div_m) // (prescale_k + 1) ≤ 65536 (SUN20I_PWM_PCNTR_SIZE) - * - * This is equivalent to: - * - * (val >> div_m) ≤ 65536 * (prescale_k + 1) + prescale_k - * ⟺ (val >> div_m) ≤ 65537 * prescale_k + 65536 - * ⟺ (val >> div_m) - 65536 ≤ 65537 * prescale_k - * ⟺ ((val >> div_m) - 65536) / 65537 ≤ prescale_k - * - * As prescale_k is integer, this becomes - * - * ((val >> div_m) - 65536) // 65537 ≤ prescale_k - * - * And is minimized at - * - * ((val >> div_m) - 65536) // 65537 - * - * Now we pick the smallest div_m that satifies prescale_k ≤ 255 - * (i.e SUN20I_PWM_CTL_PRESCAL_K_MAX), - * - * ((val >> div_m) - 65536) // 65537 ≤ 255 - * ⟺ (val >> div_m) - 65536 ≤ 255 * 65537 + 65536 - * ⟺ val >> div_m ≤ 255 * 65537 + 2 * 65536 - * ⟺ val >> div_m < (255 * 65537 + 2 * 65536 + 1) - * ⟺ div_m = fls((val) / (255 * 65537 + 2 * 65536 + 1)) - * - * Suggested by Uwe Kleine-König - */ -#define SUN20I_PWM_MAGIC (255 * 65537 + 2 * 65536 + 1) +#define SUN20I_PWM_CLOCK_SRC_HOSC (0) +#define SUN20I_PWM_CLOCK_SRC_APB (1) +#define SUN20I_PWM_CLOCK_SRC_DEFAULT SUN20I_PWM_CLOCK_SRC_HOSC +#define SUN20I_PWM_DIV_M_SHIFT_DEFAULT (0) + +#define SUN20I_PWM_CHANNELS_MAX (16) +#define SUN20I_PWM_ENTIRE_CYCLE_MAX (0xffff) struct sun20i_pwm_data { unsigned long reg_per; @@ -115,6 +75,9 @@ struct sun20i_pwm_chip { /* Mutex to protect pwm apply state */ struct mutex mutex; const struct sun20i_pwm_data *data; + + u32 clk_src_reg[(SUN20I_PWM_CHANNELS_MAX + 1) / 2]; + u32 div_m_shift_reg[(SUN20I_PWM_CHANNELS_MAX + 1) / 2]; }; static inline struct sun20i_pwm_chip *to_sun20i_pwm_chip(struct pwm_chip *chip) @@ -139,7 +102,8 @@ static int sun20i_pwm_get_state(struct pwm_chip *chip, struct pwm_state *state) { struct sun20i_pwm_chip *sun20i_chip = to_sun20i_pwm_chip(chip); - u16 ent_cycle, act_cycle, prescale_k; + u32 ent_cycle, act_cycle; + u16 prescale_k; u64 clk_rate, tmp; u8 div_m; u32 val; @@ -170,7 +134,7 @@ static int sun20i_pwm_get_state(struct pwm_chip *chip, mutex_unlock(&sun20i_chip->mutex); act_cycle = FIELD_GET(SUN20I_PWM_PERIOD_ACT_CYCLE, val); - ent_cycle = FIELD_GET(SUN20I_PWM_PERIOD_ENTIRE_CYCLE, val); + ent_cycle = FIELD_GET(SUN20I_PWM_PERIOD_ENTIRE_CYCLE, val) + 1; /* * The duration of the active phase should not be longer @@ -196,9 +160,9 @@ static int sun20i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct sun20i_pwm_chip *sun20i_chip = to_sun20i_pwm_chip(chip); - u64 bus_rate, hosc_rate, val, ent_cycle, act_cycle; - u32 clk_gate, clk_cfg, pwm_en, ctl, reg_period; - u32 prescale_k, div_m; + u64 bus_rate, hosc_rate, ent_cycle, act_cycle; + u32 clk_gate, clk_cfg, pwm_en, ctl, reg_period, clk_rate; + u32 prescale_k, div_m, div_m_shift; bool use_bus_clk; int ret = 0; @@ -229,76 +193,49 @@ static int sun20i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (state->polarity != pwm->state.polarity || state->duty_cycle != pwm->state.duty_cycle || state->period != pwm->state.period) { - ctl = sun20i_pwm_readl(sun20i_chip, SUN20I_PWM_CTL(sun20i_chip, pwm->hwpwm)); - clk_cfg = sun20i_pwm_readl(sun20i_chip, SUN20I_PWM_CLK_CFG(pwm->hwpwm)); + int idx = pwm->hwpwm / 2; + hosc_rate = clk_get_rate(sun20i_chip->clk_hosc); bus_rate = clk_get_rate(sun20i_chip->clk_apb); - if (pwm_en & SUN20I_PWM_ENABLE_EN(pwm->hwpwm ^ 1)) { - /* if the neighbor channel is enable, check period only */ - use_bus_clk = FIELD_GET(SUN20I_PWM_CLK_CFG_SRC, clk_cfg) != 0; - val = mul_u64_u64_div_u64(state->period, - (use_bus_clk ? bus_rate : hosc_rate), - NSEC_PER_SEC); - div_m = FIELD_GET(SUN20I_PWM_CLK_CFG_DIV_M, clk_cfg); - } else { - /* check period and select clock source */ - use_bus_clk = false; - val = mul_u64_u64_div_u64(state->period, hosc_rate, NSEC_PER_SEC); - if (val <= 1) { - use_bus_clk = true; - val = mul_u64_u64_div_u64(state->period, bus_rate, NSEC_PER_SEC); - if (val <= 1) { - ret = -EINVAL; - goto unlock_mutex; - } - } - div_m = fls(DIV_ROUND_DOWN_ULL(val, SUN20I_PWM_MAGIC)); - if (div_m > SUN20I_PWM_CLK_DIV_M_MAX) { - ret = -EINVAL; - goto unlock_mutex; - } + use_bus_clk = sun20i_chip->clk_src_reg[idx] == SUN20I_PWM_CLOCK_SRC_APB; + clk_rate = use_bus_clk ? bus_rate : hosc_rate; + div_m_shift = sun20i_chip->div_m_shift_reg[idx]; + div_m = 1 << div_m_shift; - /* set up the CLK_DIV_M and clock CLK_SRC */ - clk_cfg &= ~(SUN20I_PWM_CLK_CFG_DIV_M | SUN20I_PWM_CLK_CFG_SRC); - clk_cfg |= FIELD_PREP(SUN20I_PWM_CLK_CFG_DIV_M, div_m); - clk_cfg |= FIELD_PREP(SUN20I_PWM_CLK_CFG_SRC, use_bus_clk); - - sun20i_pwm_writel(sun20i_chip, clk_cfg, SUN20I_PWM_CLK_CFG(pwm->hwpwm)); + if (state->period > U64_MAX / clk_rate || state->duty_cycle > state->period) { + ret = -EINVAL; + goto unlock_mutex; } + ent_cycle = DIV_ROUND_CLOSEST(state->period * clk_rate, NSEC_PER_SEC * div_m); + act_cycle = + min(DIV_ROUND_CLOSEST(state->duty_cycle * clk_rate, NSEC_PER_SEC * div_m), + ent_cycle); + if (ent_cycle == 0 || + ent_cycle > SUN20I_PWM_ENTIRE_CYCLE_MAX * SUN20I_PWM_CTL_PRESCAL_K_MAX) { + ret = -EINVAL; + goto unlock_mutex; + } + prescale_k = clamp(DIV_ROUND_UP_ULL(ent_cycle, SUN20I_PWM_ENTIRE_CYCLE_MAX), 1, + SUN20I_PWM_CTL_PRESCAL_K_MAX); + ent_cycle = clamp(DIV_ROUND_CLOSEST_ULL(ent_cycle, prescale_k), 1, + SUN20I_PWM_ENTIRE_CYCLE_MAX); + act_cycle = clamp(DIV_ROUND_CLOSEST_ULL(act_cycle, prescale_k), 0, ent_cycle); - /* calculate prescale_k, PWM entire cycle */ - ent_cycle = val >> div_m; - prescale_k = DIV_ROUND_DOWN_ULL(ent_cycle, 65537); - if (prescale_k > SUN20I_PWM_CTL_PRESCAL_K_MAX) - prescale_k = SUN20I_PWM_CTL_PRESCAL_K_MAX; + clk_cfg = sun20i_pwm_readl(sun20i_chip, SUN20I_PWM_CLK_CFG(pwm->hwpwm)); + clk_cfg &= ~(SUN20I_PWM_CLK_CFG_DIV_M | SUN20I_PWM_CLK_CFG_SRC); + clk_cfg |= FIELD_PREP(SUN20I_PWM_CLK_CFG_DIV_M, div_m_shift); + clk_cfg |= FIELD_PREP(SUN20I_PWM_CLK_CFG_SRC, use_bus_clk); + sun20i_pwm_writel(sun20i_chip, clk_cfg, SUN20I_PWM_CLK_CFG(pwm->hwpwm)); - do_div(ent_cycle, prescale_k + 1); - - /* for N cycles, PPRx.PWM_ENTIRE_CYCLE = (N-1) */ reg_period = FIELD_PREP(SUN20I_PWM_PERIOD_ENTIRE_CYCLE, ent_cycle - 1); - - /* set duty cycle */ - val = mul_u64_u64_div_u64(state->duty_cycle, - (use_bus_clk ? bus_rate : hosc_rate), - NSEC_PER_SEC); - act_cycle = val >> div_m; - do_div(act_cycle, prescale_k + 1); - - /* - * The formula of the output period and the duty-cycle for PWM are as follows. - * T period = (PWM01_CLK / PWM0_PRESCALE_K)^-1 * (PPR0.PWM_ENTIRE_CYCLE + 1) - * T high-level = (PWM01_CLK / PWM0_PRESCALE_K)^-1 * PPR0.PWM_ACT_CYCLE - * Duty-cycle = T high-level / T period - */ reg_period |= FIELD_PREP(SUN20I_PWM_PERIOD_ACT_CYCLE, act_cycle); sun20i_pwm_writel(sun20i_chip, reg_period, SUN20I_PWM_PERIOD(sun20i_chip, pwm->hwpwm)); - ctl = FIELD_PREP(SUN20I_PWM_CTL_PRESCAL_K, prescale_k); + ctl = FIELD_PREP(SUN20I_PWM_CTL_PRESCAL_K, prescale_k - 1); if (state->polarity == PWM_POLARITY_NORMAL) ctl |= SUN20I_PWM_CTL_ACT_STA; - sun20i_pwm_writel(sun20i_chip, ctl, SUN20I_PWM_CTL(sun20i_chip, pwm->hwpwm)); } @@ -382,9 +319,10 @@ static int sun20i_pwm_probe(struct platform_device *pdev) if (ret) npwm = 8; - if (npwm > 16) { - dev_info(&pdev->dev, "Limiting number of PWM lines from %u to 16", npwm); - npwm = 16; + if (npwm > SUN20I_PWM_CHANNELS_MAX) { + dev_info(&pdev->dev, "Limiting number of PWM lines from %u to %u", npwm, + SUN20I_PWM_CHANNELS_MAX); + npwm = SUN20I_PWM_CHANNELS_MAX; } chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*sun20i_chip)); @@ -420,6 +358,36 @@ static int sun20i_pwm_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(sun20i_chip->rst), "failed to get bus reset\n"); + for (int i = 0; i < (npwm + 1) / 2; i++) { + const char *source; + u32 div_m; + + sun20i_chip->clk_src_reg[i] = SUN20I_PWM_CLOCK_SRC_DEFAULT; + sun20i_chip->div_m_shift_reg[i] = SUN20I_PWM_DIV_M_SHIFT_DEFAULT; + + ret = of_property_read_string_index(pdev->dev.of_node, + "allwinner,pwm-pair-clock-sources", i, &source); + if (!ret) { + if (!strcasecmp(source, "hosc")) + sun20i_chip->clk_src_reg[i] = SUN20I_PWM_CLOCK_SRC_HOSC; + else if (!strcasecmp(source, "apb")) + sun20i_chip->clk_src_reg[i] = SUN20I_PWM_CLOCK_SRC_APB; + else + return dev_err_probe(&pdev->dev, -EINVAL, + "Unknown clock source: %s\n", source); + } + + ret = of_property_read_u32_index(pdev->dev.of_node, + "allwinner,pwm-pair-clock-prescales", i, &div_m); + if (!ret) { + if (div_m <= SUN20I_PWM_CLK_DIV_M_MAX) + sun20i_chip->div_m_shift_reg[i] = div_m; + else + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid prescale value: %u\n", div_m); + } + } + /* Deassert reset */ ret = reset_control_deassert(sun20i_chip->rst); if (ret)