mirror of
https://github.com/Fishwaldo/build.git
synced 2025-04-18 12:01:40 +00:00
* [Allwinner] Add analogue audio to H6, enable Cedrus, remove deprecated patches, adjust config * Cleanup
363 lines
9.9 KiB
Diff
363 lines
9.9 KiB
Diff
H6 PWM block is basically the same as A20 PWM, except that it also has
|
|
bus clock and reset line which needs to be handled accordingly.
|
|
|
|
Expand Allwinner PWM binding with H6 PWM specifics.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
---
|
|
.../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 36 ++++++++++++++++++-
|
|
1 file changed, 35 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
|
|
index 0ac52f83a58c..deca5d81802f 100644
|
|
--- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
|
|
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
|
|
@@ -30,13 +30,47 @@ properties:
|
|
- items:
|
|
- const: allwinner,sun50i-h5-pwm
|
|
- const: allwinner,sun5i-a13-pwm
|
|
+ - const: allwinner,sun50i-h6-pwm
|
|
|
|
reg:
|
|
maxItems: 1
|
|
|
|
- clocks:
|
|
+ # Even though it only applies to subschemas under the conditionals,
|
|
+ # not listing them here will trigger a warning because of the
|
|
+ # additionalsProperties set to false.
|
|
+ clocks: true
|
|
+ clock-names: true
|
|
+ resets:
|
|
maxItems: 1
|
|
|
|
+allOf:
|
|
+ - if:
|
|
+ properties:
|
|
+ compatible:
|
|
+ contains:
|
|
+ const: allwinner,sun50i-h6-pwm
|
|
+
|
|
+ then:
|
|
+ properties:
|
|
+ clocks:
|
|
+ items:
|
|
+ - description: Module Clock
|
|
+ - description: Bus Clock
|
|
+
|
|
+ clock-names:
|
|
+ items:
|
|
+ - const: pwm
|
|
+ - const: bus
|
|
+
|
|
+ required:
|
|
+ - clock-names
|
|
+ - resets
|
|
+
|
|
+ else:
|
|
+ properties:
|
|
+ clocks:
|
|
+ maxItems: 1
|
|
+
|
|
required:
|
|
- "#pwm-cells"
|
|
- compatible
|
|
|
|
|
|
H6 PWM core needs deasserted reset line in order to work.
|
|
|
|
Add a quirk for it.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
|
|
---
|
|
drivers/pwm/pwm-sun4i.c | 27 +++++++++++++++++++++++++--
|
|
1 file changed, 25 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
|
|
index de78c824bbfd..1b7be8fbde86 100644
|
|
--- a/drivers/pwm/pwm-sun4i.c
|
|
+++ b/drivers/pwm/pwm-sun4i.c
|
|
@@ -16,6 +16,7 @@
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pwm.h>
|
|
+#include <linux/reset.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/time.h>
|
|
@@ -72,12 +73,14 @@ static const u32 prescaler_table[] = {
|
|
|
|
struct sun4i_pwm_data {
|
|
bool has_prescaler_bypass;
|
|
+ bool has_reset;
|
|
unsigned int npwm;
|
|
};
|
|
|
|
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;
|
|
@@ -371,6 +374,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
|
if (IS_ERR(pwm->clk))
|
|
return PTR_ERR(pwm->clk);
|
|
|
|
+ if (pwm->data->has_reset) {
|
|
+ pwm->rst = devm_reset_control_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR(pwm->rst))
|
|
+ return PTR_ERR(pwm->rst);
|
|
+
|
|
+ reset_control_deassert(pwm->rst);
|
|
+ }
|
|
+
|
|
pwm->chip.dev = &pdev->dev;
|
|
pwm->chip.ops = &sun4i_pwm_ops;
|
|
pwm->chip.base = -1;
|
|
@@ -383,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;
|
|
|
|
- return pwmchip_remove(&pwm->chip);
|
|
+ reset_control_assert(pwm->rst);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static struct platform_driver sun4i_pwm_driver = {
|
|
|
|
H6 PWM core needs bus clock to be enabled in order to work.
|
|
|
|
Add a quirk for it.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
---
|
|
drivers/pwm/pwm-sun4i.c | 15 +++++++++++++++
|
|
1 file changed, 15 insertions(+)
|
|
|
|
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
|
|
index 1b7be8fbde86..7d3ac3f2dc3f 100644
|
|
--- a/drivers/pwm/pwm-sun4i.c
|
|
+++ b/drivers/pwm/pwm-sun4i.c
|
|
@@ -72,6 +72,7 @@ static const u32 prescaler_table[] = {
|
|
};
|
|
|
|
struct sun4i_pwm_data {
|
|
+ bool has_bus_clock;
|
|
bool has_prescaler_bypass;
|
|
bool has_reset;
|
|
unsigned int npwm;
|
|
@@ -79,6 +80,7 @@ struct sun4i_pwm_data {
|
|
|
|
struct sun4i_pwm_chip {
|
|
struct pwm_chip chip;
|
|
+ struct clk *bus_clk;
|
|
struct clk *clk;
|
|
struct reset_control *rst;
|
|
void __iomem *base;
|
|
@@ -382,6 +384,16 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
|
reset_control_deassert(pwm->rst);
|
|
}
|
|
|
|
+ if (pwm->data->has_bus_clock) {
|
|
+ pwm->bus_clk = devm_clk_get(&pdev->dev, "bus");
|
|
+ if (IS_ERR(pwm->bus_clk)) {
|
|
+ ret = PTR_ERR(pwm->bus_clk);
|
|
+ goto err_bus;
|
|
+ }
|
|
+
|
|
+ clk_prepare_enable(pwm->bus_clk);
|
|
+ }
|
|
+
|
|
pwm->chip.dev = &pdev->dev;
|
|
pwm->chip.ops = &sun4i_pwm_ops;
|
|
pwm->chip.base = -1;
|
|
@@ -402,6 +414,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
|
return 0;
|
|
|
|
err_pwm_add:
|
|
+ clk_disable_unprepare(pwm->bus_clk);
|
|
+err_bus:
|
|
reset_control_assert(pwm->rst);
|
|
|
|
return ret;
|
|
@@ -416,6 +430,7 @@ static int sun4i_pwm_remove(struct platform_device *pdev)
|
|
if (ret)
|
|
return ret;
|
|
|
|
+ clk_disable_unprepare(pwm->bus_clk);
|
|
reset_control_assert(pwm->rst);
|
|
|
|
return 0;
|
|
|
|
|
|
Note that while H6 PWM has two channels, only first one is wired to
|
|
output pin. Second channel is used as a clock source to companion AC200
|
|
chip which is bundled into same package.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
---
|
|
drivers/pwm/pwm-sun4i.c | 10 ++++++++++
|
|
1 file changed, 10 insertions(+)
|
|
|
|
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
|
|
index 7d3ac3f2dc3f..9e0eca79ff88 100644
|
|
--- a/drivers/pwm/pwm-sun4i.c
|
|
+++ b/drivers/pwm/pwm-sun4i.c
|
|
@@ -331,6 +331,13 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
|
|
.npwm = 1,
|
|
};
|
|
|
|
+static const struct sun4i_pwm_data sun50i_pwm_dual_bypass_clk_rst = {
|
|
+ .has_bus_clock = true,
|
|
+ .has_prescaler_bypass = true,
|
|
+ .has_reset = true,
|
|
+ .npwm = 2,
|
|
+};
|
|
+
|
|
static const struct of_device_id sun4i_pwm_dt_ids[] = {
|
|
{
|
|
.compatible = "allwinner,sun4i-a10-pwm",
|
|
@@ -347,6 +354,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = {
|
|
}, {
|
|
.compatible = "allwinner,sun8i-h3-pwm",
|
|
.data = &sun4i_pwm_single_bypass,
|
|
+ }, {
|
|
+ .compatible = "allwinner,sun50i-h6-pwm",
|
|
+ .data = &sun50i_pwm_dual_bypass_clk_rst,
|
|
}, {
|
|
/* sentinel */
|
|
},
|
|
|
|
|
|
This mode of operation is needed to achieve high enough frequency to
|
|
serve as clock source for AC200 chip, which is integrated into same
|
|
package as H6 SoC.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
---
|
|
drivers/pwm/pwm-sun4i.c | 31 ++++++++++++++++++++++++++++++-
|
|
1 file changed, 30 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
|
|
index 9e0eca79ff88..848cff26f385 100644
|
|
--- a/drivers/pwm/pwm-sun4i.c
|
|
+++ b/drivers/pwm/pwm-sun4i.c
|
|
@@ -120,6 +120,19 @@ 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)) {
|
|
+ state->period = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, clk_rate);
|
|
+ state->duty_cycle = 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;
|
|
@@ -211,7 +224,8 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
{
|
|
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
|
struct pwm_state cstate;
|
|
- u32 ctrl;
|
|
+ u32 ctrl, clk_rate;
|
|
+ bool bypass;
|
|
int ret;
|
|
unsigned int delay_us;
|
|
unsigned long now;
|
|
@@ -226,6 +240,16 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
}
|
|
}
|
|
|
|
+ /*
|
|
+ * Although it would make much more sense to check for bypass in
|
|
+ * sun4i_pwm_calculate(), value of bypass bit also depends on "enabled".
|
|
+ * Period is allowed to be rounded up or down.
|
|
+ */
|
|
+ clk_rate = clk_get_rate(sun4i_pwm->clk);
|
|
+ bypass = (state->period == NSEC_PER_SEC / clk_rate ||
|
|
+ state->period == DIV_ROUND_UP(NSEC_PER_SEC, clk_rate)) &&
|
|
+ state->enabled;
|
|
+
|
|
spin_lock(&sun4i_pwm->ctrl_lock);
|
|
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
|
|
|
@@ -273,6 +297,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
|
}
|
|
|
|
+ if (bypass)
|
|
+ ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
|
|
+ else
|
|
+ ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm);
|
|
+
|
|
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
|
|
|
|
spin_unlock(&sun4i_pwm->ctrl_lock);
|
|
|
|
Allwinner H6 PWM is similar to that in A20 except that it has additional
|
|
bus clock and reset line.
|
|
|
|
Note that first PWM channel is connected to output pin and second
|
|
channel is used internally, as a clock source to AC200 co-packaged chip.
|
|
This means that any combination of these two channels can be used and
|
|
thus it doesn't make sense to add pinctrl nodes at this point.
|
|
|
|
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
|
|
---
|
|
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 e8bed58e7246..c1abd805cfdc 100644
|
|
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
|
|
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
|
|
@@ -229,6 +229,16 @@
|
|
status = "disabled";
|
|
};
|
|
|
|
+ pwm: pwm@300a000 {
|
|
+ compatible = "allwinner,sun50i-h6-pwm";
|
|
+ reg = <0x0300a000 0x400>;
|
|
+ clocks = <&osc24M>, <&ccu CLK_BUS_PWM>;
|
|
+ clock-names = "pwm", "bus";
|
|
+ resets = <&ccu RST_BUS_PWM>;
|
|
+ #pwm-cells = <3>;
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
pio: pinctrl@300b000 {
|
|
compatible = "allwinner,sun50i-h6-pinctrl";
|
|
reg = <0x0300b000 0x400>;
|