kernel: bump 5.4 to 5.4.112

Ran update_kernel.sh in a fresh clone without any existing toolchains.

Removed upstreamed:
  mvebu/patches-5.4/319-ARM-dts-turris-omnia-configure-LED-2--INTn-pin-as-interrupt-pin.patch

Build system : x86_64
Build-tested : ipq806x/R7800
Run-tested   : ipq806x/R7800

No dmesg regressions, everything functional

Signed-off-by: John Audia <graysky@archlinux.us>
This commit is contained in:
John Audia 2021-04-14 07:25:24 -04:00 committed by Hauke Mehrtens
parent 33f419bf2a
commit 79fdd68103
10 changed files with 225 additions and 388 deletions

View File

@ -6,10 +6,10 @@ ifdef CONFIG_TESTING_KERNEL
KERNEL_PATCHVER:=$(KERNEL_TESTING_PATCHVER)
endif
LINUX_VERSION-5.4 = .111
LINUX_VERSION-5.4 = .112
LINUX_VERSION-5.10 = .31
LINUX_KERNEL_HASH-5.4.111 = 21626132658dc34cb41b7aa7b80ecf83751890a71ac1a63d77aea9d488271a03
LINUX_KERNEL_HASH-5.4.112 = b8361d461a4254c86d4c68aa4ceab1debaa3b2ccc6785542d026837a4fc20ca4
LINUX_KERNEL_HASH-5.10.31 = 54eef1a4d29a2582281375e028ac73c2c5d90dfa21500fa8c3b00e529a2b510d
remove_uri_prefix=$(subst git://,,$(subst http://,,$(subst https://,,$(1))))

View File

@ -330,7 +330,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -3629,6 +3629,8 @@ static __net_initdata struct pernet_oper
@@ -3623,6 +3623,8 @@ static __net_initdata struct pernet_oper
static int __init proto_init(void)
{

View File

@ -185,7 +185,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
cfg->fc_flags |= RTF_REJECT;
if (rtm->rtm_type == RTN_LOCAL)
@@ -6019,6 +6050,8 @@ static int ip6_route_dev_notify(struct n
@@ -6021,6 +6052,8 @@ static int ip6_route_dev_notify(struct n
#ifdef CONFIG_IPV6_MULTIPLE_TABLES
net->ipv6.ip6_prohibit_entry->dst.dev = dev;
net->ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(dev);
@ -194,7 +194,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
net->ipv6.ip6_blk_hole_entry->dst.dev = dev;
net->ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(dev);
#endif
@@ -6030,6 +6063,7 @@ static int ip6_route_dev_notify(struct n
@@ -6032,6 +6065,7 @@ static int ip6_route_dev_notify(struct n
in6_dev_put_clear(&net->ipv6.ip6_null_entry->rt6i_idev);
#ifdef CONFIG_IPV6_MULTIPLE_TABLES
in6_dev_put_clear(&net->ipv6.ip6_prohibit_entry->rt6i_idev);
@ -202,7 +202,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
in6_dev_put_clear(&net->ipv6.ip6_blk_hole_entry->rt6i_idev);
#endif
}
@@ -6222,6 +6256,8 @@ static int __net_init ip6_route_net_init
@@ -6224,6 +6258,8 @@ static int __net_init ip6_route_net_init
#ifdef CONFIG_IPV6_MULTIPLE_TABLES
net->ipv6.fib6_has_custom_rules = false;
@ -211,7 +211,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
net->ipv6.ip6_prohibit_entry = kmemdup(&ip6_prohibit_entry_template,
sizeof(*net->ipv6.ip6_prohibit_entry),
GFP_KERNEL);
@@ -6232,11 +6268,21 @@ static int __net_init ip6_route_net_init
@@ -6234,11 +6270,21 @@ static int __net_init ip6_route_net_init
ip6_template_metrics, true);
INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->rt6i_uncached);
@ -234,7 +234,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops;
dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst,
ip6_template_metrics, true);
@@ -6260,6 +6306,8 @@ out:
@@ -6262,6 +6308,8 @@ out:
return ret;
#ifdef CONFIG_IPV6_MULTIPLE_TABLES
@ -243,7 +243,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
out_ip6_prohibit_entry:
kfree(net->ipv6.ip6_prohibit_entry);
out_ip6_null_entry:
@@ -6279,6 +6327,7 @@ static void __net_exit ip6_route_net_exi
@@ -6281,6 +6329,7 @@ static void __net_exit ip6_route_net_exi
kfree(net->ipv6.ip6_null_entry);
#ifdef CONFIG_IPV6_MULTIPLE_TABLES
kfree(net->ipv6.ip6_prohibit_entry);
@ -251,7 +251,7 @@ Signed-off-by: Jonas Gorski <jogo@openwrt.org>
kfree(net->ipv6.ip6_blk_hole_entry);
#endif
dst_entries_destroy(&net->ipv6.ip6_dst_ops);
@@ -6356,6 +6405,9 @@ void __init ip6_route_init_special_entri
@@ -6358,6 +6407,9 @@ void __init ip6_route_init_special_entri
init_net.ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
init_net.ipv6.ip6_blk_hole_entry->dst.dev = init_net.loopback_dev;
init_net.ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);

View File

@ -51,7 +51,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
struct ethtool_eee *p = &priv->dev->ports[port].eee;
--- a/drivers/net/dsa/lantiq_gswip.c
+++ b/drivers/net/dsa/lantiq_gswip.c
@@ -1508,7 +1508,9 @@ static void gswip_phylink_mac_link_down(
@@ -1657,7 +1657,9 @@ static void gswip_phylink_mac_link_down(
static void gswip_phylink_mac_link_up(struct dsa_switch *ds, int port,
unsigned int mode,
phy_interface_t interface,

View File

@ -1,6 +1,6 @@
--- a/arch/arm64/boot/dts/mediatek/mt7622-bananapi-bpi-r64.dts 2021-03-13 13:46:37.057936814 +0200
+++ b/arch/arm64/boot/dts/mediatek/mt7622-bananapi-bpi-r64.dts 2021-03-13 13:49:26.957481217 +0200
@@ -114,7 +114,7 @@
--- a/arch/arm64/boot/dts/mediatek/mt7622-bananapi-bpi-r64.dts
+++ b/arch/arm64/boot/dts/mediatek/mt7622-bananapi-bpi-r64.dts
@@ -108,7 +108,7 @@
};
&bch {
@ -9,7 +9,7 @@
};
&btif {
@@ -259,14 +259,40 @@
@@ -283,14 +283,40 @@
status = "disabled";
};

View File

@ -1,64 +0,0 @@
From: "Marek Behún" <kabel@kernel.org>
To: Gregory CLEMENT <gregory.clement@bootlin.com>
Cc: "Marek Behún" <kabel@kernel.org>, Rui Salvaterra <rsalvaterra@gmail.com>, "Uwe Kleine-König" <uwe@kleine-koenig.org>, linux-arm-kernel@lists.infradead.org, Andrew Lunn <andrew@lunn.ch>, stable@vger.kernel.org
Subject: [PATCH mvebu-dt] ARM: dts: turris-omnia: configure LED[2]/INTn pin as interrupt pin
Date: Sun, 21 Feb 2021 00:11:44 +0100
Message-Id: <20210220231144.32325-1-kabel@kernel.org>
X-Mailer: git-send-email 2.26.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use the `marvell,reg-init` DT property to configure the LED[2]/INTn pin
of the Marvell 88E1514 ethernet PHY on Turris Omnia into interrupt mode.
Without this the pin is by default in LED[2] mode, and the Marvell PHY
driver configures LED[2] into "On - Link, Blink - Activity" mode.
This fixes the issue where the pca9538 GPIO/interrupt controller (which
can't mask interrupts in HW) received too many interrupts and after a
time started ignoring the interrupt with error message:
IRQ 71: nobody cared
There is a work in progress to have the Marvell PHY driver support
parsing PHY LED nodes from OF and registering the LEDs as Linux LED
class devices. Once this is done the PHY driver can also automatically
set the pin into INTn mode if it does not find LED[2] in OF.
Until then, though, we fix this via `marvell,reg-init` DT property.
Signed-off-by: Marek Behún <kabel@kernel.org>
Reported-by: Rui Salvaterra <rsalvaterra@gmail.com>
Fixes: 26ca8b52d6e1 ("ARM: dts: add support for Turris Omnia")
Cc: Uwe Kleine-König <uwe@kleine-koenig.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: Gregory CLEMENT <gregory.clement@bootlin.com>
Cc: <stable@vger.kernel.org>
---
This patch fixes bug introduced with the commit that added Turris
Omnia's DTS (26ca8b52d6e1), but will not apply cleanly because there is
commit 8ee4a5f4f40d which changed node name and node compatible
property and this commit did not go into stable.
So either commit 8ee4a5f4f40d has also to go into stable before this, or
this patch has to be fixed a little in order to apply to 4.14+.
Please let me know how should I handle this.
---
arch/arm/boot/dts/armada-385-turris-omnia.dts | 1 +
1 file changed, 1 insertion(+)
--- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
+++ b/arch/arm/boot/dts/armada-385-turris-omnia.dts
@@ -254,6 +254,7 @@
status = "okay";
compatible = "ethernet-phy-id0141.0DD1", "ethernet-phy-ieee802.3-c22";
reg = <1>;
+ marvell,reg-init = <3 18 0 0x4985>;
/* irq is connected to &pcawan pin 7 */
};

View File

@ -1,8 +1,17 @@
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 581d23287333..487899d4cc3f 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -16,6 +16,7 @@
@@ -3,6 +3,10 @@
* Driver for Allwinner sun4i Pulse Width Modulation Controller
*
* Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * Limitations:
+ * - When outputing the source clock directly, the PWM logic will be bypassed
+ * and the currently running period is not guaranteed to be completed
*/
#include <linux/bitops.h>
@@ -16,6 +20,7 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@ -10,182 +19,70 @@ index 581d23287333..487899d4cc3f 100644
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
@@ -78,6 +79,7 @@ struct sun4i_pwm_data {
struct sun4i_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
+ struct reset_control *rst;
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
@@ -364,6 +366,22 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
@@ -72,12 +77,15 @@ static const u32 prescaler_table[] = {
+ pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(pwm->rst)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get reset failed %pe\n",
+ pwm->rst);
+ return PTR_ERR(pwm->rst);
+ }
+
+ /* Deassert reset */
+ ret = reset_control_deassert(pwm->rst);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot deassert reset control: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -376,19 +394,31 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
- return ret;
+ goto err_pwm_add;
}
platform_set_drvdata(pdev, pwm);
return 0;
+
+err_pwm_add:
+ reset_control_assert(pwm->rst);
+
+ return ret;
}
static int sun4i_pwm_remove(struct platform_device *pdev)
{
struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwmchip_remove(&pwm->chip);
+ if (ret)
+ return ret;
+
+ reset_control_assert(pwm->rst);
- return pwmchip_remove(&pwm->chip);
+ return 0;
}
static struct platform_driver sun4i_pwm_driver = {
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 487899d4cc3f..80026167044b 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -362,9 +362,34 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->base))
return PTR_ERR(pwm->base);
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk))
+ /*
+ * All hardware variants need a source clock that is divided and
+ * then feeds the counter that defines the output wave form. In the
+ * device tree this clock is either unnamed or called "mod".
+ * Some variants (e.g. H6) need another clock to access the
+ * hardware registers; this is called "bus".
+ * So we request "mod" first (and ignore the corner case that a
+ * parent provides a "mod" clock while the right one would be the
+ * unnamed one of the PWM device) and if this is not found we fall
+ * back to the first clock of the PWM.
+ */
+ pwm->clk = devm_clk_get_optional(&pdev->dev, "mod");
+ if (IS_ERR(pwm->clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get mod clock failed %pe\n",
+ pwm->clk);
return PTR_ERR(pwm->clk);
+ }
+
+ if (!pwm->clk) {
+ pwm->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pwm->clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get unnamed clock failed %pe\n",
+ pwm->clk);
+ return PTR_ERR(pwm->clk);
+ }
+ }
pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
if (IS_ERR(pwm->rst)) {
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 80026167044b..a6727dd89e28 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -78,6 +78,7 @@ struct sun4i_pwm_data {
struct sun4i_pwm_data {
bool has_prescaler_bypass;
+ bool has_direct_mod_clk_output;
unsigned int npwm;
};
struct sun4i_pwm_chip {
struct pwm_chip chip;
+ struct clk *bus_clk;
struct clk *clk;
struct reset_control *rst;
+ struct reset_control *rst;
void __iomem *base;
@@ -391,6 +392,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
}
}
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
@@ -115,6 +123,20 @@ static void sun4i_pwm_get_state(struct p
+ pwm->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
+ if (IS_ERR(pwm->bus_clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get bus clock failed %pe\n",
+ pwm->bus_clk);
+ return PTR_ERR(pwm->bus_clk);
+ }
+
pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
if (IS_ERR(pwm->rst)) {
if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
@@ -407,6 +416,17 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
return ret;
}
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ /*
+ * We're keeping the bus clock on for the sake of simplicity.
+ * Actually it only needs to be on for hardware register accesses.
+ * PWM chapter in H6 manual has a diagram which explains that if bypass
+ * bit is set, no other setting has any meaning. Even more, experiment
+ * proved that also enable bit is ignored in this case.
+ */
+ ret = clk_prepare_enable(pwm->bus_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot prepare and enable bus_clk %pe\n",
+ ERR_PTR(ret));
+ goto err_bus;
+ if ((val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) &&
+ sun4i_pwm->data->has_direct_mod_clk_output) {
+ state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate);
+ state->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2);
+ state->polarity = PWM_POLARITY_NORMAL;
+ state->enabled = true;
+ return;
+ }
+
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
@@ -427,6 +447,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
return 0;
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
sun4i_pwm->data->has_prescaler_bypass)
prescaler = 1;
@@ -146,13 +168,24 @@ static void sun4i_pwm_get_state(struct p
err_pwm_add:
+ clk_disable_unprepare(pwm->bus_clk);
+err_bus:
reset_control_assert(pwm->rst);
static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
const struct pwm_state *state,
- u32 *dty, u32 *prd, unsigned int *prsclr)
+ u32 *dty, u32 *prd, unsigned int *prsclr,
+ bool *bypass)
{
u64 clk_rate, div = 0;
unsigned int pval, prescaler = 0;
return ret;
@@ -441,6 +463,7 @@ static int sun4i_pwm_remove(struct platform_device *pdev)
if (ret)
return ret;
clk_rate = clk_get_rate(sun4i_pwm->clk);
+ clk_disable_unprepare(pwm->bus_clk);
reset_control_assert(pwm->rst);
return 0;
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index a6727dd89e28..e369b5a398f4 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -202,9 +202,9 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ *bypass = sun4i_pwm->data->has_direct_mod_clk_output &&
+ state->enabled &&
+ (state->period * clk_rate >= NSEC_PER_SEC) &&
+ (state->period * clk_rate < 2 * NSEC_PER_SEC) &&
+ (state->duty_cycle * clk_rate * 2 >= NSEC_PER_SEC);
+
+ /* Skip calculation of other parameters if we bypass them */
+ if (*bypass)
+ return 0;
+
if (sun4i_pwm->data->has_prescaler_bypass) {
/* First, test without any prescaler when available */
prescaler = PWM_PRESCAL_MASK;
@@ -200,10 +233,11 @@ static int sun4i_pwm_apply(struct pwm_ch
{
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
struct pwm_state cstate;
@ -195,9 +92,11 @@ index a6727dd89e28..e369b5a398f4 100644
- unsigned int delay_us;
+ unsigned int delay_us, prescaler;
unsigned long now;
+ bool bypass;
pwm_get_state(pwm, &cstate);
@@ -220,43 +220,37 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -218,43 +252,50 @@ static int sun4i_pwm_apply(struct pwm_ch
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
@ -205,22 +104,8 @@ index a6727dd89e28..e369b5a398f4 100644
- (cstate.duty_cycle != state->duty_cycle)) {
- u32 period, duty, val;
- unsigned int prescaler;
-
- ret = sun4i_pwm_calculate(sun4i_pwm, state,
- &duty, &period, &prescaler);
- if (ret) {
- dev_err(chip->dev, "period exceeds the maximum value\n");
- spin_unlock(&sun4i_pwm->ctrl_lock);
- if (!cstate.enabled)
- clk_disable_unprepare(sun4i_pwm->clk);
- return ret;
- }
-
- if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) {
- /* Prescaler changed, the clock has to be gated */
- ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
- sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
+ ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler);
+ ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler,
+ &bypass);
+ if (ret) {
+ dev_err(chip->dev, "period exceeds the maximum value\n");
+ spin_unlock(&sun4i_pwm->ctrl_lock);
@ -229,6 +114,30 @@ index a6727dd89e28..e369b5a398f4 100644
+ return ret;
+ }
- ret = sun4i_pwm_calculate(sun4i_pwm, state,
- &duty, &period, &prescaler);
- if (ret) {
- dev_err(chip->dev, "period exceeds the maximum value\n");
+ if (sun4i_pwm->data->has_direct_mod_clk_output) {
+ if (bypass) {
+ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
+ /* We can skip other parameter */
+ sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
spin_unlock(&sun4i_pwm->ctrl_lock);
- if (!cstate.enabled)
- clk_disable_unprepare(sun4i_pwm->clk);
- return ret;
+ return 0;
+ } else {
+ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm);
}
+ }
- if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) {
- /* Prescaler changed, the clock has to be gated */
- ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
- sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
-
- ctrl &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm);
- ctrl |= BIT_CH(prescaler, pwm->hwpwm);
- }
@ -262,120 +171,7 @@ index a6727dd89e28..e369b5a398f4 100644
if (state->enabled) {
ctrl |= BIT_CH(PWM_EN, pwm->hwpwm);
} else if (!sun4i_pwm->needs_delay[pwm->hwpwm]) {
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index e369b5a398f4..07bf7be6074b 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -3,6 +3,10 @@
* Driver for Allwinner sun4i Pulse Width Modulation Controller
*
* Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * Limitations:
+ * - When outputing the source clock directly, the PWM logic will be bypassed
+ * and the currently running period is not guaranteed to be completed
*/
#include <linux/bitops.h>
@@ -73,6 +77,7 @@ static const u32 prescaler_table[] = {
struct sun4i_pwm_data {
bool has_prescaler_bypass;
+ bool has_direct_mod_clk_output;
unsigned int npwm;
};
@@ -118,6 +123,20 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ /*
+ * PWM chapter in H6 manual has a diagram which explains that if bypass
+ * bit is set, no other setting has any meaning. Even more, experiment
+ * proved that also enable bit is ignored in this case.
+ */
+ if ((val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) &&
+ sun4i_pwm->data->has_direct_mod_clk_output) {
+ state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate);
+ state->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2);
+ state->polarity = PWM_POLARITY_NORMAL;
+ state->enabled = true;
+ return;
+ }
+
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
sun4i_pwm->data->has_prescaler_bypass)
prescaler = 1;
@@ -149,13 +168,24 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
const struct pwm_state *state,
- u32 *dty, u32 *prd, unsigned int *prsclr)
+ u32 *dty, u32 *prd, unsigned int *prsclr,
+ bool *bypass)
{
u64 clk_rate, div = 0;
unsigned int pval, prescaler = 0;
clk_rate = clk_get_rate(sun4i_pwm->clk);
+ *bypass = sun4i_pwm->data->has_direct_mod_clk_output &&
+ state->enabled &&
+ (state->period * clk_rate >= NSEC_PER_SEC) &&
+ (state->period * clk_rate < 2 * NSEC_PER_SEC) &&
+ (state->duty_cycle * clk_rate * 2 >= NSEC_PER_SEC);
+
+ /* Skip calculation of other parameters if we bypass them */
+ if (*bypass)
+ return 0;
+
if (sun4i_pwm->data->has_prescaler_bypass) {
/* First, test without any prescaler when available */
prescaler = PWM_PRESCAL_MASK;
@@ -206,6 +236,7 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
int ret;
unsigned int delay_us, prescaler;
unsigned long now;
+ bool bypass;
pwm_get_state(pwm, &cstate);
@@ -220,7 +251,8 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
- ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler);
+ ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler,
+ &bypass);
if (ret) {
dev_err(chip->dev, "period exceeds the maximum value\n");
spin_unlock(&sun4i_pwm->ctrl_lock);
@@ -229,6 +261,18 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return ret;
}
+ if (sun4i_pwm->data->has_direct_mod_clk_output) {
+ if (bypass) {
+ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
+ /* We can skip other parameter */
+ sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
+ spin_unlock(&sun4i_pwm->ctrl_lock);
+ return 0;
+ } else {
+ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm);
+ }
+ }
+
if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) {
/* Prescaler changed, the clock has to be gated */
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 07bf7be6074b..c394878c7e5d 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -360,6 +360,12 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
@@ -320,6 +361,12 @@ static const struct sun4i_pwm_data sun4i
.npwm = 1,
};
@ -388,13 +184,122 @@ index 07bf7be6074b..c394878c7e5d 100644
static const struct of_device_id sun4i_pwm_dt_ids[] = {
{
.compatible = "allwinner,sun4i-a10-pwm",
@@ -376,6 +382,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = {
}, {
@@ -337,6 +384,9 @@ static const struct of_device_id sun4i_p
.compatible = "allwinner,sun8i-h3-pwm",
.data = &sun4i_pwm_single_bypass,
+ }, {
}, {
+ .compatible = "allwinner,sun50i-h6-pwm",
+ .data = &sun50i_h6_pwm_data,
}, {
+ }, {
/* sentinel */
},
};
@@ -361,9 +411,69 @@ static int sun4i_pwm_probe(struct platfo
if (IS_ERR(pwm->base))
return PTR_ERR(pwm->base);
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk))
+ /*
+ * All hardware variants need a source clock that is divided and
+ * then feeds the counter that defines the output wave form. In the
+ * device tree this clock is either unnamed or called "mod".
+ * Some variants (e.g. H6) need another clock to access the
+ * hardware registers; this is called "bus".
+ * So we request "mod" first (and ignore the corner case that a
+ * parent provides a "mod" clock while the right one would be the
+ * unnamed one of the PWM device) and if this is not found we fall
+ * back to the first clock of the PWM.
+ */
+ pwm->clk = devm_clk_get_optional(&pdev->dev, "mod");
+ if (IS_ERR(pwm->clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get mod clock failed %pe\n",
+ pwm->clk);
return PTR_ERR(pwm->clk);
+ }
+
+ if (!pwm->clk) {
+ pwm->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pwm->clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get unnamed clock failed %pe\n",
+ pwm->clk);
+ return PTR_ERR(pwm->clk);
+ }
+ }
+
+ pwm->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
+ if (IS_ERR(pwm->bus_clk)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get bus clock failed %pe\n",
+ pwm->bus_clk);
+ return PTR_ERR(pwm->bus_clk);
+ }
+
+ pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(pwm->rst)) {
+ if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "get reset failed %pe\n",
+ pwm->rst);
+ return PTR_ERR(pwm->rst);
+ }
+
+ /* Deassert reset */
+ ret = reset_control_deassert(pwm->rst);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot deassert reset control: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /*
+ * We're keeping the bus clock on for the sake of simplicity.
+ * Actually it only needs to be on for hardware register accesses.
+ */
+ ret = clk_prepare_enable(pwm->bus_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot prepare and enable bus_clk %pe\n",
+ ERR_PTR(ret));
+ goto err_bus;
+ }
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
@@ -377,19 +487,34 @@ static int sun4i_pwm_probe(struct platfo
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
- return ret;
+ goto err_pwm_add;
}
platform_set_drvdata(pdev, pwm);
return 0;
+
+err_pwm_add:
+ clk_disable_unprepare(pwm->bus_clk);
+err_bus:
+ reset_control_assert(pwm->rst);
+
+ return ret;
}
static int sun4i_pwm_remove(struct platform_device *pdev)
{
struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwmchip_remove(&pwm->chip);
+ if (ret)
+ return ret;
- return pwmchip_remove(&pwm->chip);
+ clk_disable_unprepare(pwm->bus_clk);
+ reset_control_assert(pwm->rst);
+
+ return 0;
}
static struct platform_driver sun4i_pwm_driver = {

View File

@ -14,11 +14,9 @@ Signed-off-by: Clément Péron <peron.clem@gmail.com>
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index 29824081b43b..6d4bde488f15 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -245,6 +245,16 @@
@@ -231,6 +231,16 @@
status = "disabled";
};

View File

@ -1,10 +1,8 @@
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts b/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
index f335f7482a73..cf684bc7374d 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
@@ -45,3 +45,7 @@
reg = <1>;
};
@@ -10,3 +10,7 @@
model = "OrangePi One Plus";
compatible = "xunlong,orangepi-one-plus", "allwinner,sun50i-h6";
};
+
+&pwm {

View File

@ -1,8 +1,6 @@
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts b/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
index 12e1756..79139f3 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-orangepi-one-plus.dts
@@ -9,4 +9,39 @@
@@ -9,6 +9,41 @@
/ {
model = "OrangePi One Plus";
compatible = "xunlong,orangepi-one-plus", "allwinner,sun50i-h6";
@ -42,3 +40,5 @@ index 12e1756..79139f3 100644
+ reg = <1>;
+ };
};
&pwm {