[PATCH v2 3/4] pwm: jz4740: Make PWM start with the active part
The PWM in Ingenic SoCs starts in inactive state until the internal timer reaches the duty value, then becomes active until the timer reaches the period value. In theory, we should then use (period - duty) as the real duty value, as a high duty value would otherwise result in the PWM pin being inactive most of the time. This is the reason why the duty value was inverted in the driver until now, but it still had the problem that it would not start with the active part. To address this remaining issue, the common trick is to invert the duty, and invert the polarity when the PWM is enabled. Since the duty was already inverted, and we invert it again, we now program the hardware for the requested duty, and simply invert the polarity when the PWM is enabled. Signed-off-by: Paul Cercueil --- Notes: v2: Add documentation about why we invert the polarity, and improve commit message drivers/pwm/pwm-jz4740.c | 25 - 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 4fe9d99ac9a9..fe06ca8ce30f 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -6,7 +6,6 @@ * Limitations: * - The .apply callback doesn't complete the currently running period before * reconfiguring the hardware. - * - Each period starts with the inactive part. */ #include @@ -163,7 +162,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* Calculate duty value */ tmp = (unsigned long long)rate * state->duty_cycle; do_div(tmp, NSEC_PER_SEC); - duty = period - tmp; + duty = tmp; if (duty >= period) duty = period - 1; @@ -189,18 +188,26 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); - /* Set polarity */ - switch (state->polarity) { - case PWM_POLARITY_NORMAL: + /* +* Set polarity. +* +* The PWM starts in inactive state until the internal timer reaches the +* duty value, then becomes active until the timer reaches the period +* value. In theory, we should then use (period - duty) as the real duty +* value, as a high duty value would otherwise result in the PWM pin +* being inactive most of the time. +* +* Here, we don't do that, and instead invert the polarity of the PWM +* when it is active. This trick makes the PWM start with its active +* state instead of its inactive state. +*/ + if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, 0); - break; - case PWM_POLARITY_INVERSED: + else regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, TCU_TCSR_PWM_INITL_HIGH); - break; - } if (state->enabled) jz4740_pwm_enable(chip, pwm); -- 2.26.2
Re: [PATCH 3/3] pwm: jz4740: Add support for the JZ4725B
Hi Uwe, Le dim. 24 mai 2020 à 19:37, Uwe Kleine-König a écrit : On Mon, Apr 13, 2020 at 02:14:45PM +0200, Paul Cercueil wrote: The PWM hardware in the JZ4725B works the same as in the JZ4740, but has only six channels available. Signed-off-by: Paul Cercueil --- Notes: I did not add documentation for the new jz4725b-pwm compatible string on purpose. The reason is that the documentation file for the Timer/Counter Unit (TCU) of Ingenic SoCs will be completely rewritten from .txt to YAML in a separate patchset. drivers/pwm/pwm-jz4740.c | 20 +--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index f566f9d248d6..bb27934fb6c2 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -22,6 +22,10 @@ #define NUM_PWM 8 +struct soc_info { + unsigned int num_pwms; +}; + struct jz4740_pwm_chip { struct pwm_chip chip; struct regmap *map; @@ -36,7 +40,7 @@ static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz, unsigned int channel) { /* Enable all TCU channels for PWM use by default except channels 0/1 */ - u32 pwm_channels_mask = GENMASK(NUM_PWM - 1, 2); + u32 pwm_channels_mask = GENMASK(jz->chip.npwm - 1, 2); device_property_read_u32(jz->chip.dev->parent, "ingenic,pwm-channels-mask", @@ -214,6 +218,7 @@ static int jz4740_pwm_probe(struct platform_device *pdev) { struct device *dev = >dev; struct jz4740_pwm_chip *jz4740; + const struct soc_info *info = device_get_match_data(dev); jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL); if (!jz4740) @@ -227,8 +232,8 @@ static int jz4740_pwm_probe(struct platform_device *pdev) jz4740->chip.dev = dev; jz4740->chip.ops = _pwm_ops; - jz4740->chip.npwm = NUM_PWM; jz4740->chip.base = -1; + jz4740->chip.npwm = info ? info->num_pwms : NUM_PWM; Can info be actually NULL? I don't think so, so you can just use info->num_pwms here and drop the definition of NUM_PWM. In *theory* it can be NULL if the kernel is configured without CONFIG_OF, which will never happen on any board supported by this driver. I can add a dependency on CONFIG_OF in V2, then use info->num_pwms there. cheers, -Paul jz4740->chip.of_xlate = of_pwm_xlate_with_flags; jz4740->chip.of_pwm_n_cells = 3; Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | https://www.pengutronix.de/ |
Re: [PATCH 1/3] pwm: jz4740: Drop dependency on MACH_INGENIC
Hi, Any feedback on this patchset? Cheers, -Paul Le lun. 13 avril 2020 à 14:14, Paul Cercueil a écrit : Depending on MACH_INGENIC prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Signed-off-by: Paul Cercueil --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index eebbc917ac97..7814e5b2cad7 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -234,7 +234,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" - depends on MACH_INGENIC + depends on MIPS depends on COMMON_CLK select MFD_SYSCON help -- 2.25.1
[PATCH] ASoC: ingenic: Unconditionally depend on devicetree
All boards with Ingenic SoCs probe with devicetree already, we have no use for a non-devicetree path. This solves some compilation warnings that were caused by unused variables in the case where CONFIG_OF was disabled. Signed-off-by: Paul Cercueil Reported-by: kbuild test robot --- sound/soc/codecs/Kconfig | 3 +++ sound/soc/codecs/jz4725b.c| 4 +--- sound/soc/codecs/jz4740.c | 4 +--- sound/soc/codecs/jz4770.c | 2 +- sound/soc/jz4740/Kconfig | 2 +- sound/soc/jz4740/jz4740-i2s.c | 4 +--- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e60e0b6a689c..3a0a6824e278 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -681,6 +681,7 @@ config SND_SOC_CX2072X config SND_SOC_JZ4740_CODEC depends on MIPS || COMPILE_TEST + depends on OF select REGMAP_MMIO tristate "Ingenic JZ4740 internal CODEC" help @@ -692,6 +693,7 @@ config SND_SOC_JZ4740_CODEC config SND_SOC_JZ4725B_CODEC depends on MIPS || COMPILE_TEST + depends on OF select REGMAP tristate "Ingenic JZ4725B internal CODEC" help @@ -703,6 +705,7 @@ config SND_SOC_JZ4725B_CODEC config SND_SOC_JZ4770_CODEC depends on MIPS || COMPILE_TEST + depends on OF select REGMAP tristate "Ingenic JZ4770 internal CODEC" help diff --git a/sound/soc/codecs/jz4725b.c b/sound/soc/codecs/jz4725b.c index 2567a5d15b55..e49374c72e70 100644 --- a/sound/soc/codecs/jz4725b.c +++ b/sound/soc/codecs/jz4725b.c @@ -574,19 +574,17 @@ static int jz4725b_codec_probe(struct platform_device *pdev) return ret; } -#ifdef CONFIG_OF static const struct of_device_id jz4725b_codec_of_matches[] = { { .compatible = "ingenic,jz4725b-codec", }, { } }; MODULE_DEVICE_TABLE(of, jz4725b_codec_of_matches); -#endif static struct platform_driver jz4725b_codec_driver = { .probe = jz4725b_codec_probe, .driver = { .name = "jz4725b-codec", - .of_match_table = of_match_ptr(jz4725b_codec_of_matches), + .of_match_table = jz4725b_codec_of_matches, }, }; module_platform_driver(jz4725b_codec_driver); diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c index 460aa1fd1efe..c9900d1cd5c2 100644 --- a/sound/soc/codecs/jz4740.c +++ b/sound/soc/codecs/jz4740.c @@ -344,19 +344,17 @@ static int jz4740_codec_probe(struct platform_device *pdev) return ret; } -#ifdef CONFIG_OF static const struct of_device_id jz4740_codec_of_matches[] = { { .compatible = "ingenic,jz4740-codec", }, { } }; MODULE_DEVICE_TABLE(of, jz4740_codec_of_matches); -#endif static struct platform_driver jz4740_codec_driver = { .probe = jz4740_codec_probe, .driver = { .name = "jz4740-codec", - .of_match_table = of_match_ptr(jz4740_codec_of_matches), + .of_match_table = jz4740_codec_of_matches, }, }; diff --git a/sound/soc/codecs/jz4770.c b/sound/soc/codecs/jz4770.c index e7cf2c107607..34775aa62402 100644 --- a/sound/soc/codecs/jz4770.c +++ b/sound/soc/codecs/jz4770.c @@ -937,7 +937,7 @@ static struct platform_driver jz4770_codec_driver = { .probe = jz4770_codec_probe, .driver = { .name = "jz4770-codec", - .of_match_table = of_match_ptr(jz4770_codec_of_matches), + .of_match_table = jz4770_codec_of_matches, }, }; module_platform_driver(jz4770_codec_driver); diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig index e72f826062e9..29144720cb62 100644 --- a/sound/soc/jz4740/Kconfig +++ b/sound/soc/jz4740/Kconfig @@ -2,7 +2,7 @@ config SND_JZ4740_SOC_I2S tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC" depends on MIPS || COMPILE_TEST - depends on HAS_IOMEM + depends on OF && HAS_IOMEM select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index 6f6f8dad0356..52460adf6ca1 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -504,7 +504,6 @@ static const struct snd_soc_component_driver jz4740_i2s_component = { .resume = jz4740_i2s_resume, }; -#ifdef CONFIG_OF static const struct of_device_id jz4740_of_matches[] = { { .compatible = "ingenic,jz4740-i2s", .data = _i2s_soc_info }, { .compatible = "ingenic,jz4760-i2s", .data = _i2s_soc_info }, @@ -513,7 +512,6 @@ static const struct of_device_id jz4740_of_matches[] = { { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, jz4740_of_matches); -#endif static int jz4
Re: [PATCH v7 3/5] remoteproc: Add support for runtime PM
Hi Suman, Le ven. 22 mai 2020 à 11:47, Suman Anna a écrit : Hi Paul, On 5/15/20 5:43 AM, Paul Cercueil wrote: Call pm_runtime_get_sync() before the firmware is loaded, and pm_runtime_put() after the remote processor has been stopped. Even though the remoteproc device has no PM callbacks, this allows the parent device's PM callbacks to be properly called. I see this patch staged now for 5.8, and the latest -next branch has broken the pm-runtime autosuspend feature we have in the OMAP remoteproc driver. See commit 5f31b232c674 ("remoteproc/omap: Add support for runtime auto-suspend/resume"). What was the original purpose of this patch, because there can be differing backends across different SoCs. Did you try pm_suspend_ignore_children()? It looks like it was made for your use-case. Cheers, -Paul regards Suman Signed-off-by: Paul Cercueil --- Notes: v2-v4: No change v5: Move calls to prepare/unprepare to rproc_fw_boot/rproc_shutdown v6: Instead of prepare/unprepare callbacks, use PM runtime callbacks v7: Check return value of pm_runtime_get_sync() drivers/remoteproc/remoteproc_core.c | 17 - 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index a7f96bc98406..e33d1ef27981 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1382,6 +1383,12 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) if (ret) return ret; +ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret); + return ret; + } + dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size); /* @@ -1391,7 +1398,7 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) ret = rproc_enable_iommu(rproc); if (ret) { dev_err(dev, "can't enable iommu: %d\n", ret); - return ret; + goto put_pm_runtime; } rproc->bootaddr = rproc_get_boot_addr(rproc, fw); @@ -1435,6 +1442,8 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) rproc->table_ptr = NULL; disable_iommu: rproc_disable_iommu(rproc); +put_pm_runtime: + pm_runtime_put(dev); return ret; } @@ -1840,6 +1849,8 @@ void rproc_shutdown(struct rproc *rproc) rproc_disable_iommu(rproc); +pm_runtime_put(dev); + /* Free the copy of the resource table */ kfree(rproc->cached_table); rproc->cached_table = NULL; @@ -2118,6 +2129,9 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, rproc->state = RPROC_OFFLINE; +pm_runtime_no_callbacks(>dev); + pm_runtime_enable(>dev); + return rproc; } EXPORT_SYMBOL(rproc_alloc); @@ -2133,6 +2147,7 @@ EXPORT_SYMBOL(rproc_alloc); */ void rproc_free(struct rproc *rproc) { + pm_runtime_disable(>dev); put_device(>dev); } EXPORT_SYMBOL(rproc_free);
Re: [PATCH] usb: musb: jz4740: Prevent lockup when CONFIG_SMP is set
Hi Bin, The patch it fixes was introduced in 5.7-rc1, is it possible to queue it for the next -rc? Otherwise I'll need to Cc it to linux-stable. -Paul Le jeu. 21 mai 2020 à 15:34, Bin Liu a écrit : On Wed, May 20, 2020 at 05:01:11PM +0200, Paul Cercueil wrote: The function dma_controller_irq() locks up the exact same spinlock we locked before calling it, which obviously resulted in a deadlock when CONFIG_SMP was enabled. This flew under the radar as none of the boards supported by this driver needs SMP. Fixes: 57aadb46bd63 ("usb: musb: jz4740: Add support for DMA") Signed-off-by: Paul Cercueil Queued for v5.8. Thanks. -Bin.
[PATCH] usb: musb: jz4740: Prevent lockup when CONFIG_SMP is set
The function dma_controller_irq() locks up the exact same spinlock we locked before calling it, which obviously resulted in a deadlock when CONFIG_SMP was enabled. This flew under the radar as none of the boards supported by this driver needs SMP. Fixes: 57aadb46bd63 ("usb: musb: jz4740: Add support for DMA") Signed-off-by: Paul Cercueil --- drivers/usb/musb/jz4740.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c index 54e7b30acc69..42c1e8bfc4be 100644 --- a/drivers/usb/musb/jz4740.c +++ b/drivers/usb/musb/jz4740.c @@ -69,11 +69,11 @@ static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; struct musb *musb = __hci; - spin_lock_irqsave(>lock, flags); - if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) retval_dma = dma_controller_irq(irq, musb->dma_controller); + spin_lock_irqsave(>lock, flags); + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); -- 2.26.2
Re: [PATCH v8 1/6] MIPS: JZ4780: Introduce SMP support.
Hi Zhou, Le mer. 20 mai 2020 à 15:23, Zhou Yanjie a écrit : Hi Paul, On 2020年05月20日 03:41, Paul Cercueil wrote: Hi Zhou, Le mar. 19 mai 2020 à 22:35, 周琰杰 (Zhou Yanjie) a écrit : Forward port smp support from kernel 3.18.3 of CI20_linux to upstream kernel 5.6. Tested-by: H. Nikolaus Schaller Tested-by: Paul Boddie Signed-off-by: 周琰杰 (Zhou Yanjie) Reviewed-by: Jiaxun Yang --- Notes: v1->v2: 1.Remove unnecessary "plat_irq_dispatch(void)" in irq-ingenic.c. 2.Add a timeout check for "jz4780_boot_secondary()" to avoid a dead loop. 3.Replace hard code in smp.c with macro. v2->v3: 1.Remove unnecessary "extern void (*r4k_blast_dcache)(void)" in smp.c. 2.Use "for_each_of_cpu_node" instead "for_each_compatible_node" in smp.c. 3.Use "of_cpu_node_to_id" instead "of_property_read_u32_index" in smp.c. 4.Move LCR related operations to jz4780-cgu.c. v3->v4: Rebase on top of kernel 5.6-rc1. v4->v5: 1.Splitting changes involving "jz4780-cgu.c" into separate commit. 2.Use "request_irq()" replace "setup_irq()". v5->v6: In order to have a kernel that works on multiple SoCs at the same time, use "IS_ENABLED()" replace "#ifdef". v6->v7: 1.SMP has be decoupled from the SoC version. 2.Add mailboxes 3 and 4 for XBurst. 3.Adjust code in "jz4780_smp_prepare_cpus()". 4."jz4780_smp_init()" has be marked "__init". v7->v8: No change. arch/mips/include/asm/mach-jz4740/smp.h | 87 +++ arch/mips/jz4740/Kconfig| 2 + arch/mips/jz4740/Makefile | 5 + arch/mips/jz4740/prom.c | 4 + arch/mips/jz4740/smp-entry.S| 57 +++ arch/mips/jz4740/smp.c | 258 arch/mips/kernel/idle.c | 35 - 7 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 arch/mips/include/asm/mach-jz4740/smp.h create mode 100644 arch/mips/jz4740/smp-entry.S create mode 100644 arch/mips/jz4740/smp.c diff --git a/arch/mips/include/asm/mach-jz4740/smp.h b/arch/mips/include/asm/mach-jz4740/smp.h new file mode 100644 index ..86f660f --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/smp.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013, Paul Burton + * JZ4780 SMP definitions + */ + +#ifndef __MIPS_ASM_MACH_JZ4740_SMP_H__ +#define __MIPS_ASM_MACH_JZ4740_SMP_H__ + +#define read_c0_corectrl()__read_32bit_c0_register($12, 2) +#define write_c0_corectrl(val) __write_32bit_c0_register($12, 2, val) + +#define read_c0_corestatus() __read_32bit_c0_register($12, 3) +#define write_c0_corestatus(val) __write_32bit_c0_register($12, 3, val) + +#define read_c0_reim()__read_32bit_c0_register($12, 4) +#define write_c0_reim(val) __write_32bit_c0_register($12, 4, val) + +#define read_c0_mailbox0()__read_32bit_c0_register($20, 0) +#define write_c0_mailbox0(val) __write_32bit_c0_register($20, 0, val) + +#define read_c0_mailbox1()__read_32bit_c0_register($20, 1) +#define write_c0_mailbox1(val) __write_32bit_c0_register($20, 1, val) + +#define read_c0_mailbox2()__read_32bit_c0_register($20, 2) +#define write_c0_mailbox2(val) __write_32bit_c0_register($20, 2, val) + +#define read_c0_mailbox3()__read_32bit_c0_register($20, 3) +#define write_c0_mailbox3(val) __write_32bit_c0_register($20, 3, val) + +#define smp_clr_pending(mask) do {\ +unsigned int stat;\ +stat = read_c0_corestatus();\ +stat &= ~((mask) & 0xff);\ +write_c0_corestatus(stat);\ +} while (0) + +/* + * Core Control register + */ +#define CORECTRL_SLEEP1M_SHIFT17 +#define CORECTRL_SLEEP1M(_ULCAST_(0x1) << CORECTRL_SLEEP1M_SHIFT) +#define CORECTRL_SLEEP0M_SHIFT16 +#define CORECTRL_SLEEP0M(_ULCAST_(0x1) << CORECTRL_SLEEP0M_SHIFT) +#define CORECTRL_RPC1_SHIFT9 +#define CORECTRL_RPC1(_ULCAST_(0x1) << CORECTRL_RPC1_SHIFT) +#define CORECTRL_RPC0_SHIFT8 +#define CORECTRL_RPC0(_ULCAST_(0x1) << CORECTRL_RPC0_SHIFT) +#define CORECTRL_SWRST1_SHIFT1 +#define CORECTRL_SWRST1(_ULCAST_(0x1) << CORECTRL_SWRST1_SHIFT) +#define CORECTRL_SWRST0_SHIFT0 +#define CORECTRL_SWRST0(_ULCAST_(0x1) << CORECTRL_SWRST0_SHIFT) + +/* + * Core Status register + */ +#define CORESTATUS_SLEEP1_SHIFT17 +#define CORESTATUS_SLEEP1(_ULCAST_(0x1) << CORESTATUS_SLEEP1_SHIFT) +#define CORESTATUS_SLEEP0_SHIFT16 +#define CORESTATUS_SLEEP0(_ULCAST_(0x1) << CORESTATUS_SLEEP0_SHIFT) +#define CORESTATUS_IRQ1P_SHIFT9 +#define CORESTATUS_IRQ1P(_ULCAST_(0x1) << CORESTATUS_I
[PATCH v2 1/3] dt-bindings: pinctrl: Convert ingenic,pinctrl.txt to YAML
Convert the ingenic,pinctrl.txt doc file to ingenic,pinctrl.yaml. In the process, some compatible strings now require a fallback, as the corresponding SoCs are pin-compatible with their fallback variant. Signed-off-by: Paul Cercueil --- Notes: v2: - Use 'pinctrl' instead of 'pin-controller' as the node name - remove 'additionalProperties: false' since we will have pin conf nodes .../bindings/pinctrl/ingenic,pinctrl.txt | 81 --- .../bindings/pinctrl/ingenic,pinctrl.yaml | 136 ++ 2 files changed, 136 insertions(+), 81 deletions(-) delete mode 100644 Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.txt create mode 100644 Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.yaml diff --git a/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.txt deleted file mode 100644 index d9b2100c98e8.. --- a/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.txt +++ /dev/null @@ -1,81 +0,0 @@ -Ingenic XBurst pin controller - -Please refer to pinctrl-bindings.txt in this directory for details of the -common pinctrl bindings used by client devices, including the meaning of the -phrase "pin configuration node". - -For the XBurst SoCs, pin control is tightly bound with GPIO ports. All pins may -be used as GPIOs, multiplexed device functions are configured within the -GPIO port configuration registers and it is typical to refer to pins using the -naming scheme "PxN" where x is a character identifying the GPIO port with -which the pin is associated and N is an integer from 0 to 31 identifying the -pin within that GPIO port. For example PA0 is the first pin in GPIO port A, and -PB31 is the last pin in GPIO port B. The jz4740, the x1000 and the x1830 -contains 4 GPIO ports, PA to PD, for a total of 128 pins. The jz4760, the -jz4770 and the jz4780 contains 6 GPIO ports, PA to PF, for a total of 192 pins. - - -Required properties: - - - - compatible: One of: -- "ingenic,jz4740-pinctrl" -- "ingenic,jz4725b-pinctrl" -- "ingenic,jz4760-pinctrl" -- "ingenic,jz4760b-pinctrl" -- "ingenic,jz4770-pinctrl" -- "ingenic,jz4780-pinctrl" -- "ingenic,x1000-pinctrl" -- "ingenic,x1000e-pinctrl" -- "ingenic,x1500-pinctrl" -- "ingenic,x1830-pinctrl" - - reg: Address range of the pinctrl registers. - - -Required properties for sub-nodes (GPIO chips): - - - compatible: Must contain one of: -- "ingenic,jz4740-gpio" -- "ingenic,jz4760-gpio" -- "ingenic,jz4770-gpio" -- "ingenic,jz4780-gpio" -- "ingenic,x1000-gpio" -- "ingenic,x1830-gpio" - - reg: The GPIO bank number. - - interrupt-controller: Marks the device node as an interrupt controller. - - interrupts: Interrupt specifier for the controllers interrupt. - - #interrupt-cells: Should be 2. Refer to - ../interrupt-controller/interrupts.txt for more details. - - gpio-controller: Marks the device node as a GPIO controller. - - #gpio-cells: Should be 2. The first cell is the GPIO number and the second -cell specifies GPIO flags, as defined in . Only the -GPIO_ACTIVE_HIGH and GPIO_ACTIVE_LOW flags are supported. - - gpio-ranges: Range of pins managed by the GPIO controller. Refer to - ../gpio/gpio.txt for more details. - - -Example: - - -pinctrl: pin-controller@1001 { - compatible = "ingenic,jz4740-pinctrl"; - reg = <0x1001 0x400>; - #address-cells = <1>; - #size-cells = <0>; - - gpa: gpio@0 { - compatible = "ingenic,jz4740-gpio"; - reg = <0>; - - gpio-controller; - gpio-ranges = < 0 0 32>; - #gpio-cells = <2>; - - interrupt-controller; - #interrupt-cells = <2>; - - interrupt-parent = <>; - interrupts = <28>; - }; -}; diff --git a/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.yaml new file mode 100644 index ..5be2b1e95b36 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/ingenic,pinctrl.yaml @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/ingenic,pinctrl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs pin controller devicetree bindings + +description: > + Please refer to pinctrl-bindings.txt in this directory for details of the + common pinctrl bindings used by client devices, including the meaning of the + phrase "pin configuration node&q
[PATCH v2 3/3] dt-bindings: mtd: Convert ingenic,jz4780-nand.txt to YAML
Convert the ingenic,jz4780-nand.txt doc file to ingenic,nand.yaml. Signed-off-by: Paul Cercueil --- Notes: v2: - Don't include ingenic,nemc-client.yaml which is gone - Use 'partitions' property instead of '^partitions$' pattern .../bindings/mtd/ingenic,jz4780-nand.txt | 92 .../devicetree/bindings/mtd/ingenic,nand.yaml | 132 ++ 2 files changed, 132 insertions(+), 92 deletions(-) delete mode 100644 Documentation/devicetree/bindings/mtd/ingenic,jz4780-nand.txt create mode 100644 Documentation/devicetree/bindings/mtd/ingenic,nand.yaml diff --git a/Documentation/devicetree/bindings/mtd/ingenic,jz4780-nand.txt b/Documentation/devicetree/bindings/mtd/ingenic,jz4780-nand.txt deleted file mode 100644 index c02259353327.. --- a/Documentation/devicetree/bindings/mtd/ingenic,jz4780-nand.txt +++ /dev/null @@ -1,92 +0,0 @@ -* Ingenic JZ4780 NAND/ECC - -This file documents the device tree bindings for NAND flash devices on the -JZ4780. NAND devices are connected to the NEMC controller (described in -memory-controllers/ingenic,jz4780-nemc.txt), and thus NAND device nodes must -be children of the NEMC node. - -Required NAND controller device properties: -- compatible: Should be one of: - * ingenic,jz4740-nand - * ingenic,jz4725b-nand - * ingenic,jz4780-nand -- reg: For each bank with a NAND chip attached, should specify a bank number, - an offset of 0 and a size of 0x100 (i.e. the whole NEMC bank). - -Optional NAND controller device properties: -- ecc-engine: To make use of the hardware ECC controller, this - property must contain a phandle for the ECC controller node. The required - properties for this node are described below. If this is not specified, - software ECC will be used instead. - -Optional children nodes: -- Individual NAND chips are children of the NAND controller node. - -Required children node properties: -- reg: An integer ranging from 1 to 6 representing the CS line to use. - -Optional children node properties: -- nand-ecc-step-size: ECC block size in bytes. -- nand-ecc-strength: ECC strength (max number of correctable bits). -- nand-ecc-mode: String, operation mode of the NAND ecc mode. "hw" by default -- nand-on-flash-bbt: boolean to enable on flash bbt option, if not present false -- rb-gpios: GPIO specifier for the busy pin. -- wp-gpios: GPIO specifier for the write protect pin. - -Optional child node of NAND chip nodes: -- partitions: see Documentation/devicetree/bindings/mtd/partition.txt - -Example: - -nemc: nemc@1341 { - ... - - nandc: nand-controller@1 { - compatible = "ingenic,jz4780-nand"; - reg = <1 0 0x100>; /* Bank 1 */ - - #address-cells = <1>; - #size-cells = <0>; - - ecc-engine = <>; - - nand@1 { - reg = <1>; - - nand-ecc-step-size = <1024>; - nand-ecc-strength = <24>; - nand-ecc-mode = "hw"; - nand-on-flash-bbt; - - rb-gpios = < 20 GPIO_ACTIVE_LOW>; - wp-gpios = < 22 GPIO_ACTIVE_LOW>; - - partitions { - #address-cells = <2>; - #size-cells = <2>; - ... - } - }; - }; -}; - -The ECC controller is a separate SoC component used for error correction on -NAND devices. The following is a description of the device properties for a -ECC controller. - -Required ECC properties: -- compatible: Should be one of: - * ingenic,jz4740-ecc - * ingenic,jz4725b-bch - * ingenic,jz4780-bch -- reg: Should specify the ECC controller registers location and length. -- clocks: Clock for the ECC controller. - -Example: - -bch: bch@134d { - compatible = "ingenic,jz4780-bch"; - reg = <0x134d 0x1>; - - clocks = < JZ4780_CLK_BCH>; -}; diff --git a/Documentation/devicetree/bindings/mtd/ingenic,nand.yaml b/Documentation/devicetree/bindings/mtd/ingenic,nand.yaml new file mode 100644 index ..8abb6d463cb6 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/ingenic,nand.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/ingenic,nand.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs NAND controller devicetree bindings + +maintainers: + - Paul Cercueil + +allOf: + - $ref: nand-controller.yaml# + +properties: + compatible: +enum: + - ingenic,jz4740-nand + - ingenic,jz4725b-nand + - ingenic,jz4780-nand + + reg: +items: + - description: Bank number, offset and size of first attached NAND chip + - description: Bank num
[PATCH v2 2/3] dt-bindings: memory: Convert ingenic,jz4780-nemc.txt to YAML
Convert the ingenic,jz4780-nemc.txt doc file to ingenic,nemc.yaml. The ingenic,jz4725b-nemc compatible string was added in the process, with a fallback to ingenic,jz4740-nemc. Signed-off-by: Paul Cercueil --- Notes: v2: - Inline content of ingenic,nemc-client.yaml inside ingenic,nemc.yaml - Add missing 'reg' property to sub-nodes and mark it as required - Use a more generic wildcard to match all sub-nodes. .../ingenic,jz4780-nemc.txt | 76 --- .../memory-controllers/ingenic,nemc.yaml | 126 ++ 2 files changed, 126 insertions(+), 76 deletions(-) delete mode 100644 Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt create mode 100644 Documentation/devicetree/bindings/memory-controllers/ingenic,nemc.yaml diff --git a/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt b/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt deleted file mode 100644 index 59b8dcc118ee.. --- a/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt +++ /dev/null @@ -1,76 +0,0 @@ -* Ingenic JZ4780 NAND/external memory controller (NEMC) - -This file documents the device tree bindings for the NEMC external memory -controller in Ingenic JZ4780 - -Required properties: -- compatible: Should be set to one of: -"ingenic,jz4740-nemc" (JZ4740) -"ingenic,jz4780-nemc" (JZ4780) -- reg: Should specify the NEMC controller registers location and length. -- clocks: Clock for the NEMC controller. -- #address-cells: Must be set to 2. -- #size-cells: Must be set to 1. -- ranges: A set of ranges for each bank describing the physical memory layout. - Each should specify the following 4 integer values: - - 0 - -Each child of the NEMC node describes a device connected to the NEMC. - -Required child node properties: -- reg: Should contain at least one register specifier, given in the following - format: - - - - Multiple registers can be specified across multiple banks. This is needed, - for example, for packaged NAND devices with multiple dies. Such devices - should be grouped into a single node. - -Optional child node properties: -- ingenic,nemc-bus-width: Specifies the bus width in bits. Defaults to 8 bits. -- ingenic,nemc-tAS: Address setup time in nanoseconds. -- ingenic,nemc-tAH: Address hold time in nanoseconds. -- ingenic,nemc-tBP: Burst pitch time in nanoseconds. -- ingenic,nemc-tAW: Access wait time in nanoseconds. -- ingenic,nemc-tSTRV: Static memory recovery time in nanoseconds. - -If a child node references multiple banks in its "reg" property, the same value -for all optional parameters will be configured for all banks. If any optional -parameters are omitted, they will be left unchanged from whatever they are -configured to when the NEMC device is probed (which may be the reset value as -given in the hardware reference manual, or a value configured by the boot -loader). - -Example (NEMC node with a NAND child device attached at CS1): - -nemc: nemc@1341 { - compatible = "ingenic,jz4780-nemc"; - reg = <0x1341 0x1>; - - #address-cells = <2>; - #size-cells = <1>; - - ranges = <1 0 0x1b00 0x100 - 2 0 0x1a00 0x100 - 3 0 0x1900 0x100 - 4 0 0x1800 0x100 - 5 0 0x1700 0x100 - 6 0 0x1600 0x100>; - - clocks = < JZ4780_CLK_NEMC>; - - nand: nand@1 { - compatible = "ingenic,jz4780-nand"; - reg = <1 0 0x100>; - - ingenic,nemc-tAS = <10>; - ingenic,nemc-tAH = <5>; - ingenic,nemc-tBP = <10>; - ingenic,nemc-tAW = <15>; - ingenic,nemc-tSTRV = <100>; - - ... - }; -}; diff --git a/Documentation/devicetree/bindings/memory-controllers/ingenic,nemc.yaml b/Documentation/devicetree/bindings/memory-controllers/ingenic,nemc.yaml new file mode 100644 index ..9b478da0c479 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/ingenic,nemc.yaml @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/memory-controllers/ingenic,nemc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs NAND / External Memory Controller (NEMC) devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + $nodename: +pattern: "^memory-controller@[0-9a-f]+$" + + compatible: +oneOf: + - enum: +- ingenic,jz4740-nemc +- ingenic,jz4780-nemc + - items: +- const: ingenic,jz4725b-nemc +- const: ingenic,jz4740-nemc + + "#address-cells": +const: 2 + + &
[PATCH] MIPS: ingenic: Add missing include
Add missing include which adds the prototype to plat_time_init(). Fixes: f932449c11da ("MIPS: ingenic: Drop obsolete code, merge the rest in setup.c") Signed-off-by: Paul Cercueil Reported-by: kbuild test robot --- arch/mips/jz4740/setup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/mips/jz4740/setup.c b/arch/mips/jz4740/setup.c index 81428ddcaa97..c627fbd90892 100644 --- a/arch/mips/jz4740/setup.c +++ b/arch/mips/jz4740/setup.c @@ -20,6 +20,7 @@ #include #include #include +#include #define JZ4740_EMC_BASE_ADDR 0x1301 -- 2.26.2
Re: [PATCH v7 7/7] input: joystick: Add ADC attached joystick driver.
Hi Andy, Le mar. 19 mai 2020 à 23:43, Andy Shevchenko a écrit : On Sun, May 17, 2020 at 10:49 PM Artur Rojek wrote: Add a driver for joystick devices connected to ADC controllers supporting the Industrial I/O subsystem. ... +static int adc_joystick_handle(const void *data, void *private) +{ + struct adc_joystick *joy = private; + enum iio_endian endianness; + int bytes, msb, val, i; + bool sign; + + bytes = joy->chans[0].channel->scan_type.storagebits >> 3; + + for (i = 0; i < joy->num_chans; ++i) { + endianness = joy->chans[i].channel->scan_type.endianness; + msb = joy->chans[i].channel->scan_type.realbits - 1; + sign = (tolower(joy->chans[i].channel->scan_type.sign) == 's'); Do we need tolower()? I'll answer this one: The sign can be uppercase to specify that the value is sign-extended in all the storage bits. -Paul + + switch (bytes) { + case 1: + val = ((const u8 *)data)[i]; + break; + case 2: + if (endianness == IIO_BE) + val = be16_to_cpu(((const u16 *)data)[i]); Yeah, you have to provide bitwise types to satisfy sparse. Maybe using *_to_cpup() will cure this. + else if (endianness == IIO_LE) + val = le16_to_cpu(((const u16 *)data)[i]); + else /* IIO_CPU */ + val = ((const u16 *)data)[i]; + break; + default: + return -EINVAL; + } + + val >>= joy->chans[i].channel->scan_type.shift; + if (sign) + val = sign_extend32(val, msb); + else + val &= GENMASK(msb, 0); + input_report_abs(joy->input, joy->axes[i].code, val); + } + + input_sync(joy->input); + + return 0; +} ... + /* Count how many channels we got. NULL terminated. */ + while (joy->chans[joy->num_chans].indio_dev) + joy->num_chans++; I don't see how useful this is. Why not simple do below... + bits = joy->chans[0].channel->scan_type.storagebits; + if (!bits || (bits > 16)) { + dev_err(dev, "Unsupported channel storage size"); + return -EINVAL; + } + for (i = 1; i < joy->num_chans; ++i) + if (joy->chans[i].channel->scan_type.storagebits != bits) { + dev_err(dev, "Channels must have equal storage size"); + return -EINVAL; + } ...something like for (i = 0; joy->chans[i].indio_dev; i++) { bits = joy->chans[i].channel->scan_type.storagebits; if (bits ...) { ...error handling... } if (bits != joy->chans[0].channel->scan_type.storagebits) { ...second level of error handling... } } ... +static const struct of_device_id adc_joystick_of_match[] = { + { .compatible = "adc-joystick", }, + { }, No need comma. +}; -- With Best Regards, Andy Shevchenko
[PATCH] clocksource: Ingenic: Add high resolution timer support for SMP.
From: 周琰杰 (Zhou Yanjie) Enable clock event handling on per CPU core basis. Make sure that interrupts raised on the first core execute event handlers on the correct CPU core. Tested-by: H. Nikolaus Schaller Tested-by: Paul Boddie Signed-off-by: 周琰杰 (Zhou Yanjie) Signed-off-by: Paul Cercueil --- Zhou: I took the liberty to clean your patch so that it doesn't create a struct ingenic_tcu per CPU timer. Tested, and fully working on the JZ4770 with CONFIG_SMP disabled, and also with CONFIG_SMP enabled (even though JZ4770 has only one CPU) with a fixed smp.c and USB disabled (otherwise it crashes at boot). Cheers, -Paul drivers/clocksource/ingenic-timer.c | 180 +++- 1 file changed, 123 insertions(+), 57 deletions(-) diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c index 496333650de2..a068e4620c44 100644 --- a/drivers/clocksource/ingenic-timer.c +++ b/drivers/clocksource/ingenic-timer.c @@ -2,6 +2,7 @@ /* * JZ47xx SoCs TCU IRQ driver * Copyright (C) 2019 Paul Cercueil + * Copyright (C) 2020 周琰杰 (Zhou Yanjie) */ #include @@ -15,24 +16,35 @@ #include #include #include +#include #include #include #include #include +static DEFINE_PER_CPU(call_single_data_t, ingenic_cevt_csd); + struct ingenic_soc_info { unsigned int num_channels; }; +struct ingenic_tcu_timer { + unsigned int cpu; + unsigned int channel; + struct clock_event_device cevt; + struct clk *clk; + char name[8]; +}; + struct ingenic_tcu { struct regmap *map; - struct clk *timer_clk, *cs_clk; - unsigned int timer_channel, cs_channel; - struct clock_event_device cevt; + struct device_node *np; + struct clk *cs_clk; + unsigned int cs_channel; struct clocksource cs; - char name[4]; unsigned long pwm_channels_mask; + struct ingenic_tcu_timer timers[]; }; static struct ingenic_tcu *ingenic_tcu; @@ -52,16 +64,24 @@ static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs) return ingenic_tcu_timer_read(); } -static inline struct ingenic_tcu *to_ingenic_tcu(struct clock_event_device *evt) +static inline struct ingenic_tcu * +to_ingenic_tcu(struct ingenic_tcu_timer *timer) +{ + return container_of(timer, struct ingenic_tcu, timers[timer->cpu]); +} + +static inline struct ingenic_tcu_timer * +to_ingenic_tcu_timer(struct clock_event_device *evt) { - return container_of(evt, struct ingenic_tcu, cevt); + return container_of(evt, struct ingenic_tcu_timer, cevt); } static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt) { - struct ingenic_tcu *tcu = to_ingenic_tcu(evt); + struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt); + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); - regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel)); + regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel)); return 0; } @@ -69,27 +89,40 @@ static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt) static int ingenic_tcu_cevt_set_next(unsigned long next, struct clock_event_device *evt) { - struct ingenic_tcu *tcu = to_ingenic_tcu(evt); + struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt); + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); if (next > 0x) return -EINVAL; - regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next); - regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0); - regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel)); + regmap_write(tcu->map, TCU_REG_TDFRc(timer->channel), next); + regmap_write(tcu->map, TCU_REG_TCNTc(timer->channel), 0); + regmap_write(tcu->map, TCU_REG_TESR, BIT(timer->channel)); return 0; } +static void ingenic_per_cpu_event_handler(void *info) +{ + struct clock_event_device *cevt = (struct clock_event_device *) info; + + cevt->event_handler(cevt); +} + static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id) { - struct clock_event_device *evt = dev_id; - struct ingenic_tcu *tcu = to_ingenic_tcu(evt); + struct ingenic_tcu_timer *timer = dev_id; + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); + call_single_data_t *csd; - regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel)); + regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel)); - if (evt->event_handler) - evt->event_handler(evt); + if (timer->cevt.event_handler) { + csd = _cpu(ingenic_cevt_csd, timer->cpu); + csd->info = (void *) >cevt; + csd->func = ingenic_per_cpu_event_handler; + smp_call_function_single_async
Re: [PATCH v8 1/6] MIPS: JZ4780: Introduce SMP support.
Hi Zhou, Le mar. 19 mai 2020 à 22:35, 周琰杰 (Zhou Yanjie) a écrit : Forward port smp support from kernel 3.18.3 of CI20_linux to upstream kernel 5.6. Tested-by: H. Nikolaus Schaller Tested-by: Paul Boddie Signed-off-by: 周琰杰 (Zhou Yanjie) Reviewed-by: Jiaxun Yang --- Notes: v1->v2: 1.Remove unnecessary "plat_irq_dispatch(void)" in irq-ingenic.c. 2.Add a timeout check for "jz4780_boot_secondary()" to avoid a dead loop. 3.Replace hard code in smp.c with macro. v2->v3: 1.Remove unnecessary "extern void (*r4k_blast_dcache)(void)" in smp.c. 2.Use "for_each_of_cpu_node" instead "for_each_compatible_node" in smp.c. 3.Use "of_cpu_node_to_id" instead "of_property_read_u32_index" in smp.c. 4.Move LCR related operations to jz4780-cgu.c. v3->v4: Rebase on top of kernel 5.6-rc1. v4->v5: 1.Splitting changes involving "jz4780-cgu.c" into separate commit. 2.Use "request_irq()" replace "setup_irq()". v5->v6: In order to have a kernel that works on multiple SoCs at the same time, use "IS_ENABLED()" replace "#ifdef". v6->v7: 1.SMP has be decoupled from the SoC version. 2.Add mailboxes 3 and 4 for XBurst. 3.Adjust code in "jz4780_smp_prepare_cpus()". 4."jz4780_smp_init()" has be marked "__init". v7->v8: No change. arch/mips/include/asm/mach-jz4740/smp.h | 87 +++ arch/mips/jz4740/Kconfig| 2 + arch/mips/jz4740/Makefile | 5 + arch/mips/jz4740/prom.c | 4 + arch/mips/jz4740/smp-entry.S| 57 +++ arch/mips/jz4740/smp.c | 258 arch/mips/kernel/idle.c | 35 - 7 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 arch/mips/include/asm/mach-jz4740/smp.h create mode 100644 arch/mips/jz4740/smp-entry.S create mode 100644 arch/mips/jz4740/smp.c diff --git a/arch/mips/include/asm/mach-jz4740/smp.h b/arch/mips/include/asm/mach-jz4740/smp.h new file mode 100644 index ..86f660f --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/smp.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013, Paul Burton + * JZ4780 SMP definitions + */ + +#ifndef __MIPS_ASM_MACH_JZ4740_SMP_H__ +#define __MIPS_ASM_MACH_JZ4740_SMP_H__ + +#define read_c0_corectrl() __read_32bit_c0_register($12, 2) +#define write_c0_corectrl(val) __write_32bit_c0_register($12, 2, val) + +#define read_c0_corestatus() __read_32bit_c0_register($12, 3) +#define write_c0_corestatus(val) __write_32bit_c0_register($12, 3, val) + +#define read_c0_reim() __read_32bit_c0_register($12, 4) +#define write_c0_reim(val) __write_32bit_c0_register($12, 4, val) + +#define read_c0_mailbox0() __read_32bit_c0_register($20, 0) +#define write_c0_mailbox0(val) __write_32bit_c0_register($20, 0, val) + +#define read_c0_mailbox1() __read_32bit_c0_register($20, 1) +#define write_c0_mailbox1(val) __write_32bit_c0_register($20, 1, val) + +#define read_c0_mailbox2() __read_32bit_c0_register($20, 2) +#define write_c0_mailbox2(val) __write_32bit_c0_register($20, 2, val) + +#define read_c0_mailbox3() __read_32bit_c0_register($20, 3) +#define write_c0_mailbox3(val) __write_32bit_c0_register($20, 3, val) + +#define smp_clr_pending(mask) do { \ + unsigned int stat; \ + stat = read_c0_corestatus();\ + stat &= ~((mask) & 0xff); \ + write_c0_corestatus(stat); \ + } while (0) + +/* + * Core Control register + */ +#define CORECTRL_SLEEP1M_SHIFT 17 +#define CORECTRL_SLEEP1M (_ULCAST_(0x1) << CORECTRL_SLEEP1M_SHIFT) +#define CORECTRL_SLEEP0M_SHIFT 16 +#define CORECTRL_SLEEP0M (_ULCAST_(0x1) << CORECTRL_SLEEP0M_SHIFT) +#define CORECTRL_RPC1_SHIFT9 +#define CORECTRL_RPC1 (_ULCAST_(0x1) << CORECTRL_RPC1_SHIFT) +#define CORECTRL_RPC0_SHIFT8 +#define CORECTRL_RPC0 (_ULCAST_(0x1) << CORECTRL_RPC0_SHIFT) +#define CORECTRL_SWRST1_SHIFT 1 +#define CORECTRL_SWRST1(_ULCAST_(0x1) << CORECTRL_SWRST1_SHIFT) +#define CORECTRL_SWRST0_SHIFT 0 +#define CORECTRL_SWRST0(_ULCAST_(0x1) << CORECTRL_SWRST0_SHIFT) + +/* + * Core Status register + */ +#define CORESTATUS_SLEEP1_SHIFT17 +#define CORESTATUS_SLEEP1 (_ULCAST_(0x1) << CORESTATUS_SLEEP1_SHIFT) +#define CORESTATUS_SLEEP0_SHIFT16 +#define CORESTATUS_SLEEP0 (_ULCAST_(0x1) << CORESTATUS_SLEEP0_SHIFT) +#define CORESTATUS_IRQ1P_SHIFT 9 +#define CORESTATUS_IRQ1P (_ULCAST_(0x1) << CORESTATUS_IRQ1P_SHIFT) +#define CORESTATUS_IRQ0P_SHIFT 8 +#define CORESTATUS_IRQ0P (_ULCAST_(0x1) << CORESTATUS_IRQ8P_SHIFT) +#define CORESTATUS_MIRQ1P_SHIFT1 +#define CORESTATUS_MIRQ1P (_ULCAST_(0x1) << CORESTATUS_MIRQ1P_SHIFT) +#define
Re: [PATCH v8 3/6] clocksource: Ingenic: Add high resolution timer support for SMP.
Hi Zhou, Le mar. 19 mai 2020 à 22:35, 周琰杰 (Zhou Yanjie) a écrit : Enable clock event handling on per CPU core basis. Make sure that interrupts raised on the first core execute event handlers on the correct CPU core. Tested-by: H. Nikolaus Schaller Tested-by: Paul Boddie Signed-off-by: 周琰杰 (Zhou Yanjie) --- Notes: v1->v2: 1.Adjust function naming to make it more reasonable. 2.Replace function smp_call_function_single() with smp_call_function_single_async() in order to resolve the warning below: [0.350942] smp: Brought up 1 node, 2 CPUs [0.365497] [ cut here ] [0.365522] WARNING: CPU: 0 PID: 1 at kernel/smp.c:300 smp_call_function_single+0x110/0x200 [0.365533] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.5.0-rc1+ #5 [0.365537] Stack : 59c73bcd 0037 80074e80 8000 8067 805a 80620590 [0.365557] 8065ce38 8fc0dc8c 806d 8067 0001 8fc0dc20 59c73bcd [0.365574] 806f 8067 806dab00 2d302e35 [0.365591] 203a6d6d 806e 806e 70617773 8067 0009 [0.365610] 8fc94e20 8fc0de30 8069 0018 803592dc 806d [0.365627] ... [0.365634] Call Trace: [0.365647] [<8001b9a0>] show_stack+0x6c/0x12c [0.365663] [<804aed20>] dump_stack+0x98/0xc8 [0.365673] [<8003044c>] __warn+0xc4/0xe8 [0.365682] [<800304f4>] warn_slowpath_fmt+0x84/0xb8 [0.365690] [<800a886c>] smp_call_function_single+0x110/0x200 [0.365703] ---[ end trace 5785856ca39c79d5 ]--- [0.365557] 8065ce38 8fc0dc8c 806d 8067 0001 8fc0dc20 59c73bcd [0.365574] 806f 8067 806dab00 2d302e35 [0.365591] 203a6d6d 806e 806e 70617773 8067 0009 [0.365610] 8fc94e20 8fc0de30 8069 0018 803592dc 806d [0.365627] ... [0.365634] Call Trace: [0.365647] [<8001b9a0>] show_stack+0x6c/0x12c [0.365663] [<804aed20>] dump_stack+0x98/0xc8 [0.365673] [<8003044c>] __warn+0xc4/0xe8 [0.365682] [<800304f4>] warn_slowpath_fmt+0x84/0xb8 [0.365690] [<800a886c>] smp_call_function_single+0x110/0x200 [0.365703] ---[ end trace 5785856ca39c79d5 ]--- v2->v3: No Change. v3->v4: Rebase on top of kernel 5.6-rc1. v4->v5: Move the check for (evt->event_handler) from "ingenic_per_cpu_event_handler" to "ingenic_tcu_cevt_cb". v5->v6: No change. v6->v7: Remove unnecessary check for "NR_CPUS > 1". v7->v8: Use "num_possible_cpus()" instead "NR_CPUS". Reported-by: kbuild test robot drivers/clocksource/ingenic-timer.c | 103 1 file changed, 82 insertions(+), 21 deletions(-) diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c index 4963336..230e996 100644 --- a/drivers/clocksource/ingenic-timer.c +++ b/drivers/clocksource/ingenic-timer.c @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* - * JZ47xx SoCs TCU IRQ driver + * XBurst SoCs TCU IRQ driver If you want to get rid of the JZ47xx, then just write 'Ingenic SoCs TCU IRQ driver', since XBurst is only the name of the CPU. Also, this belongs to a separate patch. * Copyright (C) 2019 Paul Cercueil + * Copyright (C) 2020 周琰杰 (Zhou Yanjie) */ #include @@ -21,18 +22,23 @@ #include +static DEFINE_PER_CPU(call_single_data_t, ingenic_cevt_csd); + struct ingenic_soc_info { unsigned int num_channels; }; struct ingenic_tcu { struct regmap *map; + struct device_node *np; struct clk *timer_clk, *cs_clk; + unsigned int timer_local[NR_CPUS]; NR_CPUS can be very big, so this would make the struct ingenic_tcu very large. What you could do, is have a variable array at the end of the struct: unsigned int timer_local[]; Then you can create the ingenic_tcu struct using struct_size() from : tcu = kzalloc(struct_size(tcu, timer_local, num_possible_cpus()), GFP_KERNEL); -Paul unsigned int timer_channel, cs_channel; struct clock_event_device cevt; struct clocksource cs; - char name[4]; + char name[8]; unsigned long pwm_channels_mask; + int cpu; }; static struct ingenic_tcu *ingenic_tcu; @@ -81,6 +87,24 @@ static int ingenic_tcu_cevt_set_next(unsigned long next, return 0; } +static void ingenic_per_cpu_event_handler(void *info) +{ + struct clock_event_device *cevt = (struct clock_event_device *) info; + + cevt->event_han
Re: [PATCH v8 1/6] MIPS: JZ4780: Introduce SMP support.
Hi Zhou, Le mar. 19 mai 2020 à 22:35, 周琰杰 (Zhou Yanjie) a écrit : Forward port smp support from kernel 3.18.3 of CI20_linux to upstream kernel 5.6. Tested-by: H. Nikolaus Schaller Tested-by: Paul Boddie Signed-off-by: 周琰杰 (Zhou Yanjie) Reviewed-by: Jiaxun Yang --- Notes: v1->v2: 1.Remove unnecessary "plat_irq_dispatch(void)" in irq-ingenic.c. 2.Add a timeout check for "jz4780_boot_secondary()" to avoid a dead loop. 3.Replace hard code in smp.c with macro. v2->v3: 1.Remove unnecessary "extern void (*r4k_blast_dcache)(void)" in smp.c. 2.Use "for_each_of_cpu_node" instead "for_each_compatible_node" in smp.c. 3.Use "of_cpu_node_to_id" instead "of_property_read_u32_index" in smp.c. 4.Move LCR related operations to jz4780-cgu.c. v3->v4: Rebase on top of kernel 5.6-rc1. v4->v5: 1.Splitting changes involving "jz4780-cgu.c" into separate commit. 2.Use "request_irq()" replace "setup_irq()". v5->v6: In order to have a kernel that works on multiple SoCs at the same time, use "IS_ENABLED()" replace "#ifdef". v6->v7: 1.SMP has be decoupled from the SoC version. 2.Add mailboxes 3 and 4 for XBurst. 3.Adjust code in "jz4780_smp_prepare_cpus()". 4."jz4780_smp_init()" has be marked "__init". v7->v8: No change. arch/mips/include/asm/mach-jz4740/smp.h | 87 +++ arch/mips/jz4740/Kconfig| 2 + arch/mips/jz4740/Makefile | 5 + arch/mips/jz4740/prom.c | 4 + arch/mips/jz4740/smp-entry.S| 57 +++ arch/mips/jz4740/smp.c | 258 arch/mips/kernel/idle.c | 35 - 7 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 arch/mips/include/asm/mach-jz4740/smp.h create mode 100644 arch/mips/jz4740/smp-entry.S create mode 100644 arch/mips/jz4740/smp.c diff --git a/arch/mips/include/asm/mach-jz4740/smp.h b/arch/mips/include/asm/mach-jz4740/smp.h new file mode 100644 index ..86f660f --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/smp.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013, Paul Burton + * JZ4780 SMP definitions + */ + +#ifndef __MIPS_ASM_MACH_JZ4740_SMP_H__ +#define __MIPS_ASM_MACH_JZ4740_SMP_H__ + +#define read_c0_corectrl() __read_32bit_c0_register($12, 2) +#define write_c0_corectrl(val) __write_32bit_c0_register($12, 2, val) + +#define read_c0_corestatus() __read_32bit_c0_register($12, 3) +#define write_c0_corestatus(val) __write_32bit_c0_register($12, 3, val) + +#define read_c0_reim() __read_32bit_c0_register($12, 4) +#define write_c0_reim(val) __write_32bit_c0_register($12, 4, val) + +#define read_c0_mailbox0() __read_32bit_c0_register($20, 0) +#define write_c0_mailbox0(val) __write_32bit_c0_register($20, 0, val) + +#define read_c0_mailbox1() __read_32bit_c0_register($20, 1) +#define write_c0_mailbox1(val) __write_32bit_c0_register($20, 1, val) + +#define read_c0_mailbox2() __read_32bit_c0_register($20, 2) +#define write_c0_mailbox2(val) __write_32bit_c0_register($20, 2, val) + +#define read_c0_mailbox3() __read_32bit_c0_register($20, 3) +#define write_c0_mailbox3(val) __write_32bit_c0_register($20, 3, val) + +#define smp_clr_pending(mask) do { \ + unsigned int stat; \ + stat = read_c0_corestatus();\ + stat &= ~((mask) & 0xff); \ + write_c0_corestatus(stat); \ + } while (0) + +/* + * Core Control register + */ +#define CORECTRL_SLEEP1M_SHIFT 17 +#define CORECTRL_SLEEP1M (_ULCAST_(0x1) << CORECTRL_SLEEP1M_SHIFT) +#define CORECTRL_SLEEP0M_SHIFT 16 +#define CORECTRL_SLEEP0M (_ULCAST_(0x1) << CORECTRL_SLEEP0M_SHIFT) +#define CORECTRL_RPC1_SHIFT9 +#define CORECTRL_RPC1 (_ULCAST_(0x1) << CORECTRL_RPC1_SHIFT) +#define CORECTRL_RPC0_SHIFT8 +#define CORECTRL_RPC0 (_ULCAST_(0x1) << CORECTRL_RPC0_SHIFT) +#define CORECTRL_SWRST1_SHIFT 1 +#define CORECTRL_SWRST1(_ULCAST_(0x1) << CORECTRL_SWRST1_SHIFT) +#define CORECTRL_SWRST0_SHIFT 0 +#define CORECTRL_SWRST0(_ULCAST_(0x1) << CORECTRL_SWRST0_SHIFT) + +/* + * Core Status register + */ +#define CORESTATUS_SLEEP1_SHIFT17 +#define CORESTATUS_SLEEP1 (_ULCAST_(0x1) << CORESTATUS_SLEEP1_SHIFT) +#define CORESTATUS_SLEEP0_SHIFT16 +#define CORESTATUS_SLEEP0 (_ULCAST_(0x1) << CORESTATUS_SLEEP0_SHIFT) +#define CORESTATUS_IRQ1P_SHIFT 9 +#define CORESTATUS_IRQ1P (_ULCAST_(0x1) << CORESTATUS_IRQ1P_SHIFT) +#define CORESTATUS_IRQ0P_SHIFT 8 +#define CORESTATUS_IRQ0P (_ULCAST_(0x1) << CORESTATUS_IRQ8P_SHIFT) +#define CORESTATUS_MIRQ1P_SHIFT1 +#define CORESTATUS_MIRQ1P (_ULCAST_(0x1) << CORESTATUS_MIRQ1P_SHIFT) +#define
Re: [PATCH 11/12] gpu/drm: Ingenic: Add support for the IPU
Hi Emil, Le lun. 18 mai 2020 à 11:48, Emil Velikov a écrit : Hi Paul, Disclaimer: I don't know much about the hardware :-P On Sun, 17 May 2020 at 00:31, Paul Cercueil wrote: Add support for the Image Processing Unit (IPU) found in all Ingenic SoCs. Since the IPU is present on all devices supported, does it make sense to have it as separate module? Didn't check closely although I suspect doing that will remove the need for the component patch. It makes sense to me; you may not want to enable the IPU on platforms where it doesn't add much, e.g. on the JZ4725B where it only handles YUV and not RGB. Besides, while the older JZ4740 SoC has a IPU, it's mem-to-mem and cannot output directly to the CRTC, so it cannot be represented as a plane. On this SoC the current IPU code cannot be used. So the Ingenic DRM driver must still be able to probe with zero components. --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -50,7 +50,7 @@ struct jz_soc_info { struct ingenic_drm { struct drm_device drm; - struct drm_plane f0, f1; + struct drm_plane f0, f1, *ipu_plane; struct drm_crtc crtc; struct drm_encoder encoder; @@ -186,13 +186,16 @@ static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv, regmap_update_bits(priv->map, JZ_REG_LCD_CTRL, JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16, JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16); + + regmap_write(priv->map, JZ_REG_LCD_IPUR, JZ_LCD_IPUR_IPUREN | +(ht * vpe / 3) << JZ_LCD_IPUR_IPUR_LSB); This hunk also indicates that it may be better to merge the IPU within the existing driver. This writes the IPUR register of the CRTC, nothing wrong here. -Paul
Re: [PATCH 05/12] gpu/drm: Ingenic: Fix opaque pointer casted to wrong type
Hi Sam, Le dim. 17 mai 2020 à 8:21, Sam Ravnborg a écrit : On Sat, May 16, 2020 at 11:50:50PM +0200, Paul Cercueil wrote: The opaque pointer passed to the IRQ handler is a pointer to the drm_device, not a pointer to our ingenic_drm structure. It still worked, because our ingenic_drm structure contains the drm_device as its first field, so the pointer received had the same value, but this was not semantically correct. Cc: sta...@vger.kernel.org # v5.3 Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs") Signed-off-by: Paul Cercueil Acked-by: Sam Ravnborg Pushed to drm-misc-fixes, thanks for the review. -Paul --- drivers/gpu/drm/ingenic/ingenic-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 0c472382a08b..97244462599b 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -476,7 +476,7 @@ static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder, static irqreturn_t ingenic_drm_irq_handler(int irq, void *arg) { - struct ingenic_drm *priv = arg; + struct ingenic_drm *priv = drm_device_get_priv(arg); unsigned int state; regmap_read(priv->map, JZ_REG_LCD_STATE, ); -- 2.26.2 ___ dri-devel mailing list dri-de...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Re: [PATCH 04/12] gpu/drm: ingenic: Fix bogus crtc_atomic_check callback
Hi Sam, Le dim. 17 mai 2020 à 8:17, Sam Ravnborg a écrit : On Sat, May 16, 2020 at 11:50:49PM +0200, Paul Cercueil wrote: The code was comparing the SoC's maximum height with the mode's width, and vice-versa. D'oh. Cc: sta...@vger.kernel.org # v5.6 Fixes: a7c909b7c037 ("gpu/drm: ingenic: Check for display size in CRTC atomic check") Signed-off-by: Paul Cercueil Looks correct. Acked-by: Sam Ravnborg Pushed to drm-misc-fixes, thanks for the review. -Paul --- Notes: This patch was previously sent standalone. I marked it as superseded in patchwork. Nothing has been changed here. drivers/gpu/drm/ingenic/ingenic-drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 632d72177123..0c472382a08b 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -330,8 +330,8 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, if (!drm_atomic_crtc_needs_modeset(state)) return 0; - if (state->mode.hdisplay > priv->soc_info->max_height || - state->mode.vdisplay > priv->soc_info->max_width) + if (state->mode.hdisplay > priv->soc_info->max_width || + state->mode.vdisplay > priv->soc_info->max_height) return -EINVAL; rate = clk_round_rate(priv->pix_clk, -- 2.26.2 ___ dri-devel mailing list dri-de...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Re: [PATCH 02/12] dt-bindings: display: Add ingenic,ipu.yaml
Hi Sam, Le dim. 17 mai 2020 à 8:17, Sam Ravnborg a écrit : Hi Paul. On Sat, May 16, 2020 at 11:50:47PM +0200, Paul Cercueil wrote: Add documentation of the Device Tree bindings for the Image Processing Unit (IPU) found in most Ingenic SoCs. Signed-off-by: Paul Cercueil For me it fails like this: Oops. I missed the 'const:' in the item list. Will fix when I send a V2, and verify it this time. Cheers, -Paul /Documentation/devicetree/bindings/display/ingenic,ipu.yaml: ignoring, error in schema: properties: compatible: oneOf: 1: items warning: no schema found in file: Documentation/devicetree/bindings/display/ingenic,ipu.yaml make[2]: *** [Documentation/devicetree/bindings/Makefile:42: Documentation/devicetree/bindings/processed-schema.yaml] Error 255 make[2]: *** Waiting for unfinished jobs Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items: ['ingenic,jz4770-ipu', 'ingenic,jz4760-ipu'] is not valid under any of the given schemas (Possible causes of the failure): Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items: ['ingenic,jz4770-ipu', 'ingenic,jz4760-ipu'] is not of type 'object', 'boolean' Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items:0: 'ingenic,jz4770-ipu' is not of type 'object', 'boolean' Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items:1: 'ingenic,jz4760-ipu' is not of type 'object', 'boolean' Sam --- .../bindings/display/ingenic,ipu.yaml | 65 +++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/ingenic,ipu.yaml diff --git a/Documentation/devicetree/bindings/display/ingenic,ipu.yaml b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml new file mode 100644 index ..22fe02ca866d --- /dev/null +++ b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/ingenic,ipu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs Image Processing Unit (IPU) devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + compatible: +oneOf: + - enum: +- ingenic,jz4725b-ipu +- ingenic,jz4760-ipu + - items: +- ingenic,jz4770-ipu +- ingenic,jz4760-ipu + + reg: +maxItems: 1 + + interrupts: +maxItems: 1 + + clocks: +maxItems: 1 + + clock-names: +const: ipu + +patternProperties: + "^ports?$": +description: OF graph bindings (specified in bindings/graph.txt). + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + - | +#include +ipu@1308 { + compatible = "ingenic,jz4770-ipu", "ingenic,jz4760-ipu"; + reg = <0x1308 0x800>; + + interrupt-parent = <>; + interrupts = <29>; + + clocks = < JZ4770_CLK_IPU>; + clock-names = "ipu"; + + port { +ipu_ep: endpoint { + remote-endpoint = <_ep>; +}; + }; +}; -- 2.26.2 ___ dri-devel mailing list dri-de...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
[PATCH 11/12] gpu/drm: Ingenic: Add support for the IPU
Add support for the Image Processing Unit (IPU) found in all Ingenic SoCs. The IPU can upscale and downscale a source frame of arbitrary size ranging from 4x4 to 4096x4096 on newer SoCs, with bicubic filtering on newer SoCs, bilinear filtering on older SoCs. Nearest-neighbour can also be obtained with proper coefficients. Starting from the JZ4725B, the IPU supports a mode where its output is sent directly to the CRTC, without having to be written to RAM first. This makes it possible to use the IPU as a DRM plane on the compatible SoCs, and have it convert and scale anything the userspace asks for to what's available for the display. Regarding pixel formats, older SoCs support packed YUV 4:2:2 and various planar YUV formats. Newer SoCs also support RGB. The CRTC can toggle between the primary plane and the IPU, but cannot display both at the same time. The IPU plane is then a primary plane itself. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/Kconfig | 11 + drivers/gpu/drm/ingenic/Makefile | 1 + drivers/gpu/drm/ingenic/ingenic-drm.c | 67 +- drivers/gpu/drm/ingenic/ingenic-drm.h | 9 + drivers/gpu/drm/ingenic/ingenic-ipu.c | 861 ++ drivers/gpu/drm/ingenic/ingenic-ipu.h | 110 6 files changed, 1048 insertions(+), 11 deletions(-) create mode 100644 drivers/gpu/drm/ingenic/ingenic-ipu.c create mode 100644 drivers/gpu/drm/ingenic/ingenic-ipu.h diff --git a/drivers/gpu/drm/ingenic/Kconfig b/drivers/gpu/drm/ingenic/Kconfig index d82c3d37ec9c..80e10527d404 100644 --- a/drivers/gpu/drm/ingenic/Kconfig +++ b/drivers/gpu/drm/ingenic/Kconfig @@ -14,3 +14,14 @@ config DRM_INGENIC Choose this option for DRM support for the Ingenic SoCs. If M is selected the module will be called ingenic-drm. + +if DRM_INGENIC + +config DRM_INGENIC_IPU + tristate "IPU support for Ingenic SoCs" + help + Choose this option to enable support for the IPU found in Ingenic SoCs. + + If M is selected the module will be called ingenic-ipu. + +endif diff --git a/drivers/gpu/drm/ingenic/Makefile b/drivers/gpu/drm/ingenic/Makefile index 11cac42ce0bb..df156194fbe5 100644 --- a/drivers/gpu/drm/ingenic/Makefile +++ b/drivers/gpu/drm/ingenic/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_DRM_INGENIC) += ingenic-drm.o +obj-$(CONFIG_DRM_INGENIC_IPU) += ingenic-ipu.o diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index b53db3ee001f..ad1cec4f2a4d 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -50,7 +50,7 @@ struct jz_soc_info { struct ingenic_drm { struct drm_device drm; - struct drm_plane f0, f1; + struct drm_plane f0, f1, *ipu_plane; struct drm_crtc crtc; struct drm_encoder encoder; @@ -186,13 +186,16 @@ static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv, regmap_update_bits(priv->map, JZ_REG_LCD_CTRL, JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16, JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16); + + regmap_write(priv->map, JZ_REG_LCD_IPUR, JZ_LCD_IPUR_IPUREN | +(ht * vpe / 3) << JZ_LCD_IPUR_IPUR_LSB); } static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { struct ingenic_drm *priv = drm_crtc_get_priv(crtc); - struct drm_plane_state *f1_state, *f0_state; + struct drm_plane_state *f1_state, *f0_state, *ipu_state; long rate; if (!drm_atomic_crtc_needs_modeset(state)) @@ -213,14 +216,41 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, f0_state = drm_atomic_get_plane_state(state->state, >f0); + if (priv->ipu_plane) + ipu_state = drm_atomic_get_plane_state(state->state, + priv->ipu_plane); + + /* IPU and F1 planes cannot be enabled at the same time. */ + if (priv->ipu_plane && f1_state->fb && ipu_state->fb) { + dev_dbg(priv->dev, "Cannot enable both F1 and IPU\n"); + return -EINVAL; + } + /* If all the planes are disabled, we won't get a VBLANK IRQ */ - if (!f1_state->fb && !f0_state->fb) + if (!f1_state->fb && !f0_state->fb && + !(priv->ipu_plane && ipu_state->fb)) state->no_vblank = true; } return 0; } +static void ingenic_drm_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *oldstate) +{ + struct ingenic_drm *priv = drm_crtc_get_
[PATCH 12/12] gpu/drm: Ingenic: Support multiple panels/bridges
Support multiple panels or bridges connected to the same DPI output of the SoC. This setup can be found for instance on the GCW Zero, where the same DPI output interfaces the internal 320x240 TFT panel, and the ITE IT6610 HDMI chip. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 74 --- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index ad1cec4f2a4d..091e3d3c9027 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -52,7 +52,6 @@ struct ingenic_drm { struct drm_device drm; struct drm_plane f0, f1, *ipu_plane; struct drm_crtc crtc; - struct drm_encoder encoder; struct device *dev; struct regmap *map; @@ -106,12 +105,6 @@ static inline struct ingenic_drm *drm_crtc_get_priv(struct drm_crtc *crtc) return container_of(crtc, struct ingenic_drm, crtc); } -static inline struct ingenic_drm * -drm_encoder_get_priv(struct drm_encoder *encoder) -{ - return container_of(encoder, struct ingenic_drm, encoder); -} - static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *state) { @@ -456,7 +449,7 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct ingenic_drm *priv = drm_encoder_get_priv(encoder); + struct ingenic_drm *priv = drm_device_get_priv(encoder->dev); struct drm_display_mode *mode = _state->adjusted_mode; struct drm_connector *conn = conn_state->connector; struct drm_display_info *info = >display_info; @@ -670,9 +663,11 @@ static int ingenic_drm_bind(struct device *dev) struct clk *parent_clk; struct drm_bridge *bridge; struct drm_panel *panel; + struct drm_encoder *encoder; struct drm_device *drm; void __iomem *base; long parent_rate; + unsigned int i, clone_mask = 0; int ret, irq; soc_info = of_device_get_match_data(dev); @@ -744,17 +739,6 @@ static int ingenic_drm_bind(struct device *dev) return PTR_ERR(priv->pix_clk); } - ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, , ); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "Failed to get panel handle"); - return ret; - } - - if (panel) - bridge = devm_drm_panel_bridge_add_typed(dev, panel, - DRM_MODE_CONNECTOR_DPI); - priv->dma_hwdesc[0] = dma_alloc_coherent(dev, sizeof(*priv->dma_hwdesc[0]), >dma_hwdesc_phys[0], @@ -821,22 +805,50 @@ static int ingenic_drm_bind(struct device *dev) } } - priv->encoder.possible_crtcs = 1; + for (i = 0; ; i++) { + ret = drm_of_find_panel_or_bridge(dev->of_node, 0, i, + , ); + if (ret) { + if (ret == -ENODEV) + break; /* we're done */ + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get bridge handle\n"); + return ret; + } - drm_encoder_helper_add(>encoder, - _drm_encoder_helper_funcs); + if (panel) + bridge = devm_drm_panel_bridge_add_typed(dev, panel, + DRM_MODE_CONNECTOR_DPI); - ret = drm_simple_encoder_init(drm, >encoder, - DRM_MODE_ENCODER_DPI); - if (ret) { - dev_err(dev, "Failed to init encoder: %i", ret); - return ret; + encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = 1; + + drm_encoder_helper_add(encoder, + _drm_encoder_helper_funcs); + + ret = drm_simple_encoder_init(drm, encoder, + DRM_MODE_ENCODER_DPI); + if (ret) { + dev_err(dev, "Failed to init encoder: %d\n", ret); + return ret; + } + + ret = drm_bridge_attach(encoder, bridge, NULL, 0); + if (ret) { +
[PATCH 10/12] gpu/drm: Ingenic: Register driver as component master
Register the ingenic-drm driver as a component master. This will later allow to plug optional components to the driver, for instance to add support for the IPU, or the SLCD module. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 61 +-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index f139af5b7a40..b53db3ee001f 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -6,6 +6,7 @@ #include "ingenic-drm.h" +#include #include #include #include @@ -613,10 +614,17 @@ static void ingenic_drm_free_dma_hwdesc(void *d) priv->dma_hwdesc[1], priv->dma_hwdesc_phys[1]); } -static int ingenic_drm_probe(struct platform_device *pdev) +static void ingenic_drm_unbind_all(void *d) { + struct ingenic_drm *priv = d; + + component_unbind_all(priv->dev, >drm); +} + +static int ingenic_drm_bind(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); const struct jz_soc_info *soc_info; - struct device *dev = >dev; struct ingenic_drm *priv; struct clk *parent_clk; struct drm_bridge *bridge; @@ -653,6 +661,17 @@ static int ingenic_drm_probe(struct platform_device *pdev) drm->mode_config.max_height = 4095; drm->mode_config.funcs = _drm_mode_config_funcs; + ret = component_bind_all(dev, drm); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to bind components: %i", ret); + return ret; + } + + ret = devm_add_action_or_reset(dev, ingenic_drm_unbind_all, priv); + if (ret) + return ret; + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) { dev_err(dev, "Failed to get memory resource"); @@ -843,9 +862,14 @@ static int ingenic_drm_probe(struct platform_device *pdev) return ret; } -static int ingenic_drm_remove(struct platform_device *pdev) +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static void ingenic_drm_unbind(struct device *dev) { - struct ingenic_drm *priv = platform_get_drvdata(pdev); + struct ingenic_drm *priv = dev_get_drvdata(dev); if (priv->lcd_clk) clk_disable_unprepare(priv->lcd_clk); @@ -853,6 +877,35 @@ static int ingenic_drm_remove(struct platform_device *pdev) drm_dev_unregister(>drm); drm_atomic_helper_shutdown(>drm); +} + +static const struct component_master_ops ingenic_master_ops = { + .bind = ingenic_drm_bind, + .unbind = ingenic_drm_unbind, +}; + +static int ingenic_drm_probe(struct platform_device *pdev) +{ + struct device *dev = >dev; + struct component_match *match = NULL; + struct device_node *np; + unsigned int i; + + /* Probe components at port address 8 and upwards */ + for (i = 8; ; i++) { + np = of_graph_get_remote_node(dev->of_node, i, 0); + if (!np) + break; + + drm_of_component_match_add(dev, , compare_of, np); + } + + return component_master_add_with_match(dev, _master_ops, match); +} + +static int ingenic_drm_remove(struct platform_device *pdev) +{ + component_master_del(>dev, _master_ops); return 0; } -- 2.26.2
[PATCH 09/12] gpu/drm: Ingenic: Add support for OSD mode
All Ingenic SoCs starting from the JZ4725B support OSD mode. In this mode, two separate planes can be used. They can have different positions and sizes, and one can be overlayed on top of the other. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 275 +- drivers/gpu/drm/ingenic/ingenic-drm.h | 35 2 files changed, 258 insertions(+), 52 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 92c63d1303f9..f139af5b7a40 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -43,12 +43,13 @@ struct ingenic_dma_hwdesc { struct jz_soc_info { bool needs_dev_clk; + bool has_osd; unsigned int max_width, max_height; }; struct ingenic_drm { struct drm_device drm; - struct drm_plane primary; + struct drm_plane f0, f1; struct drm_crtc crtc; struct drm_encoder encoder; @@ -57,8 +58,8 @@ struct ingenic_drm { struct clk *lcd_clk, *pix_clk; const struct jz_soc_info *soc_info; - struct ingenic_dma_hwdesc *dma_hwdesc; - dma_addr_t dma_hwdesc_phys; + struct ingenic_dma_hwdesc *dma_hwdesc[2]; + dma_addr_t dma_hwdesc_phys[2]; bool panel_is_sharp; }; @@ -90,7 +91,7 @@ static const struct regmap_config ingenic_drm_regmap_config = { .val_bits = 32, .reg_stride = 4, - .max_register = JZ_REG_LCD_CMD1, + .max_register = JZ_REG_LCD_SIZE1, .writeable_reg = ingenic_drm_writeable_reg, }; @@ -110,11 +111,6 @@ drm_encoder_get_priv(struct drm_encoder *encoder) return container_of(encoder, struct ingenic_drm, encoder); } -static inline struct ingenic_drm *drm_plane_get_priv(struct drm_plane *plane) -{ - return container_of(plane, struct ingenic_drm, primary); -} - static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *state) { @@ -185,34 +181,17 @@ static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv, regmap_write(priv->map, JZ_REG_LCD_SPL, hpe << 16 | (hpe + 1)); regmap_write(priv->map, JZ_REG_LCD_REV, mode->htotal << 16); } -} - -static void ingenic_drm_crtc_update_ctrl(struct ingenic_drm *priv, -const struct drm_format_info *finfo) -{ - unsigned int ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16; - - switch (finfo->format) { - case DRM_FORMAT_XRGB1555: - ctrl |= JZ_LCD_CTRL_RGB555; - /* fall-through */ - case DRM_FORMAT_RGB565: - ctrl |= JZ_LCD_CTRL_BPP_15_16; - break; - case DRM_FORMAT_XRGB: - ctrl |= JZ_LCD_CTRL_BPP_18_24; - break; - } regmap_update_bits(priv->map, JZ_REG_LCD_CTRL, - JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16 | - JZ_LCD_CTRL_BPP_MASK, ctrl); + JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16, + JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16); } static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { struct ingenic_drm *priv = drm_crtc_get_priv(crtc); + struct drm_plane_state *f1_state, *f0_state; long rate; if (!drm_atomic_crtc_needs_modeset(state)) @@ -227,6 +206,17 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, if (rate < 0) return rate; + if (priv->soc_info->has_osd) { + f1_state = drm_atomic_get_plane_state(state->state, + >f1); + f0_state = drm_atomic_get_plane_state(state->state, + >f0); + + /* If all the planes are disabled, we won't get a VBLANK IRQ */ + if (!f1_state->fb && !f0_state->fb) + state->no_vblank = true; + } + return 0; } @@ -236,14 +226,9 @@ static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc, struct ingenic_drm *priv = drm_crtc_get_priv(crtc); struct drm_crtc_state *state = crtc->state; struct drm_pending_vblank_event *event = state->event; - struct drm_framebuffer *drm_fb = crtc->primary->state->fb; - const struct drm_format_info *finfo; if (drm_atomic_crtc_needs_modeset(state)) { - finfo = drm_format_info(drm_fb->format->format); - ingenic_drm_crtc_update_timings(priv, >mode); - ingenic_drm_crtc_update_ctrl(priv, finfo); clk_set_rate(priv->pix_clk, state->adjusted_mode.clock * 1000);
[PATCH 07/12] gpu/drm: ingenic: Set DMA descriptor chain address in probe
The address of the DMA descriptor never changes. It can therefore be set in the probe function. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 3207105755c9..e43318938e9e 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -358,8 +358,6 @@ static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc, ingenic_drm_crtc_update_ctrl(priv, finfo); clk_set_rate(priv->pix_clk, state->adjusted_mode.clock * 1000); - - regmap_write(priv->map, JZ_REG_LCD_DA0, priv->dma_hwdesc->next); } if (event) { @@ -768,6 +766,9 @@ static int ingenic_drm_probe(struct platform_device *pdev) } } + /* Set address of our DMA descriptor chain */ + regmap_write(priv->map, JZ_REG_LCD_DA0, priv->dma_hwdesc_phys); + ret = drm_dev_register(drm, 0); if (ret) { dev_err(dev, "Failed to register DRM driver"); -- 2.26.2
[PATCH 08/12] gpu/drm: Ingenic: Move register definitions to ingenic-drm.h
Move the register definitions to ingenic-drm.h, to keep ingenic-drm.c tidy. Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 116 +-- drivers/gpu/drm/ingenic/ingenic-drm.h | 127 ++ 2 files changed, 129 insertions(+), 114 deletions(-) create mode 100644 drivers/gpu/drm/ingenic/ingenic-drm.h diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index e43318938e9e..92c63d1303f9 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -4,6 +4,8 @@ // // Copyright (C) 2019, Paul Cercueil +#include "ingenic-drm.h" + #include #include #include @@ -32,120 +34,6 @@ #include #include -#define JZ_REG_LCD_CFG 0x00 -#define JZ_REG_LCD_VSYNC 0x04 -#define JZ_REG_LCD_HSYNC 0x08 -#define JZ_REG_LCD_VAT 0x0C -#define JZ_REG_LCD_DAH 0x10 -#define JZ_REG_LCD_DAV 0x14 -#define JZ_REG_LCD_PS 0x18 -#define JZ_REG_LCD_CLS 0x1C -#define JZ_REG_LCD_SPL 0x20 -#define JZ_REG_LCD_REV 0x24 -#define JZ_REG_LCD_CTRL0x30 -#define JZ_REG_LCD_STATE 0x34 -#define JZ_REG_LCD_IID 0x38 -#define JZ_REG_LCD_DA0 0x40 -#define JZ_REG_LCD_SA0 0x44 -#define JZ_REG_LCD_FID00x48 -#define JZ_REG_LCD_CMD00x4C -#define JZ_REG_LCD_DA1 0x50 -#define JZ_REG_LCD_SA1 0x54 -#define JZ_REG_LCD_FID10x58 -#define JZ_REG_LCD_CMD10x5C - -#define JZ_LCD_CFG_SLCDBIT(31) -#define JZ_LCD_CFG_PS_DISABLE BIT(23) -#define JZ_LCD_CFG_CLS_DISABLE BIT(22) -#define JZ_LCD_CFG_SPL_DISABLE BIT(21) -#define JZ_LCD_CFG_REV_DISABLE BIT(20) -#define JZ_LCD_CFG_HSYNCM BIT(19) -#define JZ_LCD_CFG_PCLKM BIT(18) -#define JZ_LCD_CFG_INV BIT(17) -#define JZ_LCD_CFG_SYNC_DIRBIT(16) -#define JZ_LCD_CFG_PS_POLARITY BIT(15) -#define JZ_LCD_CFG_CLS_POLARITYBIT(14) -#define JZ_LCD_CFG_SPL_POLARITYBIT(13) -#define JZ_LCD_CFG_REV_POLARITYBIT(12) -#define JZ_LCD_CFG_HSYNC_ACTIVE_LOWBIT(11) -#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10) -#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9) -#define JZ_LCD_CFG_VSYNC_ACTIVE_LOWBIT(8) -#define JZ_LCD_CFG_18_BIT BIT(7) -#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4)) - -#define JZ_LCD_CFG_MODE_GENERIC_16BIT 0 -#define JZ_LCD_CFG_MODE_GENERIC_18BIT BIT(7) -#define JZ_LCD_CFG_MODE_GENERIC_24BIT BIT(6) - -#define JZ_LCD_CFG_MODE_SPECIAL_TFT_1 1 -#define JZ_LCD_CFG_MODE_SPECIAL_TFT_2 2 -#define JZ_LCD_CFG_MODE_SPECIAL_TFT_3 3 - -#define JZ_LCD_CFG_MODE_TV_OUT_P 4 -#define JZ_LCD_CFG_MODE_TV_OUT_I 6 - -#define JZ_LCD_CFG_MODE_SINGLE_COLOR_STN 8 -#define JZ_LCD_CFG_MODE_SINGLE_MONOCHROME_STN 9 -#define JZ_LCD_CFG_MODE_DUAL_COLOR_STN 10 -#define JZ_LCD_CFG_MODE_DUAL_MONOCHROME_STN11 - -#define JZ_LCD_CFG_MODE_8BIT_SERIAL12 -#define JZ_LCD_CFG_MODE_LCM13 - -#define JZ_LCD_VSYNC_VPS_OFFSET16 -#define JZ_LCD_VSYNC_VPE_OFFSET0 - -#define JZ_LCD_HSYNC_HPS_OFFSET16 -#define JZ_LCD_HSYNC_HPE_OFFSET0 - -#define JZ_LCD_VAT_HT_OFFSET 16 -#define JZ_LCD_VAT_VT_OFFSET 0 - -#define JZ_LCD_DAH_HDS_OFFSET 16 -#define JZ_LCD_DAH_HDE_OFFSET 0 - -#define JZ_LCD_DAV_VDS_OFFSET 16 -#define JZ_LCD_DAV_VDE_OFFSET 0 - -#define JZ_LCD_CTRL_BURST_4(0x0 << 28) -#define JZ_LCD_CTRL_BURST_8(0x1 << 28) -#define JZ_LCD_CTRL_BURST_16 (0x2 << 28) -#define JZ_LCD_CTRL_RGB555 BIT(27) -#define JZ_LCD_CTRL_OFUP BIT(26) -#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24) -#define JZ_LCD_CTRL_FRC_GRAYSCALE_4(0x1 << 24) -#define JZ_LCD_CTRL_FRC_GRAYSCALE_2(0x2 << 24) -#define JZ_LCD_CTRL_PDD_MASK (0xff << 16) -#define JZ_LCD_CTRL_EOF_IRQBIT(13) -#define JZ_LCD_CTRL_SOF_IRQBIT(12) -#define JZ_
[PATCH 06/12] gpu/drm: Ingenic: Fix incorrect assumption about plane->index
plane->index is NOT the index of the color plane in a YUV frame. Actually, a YUV frame is represented by a single drm_plane, even though it contains three Y, U, V planes. Cc: sta...@vger.kernel.org # v5.3 Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs") Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 97244462599b..3207105755c9 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -386,7 +386,7 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane, addr = drm_fb_cma_get_gem_addr(state->fb, state, 0); width = state->src_w >> 16; height = state->src_h >> 16; - cpp = state->fb->format->cpp[plane->index]; + cpp = state->fb->format->cpp[0]; priv->dma_hwdesc->addr = addr; priv->dma_hwdesc->cmd = width * height * cpp / 4; -- 2.26.2
[PATCH 04/12] gpu/drm: ingenic: Fix bogus crtc_atomic_check callback
The code was comparing the SoC's maximum height with the mode's width, and vice-versa. D'oh. Cc: sta...@vger.kernel.org # v5.6 Fixes: a7c909b7c037 ("gpu/drm: ingenic: Check for display size in CRTC atomic check") Signed-off-by: Paul Cercueil --- Notes: This patch was previously sent standalone. I marked it as superseded in patchwork. Nothing has been changed here. drivers/gpu/drm/ingenic/ingenic-drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 632d72177123..0c472382a08b 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -330,8 +330,8 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, if (!drm_atomic_crtc_needs_modeset(state)) return 0; - if (state->mode.hdisplay > priv->soc_info->max_height || - state->mode.vdisplay > priv->soc_info->max_width) + if (state->mode.hdisplay > priv->soc_info->max_width || + state->mode.vdisplay > priv->soc_info->max_height) return -EINVAL; rate = clk_round_rate(priv->pix_clk, -- 2.26.2
[PATCH 05/12] gpu/drm: Ingenic: Fix opaque pointer casted to wrong type
The opaque pointer passed to the IRQ handler is a pointer to the drm_device, not a pointer to our ingenic_drm structure. It still worked, because our ingenic_drm structure contains the drm_device as its first field, so the pointer received had the same value, but this was not semantically correct. Cc: sta...@vger.kernel.org # v5.3 Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs") Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 0c472382a08b..97244462599b 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -476,7 +476,7 @@ static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder, static irqreturn_t ingenic_drm_irq_handler(int irq, void *arg) { - struct ingenic_drm *priv = arg; + struct ingenic_drm *priv = drm_device_get_priv(arg); unsigned int state; regmap_read(priv->map, JZ_REG_LCD_STATE, ); -- 2.26.2
[PATCH 03/12] component: Support binding with no matches
Support binding the master even though no components have been registered. This permits to support cases where components are optional. Signed-off-by: Paul Cercueil --- drivers/base/component.c | 35 ++- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/base/component.c b/drivers/base/component.c index e97704104784..a9de7ee1677f 100644 --- a/drivers/base/component.c +++ b/drivers/base/component.c @@ -100,7 +100,7 @@ static int component_devices_show(struct seq_file *s, void *data) seq_printf(s, "%-40s %20s\n", "device name", "status"); seq_puts(s, "-\n"); - for (i = 0; i < match->num; i++) { + for (i = 0; !!match && i < match->num; i++) { struct component *component = match->compare[i].component; seq_printf(s, "%-40s %20s\n", @@ -184,6 +184,11 @@ static int find_components(struct master *master) size_t i; int ret = 0; + if (!match) { + dev_dbg(master->dev, "No components\n"); + return 0; + } + /* * Scan the array of match functions and attach * any components which are found to this master. @@ -218,10 +223,12 @@ static void remove_component(struct master *master, struct component *c) { size_t i; - /* Detach the component from this master. */ - for (i = 0; i < master->match->num; i++) - if (master->match->compare[i].component == c) - master->match->compare[i].component = NULL; + if (master->match) { + /* Detach the component from this master. */ + for (i = 0; i < master->match->num; i++) + if (master->match->compare[i].component == c) + master->match->compare[i].component = NULL; + } } /* @@ -470,10 +477,12 @@ int component_master_add_with_match(struct device *dev, struct master *master; int ret; - /* Reallocate the match array for its true size */ - ret = component_match_realloc(dev, match, match->num); - if (ret) - return ret; + if (match) { + /* Reallocate the match array for its true size */ + ret = component_match_realloc(dev, match, match->num); + if (ret) + return ret; + } master = kzalloc(sizeof(*master), GFP_KERNEL); if (!master) @@ -557,6 +566,10 @@ void component_unbind_all(struct device *master_dev, void *data) if (!master) return; + /* No match, nothing to unbind */ + if (!master->match) + return; + /* Unbind components in reverse order */ for (i = master->match->num; i--; ) if (!master->match->compare[i].duplicate) { @@ -640,6 +653,10 @@ int component_bind_all(struct device *master_dev, void *data) if (!master) return -EINVAL; + /* No match, nothing to bind */ + if (!master->match) + return 0; + /* Bind components in match order */ for (i = 0; i < master->match->num; i++) if (!master->match->compare[i].duplicate) { -- 2.26.2
[PATCH 01/12] dt-bindings: display: Convert ingenic,lcd.txt to YAML
Convert the ingenic,lcd.txt to a new ingenic,lcd.yaml file. In the process, the new ingenic,jz4780-lcd compatible string has been added. Signed-off-by: Paul Cercueil --- Notes: This patch comes from a different patchset so it's effectively a V2. Changes were: - lcd_pclk and lcd clocks are in the correct order now, - Add 'port' and 'ports' properties, and document the valid ports. .../bindings/display/ingenic,lcd.txt | 45 --- .../bindings/display/ingenic,lcd.yaml | 126 ++ 2 files changed, 126 insertions(+), 45 deletions(-) delete mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.txt create mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.yaml diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.txt b/Documentation/devicetree/bindings/display/ingenic,lcd.txt deleted file mode 100644 index 01e3261defb6.. --- a/Documentation/devicetree/bindings/display/ingenic,lcd.txt +++ /dev/null @@ -1,45 +0,0 @@ -Ingenic JZ47xx LCD driver - -Required properties: -- compatible: one of: - * ingenic,jz4740-lcd - * ingenic,jz4725b-lcd - * ingenic,jz4770-lcd -- reg: LCD registers location and length -- clocks: LCD pixclock and device clock specifiers. - The device clock is only required on the JZ4740. -- clock-names: "lcd_pclk" and "lcd" -- interrupts: Specifies the interrupt line the LCD controller is connected to. - -Example: - -panel { - compatible = "sharp,ls020b1dd01d"; - - backlight = <>; - power-supply = <>; - - port { - panel_input: endpoint { - remote-endpoint = <_output>; - }; - }; -}; - - -lcd: lcd-controller@1305 { - compatible = "ingenic,jz4725b-lcd"; - reg = <0x1305 0x1000>; - - interrupt-parent = <>; - interrupts = <31>; - - clocks = < JZ4725B_CLK_LCD>; - clock-names = "lcd"; - - port { - panel_output: endpoint { - remote-endpoint = <_input>; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.yaml b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml new file mode 100644 index ..d56db1802fad --- /dev/null +++ b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/ingenic,lcd.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs LCD controller devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + $nodename: +pattern: "^lcd-controller@[0-9a-f]+$" + + compatible: +enum: + - ingenic,jz4740-lcd + - ingenic,jz4725b-lcd + - ingenic,jz4770-lcd + - ingenic,jz4780-lcd + + reg: +maxItems: 1 + + interrupts: +maxItems: 1 + + clocks: +items: + - description: Pixel clock + - description: Module clock +minItems: 1 + + clock-names: +items: + - const: lcd_pclk + - const: lcd +minItems: 1 + + port: +description: OF graph bindings (specified in bindings/graph.txt). + + ports: +description: OF graph bindings (specified in bindings/graph.txt). +type: object +properties: + port@0: +type: object +description: DPI output, to interface with TFT panels. + + port@8: +type: object +description: Link to the Image Processing Unit (IPU). + (See ingenic,ipu.yaml). + +required: + - port@0 + +required: +- compatible +- reg +- interrupts +- clocks +- clock-names + +if: + properties: +compatible: + contains: +enum: + - ingenic,jz4740-lcd + - ingenic,jz4780-lcd +then: + properties: +clocks: + minItems: 2 +clock-names: + minItems: 2 +else: + properties: +clocks: + maxItems: 1 +clock-names: + maxItems: 1 + +additionalProperties: false + +examples: + - | +#include +lcd-controller@1305 { + compatible = "ingenic,jz4740-lcd"; + reg = <0x1305 0x1000>; + + interrupt-parent = <>; + interrupts = <30>; + + clocks = < JZ4740_CLK_LCD_PCLK>, < JZ4740_CLK_LCD>; + clock-names = "lcd_pclk", "lcd"; + + port { +endpoint { + remote-endpoint = <_input>; +}; + }; +}; + + - | +#include +lcd-controller@1305 { + compatible = "ingenic,jz4725b-lcd"; + reg = <0x1305 0x1000>; + + interrupt-parent = <>; + interrupts = <31>; + + clocks = < JZ4725B_CLK_LCD>; + clock-names = "lcd_pclk"; + + port { +endpoint { + remote-endpoint = <_input>; +}; + }; +}; -- 2.26.2
[PATCH 02/12] dt-bindings: display: Add ingenic,ipu.yaml
Add documentation of the Device Tree bindings for the Image Processing Unit (IPU) found in most Ingenic SoCs. Signed-off-by: Paul Cercueil --- .../bindings/display/ingenic,ipu.yaml | 65 +++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/ingenic,ipu.yaml diff --git a/Documentation/devicetree/bindings/display/ingenic,ipu.yaml b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml new file mode 100644 index ..22fe02ca866d --- /dev/null +++ b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/ingenic,ipu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs Image Processing Unit (IPU) devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + compatible: +oneOf: + - enum: +- ingenic,jz4725b-ipu +- ingenic,jz4760-ipu + - items: +- ingenic,jz4770-ipu +- ingenic,jz4760-ipu + + reg: +maxItems: 1 + + interrupts: +maxItems: 1 + + clocks: +maxItems: 1 + + clock-names: +const: ipu + +patternProperties: + "^ports?$": +description: OF graph bindings (specified in bindings/graph.txt). + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + - | +#include +ipu@1308 { + compatible = "ingenic,jz4770-ipu", "ingenic,jz4760-ipu"; + reg = <0x1308 0x800>; + + interrupt-parent = <>; + interrupts = <29>; + + clocks = < JZ4770_CLK_IPU>; + clock-names = "ipu"; + + port { +ipu_ep: endpoint { + remote-endpoint = <_ep>; +}; + }; +}; -- 2.26.2
[PATCH v7 5/5] MAINTAINERS: Add myself as reviewer for Ingenic rproc driver
Add myself as the reviewer for the Ingenic VPU remoteproc driver. Signed-off-by: Paul Cercueil --- Notes: v4: New patch v5-v7: No change MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 091ec22c1a23..5d864d785574 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8414,6 +8414,7 @@ F:drivers/mtd/nand/raw/ingenic/ F: drivers/pinctrl/pinctrl-ingenic.c F: drivers/power/supply/ingenic-battery.c F: drivers/pwm/pwm-jz4740.c +F: drivers/remoteproc/ingenic_rproc.c F: drivers/rtc/rtc-jz4740.c F: drivers/tty/serial/8250/8250_ingenic.c F: drivers/usb/musb/jz4740.c -- 2.26.2
[PATCH v7 1/5] dt-bindings: Document JZ47xx VPU auxiliary processor
Inside the Video Processing Unit (VPU) of the recent JZ47xx SoCs from Ingenic is a second Xburst MIPS CPU very similar to the main core. This document describes the devicetree bindings for this auxiliary processor. Signed-off-by: Paul Cercueil Reviewed-by: Rob Herring --- Notes: v2: Update TCSM0 address in example v3: Change node name to 'video-decoder' v4: Convert to YAML. I didn't add Rob's Ack on v3 because of that (sorry Rob) v5: - Fix 'reg' not in pairs - Add missing include to devicetree example v6-v7: No change .../bindings/remoteproc/ingenic,vpu.yaml | 77 +++ 1 file changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/remoteproc/ingenic,vpu.yaml diff --git a/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.yaml b/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.yaml new file mode 100644 index ..c019f9fbe916 --- /dev/null +++ b/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/remoteproc/ingenic,vpu.yaml#; +$schema: "http://devicetree.org/meta-schemas/core.yaml#; + +title: Ingenic Video Processing Unit bindings + +description: + Inside the Video Processing Unit (VPU) of the recent JZ47xx SoCs from + Ingenic is a second Xburst MIPS CPU very similar to the main core. + This document describes the devicetree bindings for this auxiliary + processor. + +maintainers: + - Paul Cercueil + +properties: + compatible: +const: ingenic,jz4770-vpu-rproc + + reg: +items: + - description: aux registers + - description: tcsm0 registers + - description: tcsm1 registers + - description: sram registers + + reg-names: +items: + - const: aux + - const: tcsm0 + - const: tcsm1 + - const: sram + + clocks: +items: + - description: aux clock + - description: vpu clock + + clock-names: +items: + - const: aux + - const: vpu + + interrupts: +description: VPU hardware interrupt + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + +additionalProperties: false + +examples: + - | +#include + +vpu: video-decoder@132a { + compatible = "ingenic,jz4770-vpu-rproc"; + + reg = <0x132a 0x20>, /* AUX */ +<0x132b 0x4000>, /* TCSM0 */ +<0x132c 0xc000>, /* TCSM1 */ +<0x132f 0x7000>; /* SRAM */ + reg-names = "aux", "tcsm0", "tcsm1", "sram"; + + clocks = < JZ4770_CLK_AUX>, < JZ4770_CLK_VPU>; + clock-names = "aux", "vpu"; + + interrupt-parent = <>; + interrupts = <3>; +}; -- 2.26.2
[PATCH v7 2/5] remoteproc: Add device-managed variants of rproc_alloc/rproc_add
Add API functions devm_rproc_alloc() and devm_rproc_add(), which behave like rproc_alloc() and rproc_add() respectively, but register their respective cleanup function to be called on driver detach. Signed-off-by: Paul Cercueil Reviewed-by: Bjorn Andersson --- Notes: v3: New patch v4: No change v5: - Fix return value documentation - Fix typo in documentation v6-v7: No change drivers/remoteproc/remoteproc_core.c | 67 include/linux/remoteproc.h | 5 +++ 2 files changed, 72 insertions(+) diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index e12a54e67588..a7f96bc98406 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -1949,6 +1949,33 @@ int rproc_add(struct rproc *rproc) } EXPORT_SYMBOL(rproc_add); +static void devm_rproc_remove(void *rproc) +{ + rproc_del(rproc); +} + +/** + * devm_rproc_add() - resource managed rproc_add() + * @dev: the underlying device + * @rproc: the remote processor handle to register + * + * This function performs like rproc_add() but the registered rproc device will + * automatically be removed on driver detach. + * + * Returns: 0 on success, negative errno on failure + */ +int devm_rproc_add(struct device *dev, struct rproc *rproc) +{ + int err; + + err = rproc_add(rproc); + if (err) + return err; + + return devm_add_action_or_reset(dev, devm_rproc_remove, rproc); +} +EXPORT_SYMBOL(devm_rproc_add); + /** * rproc_type_release() - release a remote processor instance * @dev: the rproc's device @@ -2171,6 +2198,46 @@ int rproc_del(struct rproc *rproc) } EXPORT_SYMBOL(rproc_del); +static void devm_rproc_free(struct device *dev, void *res) +{ + rproc_free(*(struct rproc **)res); +} + +/** + * devm_rproc_alloc() - resource managed rproc_alloc() + * @dev: the underlying device + * @name: name of this remote processor + * @ops: platform-specific handlers (mainly start/stop) + * @firmware: name of firmware file to load, can be NULL + * @len: length of private data needed by the rproc driver (in bytes) + * + * This function performs like rproc_alloc() but the acquired rproc device will + * automatically be released on driver detach. + * + * Returns: new rproc instance, or NULL on failure + */ +struct rproc *devm_rproc_alloc(struct device *dev, const char *name, + const struct rproc_ops *ops, + const char *firmware, int len) +{ + struct rproc **ptr, *rproc; + + ptr = devres_alloc(devm_rproc_free, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + rproc = rproc_alloc(dev, name, ops, firmware, len); + if (rproc) { + *ptr = rproc; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return rproc; +} +EXPORT_SYMBOL(devm_rproc_alloc); + /** * rproc_add_subdev() - add a subdevice to a remoteproc * @rproc: rproc handle to add the subdevice to diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 9c07d7958c53..8c9c0dda03c3 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -599,6 +599,11 @@ int rproc_add(struct rproc *rproc); int rproc_del(struct rproc *rproc); void rproc_free(struct rproc *rproc); +struct rproc *devm_rproc_alloc(struct device *dev, const char *name, + const struct rproc_ops *ops, + const char *firmware, int len); +int devm_rproc_add(struct device *dev, struct rproc *rproc); + void rproc_add_carveout(struct rproc *rproc, struct rproc_mem_entry *mem); struct rproc_mem_entry * -- 2.26.2
[PATCH v7 3/5] remoteproc: Add support for runtime PM
Call pm_runtime_get_sync() before the firmware is loaded, and pm_runtime_put() after the remote processor has been stopped. Even though the remoteproc device has no PM callbacks, this allows the parent device's PM callbacks to be properly called. Signed-off-by: Paul Cercueil --- Notes: v2-v4: No change v5: Move calls to prepare/unprepare to rproc_fw_boot/rproc_shutdown v6: Instead of prepare/unprepare callbacks, use PM runtime callbacks v7: Check return value of pm_runtime_get_sync() drivers/remoteproc/remoteproc_core.c | 17 - 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index a7f96bc98406..e33d1ef27981 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1382,6 +1383,12 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) if (ret) return ret; + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret); + return ret; + } + dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size); /* @@ -1391,7 +1398,7 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) ret = rproc_enable_iommu(rproc); if (ret) { dev_err(dev, "can't enable iommu: %d\n", ret); - return ret; + goto put_pm_runtime; } rproc->bootaddr = rproc_get_boot_addr(rproc, fw); @@ -1435,6 +1442,8 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) rproc->table_ptr = NULL; disable_iommu: rproc_disable_iommu(rproc); +put_pm_runtime: + pm_runtime_put(dev); return ret; } @@ -1840,6 +1849,8 @@ void rproc_shutdown(struct rproc *rproc) rproc_disable_iommu(rproc); + pm_runtime_put(dev); + /* Free the copy of the resource table */ kfree(rproc->cached_table); rproc->cached_table = NULL; @@ -2118,6 +2129,9 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, rproc->state = RPROC_OFFLINE; + pm_runtime_no_callbacks(>dev); + pm_runtime_enable(>dev); + return rproc; } EXPORT_SYMBOL(rproc_alloc); @@ -2133,6 +2147,7 @@ EXPORT_SYMBOL(rproc_alloc); */ void rproc_free(struct rproc *rproc) { + pm_runtime_disable(>dev); put_device(>dev); } EXPORT_SYMBOL(rproc_free); -- 2.26.2
[PATCH v7 4/5] remoteproc: ingenic: Added remoteproc driver
This driver is used to boot, communicate with and load firmwares to the MIPS co-processor found in the VPU hardware of the JZ47xx SoCs from Ingenic. Signed-off-by: Paul Cercueil Acked-by: Mathieu Poirier --- Notes: v2: Remove exception for always-mapped memories v3: - Use clk_bulk API - Move device-managed code to its own patch [3/4] - Move devicetree table right above ingenic_rproc_driver - Removed #ifdef CONFIG_OF around devicetree table - Removed .owner = THIS_MODULE in ingenic_rproc_driver - Removed useless platform_set_drvdata() v4: - Add fix reported by Julia - Change Kconfig symbol to INGENIC_VPU_RPROC - Add documentation to struct vpu - disable_irq_nosync() -> disable_irq() v5: No change v6: Instead of prepare/unprepare callbacks, use PM runtime callbacks v7: - Remove use of of_match_ptr() - Move Kconfig symbol so that it's in alphabetical order - Add missing doc for private structure field aux_base - Don't check for (len <= 0) in da_to_va() - Add missing \n in dev_info/dev_err messages drivers/remoteproc/Kconfig | 9 + drivers/remoteproc/Makefile| 1 + drivers/remoteproc/ingenic_rproc.c | 280 + 3 files changed, 290 insertions(+) create mode 100644 drivers/remoteproc/ingenic_rproc.c diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index fbaed079b299..c4d1731295eb 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -23,6 +23,15 @@ config IMX_REMOTEPROC It's safe to say N here. +config INGENIC_VPU_RPROC + tristate "Ingenic JZ47xx VPU remoteproc support" + depends on MIPS || COMPILE_TEST + help + Say y or m here to support the VPU in the JZ47xx SoCs from Ingenic. + + This can be either built-in or a loadable module. + If unsure say N. + config MTK_SCP tristate "Mediatek SCP support" depends on ARCH_MEDIATEK diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 0effd3825035..e8b886e511f0 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -10,6 +10,7 @@ remoteproc-y += remoteproc_sysfs.o remoteproc-y += remoteproc_virtio.o remoteproc-y += remoteproc_elf_loader.o obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o +obj-$(CONFIG_INGENIC_VPU_RPROC)+= ingenic_rproc.o obj-$(CONFIG_MTK_SCP) += mtk_scp.o mtk_scp_ipi.o obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o obj-$(CONFIG_WKUP_M3_RPROC)+= wkup_m3_rproc.o diff --git a/drivers/remoteproc/ingenic_rproc.c b/drivers/remoteproc/ingenic_rproc.c new file mode 100644 index ..189020d77b25 --- /dev/null +++ b/drivers/remoteproc/ingenic_rproc.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Ingenic JZ47xx remoteproc driver + * Copyright 2019, Paul Cercueil + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "remoteproc_internal.h" + +#define REG_AUX_CTRL 0x0 +#define REG_AUX_MSG_ACK0x10 +#define REG_AUX_MSG0x14 +#define REG_CORE_MSG_ACK 0x18 +#define REG_CORE_MSG 0x1C + +#define AUX_CTRL_SLEEP BIT(31) +#define AUX_CTRL_MSG_IRQ_ENBIT(3) +#define AUX_CTRL_NMI_RESETSBIT(2) +#define AUX_CTRL_NMI BIT(1) +#define AUX_CTRL_SW_RESET BIT(0) + +struct vpu_mem_map { + const char *name; + unsigned int da; +}; + +struct vpu_mem_info { + const struct vpu_mem_map *map; + unsigned long len; + void __iomem *base; +}; + +static const struct vpu_mem_map vpu_mem_map[] = { + { "tcsm0", 0x132b }, + { "tcsm1", 0xf400 }, + { "sram", 0x132f }, +}; + +/** + * struct vpu - Ingenic VPU remoteproc private structure + * @irq: interrupt number + * @clks: pointers to the VPU and AUX clocks + * @aux_base: raw pointer to the AUX interface registers + * @mem_info: array of struct vpu_mem_info, which contain the mapping info of + *each of the external memories + * @dev: private pointer to the device + */ +struct vpu { + int irq; + struct clk_bulk_data clks[2]; + void __iomem *aux_base; + struct vpu_mem_info mem_info[ARRAY_SIZE(vpu_mem_map)]; + struct device *dev; +}; + +static int ingenic_rproc_start(struct rproc *rproc) +{ + struct vpu *vpu = rproc->priv; + u32 ctrl; + + enable_irq(vpu->irq); + + /* Reset the AUX and enable message IRQ */ + ctrl = AUX_CTRL_NMI_RESETS | AUX_CTRL_NMI | AUX_CTRL_MSG_IRQ_EN; + writel(ctrl, vpu->aux_base + REG_AUX_CTRL); + + return 0; +} + +static int ingenic_rproc_stop(struct rproc *rpro
Re: [PATCH] drm/etnaviv: fix perfmon domain interation
Hi Christian, Le ven. 15 mai 2020 à 12:09, Christian Gmeiner a écrit : Am Mo., 11. Mai 2020 um 14:38 Uhr schrieb Christian Gmeiner : The GC860 has one GPU device which has a 2d and 3d core. In this case we want to expose perfmon information for both cores. The driver has one array which contains all possible perfmon domains with some meta data - doms_meta. Here we can see that for the GC860 two elements of that array are relevant: doms_3d: is at index 0 in the doms_meta array with 8 perfmon domains doms_2d: is at index 1 in the doms_meta array with 1 perfmon domain The userspace driver wants to get a list of all perfmon domains and their perfmon signals. This is done by iterating over all domains and their signals. If the userspace driver wants to access the domain with id 8 the kernel driver fails and returns invalid data from doms_3d with and invalid offset. This results in: Unable to handle kernel paging request at virtual address On such a device it is not possible to use the userspace driver at all. The fix for this off-by-one error is quite simple. Reported-by: Paul Cercueil Tested-by: Paul Cercueil Fixes: ed1dd899baa3 ("drm/etnaviv: rework perfmon query infrastructure") Cc: sta...@vger.kernel.org Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/etnaviv_perfmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_perfmon.c b/drivers/gpu/drm/etnaviv/etnaviv_perfmon.c index e6795bafcbb9..35f7171e779a 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_perfmon.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_perfmon.c @@ -453,7 +453,7 @@ static const struct etnaviv_pm_domain *pm_domain(const struct etnaviv_gpu *gpu, if (!(gpu->identity.features & meta->feature)) continue; - if (meta->nr_domains < (index - offset)) { + if ((meta->nr_domains - 1) < (index - offset)) { offset += meta->nr_domains; continue; } -- 2.26.2 ping I'll merge it tomorrow if there's no further feedback. -Paul
Re: DRM interaction problems on Ingenic CI20 / jz4780 with dw-hdmi and ingenic-drm
Hi Paul, Le mar. 5 mai 2020 à 20:26, Paul Boddie a écrit : On Monday 4. May 2020 03.05.22 Paul Cercueil wrote: > Le sam. 11 avril 2020 à 16:14, H. Nikolaus Schaller a > écrit : >> >> So far we have identified two issues. >> >> The first is that HPD interrupts are not properly processed. >> >> drm_helper_hpd_irq_event() is called by HPD events but >> dev->mode_config.poll_enabled is false. This is to be used when there's no hardware interrupt. I believe you have one, right? Then call drm_kms_helper_hotplug_event() from the interrupt handler instead. What we have in drivers/gpu/drm/bridge/synopsys/dw-hdmi.c is a function called dw_hdmi_irq which appears to be the thread_fn for HDMI interrupts (alongside the dw_hdmi_hardirq which is the handler), initialised by a call to devm_request_threaded_irq. In dw_hdmi_irq, a hotplug event seems to cause drm_helper_hpd_irq_event to be called. However... [...] >> The jz4780 hdmi subsystem (drm/bridge/dw-hdmi.c) uses >> >> connector->polled = DRM_CONNECTOR_POLL_HPD; >> >> but shouldn't this enable polling? Note that there seems to be >> no (direct) call to drm_kms_helper_poll_init(). >> >> If we set dev->mode_config.poll_enabled = true in >> drm_helper_hpd_irq_event() things start to work. >> >> Please can you clarify what would be best practise here to >> get HPD event handling working. Remove that - this stuff is for hardware without interrupts, where everything has to be polled. Yes, I think this must be a mistake in the driver. In drm_helper_hpd_irq_event, the connector is tested for the DRM_CONNECTOR_POLL_HPD flag and skips the connector. It isn't clear whether this actually matters for the other hardware using this technology, documentation being rather thin on the ground. >> The other issue is in dw-hdmi.c: >> >> We found out that ingenic_drm_encoder_atomic_check() fails because >> >> info->num_bus_formats == 0 >> >> and not 1. This blocks further initialization. >> >> The reason seems to be that dw_hdmi_bridge_attach() does not call >> drm_display_info_set_bus_formats() with a proper format like >> other drivers (e.g. drm/bridge/ti-tfp410.c) are doing. >> >> We have patched to set a single bus format MEDIA_BUS_FMT_RGB888_1X24 >> and then DRM setup seems to work (although we still have no valid >> HDMI signal but that is likely something else). >> >> Please can you explain how setting the bus format should be fixed >> in dw-hdmi.c. I'm not sure, but that information may come from EDID data. Are you able to obtain video modes from the connected monitor? The modes are definitely received, or at least the list of modes given by /sys/devices/platform/1305.lcd/drm/card0/card0-HDMI-A-1/modes is viable. However, it rather looked like the bus format information wasn't being set and that this inhibited the completion of the initialisation process which, if completed, would ultimately cause the format to be set. (This being the short version of the story as I remember it right now.) So, the problem presents itself as an initialisation order problem. It's not an initialization order problem, the ingenic-drm expects the bus_formats to be provided and the synopsis driver never calls drm_display_info_set_bus_formats(). I removed the flag from the connector to see if it makes any difference, but it doesn't look like it. Here is what /sys/kernel/debug/dri/0/state contains: plane[31]: plane-0 crtc=(null) fb=0 crtc-pos=0x0+0+0 src-pos=0.00x0.00+0.00+0.00 rotation=1 normalized-zpos=0 color-encoding=ITU-R BT.601 YCbCr color-range=YCbCr limited range crtc[32]: crtc-0 enable=0 active=0 self_refresh_active=0 planes_changed=0 mode_changed=0 active_changed=0 connectors_changed=0 color_mgmt_changed=0 plane_mask=0 connector_mask=0 encoder_mask=0 mode: "": 0 0 0 0 0 0 0 0 0 0 0x0 0x0 connector[34]: HDMI-A-1 crtc=(null) self_refresh_aware=0 The crtc member values do not look encouraging. In fact, it just looks like most structure members are uninitialised. Thanks for the advice: I spent some time the other day reviewing various aspects of the Synopsys drivers of different vintages (Ingenic 3.0.8 non-DRM driver for JZ4780, MIPS/Ingenic 3.18 DRM driver for JZ4780 based on Freescale driver code, the recent generic DRM bridge driver), and so this information is timely and valuable. With a hardcoded bus_format of RGB888 in the ingenic-drm driver, and jz4780_dw_hdmi_plat_data.input_bus_format initialized too: modetest -D /dev/dri/card0 -M
[PATCH 5/7] rtc: ingenic: Remove unused fields from private structure
The 'clk' and 'irq' fields were only ever used in the probe function. Therefore they can be moved to be simple local variables of the probe function. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 26 -- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 8927fd0fb086..3193eb8bd131 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -55,9 +55,6 @@ struct jz4740_rtc { enum jz4740_rtc_type type; struct rtc_device *rtc; - struct clk *clk; - - int irq; spinlock_t lock; }; @@ -313,9 +310,10 @@ static int jz4740_rtc_probe(struct platform_device *pdev) { struct device *dev = >dev; struct device_node *np = dev->of_node; - int ret; struct jz4740_rtc *rtc; unsigned long rate; + struct clk *clk; + int ret, irq; rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) @@ -323,27 +321,27 @@ static int jz4740_rtc_probe(struct platform_device *pdev) rtc->type = (enum jz4740_rtc_type)device_get_match_data(dev); - rtc->irq = platform_get_irq(pdev, 0); - if (rtc->irq < 0) + irq = platform_get_irq(pdev, 0); + if (irq < 0) return -ENOENT; rtc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rtc->base)) return PTR_ERR(rtc->base); - rtc->clk = devm_clk_get(dev, "rtc"); - if (IS_ERR(rtc->clk)) { + clk = devm_clk_get(dev, "rtc"); + if (IS_ERR(clk)) { dev_err(dev, "Failed to get RTC clock\n"); - return PTR_ERR(rtc->clk); + return PTR_ERR(clk); } - ret = clk_prepare_enable(rtc->clk); + ret = clk_prepare_enable(clk); if (ret) { dev_err(dev, "Failed to enable clock\n"); return ret; } - ret = devm_add_action_or_reset(dev, jz4740_rtc_clk_disable, rtc->clk); + ret = devm_add_action_or_reset(dev, jz4740_rtc_clk_disable, clk); if (ret) { dev_err(dev, "Failed to register devm action\n"); return ret; @@ -355,7 +353,7 @@ static int jz4740_rtc_probe(struct platform_device *pdev) device_init_wakeup(dev, 1); - ret = dev_pm_set_wake_irq(dev, rtc->irq); + ret = dev_pm_set_wake_irq(dev, irq); if (ret) { dev_err(dev, "Failed to set wake irq: %d\n", ret); return ret; @@ -371,14 +369,14 @@ static int jz4740_rtc_probe(struct platform_device *pdev) rtc->rtc->ops = _rtc_ops; rtc->rtc->range_max = U32_MAX; - rate = clk_get_rate(rtc->clk); + rate = clk_get_rate(clk); jz4740_rtc_set_wakeup_params(rtc, np, rate); ret = rtc_register_device(rtc->rtc); if (ret) return ret; - ret = devm_request_irq(dev, rtc->irq, jz4740_rtc_irq, 0, + ret = devm_request_irq(dev, irq, jz4740_rtc_irq, 0, pdev->name, rtc); if (ret) { dev_err(dev, "Failed to request rtc irq: %d\n", ret); -- 2.26.2
[PATCH 4/7] rtc: ingenic: Set wakeup params in probe
We can write the wakeup timing parameters as soon as the driver probes, there's no need to wait the very last moment. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 95 +++- 1 file changed, 44 insertions(+), 51 deletions(-) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 129c68cebb92..8927fd0fb086 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -60,9 +60,6 @@ struct jz4740_rtc { int irq; spinlock_t lock; - - unsigned int min_wakeup_pin_assert_time; - unsigned int reset_pin_assert_time; }; static struct device *dev_for_power_off; @@ -259,38 +256,6 @@ static void jz4740_rtc_poweroff(struct device *dev) static void jz4740_rtc_power_off(void) { - struct jz4740_rtc *rtc = dev_get_drvdata(dev_for_power_off); - unsigned long rtc_rate; - unsigned long wakeup_filter_ticks; - unsigned long reset_counter_ticks; - - rtc_rate = clk_get_rate(rtc->clk); - - /* -* Set minimum wakeup pin assertion time: 100 ms. -* Range is 0 to 2 sec if RTC is clocked at 32 kHz. -*/ - wakeup_filter_ticks = - (rtc->min_wakeup_pin_assert_time * rtc_rate) / 1000; - if (wakeup_filter_ticks < JZ_RTC_WAKEUP_FILTER_MASK) - wakeup_filter_ticks &= JZ_RTC_WAKEUP_FILTER_MASK; - else - wakeup_filter_ticks = JZ_RTC_WAKEUP_FILTER_MASK; - jz4740_rtc_reg_write(rtc, -JZ_REG_RTC_WAKEUP_FILTER, wakeup_filter_ticks); - - /* -* Set reset pin low-level assertion time after wakeup: 60 ms. -* Range is 0 to 125 ms if RTC is clocked at 32 kHz. -*/ - reset_counter_ticks = (rtc->reset_pin_assert_time * rtc_rate) / 1000; - if (reset_counter_ticks < JZ_RTC_RESET_COUNTER_MASK) - reset_counter_ticks &= JZ_RTC_RESET_COUNTER_MASK; - else - reset_counter_ticks = JZ_RTC_RESET_COUNTER_MASK; - jz4740_rtc_reg_write(rtc, -JZ_REG_RTC_RESET_COUNTER, reset_counter_ticks); - jz4740_rtc_poweroff(dev_for_power_off); kernel_halt(); } @@ -308,12 +273,49 @@ static const struct of_device_id jz4740_rtc_of_match[] = { }; MODULE_DEVICE_TABLE(of, jz4740_rtc_of_match); +static void jz4740_rtc_set_wakeup_params(struct jz4740_rtc *rtc, +struct device_node *np, +unsigned long rate) +{ + unsigned long wakeup_ticks, reset_ticks; + unsigned int min_wakeup_pin_assert_time = 60; /* Default: 60ms */ + unsigned int reset_pin_assert_time = 100; /* Default: 100ms */ + + of_property_read_u32(np, "ingenic,reset-pin-assert-time-ms", +_pin_assert_time); + of_property_read_u32(np, "ingenic,min-wakeup-pin-assert-time-ms", +_wakeup_pin_assert_time); + + /* +* Set minimum wakeup pin assertion time: 100 ms. +* Range is 0 to 2 sec if RTC is clocked at 32 kHz. +*/ + wakeup_ticks = (min_wakeup_pin_assert_time * rate) / 1000; + if (wakeup_ticks < JZ_RTC_WAKEUP_FILTER_MASK) + wakeup_ticks &= JZ_RTC_WAKEUP_FILTER_MASK; + else + wakeup_ticks = JZ_RTC_WAKEUP_FILTER_MASK; + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_WAKEUP_FILTER, wakeup_ticks); + + /* +* Set reset pin low-level assertion time after wakeup: 60 ms. +* Range is 0 to 125 ms if RTC is clocked at 32 kHz. +*/ + reset_ticks = (reset_pin_assert_time * rate) / 1000; + if (reset_ticks < JZ_RTC_RESET_COUNTER_MASK) + reset_ticks &= JZ_RTC_RESET_COUNTER_MASK; + else + reset_ticks = JZ_RTC_RESET_COUNTER_MASK; + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_RESET_COUNTER, reset_ticks); +} + static int jz4740_rtc_probe(struct platform_device *pdev) { struct device *dev = >dev; struct device_node *np = dev->of_node; int ret; struct jz4740_rtc *rtc; + unsigned long rate; rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) @@ -369,6 +371,9 @@ static int jz4740_rtc_probe(struct platform_device *pdev) rtc->rtc->ops = _rtc_ops; rtc->rtc->range_max = U32_MAX; + rate = clk_get_rate(rtc->clk); + jz4740_rtc_set_wakeup_params(rtc, np, rate); + ret = rtc_register_device(rtc->rtc); if (ret) return ret; @@ -381,24 +386,12 @@ static int jz4740_rtc_probe(struct platform_device *pdev) } if (of_device_is_system_power_controller(np)) { - if (!pm_power_off) { - /* Default: 60ms */ - rtc->reset_pin_assert_time = 60; - of_property_read_u3
[PATCH 6/7] rtc: ingenic: Fix masking of error code
The code was returning -ENOENT on any error of platform_get_irq(), even if it returned a different error. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 3193eb8bd131..65e130726fc6 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -323,7 +323,7 @@ static int jz4740_rtc_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return -ENOENT; + return irq; rtc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rtc->base)) -- 2.26.2
[PATCH 1/7] rtc: ingenic: Only support probing from devicetree
With the recent work on supporting Device Tree on Ingenic SoCs, no driver ever probes from platform code anymore, so we can clean a bit this driver by removing the non-devicetree paths. Signed-off-by: Paul Cercueil --- drivers/rtc/Kconfig | 1 + drivers/rtc/rtc-jz4740.c | 20 +++- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index ec873f09c763..82a210920c1d 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1680,6 +1680,7 @@ config RTC_DRV_MPC5121 config RTC_DRV_JZ4740 tristate "Ingenic JZ4740 SoC" depends on MIPS || COMPILE_TEST + depends on OF help If you say yes here you get support for the Ingenic JZ47xx SoCs RTC controllers. diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index e4c719085c31..949d395066e2 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -309,19 +309,13 @@ static int jz4740_rtc_probe(struct platform_device *pdev) { int ret; struct jz4740_rtc *rtc; - const struct platform_device_id *id = platform_get_device_id(pdev); - const struct of_device_id *of_id = of_match_device( - jz4740_rtc_of_match, >dev); struct device_node *np = pdev->dev.of_node; rtc = devm_kzalloc(>dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; - if (of_id) - rtc->type = (enum jz4740_rtc_type)of_id->data; - else - rtc->type = id->driver_data; + rtc->type = (enum jz4740_rtc_type)device_get_match_data(dev); rtc->irq = platform_get_irq(pdev, 0); if (rtc->irq < 0) @@ -370,7 +364,7 @@ static int jz4740_rtc_probe(struct platform_device *pdev) return ret; } - if (np && of_device_is_system_power_controller(np)) { + if (of_device_is_system_power_controller(np)) { if (!pm_power_off) { /* Default: 60ms */ rtc->reset_pin_assert_time = 60; @@ -395,20 +389,12 @@ static int jz4740_rtc_probe(struct platform_device *pdev) return 0; } -static const struct platform_device_id jz4740_rtc_ids[] = { - { "jz4740-rtc", ID_JZ4740 }, - { "jz4780-rtc", ID_JZ4780 }, - {} -}; -MODULE_DEVICE_TABLE(platform, jz4740_rtc_ids); - static struct platform_driver jz4740_rtc_driver = { .probe = jz4740_rtc_probe, .driver = { .name = "jz4740-rtc", - .of_match_table = of_match_ptr(jz4740_rtc_of_match), + .of_match_table = jz4740_rtc_of_match, }, - .id_table = jz4740_rtc_ids, }; module_platform_driver(jz4740_rtc_driver); -- 2.26.2
[PATCH 7/7] rtc: ingenic: Reset regulator register in probe
The regulator register specifies how many input clock cycles (minus one) are contained in one tick of the 1 Hz clock. Since this register can contain bogus values after the system boots, it needs to be reset in the probe register, otherwise the RTC may count way to slow or way too fast. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 65e130726fc6..9607e6b6e0b3 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -372,6 +372,9 @@ static int jz4740_rtc_probe(struct platform_device *pdev) rate = clk_get_rate(clk); jz4740_rtc_set_wakeup_params(rtc, np, rate); + /* Each 1 Hz pulse should happen after (rate) ticks */ + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_REGULATOR, rate - 1); + ret = rtc_register_device(rtc->rtc); if (ret) return ret; -- 2.26.2
[PATCH 3/7] rtc: ingenic: Enable clock in probe
It makes no sense to request a clock and not enable it even though the hardware is being used. So the driver now enables the clock in the probe. Besides, now we can properly handle errors. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 19 +-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 06ee08089815..129c68cebb92 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -264,8 +264,6 @@ static void jz4740_rtc_power_off(void) unsigned long wakeup_filter_ticks; unsigned long reset_counter_ticks; - clk_prepare_enable(rtc->clk); - rtc_rate = clk_get_rate(rtc->clk); /* @@ -297,6 +295,11 @@ static void jz4740_rtc_power_off(void) kernel_halt(); } +static void jz4740_rtc_clk_disable(void *data) +{ + clk_disable_unprepare(data); +} + static const struct of_device_id jz4740_rtc_of_match[] = { { .compatible = "ingenic,jz4740-rtc", .data = (void *)ID_JZ4740 }, { .compatible = "ingenic,jz4760-rtc", .data = (void *)ID_JZ4760 }, @@ -332,6 +335,18 @@ static int jz4740_rtc_probe(struct platform_device *pdev) return PTR_ERR(rtc->clk); } + ret = clk_prepare_enable(rtc->clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, jz4740_rtc_clk_disable, rtc->clk); + if (ret) { + dev_err(dev, "Failed to register devm action\n"); + return ret; + } + spin_lock_init(>lock); platform_set_drvdata(pdev, rtc); -- 2.26.2
[PATCH 2/7] rtc: ingenic: Use local 'dev' variable in probe
Clean a bit the probe function by adding a local struct device *dev variable. Signed-off-by: Paul Cercueil --- drivers/rtc/rtc-jz4740.c | 30 +++--- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c index 949d395066e2..06ee08089815 100644 --- a/drivers/rtc/rtc-jz4740.c +++ b/drivers/rtc/rtc-jz4740.c @@ -307,11 +307,12 @@ MODULE_DEVICE_TABLE(of, jz4740_rtc_of_match); static int jz4740_rtc_probe(struct platform_device *pdev) { + struct device *dev = >dev; + struct device_node *np = dev->of_node; int ret; struct jz4740_rtc *rtc; - struct device_node *np = pdev->dev.of_node; - rtc = devm_kzalloc(>dev, sizeof(*rtc), GFP_KERNEL); + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; @@ -325,9 +326,9 @@ static int jz4740_rtc_probe(struct platform_device *pdev) if (IS_ERR(rtc->base)) return PTR_ERR(rtc->base); - rtc->clk = devm_clk_get(>dev, "rtc"); + rtc->clk = devm_clk_get(dev, "rtc"); if (IS_ERR(rtc->clk)) { - dev_err(>dev, "Failed to get RTC clock\n"); + dev_err(dev, "Failed to get RTC clock\n"); return PTR_ERR(rtc->clk); } @@ -335,18 +336,18 @@ static int jz4740_rtc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, rtc); - device_init_wakeup(>dev, 1); + device_init_wakeup(dev, 1); - ret = dev_pm_set_wake_irq(>dev, rtc->irq); + ret = dev_pm_set_wake_irq(dev, rtc->irq); if (ret) { - dev_err(>dev, "Failed to set wake irq: %d\n", ret); + dev_err(dev, "Failed to set wake irq: %d\n", ret); return ret; } - rtc->rtc = devm_rtc_allocate_device(>dev); + rtc->rtc = devm_rtc_allocate_device(dev); if (IS_ERR(rtc->rtc)) { ret = PTR_ERR(rtc->rtc); - dev_err(>dev, "Failed to allocate rtc device: %d\n", ret); + dev_err(dev, "Failed to allocate rtc device: %d\n", ret); return ret; } @@ -357,10 +358,10 @@ static int jz4740_rtc_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_request_irq(>dev, rtc->irq, jz4740_rtc_irq, 0, - pdev->name, rtc); + ret = devm_request_irq(dev, rtc->irq, jz4740_rtc_irq, 0, + pdev->name, rtc); if (ret) { - dev_err(>dev, "Failed to request rtc irq: %d\n", ret); + dev_err(dev, "Failed to request rtc irq: %d\n", ret); return ret; } @@ -378,11 +379,10 @@ static int jz4740_rtc_probe(struct platform_device *pdev) "ingenic,min-wakeup-pin-assert-time-ms", >min_wakeup_pin_assert_time); - dev_for_power_off = >dev; + dev_for_power_off = dev; pm_power_off = jz4740_rtc_power_off; } else { - dev_warn(>dev, -"Poweroff handler already present!\n"); + dev_warn(dev, "Poweroff handler already present!\n"); } } -- 2.26.2
Re: [PATCH] drm: ingenic-drm: add MODULE_DEVICE_TABLE
Hi Nikolaus, Le lun. 4 mai 2020 à 8:35, H. Nikolaus Schaller a écrit : so that the driver can load by matching the device tree if compiled as module. Cc: sta...@vger.kernel.org # v5.3+ Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs") Signed-off-by: H. Nikolaus Schaller Applied, thanks. -Paul --- drivers/gpu/drm/ingenic/ingenic-drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 9dfe7cb530e11..1754c05470690 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -843,6 +843,7 @@ static const struct of_device_id ingenic_drm_of_match[] = { { .compatible = "ingenic,jz4770-lcd", .data = _soc_info }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, ingenic_drm_of_match); static struct platform_driver ingenic_drm_driver = { .driver = { -- 2.26.2
Re: [RFC v3 2/8] drm: ingenic-drm: add MODULE_DEVICE_TABLE
Hi Nikolaus, Le dim. 29 mars 2020 à 19:35, H. Nikolaus Schaller a écrit : so that the driver can load by matching the device tree if compiled as module. Signed-off-by: H. Nikolaus Schaller Please add: Cc: sta...@vger.kernel.org # v5.3+ Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs") And re-send this patch alone, then I can merge it ASAP. Cheers, -Paul --- drivers/gpu/drm/ingenic/ingenic-drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 6d47ef7b148c..bcba2f024842 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -843,6 +843,7 @@ static const struct of_device_id ingenic_drm_of_match[] = { { .compatible = "ingenic,jz4770-lcd", .data = _soc_info }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, ingenic_drm_of_match); static struct platform_driver ingenic_drm_driver = { .driver = { -- 2.25.1
Re: DRM interaction problems on Ingenic CI20 / jz4780 with dw-hdmi and ingenic-drm
Hi Nikolaus, So I just wrote a HDMI driver for a different chip, I guess I can answer some of your questions now. Le lun. 13 avril 2020 à 13:25, Paul Cercueil a écrit : Hi Nikolaus, Le sam. 11 avril 2020 à 16:14, H. Nikolaus Schaller a écrit : Hi, we (Paul Boddie and me) are working to get HDMI functional on the Ingenic CI20 board with jz4780 SoC which uses a specialization of the dw-hdmi driver. So far we have identified two issues. The first is that HPD interrupts are not properly processed. drm_helper_hpd_irq_event() is called by HPD events but dev->mode_config.poll_enabled is false. This is to be used when there's no hardware interrupt. I believe you have one, right? Then call drm_kms_helper_hotplug_event() from the interrupt handler instead. Therefore the interrupt is ignored and nothing happens. Now I wonder about the logic behind checking for poll_enabled. I understand that a driver can do either polling or irq or both. Therefore handling the irq_event shouldn't be disabled by poll_enabled being false. Otherwise we can only do: nothing, polling, polling+irq but not irq alone. The jz4780 hdmi subsystem (drm/bridge/dw-hdmi.c) uses connector->polled = DRM_CONNECTOR_POLL_HPD; but shouldn't this enable polling? Note that there seems to be no (direct) call to drm_kms_helper_poll_init(). If we set dev->mode_config.poll_enabled = true in drm_helper_hpd_irq_event() things start to work. Please can you clarify what would be best practise here to get HPD event handling working. Remove that - this stuff is for hardware without interrupts, where everything has to be polled. The other issue is in dw-hdmi.c: We found out that ingenic_drm_encoder_atomic_check() fails because info->num_bus_formats == 0 and not 1. This blocks further initialization. The reason seems to be that dw_hdmi_bridge_attach() does not call drm_display_info_set_bus_formats() with a proper format like other drivers (e.g. drm/bridge/ti-tfp410.c) are doing. We have patched to set a single bus format MEDIA_BUS_FMT_RGB888_1X24 and then DRM setup seems to work (although we still have no valid HDMI signal but that is likely something else). Please can you explain how setting the bus format should be fixed in dw-hdmi.c. I'm not sure, but that information may come from EDID data. Are you able to obtain video modes from the connected monitor? -Paul If these questions should be forwarded to other specialists, please do so. It should be sent to the DRI mailing list, you missed the most important one. -Paul BR and thanks, Nikolaus Schaller
[PATCH] gpu/drm: ingenic: Fix bogus crtc_atomic_check callback
The code was comparing the SoC's maximum height with the mode's width, and vice-versa. D'oh. Cc: sta...@vger.kernel.org # v5.6+ Fixes: a7c909b7c037 ("gpu/drm: ingenic: Check for display size in CRTC atomic check") Signed-off-by: Paul Cercueil --- drivers/gpu/drm/ingenic/ingenic-drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c index 9dfe7cb530e1..5f19e07c152e 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c @@ -328,8 +328,8 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc, if (!drm_atomic_crtc_needs_modeset(state)) return 0; - if (state->mode.hdisplay > priv->soc_info->max_height || - state->mode.vdisplay > priv->soc_info->max_width) + if (state->mode.hdisplay > priv->soc_info->max_width || + state->mode.vdisplay > priv->soc_info->max_height) return -EINVAL; rate = clk_round_rate(priv->pix_clk, -- 2.26.2
[PATCH] pinctrl: ingenic: Add irq_{request,release}_resources callbacks
These are called when a GPIO is to be used as IRQ. Without these custom callbacks, when an interrupt is requested directly and not through gpiod_to_irq(), the request fails because the GPIO is not necesarily in input mode. These callbacks simply enforce that the requested GPIO is in input mode. Signed-off-by: Paul Cercueil --- drivers/pinctrl/pinctrl-ingenic.c | 21 + 1 file changed, 21 insertions(+) diff --git a/drivers/pinctrl/pinctrl-ingenic.c b/drivers/pinctrl/pinctrl-ingenic.c index 96f04d121ebd..f2b95ee31ffe 100644 --- a/drivers/pinctrl/pinctrl-ingenic.c +++ b/drivers/pinctrl/pinctrl-ingenic.c @@ -1933,6 +1933,25 @@ static const struct pinctrl_ops ingenic_pctlops = { .dt_free_map = pinconf_generic_dt_free_map, }; +static int ingenic_gpio_irq_request(struct irq_data *data) +{ + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data); + int ret; + + ret = ingenic_gpio_direction_input(gpio_chip, data->hwirq); + if (ret) + return ret; + + return gpiochip_reqres_irq(gpio_chip, data->hwirq); +} + +static void ingenic_gpio_irq_release(struct irq_data *data) +{ + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(data); + + return gpiochip_relres_irq(gpio_chip, data->hwirq); +} + static int ingenic_pinmux_set_pin_fn(struct ingenic_pinctrl *jzpc, int pin, int func) { @@ -2296,6 +2315,8 @@ static int __init ingenic_gpio_probe(struct ingenic_pinctrl *jzpc, jzgc->irq_chip.irq_ack = ingenic_gpio_irq_ack; jzgc->irq_chip.irq_set_type = ingenic_gpio_irq_set_type; jzgc->irq_chip.irq_set_wake = ingenic_gpio_irq_set_wake; + jzgc->irq_chip.irq_request_resources = ingenic_gpio_irq_request; + jzgc->irq_chip.irq_release_resources = ingenic_gpio_irq_release; jzgc->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND; girq = >gc.irq; -- 2.26.2
Re: [PATCH v7 01/12] dt-bindings: add img,pvrsgx.yaml for Imagination GPUs
Le dim. 3 mai 2020 à 15:31, H. Nikolaus Schaller a écrit : Hi Paul, Am 03.05.2020 um 14:52 schrieb Paul Cercueil : It's possible to forbid the presence of the 'clocks' property on some implementations, and require it on others. To be precise we have to specify the exact number of clocks (between 0 and 4) for every architecture. This also contradicts my dream to get rid of the architecture specific components in the long run. My dream (because I can't tell how it can be done) is that we can one day develop something which just needs compatible = img,530 or imp,540 or img,544. Then we can't make the number clocks depend on the implementation any more. As we said before, the number of clocks is a property of the GPU and *not* its integration into the SoC. Well, it is a not very well documented property of the GPU. We have no data sheet of the standalone GPU. Only several SoC data sheets which give some indications. Maybe we can nicely ask them? I expect Paul Burton to have some contacts at ImgTec. Asking for a doc would be too much, but maybe they can help a bit with the DT bindings. It appears as if some sgx5xx versions have 3 clocks and some have 4. So you are right, the number of clocks depends on the sgx5xx version and that could be made dependent in the bindings (if necessary). So you would *not* have a number of clocks between 0 and 4. You get either 0, or 4, depending on whether or not you have a wrapper. I think this is contradicting your previous sentence. If the number of clocks is a property of the GPU and not the integration it must also not depend on whether there is a wrapper. I.e. it must be a constant for any type of integration. Well, I expected all SGX versions to have 4 clocks. If some SGX versions have 3 clocks, and others have 4 clocks, it's still OK as long as the number of clocks is enforced, so that all implementations of a given SGX core will have to use the same number of clocks. The really correct variant would be to always make the SoC integration (wrapper) a separate subsystem (because it is never part of the SGX core but some interface bus) and clock provider and connect it explicitly to the clock inputs. About the wrapper... I don't really know how it's done there. But you could very well pass all clocks unconditionally to the SGX node, even if it's inside a wrapper. The wrapper itself probably needs only one clock, the one that allows it to access its registers. To be clear: I am not at all against describing the clocks. I just doubt that the time we invest into discussing on this level of detail and adding conditional clock requirements is worth the result. IMHO the bindings and .dts do not become better by describing them in more detail than just "optional". It just takes our time from contributing to other subsystems. You have a new SoC with a SGX, and you only need to enable one clock to get it to work. So you create a devicetree node which receives only one clock. Turns out, that the bootloader was enabling the other 3 clocks, and since the last release, it doesn't anymore. You're left with having to support a broken devicetree. That's the kind of problem that can be easily avoided by enforcing the number of clocks that have to be provided. See how it's done for instance on Documentation/devicetree/bindings/serial/samsung_uart.yaml. Yes I know the design pattern, but I wonder if such a move makes the whole thing even less maintainable. Assume we have finished DTS for some SoC. Then these DTS have been tested on real hardware and are working. Clocks are there where needed and missing where not. We may now forbid or not forbid them for some implementations in the bindings.yaml but the result of dtbs_check won't change! Because they are tested and working and the bindings.yaml has been adapted to the result. So we have just duplicated something for no practical benefit. Next, assume there is coming support for more and more new SoC. Then, developers not only have to figure out which clocks they need in the DTS but they also have to add a patch to the implementation specific part of the bindings.yaml to clearly define exactly the same what they already have written into their .dts (the clocks are either there for the of_node or they are not). So again the rules are for no benefit, since a new SoC is introduced exactly once. And tested if it works. And if it is there, it will stay as it is. It is just work for maintainers to review that patch as well. If you add support for a new SoC, you'd still need to modify the binding to add the compatible string. So the argument of "more work" is moot. Agreed, I forgot this aspect. Nevertheless, it is easier to review a new compatible string than a new clock number rule (question: how do you practically review this? By looking if it does match the DTS?). We have to add the compatible string as lon
Re: [PATCH v7 01/12] dt-bindings: add img,pvrsgx.yaml for Imagination GPUs
Hi Nikolaus, Le sam. 2 mai 2020 à 22:26, H. Nikolaus Schaller a écrit : Hi Paul, Am 26.04.2020 um 15:11 schrieb Paul Cercueil : Hi Nikolaus, Le ven. 24 avril 2020 à 22:34, H. Nikolaus Schaller a écrit : The Imagination PVR/SGX GPU is part of several SoC from multiple vendors, e.g. TI OMAP, Ingenic JZ4780, Intel Poulsbo, Allwinner A83 and others. With this binding, we describe how the SGX processor is interfaced to the SoC (registers and interrupt). The interface also consists of clocks, reset, power but information from data sheets is vague and some SoC integrators (TI) deciced to use a PRCM wrapper (ti,sysc) which does all clock, reset and power-management through registers outside of the sgx register block. Therefore all these properties are optional. Tested by make dt_binding_check Signed-off-by: H. Nikolaus Schaller --- .../devicetree/bindings/gpu/img,pvrsgx.yaml | 150 ++ 1 file changed, 150 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpu/img,pvrsgx.yaml diff --git a/Documentation/devicetree/bindings/gpu/img,pvrsgx.yaml b/Documentation/devicetree/bindings/gpu/img,pvrsgx.yaml new file mode 100644 index ..33a9c4c6e784 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/img,pvrsgx.yaml @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpu/img,pvrsgx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Imagination PVR/SGX GPU + +maintainers: + - H. Nikolaus Schaller + +description: |+ + This binding describes the Imagination SGX5 series of 3D accelerators which + are found in several different SoC like TI OMAP, Sitara, Ingenic JZ4780, + Allwinner A83, and Intel Poulsbo and CedarView and more. + + For an extensive list see: https://en.wikipedia.org/wiki/PowerVR#Implementations + + The SGX node is usually a child node of some DT node belonging to the SoC + which handles clocks, reset and general address space mapping of the SGX + register area. If not, an optional clock can be specified here. + +properties: + $nodename: +pattern: '^gpu@[a-f0-9]+$' + compatible: +oneOf: + - description: SGX530-121 based SoC +items: + - enum: +- ti,omap3-sgx530-121 # BeagleBoard A/B/C, OpenPandora 600MHz and similar + - const: img,sgx530-121 + - const: img,sgx530 + + - description: SGX530-125 based SoC +items: + - enum: +- ti,am3352-sgx530-125 # BeagleBone Black +- ti,am3517-sgx530-125 +- ti,am4-sgx530-125 +- ti,omap3-sgx530-125 # BeagleBoard XM, GTA04, OpenPandora 1GHz and similar +- ti,ti81xx-sgx530-125 + - const: ti,omap3-sgx530-125 + - const: img,sgx530-125 + - const: img,sgx530 + + - description: SGX535-116 based SoC +items: + - const: intel,poulsbo-gma500-sgx535 # Atom Z5xx + - const: img,sgx535-116 + - const: img,sgx535 + + - description: SGX540-116 based SoC +items: + - const: intel,medfield-gma-sgx540 # Atom Z24xx + - const: img,sgx540-116 + - const: img,sgx540 + + - description: SGX540-120 based SoC +items: + - enum: +- samsung,s5pv210-sgx540-120 +- ti,omap4-sgx540-120 # Pandaboard, Pandaboard ES and similar + - const: img,sgx540-120 + - const: img,sgx540 + + - description: SGX540-130 based SoC +items: + - enum: +- ingenic,jz4780-sgx540-130 # CI20 + - const: img,sgx540-130 + - const: img,sgx540 + + - description: SGX544-112 based SoC +items: + - const: ti,omap4470-sgx544-112 + - const: img,sgx544-112 + - const: img,sgx544 + + - description: SGX544-115 based SoC +items: + - enum: +- allwinner,sun8i-a31-sgx544-115 +- allwinner,sun8i-a31s-sgx544-115 +- allwinner,sun8i-a83t-sgx544-115 # Banana-Pi-M3 (Allwinner A83T) and similar + - const: img,sgx544-115 + - const: img,sgx544 + + - description: SGX544-116 based SoC +items: + - enum: +- ti,dra7-sgx544-116 # DRA7 +- ti,omap5-sgx544-116 # OMAP5 UEVM, Pyra Handheld and similar + - const: img,sgx544-116 + - const: img,sgx544 + + - description: SGX545 based SoC +items: + - const: intel,cedarview-gma3600-sgx545 # Atom N2600, D2500 + - const: img,sgx545-116 + - const: img,sgx545 + + reg: +maxItems: 1 + + interrupts: +maxItems: 1 + + interrupt-names: +maxItems: 1 +items: + - const: sgx + + clocks: +maxItems: 4 + + clock-names
Re: [PATCH 07/13] MIPS: ingenic: DTS: Respect cell count of common properties
Hi Thomas, Le mer. 29 avril 2020 à 23:04, Thomas Bogendoerfer a écrit : On Mon, Apr 13, 2020 at 05:26:27PM +0200, Paul Cercueil wrote: If N fields of X cells should be provided, then that's what the devicetree should represent, instead of having one single field of (N*X) cells. Signed-off-by: Paul Cercueil --- arch/mips/boot/dts/ingenic/jz4740.dtsi | 19 +-- arch/mips/boot/dts/ingenic/jz4770.dtsi | 12 +--- arch/mips/boot/dts/ingenic/jz4780.dtsi | 24 +++- arch/mips/boot/dts/ingenic/x1000.dtsi | 9 - 4 files changed, 29 insertions(+), 35 deletions(-) applied to mips-next. Please check if my resolution of the conflict in arch/mips/boot/dts/ingenic/jz4780.dtsi is correct. Looks good, thanks. -Paul Thomas. -- Crap can work. Given enough thrust pigs will fly, but it's not necessarily a good idea.[ RFC1925, 2.3 ]
Re: [PATCH v7 08/12] arm: dts: s5pv210: Add node for SGX 540
Hi Jonathan, Le mar. 28 avril 2020 à 15:58, Jonathan Bakker a écrit : Hi all, On 2020-04-28 2:39 p.m., Jonathan Bakker wrote: Hi Krzysztof, On 2020-04-27 8:46 a.m., Krzysztof Kozlowski wrote: On Sun, Apr 26, 2020 at 07:57:12AM -0700, Jonathan Bakker wrote: Hi Paul, On 2020-04-26 5:56 a.m., Paul Cercueil wrote: Le ven. 24 avril 2020 à 22:34, H. Nikolaus Schaller a écrit : From: Jonathan Bakker All s5pv210 devices have a PowerVR SGX 540 (revision 120) attached. There is no external regulator for it so it can be enabled by default. Signed-off-by: Jonathan Bakker Signed-off-by: H. Nikolaus Schaller --- arch/arm/boot/dts/s5pv210.dtsi | 13 + 1 file changed, 13 insertions(+) diff --git a/arch/arm/boot/dts/s5pv210.dtsi b/arch/arm/boot/dts/s5pv210.dtsi index 2ad642f51fd9..abbdda205c1b 100644 --- a/arch/arm/boot/dts/s5pv210.dtsi +++ b/arch/arm/boot/dts/s5pv210.dtsi @@ -512,6 +512,19 @@ vic3: interrupt-controller@f230 { #interrupt-cells = <1>; }; +gpu: gpu@f300 { +compatible = "samsung,s5pv210-sgx540-120"; This should not pass the bindings check because you missed last compatibles. Thanks for pointing that out, I'll add it and make sure it passes the bindings check. +reg = <0xf300 0x1>; +interrupt-parent = <>; +interrupts = <10>; +clock-names = "core"; +clocks = < CLK_G3D>; + +assigned-clocks = < MOUT_G3D>, < DOUT_G3D>; +assigned-clock-rates = <0>, <6670>; +assigned-clock-parents = < MOUT_MPLL>; What are these clocks for, and why are they reparented / reclocked? Shouldn't they be passed to 'clocks' as well? -Paul The G3D clock system can have multiple parents, and for stable operation it's recommended to use the MPLL clock as the parent (which in turn is actually a mux as well). MOUT_G3D is simply the mux for CLK_G3D (SGX core clock), DOUT_G3D is the divider. DOUT_G3D could equally be CLK_G3D (and probably should be, for readability) as CLK_G3D is simply the gate and DOUT_G3D is the divider for it. Good point, it should be CLK_G3D instead of DOUT. Can you fix this as well? Yep, will do. Nikolaus, I'll send you an updated patch to include. How are assigned-clocks handled in the yaml DT schema? When running make dtbs_check, I end up with messages such as arch/arm/boot/dts/s5pv210-aquila.dt.yaml: gpu@f300: 'assigned-clock-parents', 'assigned-clock-rates', 'assigned-clocks' do not match any of the regexes: 'pinctrl-[0-9]+' Do they need to explicitly be listed as valid entries? The assigned-* can also be moved inside the node of the clocks provider. I would say it makes more sense to have them there. -Paul
Re: [PATCH 8/8] dt-bindings: display: Convert ingenic,lcd.txt to YAML
This one patch will need a V2, I messed up with the clocks. -Paul Le dim. 26 avril 2020 à 20:58, Paul Cercueil a écrit : Convert the ingenic,lcd.txt to a new ingenic,lcd.yaml file. In the process, the new ingenic,jz4780-lcd compatible string has been added. Signed-off-by: Paul Cercueil --- .../bindings/display/ingenic,lcd.txt | 45 --- .../bindings/display/ingenic,lcd.yaml | 113 ++ 2 files changed, 113 insertions(+), 45 deletions(-) delete mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.txt create mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.yaml diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.txt b/Documentation/devicetree/bindings/display/ingenic,lcd.txt deleted file mode 100644 index 01e3261defb6.. --- a/Documentation/devicetree/bindings/display/ingenic,lcd.txt +++ /dev/null @@ -1,45 +0,0 @@ -Ingenic JZ47xx LCD driver - -Required properties: -- compatible: one of: - * ingenic,jz4740-lcd - * ingenic,jz4725b-lcd - * ingenic,jz4770-lcd -- reg: LCD registers location and length -- clocks: LCD pixclock and device clock specifiers. - The device clock is only required on the JZ4740. -- clock-names: "lcd_pclk" and "lcd" -- interrupts: Specifies the interrupt line the LCD controller is connected to. - -Example: - -panel { - compatible = "sharp,ls020b1dd01d"; - - backlight = <>; - power-supply = <>; - - port { - panel_input: endpoint { - remote-endpoint = <_output>; - }; - }; -}; - - -lcd: lcd-controller@1305 { - compatible = "ingenic,jz4725b-lcd"; - reg = <0x1305 0x1000>; - - interrupt-parent = <>; - interrupts = <31>; - - clocks = < JZ4725B_CLK_LCD>; - clock-names = "lcd"; - - port { - panel_output: endpoint { - remote-endpoint = <_input>; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.yaml b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml new file mode 100644 index ..8e9c851dc7c5 --- /dev/null +++ b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/ingenic,lcd.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs LCD controller devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + $nodename: +pattern: "^lcd-controller@[0-9a-f]+$" + + compatible: +enum: + - ingenic,jz4740-lcd + - ingenic,jz4725b-lcd + - ingenic,jz4770-lcd + - ingenic,jz4780-lcd + + reg: +maxItems: 1 + + interrupts: +maxItems: 1 + + clocks: +items: + - description: Module clock + - description: Pixel clock +minItems: 1 + + clock-names: +items: + - const: lcd + - const: lcd_pclk +minItems: 1 + + port: +type: object +description: + A port node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt + +required: +- compatible +- reg +- interrupts +- clocks +- clock-names + +if: + properties: +compatible: + contains: +enum: + - ingenic,jz4740-lcd + - ingenic,jz4780-lcd +then: + properties: +clocks: + minItems: 2 +clock-names: + minItems: 2 +else: + properties: +clocks: + maxItems: 1 +clock-names: + maxItems: 1 + +additionalProperties: false + +examples: + - | +#include +lcd-controller@1305 { + compatible = "ingenic,jz4740-lcd"; + reg = <0x1305 0x1000>; + + interrupt-parent = <>; + interrupts = <30>; + + clocks = < JZ4740_CLK_LCD>, < JZ4740_CLK_LCD_PCLK>; + clock-names = "lcd", "lcd_pclk"; + + port { +endpoint { + remote-endpoint = <_input>; +}; + }; +}; + + - | +#include +lcd-controller@1305 { + compatible = "ingenic,jz4725b-lcd"; + reg = <0x1305 0x1000>; + + interrupt-parent = <>; + interrupts = <31>; + + clocks = < JZ4725B_CLK_LCD>; + clock-names = "lcd"; + + port { +endpoint { + remote-endpoint = <_input>; +}; + }; +}; -- 2.26.2
Re: [PATCH 2/8] dt-bindings: intc: Convert ingenic,intc.txt to YAML
Hi Sergei, Le lun. 27 avril 2020 à 12:11, Sergei Shtylyov a écrit : Hello! On 26.04.2020 21:58, Paul Cercueil wrote: Convert the ingenic,intc.txt doc file to ingenic,intc.yaml. Some compatible strings now require a fallback, as the controller generally works the same across the SoCs families. Signed-off-by: Paul Cercueil [...] diff --git a/Documentation/devicetree/bindings/interrupt-controller/ingenic,intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/ingenic,intc.yaml new file mode 100644 index ..28b27e1a6e9d --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/ingenic,intc.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/ingenic,intc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ingenic SoCs interrupt controller devicetree bindings + +maintainers: + - Paul Cercueil + +properties: + $nodename: +pattern: "^interrupt-controller@[0-9a-f]+$" + + compatible: +oneOf: + - enum: +- ingenic,jz4740-intc +- ingenic,jz4760-intc +- ingenic,jz4780-intc + - items: +- enum: + - ingenic,jz4775-intc + - ingenic,jz4770-intc +- const: ingenic,jz4760-intc + - items: +- const: ingenic,x1000-intc +- const: ingenic,jz4780-intc + - items: +- const: ingenic,jz4725b-intc +- const: ingenic,jz4740-intc + + "#interrupt-cells": +const: 1 Do double quotes work the same as the single ones? Yes. The only difference is that you can escape characters in double quotes. -Paul [...] MBR, Sergei
[PATCH v2 2/3] watchdog: jz4740: Use regmap provided by TCU driver
Since we broke the ABI by changing the clock, the driver was also updated to use the regmap provided by the TCU driver. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek Acked-by: Guenter Roeck --- Notes: v2: Rebase on top of 5.4-rc4 drivers/watchdog/Kconfig | 1 + drivers/watchdog/jz4740_wdt.c | 35 --- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 6421187769cf..dbef995856bf 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1644,6 +1644,7 @@ config JZ4740_WDT depends on MACH_JZ4740 || MACH_JZ4780 depends on COMMON_CLK select WATCHDOG_CORE + select MFD_SYSCON help Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs. diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index 72920f09f4a7..bdf9564efa29 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #define DEFAULT_HEARTBEAT 5 #define MAX_HEARTBEAT 2048 @@ -36,7 +38,7 @@ MODULE_PARM_DESC(heartbeat, struct jz4740_wdt_drvdata { struct watchdog_device wdt; - void __iomem *base; + struct regmap *map; struct clk *clk; unsigned long clk_rate; }; @@ -45,7 +47,8 @@ static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); + return 0; } @@ -54,16 +57,16 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); - u8 tcer; + unsigned int tcer; - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); - writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); + regmap_read(drvdata->map, TCU_REG_WDT_TCER, ); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); - writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); - writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); + regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); if (tcer & TCU_WDT_TCER_TCEN) - writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); wdt_dev->timeout = new_timeout; return 0; @@ -72,20 +75,20 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, static int jz4740_wdt_start(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + unsigned int tcer; int ret; - u8 tcer; ret = clk_prepare_enable(drvdata->clk); if (ret) return ret; - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); + regmap_read(drvdata->map, TCU_REG_WDT_TCER, ); jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); /* Start watchdog if it wasn't started already */ if (!(tcer & TCU_WDT_TCER_TCEN)) - writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); return 0; } @@ -94,7 +97,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); clk_disable_unprepare(drvdata->clk); return 0; @@ -172,9 +175,11 @@ static int jz4740_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(jz4740_wdt, nowayout); watchdog_set_drvdata(jz4740_wdt, drvdata); - drvdata->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(drvdata->base)) - return PTR_ERR(drvdata->base); + drvdata->map = device_node_to_regmap(dev->parent->of_node); + if (!drvdata->map) { + dev_err(dev, "regmap not found\n"); + return -EINVAL; + } return devm_watchdog_register_device(dev, >wdt); } -- 2.23.0
[PATCH v2 3/3] watchdog: jz4740: Drop dependency on MACH_JZ47xx
Depending on MACH_JZ47xx prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Signed-off-by: Paul Cercueil Acked-by: Guenter Roeck --- Notes: v2: Rebase on top of 5.4-rc4 drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index dbef995856bf..fd4844f0a8f3 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1641,7 +1641,7 @@ config INDYDOG config JZ4740_WDT tristate "Ingenic jz4740 SoC hardware watchdog" - depends on MACH_JZ4740 || MACH_JZ4780 + depends on MIPS depends on COMMON_CLK select WATCHDOG_CORE select MFD_SYSCON -- 2.23.0
[PATCH v2 1/3] watchdog: jz4740: Use WDT clock provided by TCU driver
Instead of requesting the "ext" clock and handling the watchdog clock divider and gating in the watchdog driver, we now request and use the "wdt" clock that is supplied by the ingenic-timer "TCU" driver. The major benefit is that the watchdog's clock rate and parent can now be specified from within devicetree, instead of hardcoded in the driver. Also, this driver won't poke anymore into the TCU registers to enable/disable the clock, as this is now handled by the TCU driver. On the bad side, we break the ABI with devicetree - as we now request a different clock. In this very specific case it is still okay, as every Ingenic JZ47xx-based board out there compile the devicetree within the kernel; so it's still time to push breaking changes, in order to get a clean devicetree that won't break once it musn't. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek Acked-by: Guenter Roeck --- Notes: v2: Rebase on top of 5.4-rc4 drivers/watchdog/Kconfig | 1 + drivers/watchdog/jz4740_wdt.c | 75 ++- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 58e7c100b6ad..6421187769cf 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1642,6 +1642,7 @@ config INDYDOG config JZ4740_WDT tristate "Ingenic jz4740 SoC hardware watchdog" depends on MACH_JZ4740 || MACH_JZ4780 + depends on COMMON_CLK select WATCHDOG_CORE help Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs. diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index c6052ae54f32..72920f09f4a7 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -18,19 +18,6 @@ #include #include -#include - -#define JZ_WDT_CLOCK_PCLK 0x1 -#define JZ_WDT_CLOCK_RTC 0x2 -#define JZ_WDT_CLOCK_EXT 0x4 - -#define JZ_WDT_CLOCK_DIV_1(0 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_4(1 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_16 (2 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_64 (3 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_256 (4 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_1024 (5 << TCU_TCSR_PRESCALE_LSB) - #define DEFAULT_HEARTBEAT 5 #define MAX_HEARTBEAT 2048 @@ -50,7 +37,8 @@ MODULE_PARM_DESC(heartbeat, struct jz4740_wdt_drvdata { struct watchdog_device wdt; void __iomem *base; - struct clk *rtc_clk; + struct clk *clk; + unsigned long clk_rate; }; static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) @@ -65,32 +53,14 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int new_timeout) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - unsigned int rtc_clk_rate; - unsigned int timeout_value; - unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; + u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); u8 tcer; - rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); - - timeout_value = rtc_clk_rate * new_timeout; - while (timeout_value > 0x) { - if (clock_div == JZ_WDT_CLOCK_DIV_1024) { - /* Requested timeout too high; - * use highest possible value. */ - timeout_value = 0x; - break; - } - timeout_value >>= 2; - clock_div += (1 << TCU_TCSR_PRESCALE_LSB); - } - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); - writew(clock_div, drvdata->base + TCU_REG_WDT_TCSR); writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); - writew(clock_div | JZ_WDT_CLOCK_RTC, drvdata->base + TCU_REG_WDT_TCSR); if (tcer & TCU_WDT_TCER_TCEN) writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); @@ -102,11 +72,15 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, static int jz4740_wdt_start(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + int ret; u8 tcer; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + tcer = readb(drvdata->base + TCU_REG_WDT_TCER); - jz4740_timer_enable_watchdog(); jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); /* Start watchdog if it wasn't started already */ @@ -121,7 +95,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_de
Re: [PATCH RESEND 2/2] dmaengine: JZ4780: Add support for the X1000.
Hi Zhou, Le mer., oct. 23, 2019 at 15:02, Zhou Yanjie a écrit : Add support for probing the dma-jz4780 driver on the X1000 Soc. Signed-off-by: Zhou Yanjie --- drivers/dma/dma-jz4780.c | 7 +++ 1 file changed, 7 insertions(+) diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c index cafb1cc0..f809a6e 100644 --- a/drivers/dma/dma-jz4780.c +++ b/drivers/dma/dma-jz4780.c @@ -1019,11 +1019,18 @@ static const struct jz4780_dma_soc_data jz4780_dma_soc_data = { .flags = JZ_SOC_DATA_ALLOW_LEGACY_DT | JZ_SOC_DATA_PROGRAMMABLE_DMA, }; +static const struct jz4780_dma_soc_data x1000_dma_soc_data = { + .nb_channels = 8, + .transfer_ord_max = 7, + .flags = JZ_SOC_DATA_ALLOW_LEGACY_DT | JZ_SOC_DATA_PROGRAMMABLE_DMA, Please don't use JZ_SOC_DATA_ALLOW_LEGACY_DT for new bindings. With that flag removed: Reviewed-by: Paul Cercueil +}; + static const struct of_device_id jz4780_dma_dt_match[] = { { .compatible = "ingenic,jz4740-dma", .data = _dma_soc_data }, { .compatible = "ingenic,jz4725b-dma", .data = _dma_soc_data }, { .compatible = "ingenic,jz4770-dma", .data = _dma_soc_data }, { .compatible = "ingenic,jz4780-dma", .data = _dma_soc_data }, + { .compatible = "ingenic,x1000-dma", .data = _dma_soc_data }, {}, }; MODULE_DEVICE_TABLE(of, jz4780_dma_dt_match); -- 2.7.4
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Hi, Le mer., août 14, 2019 at 19:32, Uwe Kleine-König a écrit : Hello Paul, On Wed, Aug 14, 2019 at 06:10:35PM +0200, Paul Cercueil wrote: Le mar. 13 août 2019 à 16:09, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : > On Tue, Aug 13, 2019 at 02:47:28PM +0200, Paul Cercueil wrote: > > Le mar. 13 août 2019 à 14:33, Uwe Kleine-König a écrit : > > > On Tue, Aug 13, 2019 at 01:01:06PM +0200, Paul Cercueil wrote: > > > > Well, you said that I shouln't rely on the fact that clk_round_rate() will > > > > round down. That completely defeats the previous algorithm. So please tell > > > > me how to use it correctly, because I don't see it. > > > > > > Using clk_round_rate correctly without additional knowledge is hard. If > > > you assume at least some sane behaviour you'd still have to call it > > > multiple times. Assuming maxrate is the maximal rate you can handle > > > without overflowing your PWM registers you have to do: > > > > > > rate = maxrate; > > > rounded_rate = clk_round_rate(clk, rate); > > > while (rounded_rate > rate) { > > > if (rate < rounded_rate - rate) { > > > /* > > > * clk doesn't support a rate smaller than > > > * maxrate (or the round_rate callback doesn't > > > * round consistently). > > > */ > > > return -ESOMETHING; > > > } > > > rate = rate - (rounded_rate - rate) > > > rounded_rate = clk_round_rate(clk, rate); > > > } > > > > > > return rate; > > > > > > Probably it would be sensible to put that in a function provided by the > > > clk framework (maybe call it clk_round_rate_down and maybe with > > > additional checks). > > > > clk_round_rate_down() has been refused multiple times in the past for > > reasons that Stephen can explain. > > I'd be really interested in these reasons as I think the clk framework > should make it easy to solve common tasks related to clocks. And finding > out the biggest supported rate not bigger than a given maxrate is > something I consider such a common task. > > The first hit I found when searching was > https://lkml.org/lkml/2010/7/14/260 . In there Stephen suggested that > clk_round_rate with the current semantic is hardly useful and suggested > clk_round_rate_up() and clk_round_rate_down() himself. That's from 2010, though. If you have a better link please tell me. I agree that clk_round_rate_up() and clk_round_rate_down() should exist. Even if they return -ENOSYS if it's not implemented for a given clock controller. ack. > > > > I came up with a much smarter alternative, that doesn't rely on the rounding > > > > method of clk_round_rate, and which is better overall (no loop needed). It > > > > sounds to me like you're bashing the code without making the effort to > > > > understand what it does. > > > > > > > > Thierry called it a "neat trick" > > > > (https://patchwork.kernel.org/patch/10836879/) so it cannot be as bad as you > > > > say. > > > > > > Either that or Thierry failed to see the downside. The obvious downside > > > is that once you set the period to something long (and so the clk was > > > limited to a small frequency) you never make the clock any faster > > > afterwards. > > > > Read the algorithm again. > > I indeed missed a call to clk_set_rate(clk, parent_rate). I thought I > grepped for clk_set_rate before claiming the code was broken. Sorry. > > So I think the code works indeed, but it feels like abusing > clk_set_max_rate. So I'd like to see some words from Stephen about this > procedure. > > Also I think this is kind of inelegant to set the maximal rate twice. At > least call clk_set_max_rate only once please. Ok. I can do that. I would still prefer to hear from Stephen about this approach. It seems wrong to have two different ways to achieve the same goal and my impression is that clk_round_rate is the function designed for this use case. Stephen, any feedback? I'm still stuck here. > > > > > > > > E.g. if at a rate of 12 MHz your computed hardware value for the period > > > > > > > > is 0xf000, then at a rate of 24 MHz it won't fit in 16 bits. So
Re: [PATCH 2/2] clk: Ingenic: Add CGU driver for X1000.
Hi Zhou, Le sam., oct. 19, 2019 at 01:50, Zhou Yanjie a écrit : Add support for the clocks provided by the CGU in the Ingenic X1000 SoC, making use of the cgu code to do the heavy lifting. Signed-off-by: Zhou Yanjie --- drivers/clk/ingenic/Kconfig | 10 ++ drivers/clk/ingenic/Makefile| 1 + drivers/clk/ingenic/x1000-cgu.c | 253 3 files changed, 264 insertions(+) create mode 100644 drivers/clk/ingenic/x1000-cgu.c diff --git a/drivers/clk/ingenic/Kconfig b/drivers/clk/ingenic/Kconfig index fe8db93..2aebf0d 100644 --- a/drivers/clk/ingenic/Kconfig +++ b/drivers/clk/ingenic/Kconfig @@ -45,4 +45,14 @@ config INGENIC_CGU_JZ4780 If building for a JZ4780 SoC, you want to say Y here. +config INGENIC_CGU_X1000 + bool "Ingenic X1000 CGU driver" + default MACH_X1000 + select INGENIC_CGU_COMMON + help + Support the clocks provided by the CGU hardware on Ingenic X1000 + and compatible SoCs. + + If building for a X1000 SoC, you want to say Y here. + endmenu diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile index 250570a..0f0e784 100644 --- a/drivers/clk/ingenic/Makefile +++ b/drivers/clk/ingenic/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_INGENIC_CGU_JZ4740)+= jz4740-cgu.o obj-$(CONFIG_INGENIC_CGU_JZ4725B) += jz4725b-cgu.o obj-$(CONFIG_INGENIC_CGU_JZ4770) += jz4770-cgu.o obj-$(CONFIG_INGENIC_CGU_JZ4780) += jz4780-cgu.o +obj-$(CONFIG_INGENIC_CGU_X1000)+= x1000-cgu.o diff --git a/drivers/clk/ingenic/x1000-cgu.c b/drivers/clk/ingenic/x1000-cgu.c new file mode 100644 index ..c9d744c --- /dev/null +++ b/drivers/clk/ingenic/x1000-cgu.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * X1000 SoC CGU driver + * Copyright (c) 2019 Zhou Yanjie + */ + +#include +#include +#include +#include +#include "cgu.h" + +/* CGU register offsets */ +#define CGU_REG_CPCCR 0x00 +#define CGU_REG_APLL 0x10 +#define CGU_REG_MPLL 0x14 +#define CGU_REG_CLKGR 0x20 +#define CGU_REG_OPCR 0x24 +#define CGU_REG_DDRCDR 0x2c +#define CGU_REG_MACPHYCDR 0x54 +#define CGU_REG_I2SCDR 0x60 +#define CGU_REG_LPCDR 0x64 +#define CGU_REG_MSC0CDR0x68 +#define CGU_REG_I2SCDR10x70 +#define CGU_REG_SSICDR 0x74 +#define CGU_REG_CIMCDR 0x7c +#define CGU_REG_PCMCDR 0x84 +#define CGU_REG_MSC1CDR0xa4 +#define CGU_REG_CMP_INTR 0xb0 +#define CGU_REG_CMP_INTRE 0xb4 +#define CGU_REG_DRCG 0xd0 +#define CGU_REG_CLOCKSTATUS0xd4 +#define CGU_REG_PCMCDR10xe0 +#define CGU_REG_MACPHYC0xe8 + +/* bits within the OPCR register */ +#define OPCR_SPENDN0 (1 << 7) +#define OPCR_SPENDN1 (1 << 6) Please use the BIT() macro from + +static struct ingenic_cgu *cgu; + +static const s8 pll_od_encoding[8] = { + 0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3, +}; + +static const struct ingenic_cgu_clk_info x1000_cgu_clocks[] = { + + /* External clocks */ + + [X1000_CLK_EXCLK] = { "ext", CGU_CLK_EXT }, + [X1000_CLK_RTCLK] = { "rtc", CGU_CLK_EXT }, + + /* PLLs */ + + [X1000_CLK_APLL] = { + "apll", CGU_CLK_PLL, + .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, + .pll = { + .reg = CGU_REG_APLL, + .m_shift = 24, + .m_bits = 7, + .m_offset = 1, + .n_shift = 18, + .n_bits = 5, + .n_offset = 1, + .od_shift = 16, + .od_bits = 2, + .od_max = 8, + .od_encoding = pll_od_encoding, + .bypass_bit = 9, + .enable_bit = 8, + .stable_bit = 10, + }, + }, + + [X1000_CLK_MPLL] = { + "mpll", CGU_CLK_PLL, + .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, + .pll = { + .reg = CGU_REG_MPLL, + .m_shift = 24, + .m_bits = 7, + .m_offset = 1, + .n_shift = 18, + .n_bits = 5, + .n_offset = 1, + .od_shift = 16, + .od_bits = 2, + .od_max = 8, + .od_encoding = pll_od_encoding, + .bypass_bit = 6, + .enable_bit = 7, + .stable_bit = 0, + }, + }, + + /* Muxes & dividers */ + + [X1000_CLK_SCLKA] = { + "sclk_a", CGU_CLK_MUX, + .parents = { -1, X1000_CLK_EXCLK, X1000_CLK_APLL, -1 }, + .mux = {
Re: [PATCH 6/6 v2] MMC: JZ4740: Add support for LPM.
Hi Uffe, Le ven., oct. 18, 2019 at 10:52, Ulf Hansson a écrit : On Sat, 12 Oct 2019 at 07:19, Zhou Yanjie wrote: add support for low power mode of Ingenic's MMC/SD Controller. Signed-off-by: Zhou Yanjie I couldn't find a proper coverletter for the series, please provide that next time as it really helps review. Additionally, it seems like you forgot to change the prefix of the patches to "mmc: jz4740" (or at least you chosed upper case letters), but I will take care of that this time. So, I have applied the series for next, thanks! I also have a general question. Should we perhaps rename the driver from jz4740_mmc.c to ingenic.c (and the file for the DT bindings, the Kconfig, etc), as that seems like a more appropriate name? No? Is there a kernel policy regarding renaming drivers? Since it trashes the git history. Anyway you're the subsystem maintainer so I guess that's up to you. I can send a patch to rename it if you want. Cheers, -Paul Kind regards Uffe --- drivers/mmc/host/jz4740_mmc.c | 23 +++ 1 file changed, 23 insertions(+) diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index 44a04fe..4cbe7fb 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -43,6 +43,7 @@ #define JZ_REG_MMC_RESP_FIFO 0x34 #define JZ_REG_MMC_RXFIFO 0x38 #define JZ_REG_MMC_TXFIFO 0x3C +#define JZ_REG_MMC_LPM 0x40 #define JZ_REG_MMC_DMAC0x44 #define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) @@ -102,6 +103,12 @@ #define JZ_MMC_DMAC_DMA_SEL BIT(1) #define JZ_MMC_DMAC_DMA_EN BIT(0) +#defineJZ_MMC_LPM_DRV_RISING BIT(31) +#defineJZ_MMC_LPM_DRV_RISING_QTR_PHASE_DLY BIT(31) +#defineJZ_MMC_LPM_DRV_RISING_1NS_DLY BIT(30) +#defineJZ_MMC_LPM_SMP_RISING_QTR_OR_HALF_PHASE_DLY BIT(29) +#defineJZ_MMC_LPM_LOW_POWER_MODE_EN BIT(0) + #define JZ_MMC_CLK_RATE 2400 enum jz4740_mmc_version { @@ -860,6 +867,22 @@ static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) } writew(div, host->base + JZ_REG_MMC_CLKRT); + + if (real_rate > 2500) { + if (host->version >= JZ_MMC_X1000) { + writel(JZ_MMC_LPM_DRV_RISING_QTR_PHASE_DLY | + JZ_MMC_LPM_SMP_RISING_QTR_OR_HALF_PHASE_DLY | + JZ_MMC_LPM_LOW_POWER_MODE_EN, + host->base + JZ_REG_MMC_LPM); + } else if (host->version >= JZ_MMC_JZ4760) { + writel(JZ_MMC_LPM_DRV_RISING | + JZ_MMC_LPM_LOW_POWER_MODE_EN, + host->base + JZ_REG_MMC_LPM); + } else if (host->version >= JZ_MMC_JZ4725B) + writel(JZ_MMC_LPM_LOW_POWER_MODE_EN, + host->base + JZ_REG_MMC_LPM); + } + return real_rate; } -- 2.7.4
Re: [PATCH] dmaengine: jz4780: Use devm_platform_ioremap_resource() in jz4780_dma_probe()
Hi Vinod, Le lun., oct. 14, 2019 at 12:44, Vinod Koul a écrit : On 24-09-19, 21:32, Paul Cercueil wrote: Hi Markus, Le dim. 22 sept. 2019 à 11:25, Markus Elfring a écrit : > From: Markus Elfring > Date: Sun, 22 Sep 2019 11:18:27 +0200 > > Simplify this function implementation a bit by using > a known wrapper function. > > This issue was detected by using the Coccinelle software. > > Signed-off-by: Markus Elfring Looks good to me. Signed-off-by: Paul Cercueil Did you mean Acked or Reviewed ...?? Definitely. Sorry about that. Reviewed-by: Paul Cercueil -- ~Vinod
Re: [PATCH 5/5 v5] irqchip: Ingenic: Add process for more than one irq at the same time.
Le mer., oct. 2, 2019 at 19:25, Zhou Yanjie a écrit : Add process for the situation that more than one irq is coming to a single chip at the same time. The original code will only respond to the lowest setted bit in JZ_REG_INTC_PENDING, and then exit the interrupt dispatch function. After exiting the interrupt dispatch function, since the second interrupt has not yet responded, the interrupt dispatch function is again entered to process the second interrupt. This creates additional unnecessary overhead, and the more interrupts that occur at the same time, the more overhead is added. The improved method in this patch is to check whether there are still unresponsive interrupts after processing the lowest setted bit interrupt. If there are any, the processing will be processed according to the bit in JZ_REG_INTC_PENDING, and the interrupt dispatch function will be exited until all processing is completed. Signed-off-by: Zhou Yanjie Looks good to me. Reviewed-by: Paul Cercueil --- drivers/irqchip/irq-ingenic.c | 17 +++-- 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-ingenic.c b/drivers/irqchip/irq-ingenic.c index 06ab3ad..c1be3d5 100644 --- a/drivers/irqchip/irq-ingenic.c +++ b/drivers/irqchip/irq-ingenic.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2009-2010, Lars-Peter Clausen - * JZ4740 platform IRQ support + * Ingenic XBurst platform IRQ support */ #include @@ -37,18 +37,23 @@ static irqreturn_t intc_cascade(int irq, void *data) struct ingenic_intc_data *intc = irq_get_handler_data(irq); struct irq_domain *domain = intc->domain; struct irq_chip_generic *gc; - uint32_t irq_reg; + uint32_t pending; unsigned i; for (i = 0; i < intc->num_chips; i++) { gc = irq_get_domain_generic_chip(domain, i * 32); - irq_reg = irq_reg_readl(gc, JZ_REG_INTC_PENDING); - if (!irq_reg) + pending = irq_reg_readl(gc, JZ_REG_INTC_PENDING); + if (!pending) continue; - irq = irq_find_mapping(domain, __fls(irq_reg) + (i * 32)); - generic_handle_irq(irq); + while (pending) { + int bit = __fls(pending); + + irq = irq_find_mapping(domain, bit + (i * 32)); + generic_handle_irq(irq); + pending &= ~BIT(bit); + } } return IRQ_HANDLED; -- 2.7.4
Re: [PATCH 1/5 v5] irqchip: ingenic: Drop redundant irq_suspend / irq_resume functions
Hi Zhou, Le mer., oct. 2, 2019 at 19:25, Zhou Yanjie a écrit : From: Paul Cercueil The same behaviour can be obtained by using the IRQCHIP_MASK_ON_SUSPEND flag on the IRQ chip. Signed-off-by: Paul Cercueil If you sumbit a patchset that contains someone else's patches you need to add your Signed-off-by too. --- drivers/irqchip/irq-ingenic.c | 24 +--- include/linux/irqchip/ingenic.h | 14 -- 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 include/linux/irqchip/ingenic.h diff --git a/drivers/irqchip/irq-ingenic.c b/drivers/irqchip/irq-ingenic.c index f126255..06fa810 100644 --- a/drivers/irqchip/irq-ingenic.c +++ b/drivers/irqchip/irq-ingenic.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -50,26 +49,6 @@ static irqreturn_t intc_cascade(int irq, void *data) return IRQ_HANDLED; } -static void intc_irq_set_mask(struct irq_chip_generic *gc, uint32_t mask) -{ - struct irq_chip_regs *regs = >chip_types->regs; - - writel(mask, gc->reg_base + regs->enable); - writel(~mask, gc->reg_base + regs->disable); -} - -void ingenic_intc_irq_suspend(struct irq_data *data) -{ - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); - intc_irq_set_mask(gc, gc->wake_active); -} - -void ingenic_intc_irq_resume(struct irq_data *data) -{ - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); - intc_irq_set_mask(gc, gc->mask_cache); -} - static struct irqaction intc_cascade_action = { .handler = intc_cascade, .name = "SoC intc cascade interrupt", @@ -127,8 +106,7 @@ static int __init ingenic_intc_of_init(struct device_node *node, ct->chip.irq_mask = irq_gc_mask_disable_reg; ct->chip.irq_mask_ack = irq_gc_mask_disable_reg; ct->chip.irq_set_wake = irq_gc_set_wake; - ct->chip.irq_suspend = ingenic_intc_irq_suspend; - ct->chip.irq_resume = ingenic_intc_irq_resume; + ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND; irq_setup_generic_chip(gc, IRQ_MSK(32), 0, 0, IRQ_NOPROBE | IRQ_LEVEL); diff --git a/include/linux/irqchip/ingenic.h b/include/linux/irqchip/ingenic.h deleted file mode 100644 index 1465588..000 --- a/include/linux/irqchip/ingenic.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2010, Lars-Peter Clausen - */ - -#ifndef __LINUX_IRQCHIP_INGENIC_H__ -#define __LINUX_IRQCHIP_INGENIC_H__ - -#include - -extern void ingenic_intc_irq_suspend(struct irq_data *data); -extern void ingenic_intc_irq_resume(struct irq_data *data); - -#endif -- 2.7.4
Re: [PATCH] dmaengine: jz4780: Use devm_platform_ioremap_resource() in jz4780_dma_probe()
Hi Markus, Le dim. 22 sept. 2019 à 11:25, Markus Elfring a écrit : From: Markus Elfring Date: Sun, 22 Sep 2019 11:18:27 +0200 Simplify this function implementation a bit by using a known wrapper function. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Looks good to me. Signed-off-by: Paul Cercueil --- drivers/dma/dma-jz4780.c | 8 +--- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c index cafb1cc065bb..f42b3ef8e036 100644 --- a/drivers/dma/dma-jz4780.c +++ b/drivers/dma/dma-jz4780.c @@ -858,13 +858,7 @@ static int jz4780_dma_probe(struct platform_device *pdev) jzdma->soc_data = soc_data; platform_set_drvdata(pdev, jzdma); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "failed to get I/O memory\n"); - return -EINVAL; - } - - jzdma->chn_base = devm_ioremap_resource(dev, res); + jzdma->chn_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(jzdma->chn_base)) return PTR_ERR(jzdma->chn_base); -- 2.23.0
Re: [PATCH v2 1/3] dt-bindings: Document JZ47xx VPU auxiliary processor
Hi Rob, Le mar. 20 août 2019 à 22:50, Rob Herring a écrit : On Mon, Jul 29, 2019 at 02:31:07PM -0400, Paul Cercueil wrote: Inside the Video Processing Unit (VPU) of the recent JZ47xx SoCs from Ingenic is a second Xburst MIPS CPU very similar to the main core. This document describes the devicetree bindings for this auxiliary processor. Signed-off-by: Paul Cercueil --- Notes: v2: Update TCSM0 address in example .../bindings/remoteproc/ingenic,vpu.txt | 36 +++ 1 file changed, 36 insertions(+) create mode 100644 Documentation/devicetree/bindings/remoteproc/ingenic,vpu.txt diff --git a/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.txt b/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.txt new file mode 100644 index ..576f9e582780 --- /dev/null +++ b/Documentation/devicetree/bindings/remoteproc/ingenic,vpu.txt @@ -0,0 +1,36 @@ +* Ingenic JZ47xx auxiliary processor + +Inside the Video Processing Unit (VPU) of the recent JZ47xx SoCs from Ingenic +is a second Xburst MIPS CPU very similar to the main core. +This document describes the devicetree bindings for this auxiliary processor. + +Required properties: +- compatible: Should be "ingenic,jz4770-vpu-rproc" +- reg: Must contain the registers location and length for: + * the auxiliary processor, + * the Tightly Coupled Shared Memory 0 (TCSM0), + * the Tightly Coupled Shared Memory 1 (TCSM1), + * the shared SRAM. +- reg-names: Must contain "aux", "tcsm0", "tcsm1", "sram". +- clocks: Clock specifier for the AUX and VPU clocks. +- clock-names: Must contain "aux", "vpu". +- interrupts: Interrupt specifier for the VPU hardware block. + +Example: + +vpu: cpu@132a { cpu is reserved for CPUs under /cpus/. Use video-codec or video-decoder or ?? It's not clear what type of video processing this does. Hardware decode and encode of mpeg-2 and h264. I guess I'll use 'video-decoder' then. + compatible = "ingenic,jz4770-vpu-rproc"; + + reg = <0x132a 0x20 /* AUX */ + 0x132b 0x4000 /* TCSM0 */ + 0x132c 0xc000 /* TCSM1 */ + 0x132f 0x7000 /* SRAM */ + >; + reg-names = "aux", "tcsm0", "tcsm1", "sram"; + + clocks = < JZ4770_CLK_AUX>, < JZ4770_CLK_VPU>; + clock-names = "aux", "vpu"; + + interrupt-parent = <>; + interrupts = <3>; +}; -- 2.21.0.593.g511ec345e18
Re: linux-next: Tree for Aug 19 (irqchip: irq-ingenic-tcu.c)
Hi Randy, The fix was merged a couple of hours ago in mips-next. Cheers -Paul Le mar. 20 août 2019 à 0:06, Randy Dunlap a écrit : On 8/19/19 2:18 AM, Stephen Rothwell wrote: Hi all, Changes since 20190816: on i386: ld: drivers/irqchip/irq-ingenic-tcu.o: in function `ingenic_tcu_intc_cascade': irq-ingenic-tcu.c:(.text+0xb1): undefined reference to `irq_get_domain_generic_chip' ld: drivers/irqchip/irq-ingenic-tcu.o: in function `ingenic_tcu_irq_init': irq-ingenic-tcu.c:(.init.text+0x91): undefined reference to `irq_generic_chip_ops' ld: irq-ingenic-tcu.c:(.init.text+0xd2): undefined reference to `__irq_alloc_domain_generic_chips' ld: irq-ingenic-tcu.c:(.init.text+0xfb): undefined reference to `irq_get_domain_generic_chip' Full randconfig file is attached.Jason Cooper -- ~Randy
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Hi Uwe, Le mar. 13 août 2019 à 16:09, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : Hello Paul, On Tue, Aug 13, 2019 at 02:47:28PM +0200, Paul Cercueil wrote: Le mar. 13 août 2019 à 14:33, Uwe Kleine-König a écrit : > On Tue, Aug 13, 2019 at 01:01:06PM +0200, Paul Cercueil wrote: > > Well, you said that I shouln't rely on the fact that clk_round_rate() will > > round down. That completely defeats the previous algorithm. So please tell > > me how to use it correctly, because I don't see it. > > Using clk_round_rate correctly without additional knowledge is hard. If > you assume at least some sane behaviour you'd still have to call it > multiple times. Assuming maxrate is the maximal rate you can handle > without overflowing your PWM registers you have to do: > > rate = maxrate; > rounded_rate = clk_round_rate(clk, rate); > while (rounded_rate > rate) { > if (rate < rounded_rate - rate) { > /* >* clk doesn't support a rate smaller than >* maxrate (or the round_rate callback doesn't >* round consistently). >*/ >return -ESOMETHING; > } > rate = rate - (rounded_rate - rate) > rounded_rate = clk_round_rate(clk, rate); > } > > return rate; > > Probably it would be sensible to put that in a function provided by the > clk framework (maybe call it clk_round_rate_down and maybe with > additional checks). clk_round_rate_down() has been refused multiple times in the past for reasons that Stephen can explain. I'd be really interested in these reasons as I think the clk framework should make it easy to solve common tasks related to clocks. And finding out the biggest supported rate not bigger than a given maxrate is something I consider such a common task. The first hit I found when searching was https://lkml.org/lkml/2010/7/14/260 . In there Stephen suggested that clk_round_rate with the current semantic is hardly useful and suggested clk_round_rate_up() and clk_round_rate_down() himself. That's from 2010, though. I agree that clk_round_rate_up() and clk_round_rate_down() should exist. Even if they return -ENOSYS if it's not implemented for a given clock controller. > > I came up with a much smarter alternative, that doesn't rely on the rounding > > method of clk_round_rate, and which is better overall (no loop needed). It > > sounds to me like you're bashing the code without making the effort to > > understand what it does. > > > > Thierry called it a "neat trick" > > (https://patchwork.kernel.org/patch/10836879/) so it cannot be as bad as you > > say. > > Either that or Thierry failed to see the downside. The obvious downside > is that once you set the period to something long (and so the clk was > limited to a small frequency) you never make the clock any faster > afterwards. Read the algorithm again. I indeed missed a call to clk_set_rate(clk, parent_rate). I thought I grepped for clk_set_rate before claiming the code was broken. Sorry. So I think the code works indeed, but it feels like abusing clk_set_max_rate. So I'd like to see some words from Stephen about this procedure. Also I think this is kind of inelegant to set the maximal rate twice. At least call clk_set_max_rate only once please. Ok. I can do that. > > > > > > E.g. if at a rate of 12 MHz your computed hardware value for the period > > > > > > is 0xf000, then at a rate of 24 MHz it won't fit in 16 bits. So the clock > > > > > > rate must be reduced to the highest possible that will still give you a > > > > > > < 16-bit value. > > > > > > > > > > > > We always want the highest possible clock rate that works, for the sake of > > > > > > precision. > > > > > > > > > > This is dubious; but ok to keep the driver simple. (Consider a PWM that > > > > > can run at i MHz for i in [1, .. 30]. If a period of 120 ns and a duty > > > > > cycle of 40 ns is requested you can get an exact match with 25 MHz, but > > > > > not with 30 MHz.) > > > > > > > > The clock rate is actually (parent_rate >> (2 * x) ) > > > > for x = 0, 1, 2, ... > > > > > > > > So if your parent_rate is 30 MHz the next valid one is 7.5 MHz, and the > > > > next one is 1.875 MHz. It'd be very unlikely that you get a better match at > > > > a lower clock. > > > > > > If the smaller freqs are all divide
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Le mar. 13 août 2019 à 14:33, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : Hello Paul, On Tue, Aug 13, 2019 at 01:01:06PM +0200, Paul Cercueil wrote: Le mar. 13 août 2019 à 7:27, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : > [adding Stephen Boyd to Cc] > > On Tue, Aug 13, 2019 at 12:16:23AM +0200, Paul Cercueil wrote: > > Le lun. 12 août 2019 à 23:48, Uwe Kleine-König a écrit : > > > On Mon, Aug 12, 2019 at 10:43:10PM +0200, Paul Cercueil wrote: > > > > Le lun. 12 août 2019 à 8:15, Uwe Kleine-König a écrit : > > > > > On Fri, Aug 09, 2019 at 07:14:45PM +0200, Paul Cercueil wrote: > > > > > > Le ven. 9 août 2019 à 19:05, Uwe Kleine-König a écrit : > > > > > > > On Fri, Aug 09, 2019 at 02:30:28PM +0200, Paul Cercueil wrote: > > > > > > > > [...] > > > > > > > > + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ > > > > > > > > + ret = clk_set_max_rate(clk, parent_rate); > > > > > > > > > > > > > > What is the purpose of this call? IIUC this limits the allowed range of > > > > > > > rates for clk. I assume the idea is to prevent other consumers to change > > > > > > > the rate in a way that makes it unsuitable for this pwm. But this only > > > > > > > makes sense if you had a notifier for clk changes, doesn't it? I'm > > > > > > > confused. > > > > > > > > > > > > Nothing like that. The second call to clk_set_max_rate() might have set > > > > > > a maximum clock rate that's lower than the parent's rate, and we want to > > > > > > undo that. > > > > > > > > > > I still don't get the purpose of this call. Why do you limit the clock > > > > > rate at all? > > > > > > > > As it says below, we "limit the clock to a maximum rate that still gives > > > > us a period value which fits in 16 bits". So that the computed hardware > > > > values won't overflow. > > > > > > But why not just using clk_set_rate? You want to have the clock running > > > at a certain rate, not any rate below that certain rate, don't you? > > > > I'll let yourself answer yourself: > > https://patchwork.ozlabs.org/patch/1018969/ > > In that thread I claimed that you used clk_round_rate wrongly, not that > you should use clk_set_max_rate(). (The claim was somewhat weakend by > Stephen, but still I think that clk_round_rate is the right approach.) Well, you said that I shouln't rely on the fact that clk_round_rate() will round down. That completely defeats the previous algorithm. So please tell me how to use it correctly, because I don't see it. Using clk_round_rate correctly without additional knowledge is hard. If you assume at least some sane behaviour you'd still have to call it multiple times. Assuming maxrate is the maximal rate you can handle without overflowing your PWM registers you have to do: rate = maxrate; rounded_rate = clk_round_rate(clk, rate); while (rounded_rate > rate) { if (rate < rounded_rate - rate) { /* * clk doesn't support a rate smaller than * maxrate (or the round_rate callback doesn't * round consistently). */ return -ESOMETHING; } rate = rate - (rounded_rate - rate) rounded_rate = clk_round_rate(clk, rate); } return rate; Probably it would be sensible to put that in a function provided by the clk framework (maybe call it clk_round_rate_down and maybe with additional checks). clk_round_rate_down() has been refused multiple times in the past for reasons that Stephen can explain. I came up with a much smarter alternative, that doesn't rely on the rounding method of clk_round_rate, and which is better overall (no loop needed). It sounds to me like you're bashing the code without making the effort to understand what it does. Thierry called it a "neat trick" (https://patchwork.kernel.org/patch/10836879/) so it cannot be as bad as you say. Either that or Thierry failed to see the downside. The obvious downside is that once you set the period to something long (and so the clk was limited to a small frequency) you never make the clock any faster afterwards. Read the algorithm again. Also I wonder how clk_set_max_rate() is supposed to be used like that or if instead some work should be invest
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Le mar. 13 août 2019 à 7:27, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : Hello Paul, [adding Stephen Boyd to Cc] On Tue, Aug 13, 2019 at 12:16:23AM +0200, Paul Cercueil wrote: Le lun. 12 août 2019 à 23:48, Uwe Kleine-König a écrit : > On Mon, Aug 12, 2019 at 10:43:10PM +0200, Paul Cercueil wrote: > > Le lun. 12 août 2019 à 8:15, Uwe Kleine-König a écrit : > > > On Fri, Aug 09, 2019 at 07:14:45PM +0200, Paul Cercueil wrote: > > > > Le ven. 9 août 2019 à 19:05, Uwe Kleine-König a écrit : > > > > > On Fri, Aug 09, 2019 at 02:30:28PM +0200, Paul Cercueil wrote: > > > > > > [...] > > > > > > + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ > > > > > > + ret = clk_set_max_rate(clk, parent_rate); > > > > > > > > > > What is the purpose of this call? IIUC this limits the allowed range of > > > > > rates for clk. I assume the idea is to prevent other consumers to change > > > > > the rate in a way that makes it unsuitable for this pwm. But this only > > > > > makes sense if you had a notifier for clk changes, doesn't it? I'm > > > > > confused. > > > > > > > > Nothing like that. The second call to clk_set_max_rate() might have set > > > > a maximum clock rate that's lower than the parent's rate, and we want to > > > > undo that. > > > > > > I still don't get the purpose of this call. Why do you limit the clock > > > rate at all? > > > > As it says below, we "limit the clock to a maximum rate that still gives > > us a period value which fits in 16 bits". So that the computed hardware > > values won't overflow. > > But why not just using clk_set_rate? You want to have the clock running > at a certain rate, not any rate below that certain rate, don't you? I'll let yourself answer yourself: https://patchwork.ozlabs.org/patch/1018969/ In that thread I claimed that you used clk_round_rate wrongly, not that you should use clk_set_max_rate(). (The claim was somewhat weakend by Stephen, but still I think that clk_round_rate is the right approach.) Well, you said that I shouln't rely on the fact that clk_round_rate() will round down. That completely defeats the previous algorithm. So please tell me how to use it correctly, because I don't see it. I came up with a much smarter alternative, that doesn't rely on the rounding method of clk_round_rate, and which is better overall (no loop needed). It sounds to me like you're bashing the code without making the effort to understand what it does. Thierry called it a "neat trick" (https://patchwork.kernel.org/patch/10836879/) so it cannot be as bad as you say. The upside of clk_round_rate is that it allows you to test for the capabilities of the clock without actually changing it before you found a setting you consider to be good. I know what clk_round_rate() is for. But here we don't do trial-and-error to find the first highest clock rate that works, we compute the maximum clock we can use and limit the clock rate to that. It's enough to run it below a certain rate, yes. The actual rate doesn't actually matter that much. 1 Hz would be fine? I doubt it. We use the highest possible clock rate. We wouldn't use 1 Hz unless it's the highest clock rate available. > > E.g. if at a rate of 12 MHz your computed hardware value for the period > > is 0xf000, then at a rate of 24 MHz it won't fit in 16 bits. So the clock > > rate must be reduced to the highest possible that will still give you a > > < 16-bit value. > > > > We always want the highest possible clock rate that works, for the sake of > > precision. > > This is dubious; but ok to keep the driver simple. (Consider a PWM that > can run at i MHz for i in [1, .. 30]. If a period of 120 ns and a duty > cycle of 40 ns is requested you can get an exact match with 25 MHz, but > not with 30 MHz.) The clock rate is actually (parent_rate >> (2 * x) ) for x = 0, 1, 2, ... So if your parent_rate is 30 MHz the next valid one is 7.5 MHz, and the next one is 1.875 MHz. It'd be very unlikely that you get a better match at a lower clock. If the smaller freqs are all dividers of the fastest that's fine. Please note in a code comment that you're assuming this. No, I am not assuming this. The current driver just picks the highest clock rate that works. We're not changing the behaviour here. > > > > Basically, we start from the maximum clock rate we can get for that PWM > > > > - which is the rate of the parent clk - and from that compute t
Re: [PATCH 10/11] mfd: Drop obsolete JZ4740 driver
Hi Philippe, Le mar. 13 août 2019 à 10:44, Philippe =?iso-8859-1?q?Mathieu-Daud=E9?= a écrit : Hi Lee, On 8/12/19 10:16 AM, Lee Jones wrote: On Thu, 25 Jul 2019, Paul Cercueil wrote: It has been replaced with the ingenic-iio driver for the ADC. Signed-off-by: Paul Cercueil Tested-by: Artur Rojek --- drivers/mfd/Kconfig | 9 -- drivers/mfd/Makefile | 1 - drivers/mfd/jz4740-adc.c | 324 --- 3 files changed, 334 deletions(-) delete mode 100644 drivers/mfd/jz4740-adc.c Applied, thanks. It seems the replacement is done in "MIPS: qi_lb60: Migrate to devicetree" which is not yet merged. It's merged in the MIPS tree, though, so it'll blend together just fine in linux-next. Probably easier if this patch goes thru the MIPS tree as part of the "JZ4740 SoC cleanup" series. Regards, Phil.
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
[Re-send my message in plain text, as it was bounced by the lists - sorry about that] Le lun. 12 août 2019 à 23:48, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : Hello Paul, On Mon, Aug 12, 2019 at 10:43:10PM +0200, Paul Cercueil wrote: Le lun. 12 août 2019 à 8:15, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : > On Fri, Aug 09, 2019 at 07:14:45PM +0200, Paul Cercueil wrote: > > Le ven. 9 août 2019 à 19:05, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= > > a écrit : > > > On Fri, Aug 09, 2019 at 02:30:28PM +0200, Paul Cercueil wrote: > > > > [...] > > > > + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ > > > > +ret = clk_set_max_rate(clk, parent_rate); > > > > > > What is the purpose of this call? IIUC this limits the allowed range of > > > rates for clk. I assume the idea is to prevent other consumers to change > > > the rate in a way that makes it unsuitable for this pwm. But this only > > > makes sense if you had a notifier for clk changes, doesn't it? I'm > > > confused. > > > > Nothing like that. The second call to clk_set_max_rate() might have set > > a maximum clock rate that's lower than the parent's rate, and we want to > > undo that. > > I still don't get the purpose of this call. Why do you limit the clock > rate at all? As it says below, we "limit the clock to a maximum rate that still gives us a period value which fits in 16 bits". So that the computed hardware values won't overflow. But why not just using clk_set_rate? You want to have the clock running at a certain rate, not any rate below that certain rate, don't you? I'll let yourself answer yourself: https://patchwork.ozlabs.org/patch/1018969/ It's enough to run it below a certain rate, yes. The actual rate doesn't actually matter that much. E.g. if at a rate of 12 MHz your computed hardware value for the period is 0xf000, then at a rate of 24 MHz it won't fit in 16 bits. So the clock rate must be reduced to the highest possible that will still give you a < 16-bit value. We always want the highest possible clock rate that works, for the sake of precision. This is dubious; but ok to keep the driver simple. (Consider a PWM that can run at i MHz for i in [1, .. 30]. If a period of 120 ns and a duty cycle of 40 ns is requested you can get an exact match with 25 MHz, but not with 30 MHz.) The clock rate is actually (parent_rate >> (2 * x) ) for x = 0, 1, 2, ... So if your parent_rate is 30 MHz the next valid one is 7.5 MHz, and the next one is 1.875 MHz. It'd be very unlikely that you get a better match at a lower clock. > > Basically, we start from the maximum clock rate we can get for that PWM > > - which is the rate of the parent clk - and from that compute the maximum > > clock rate that we can support that still gives us < 16-bits hardware > > values for the period and duty. > > > > We then pass that computed maximum clock rate to clk_set_max_rate(), which > > may or may not update the current PWM clock's rate to match the new limits. > > Finally we read back the PWM clock's rate and compute the period and duty > > from that. > > If you change the clk rate, is this externally visible on the PWM > output? Does this affect other PWM instances? The clock rate doesn't change the PWM output because the hardware values for the period and duty are adapted accordingly to reflect the change. It doesn't change it in the end. But in the (short) time frame between the call to change the clock and the update of the PWM registers there is a glitch, right? The PWM is disabled, so the line is in inactive state, and will be in that state until the PWM is enabled again. No glitch to fear. You didn't answer to the question about other PWM instances. Does that mean others are not affected? Sorry. Yes, they are not affected - all PWM channels are independent. Best regards Uwe PS: It would be great if you could fix your mailer to not damage the quoted mail. Also it doesn't seem to understand how my name is encoded in the From line. I fixed up the quotes in my reply. I guess I'll submit a bug report to Geary then. -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | http://www.pengutronix.de/ |
Re: [PATCH 6/7] pwm: jz4740: Make PWM start with the active part
Le lun. 12 août 2019 à 7:55, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : On Fri, Aug 09, 2019 at 07:33:24PM +0200, Paul Cercueil wrote: Le ven. 9 août 2019 à 19:10, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : > On Fri, Aug 09, 2019 at 02:30:30PM +0200, Paul Cercueil wrote: > > The PWM will always start with the inactive part. To counter that, > > when PWM is enabled we switch the configured polarity, and use > > 'period - duty + 1' as the real duty. > > Where does the + 1 come from? This looks wrong. (So if duty=0 is > requested you use duty = period + 1?) You'd never request duty == 0, would you? Your duty must always be in the inclusive range [1, period] (hardware values, not ns). A duty of 0 is a hardware fault (on the jz4740 it is). From the PWM framework's POV duty cycle = 0 is perfectly valid. Similar to duty == period. Not supporting dutz cycle 0 is another limitation of your PWM that should be documented. For actual use cases of duty cycle = 0 see drivers/hwmon/pwm-fan.c or drivers/leds/leds-pwm.c. Perfectly valid for the PWM framework, maybe; but what is the expected output then? A constant inactive state? Then I guess I can just disable the PWM output in the driver when configured with duty == 0. If you request duty == 1 (the minimum), then the new duty is equal to (period - 1 + 1) == period, which is the maximum of your range. If you request duty == period (the maximum), then the new duty calculated is equal to (period - period + 1) == 1, which is the minimum of your range. > > > > Signed-off-by: Paul Cercueil > > --- > > drivers/pwm/pwm-jz4740.c | 22 +- > > 1 file changed, 13 insertions(+), 9 deletions(-) > > > > diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c > > index 85e2110aae4f..8df898429d47 100644 > > --- a/drivers/pwm/pwm-jz4740.c > > +++ b/drivers/pwm/pwm-jz4740.c > > @@ -121,6 +121,7 @@ static int jz4740_pwm_apply(struct pwm_chip > > *chip, struct pwm_device *pwm, > > *parent_clk = clk_get_parent(clk); > > unsigned long rate, parent_rate, period, duty; > > unsigned long long tmp; > > + bool polarity_inversed; > > int ret; > > > > parent_rate = clk_get_rate(parent_clk); > > @@ -183,24 +184,27 @@ static int jz4740_pwm_apply(struct pwm_chip > > *chip, struct pwm_device *pwm, > > /* Reset counter to 0 */ > > regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); > > > > - /* Set duty */ > > - regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); > > - > > /* Set period */ > > regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); > > > > + /* > > + * The PWM will always start with the inactive part. To counter that, > > + * when PWM is enabled we switch the configured polarity, and use > > +* 'period - duty + 1' as the real duty. > > +*/ > > + > > + /* Set duty */ > > + regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), period - duty + 1); > > + > > Before you set duty first, then period, now you do it the other way > round. Is there a good reason? To move it below the comment that explains why we use 'period - duty + 1'. We modify that line anyway, so it's not like it makes the patch much more verbose. It doesn't make it more verbose, but that's not the background of my question. For most(?) PWM implementation the order of hardware accesses matters and introducing such a difference as an unneeded side effect isn't optimal. There's no side effect. The PWM is disabled when reconfigured. Why not add the comment above the line that already used to set the duty in hardware? I thought it made sense to have the two parts of the trick closer together in the code, below the comment, so that it's clearer what it does. > > /* Set polarity */ > > - switch (state->polarity) { > > - case PWM_POLARITY_NORMAL: > > + polarity_inversed = state->polarity == PWM_POLARITY_INVERSED; > > + if (!polarity_inversed ^ state->enabled) > > Why does state->enabled suddenly matter here? The pin stay inactive when the PWM is disabled, but the level of the inactive state depends on the polarity of the pin. So we need to switch the polarity only when the PWM is enabled. After some thought I got that. When knowing this, this is already mentioned in the comment you introduced as you write about enabled PWMs only. Maybe it's just me, but mentioning that case more explicit would have helped me. Something like: /* * The hardware always starts a period w
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Le lun. 12 août 2019 à 8:15, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : Hello Paul, On Fri, Aug 09, 2019 at 07:14:45PM +0200, Paul Cercueil wrote: Le ven. 9 août 2019 à 19:05, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : > On Fri, Aug 09, 2019 at 02:30:28PM +0200, Paul Cercueil wrote: > > The previous algorithm hardcoded details about how the TCU clocks > > work. > > The new algorithm will use clk_round_rate to find the perfect clock > > rate > > for the PWM channel. > > > > Signed-off-by: Paul Cercueil > > Tested-by: Mathieu Malaterre > > Tested-by: Artur Rojek > > --- > > drivers/pwm/pwm-jz4740.c | 60 > > +--- > > 1 file changed, 44 insertions(+), 16 deletions(-) > > > > diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c > > index 6ec8794d3b99..f20dc2e19240 100644 > > --- a/drivers/pwm/pwm-jz4740.c > > +++ b/drivers/pwm/pwm-jz4740.c > > @@ -110,24 +110,56 @@ static int jz4740_pwm_apply(struct pwm_chip > > *chip, struct pwm_device *pwm, > > struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); > > struct clk *clk = pwm_get_chip_data(pwm), > > *parent_clk = clk_get_parent(clk); > > - unsigned long rate, period, duty; > > + unsigned long rate, parent_rate, period, duty; > > unsigned long long tmp; > > - unsigned int prescaler = 0; > > + int ret; > > > > - rate = clk_get_rate(parent_clk); > > - tmp = (unsigned long long)rate * state->period; > > - do_div(tmp, 10); > > - period = tmp; > > + parent_rate = clk_get_rate(parent_clk); > > + > > + jz4740_pwm_disable(chip, pwm); > > > > - while (period > 0x && prescaler < 6) { > > - period >>= 2; > > - rate >>= 2; > > - ++prescaler; > > + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ > > + ret = clk_set_max_rate(clk, parent_rate); > > What is the purpose of this call? IIUC this limits the allowed range of > rates for clk. I assume the idea is to prevent other consumers to change > the rate in a way that makes it unsuitable for this pwm. But this only > makes sense if you had a notifier for clk changes, doesn't it? I'm > confused. Nothing like that. The second call to clk_set_max_rate() might have set a maximum clock rate that's lower than the parent's rate, and we want to undo that. I still don't get the purpose of this call. Why do you limit the clock rate at all? As it says below, we "limit the clock to a maximum rate that still gives us a period value which fits in 16 bits". So that the computed hardware values won't overflow. E.g. if at a rate of 12 MHz your computed hardware value for the period is 0xf000, then at a rate of 24 MHz it won't fit in 16 bits. So the clock rate must be reduced to the highest possible that will still give you a < 16-bit value. We always want the highest possible clock rate that works, for the sake of precision. > I think this doesn't match the commit log, you didn't even introduced a > call to clk_round_rate(). Right, I'll edit the commit message. > > + if (ret) { > > + dev_err(chip->dev, "Unable to set max rate: %d\n", ret); > > + return ret; > > } > > > > - if (prescaler == 6) > > - return -EINVAL; > > + ret = clk_set_rate(clk, parent_rate); > > + if (ret) { > > + dev_err(chip->dev, "Unable to reset to parent rate (%lu Hz)", > > + parent_rate); > > + return ret; > > + } > > + > > + /* > > + * Limit the clock to a maximum rate that still gives us a period value > > +* which fits in 16 bits. > > +*/ > > + tmp = 0xull * NSEC_PER_SEC; > > + do_div(tmp, state->period); > > > > + ret = clk_set_max_rate(clk, tmp); > > And now you change the maximal rate again? Basically, we start from the maximum clock rate we can get for that PWM - which is the rate of the parent clk - and from that compute the maximum clock rate that we can support that still gives us < 16-bits hardware values for the period and duty. We then pass that computed maximum clock rate to clk_set_max_rate(), which may or may not update the current PWM clock's rate to match the new limits. Finally we read back the PWM clock's rate and compute the period and duty from that. If you change the clk
[PATCH v2] clk: ingenic: Use CLK_OF_DECLARE_DRIVER macro
By using CLK_OF_DECLARE_DRIVER instead of the CLK_OF_DECLARE macro, we allow the driver to probe also as a platform driver. While this driver does not have code to probe as a platform driver, this is still useful for probing children devices in the case where the device node is compatible with "simple-mfd". Signed-off-by: Paul Cercueil --- Notes: v2: Rebased on v5.3-rc3 drivers/clk/ingenic/jz4725b-cgu.c | 2 +- drivers/clk/ingenic/jz4740-cgu.c | 2 +- drivers/clk/ingenic/jz4770-cgu.c | 2 +- drivers/clk/ingenic/jz4780-cgu.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/clk/ingenic/jz4725b-cgu.c b/drivers/clk/ingenic/jz4725b-cgu.c index 2642d36d1e2c..a3b4635f6278 100644 --- a/drivers/clk/ingenic/jz4725b-cgu.c +++ b/drivers/clk/ingenic/jz4725b-cgu.c @@ -257,4 +257,4 @@ static void __init jz4725b_cgu_init(struct device_node *np) ingenic_cgu_register_syscore_ops(cgu); } -CLK_OF_DECLARE(jz4725b_cgu, "ingenic,jz4725b-cgu", jz4725b_cgu_init); +CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-cgu", jz4725b_cgu_init); diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c index 4c0a20949c2c..dd86296fb8c1 100644 --- a/drivers/clk/ingenic/jz4740-cgu.c +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -241,4 +241,4 @@ static void __init jz4740_cgu_init(struct device_node *np) ingenic_cgu_register_syscore_ops(cgu); } -CLK_OF_DECLARE(jz4740_cgu, "ingenic,jz4740-cgu", jz4740_cgu_init); +CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-cgu", jz4740_cgu_init); diff --git a/drivers/clk/ingenic/jz4770-cgu.c b/drivers/clk/ingenic/jz4770-cgu.c index eebc1bea3841..956dd653a43d 100644 --- a/drivers/clk/ingenic/jz4770-cgu.c +++ b/drivers/clk/ingenic/jz4770-cgu.c @@ -443,4 +443,4 @@ static void __init jz4770_cgu_init(struct device_node *np) } /* We only probe via devicetree, no need for a platform driver */ -CLK_OF_DECLARE(jz4770_cgu, "ingenic,jz4770-cgu", jz4770_cgu_init); +CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-cgu", jz4770_cgu_init); diff --git a/drivers/clk/ingenic/jz4780-cgu.c b/drivers/clk/ingenic/jz4780-cgu.c index 8c67f89df25e..ea905ff72bf0 100644 --- a/drivers/clk/ingenic/jz4780-cgu.c +++ b/drivers/clk/ingenic/jz4780-cgu.c @@ -725,4 +725,4 @@ static void __init jz4780_cgu_init(struct device_node *np) ingenic_cgu_register_syscore_ops(cgu); } -CLK_OF_DECLARE(jz4780_cgu, "ingenic,jz4780-cgu", jz4780_cgu_init); +CLK_OF_DECLARE_DRIVER(jz4780_cgu, "ingenic,jz4780-cgu", jz4780_cgu_init); -- 2.21.0.593.g511ec345e18
Re: [PATCH] clk: ingenic: Use CLK_OF_DECLARE_DRIVER macro
Le jeu. 8 août 2019 à 6:23, Stephen Boyd a écrit : Quoting Paul Cercueil (2019-07-16 10:08:00) By using CLK_OF_DECLARE_DRIVER instead of the CLK_OF_DECLARE macro, we allow the driver to probe also as a platform driver. While this driver does not have code to probe as a platform driver, this is still useful for probing children devices in the case where the device node is compatible with "simple-mfd". Signed-off-by: Paul Cercueil --- What's the baseline for this? It doesn't apply cleanly to v5.3-rc1 I think it was v5.2-rc7. I'll send a V2 rebased on v5.3-rc3. -Paul
[PATCH 1/2] mmc: jz4740: Code cleanup
Fix wrong code indentation which made the code hard to read, and fix return with value in void function. Signed-off-by: Paul Cercueil --- drivers/mmc/host/jz4740_mmc.c | 14 +++--- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index ffdbfaadd3f2..59f81e8afcce 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -186,9 +186,9 @@ static void jz4740_mmc_write_irq_reg(struct jz4740_mmc_host *host, uint32_t val) { if (host->version >= JZ_MMC_JZ4780) - return writel(val, host->base + JZ_REG_MMC_IREG); + writel(val, host->base + JZ_REG_MMC_IREG); else - return writew(val, host->base + JZ_REG_MMC_IREG); + writew(val, host->base + JZ_REG_MMC_IREG); } static uint32_t jz4740_mmc_read_irq_reg(struct jz4740_mmc_host *host) @@ -820,14 +820,14 @@ static irqreturn_t jz_mmc_irq(int irq, void *devid) del_timer(>timeout_timer); if (status & JZ_MMC_STATUS_TIMEOUT_RES) { - cmd->error = -ETIMEDOUT; + cmd->error = -ETIMEDOUT; } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { - cmd->error = -EIO; + cmd->error = -EIO; } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | JZ_MMC_STATUS_CRC_WRITE_ERROR)) { - if (cmd->data) - cmd->data->error = -EIO; - cmd->error = -EIO; + if (cmd->data) + cmd->data->error = -EIO; + cmd->error = -EIO; } jz4740_mmc_set_irq_enabled(host, irq_reg, false); -- 2.21.0.593.g511ec345e18
[PATCH 2/2] mmc: jz4740: Drop dependency on arch header
We don't need to set the 'slave_id' anymore - that field is never read by the DMA driver. Signed-off-by: Paul Cercueil --- drivers/mmc/host/jz4740_mmc.c | 4 1 file changed, 4 deletions(-) diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index 59f81e8afcce..7ff2034d739a 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -25,8 +25,6 @@ #include -#include - #define JZ_REG_MMC_STRPCL 0x00 #define JZ_REG_MMC_STATUS 0x04 #define JZ_REG_MMC_CLKRT 0x08 @@ -292,11 +290,9 @@ static int jz4740_mmc_start_dma_transfer(struct jz4740_mmc_host *host, if (data->flags & MMC_DATA_WRITE) { conf.direction = DMA_MEM_TO_DEV; conf.dst_addr = host->mem_res->start + JZ_REG_MMC_TXFIFO; - conf.slave_id = JZ4740_DMA_TYPE_MMC_TRANSMIT; } else { conf.direction = DMA_DEV_TO_MEM; conf.src_addr = host->mem_res->start + JZ_REG_MMC_RXFIFO; - conf.slave_id = JZ4740_DMA_TYPE_MMC_RECEIVE; } sg_count = jz4740_mmc_prepare_dma_data(host, data, COOKIE_MAPPED); -- 2.21.0.593.g511ec345e18
Re: [PATCH 3/7] pwm: jz4740: Drop dependency on MACH_INGENIC
Le ven. 9 août 2019 à 18:41, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : On Fri, Aug 09, 2019 at 02:30:27PM +0200, Paul Cercueil wrote: Depending on MACH_INGENIC prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Signed-off-by: Paul Cercueil --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d2557c6fcf65..82a75e0b72e5 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -224,7 +224,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" - depends on MACH_INGENIC + depends on MIPS If this isn't actually useful on MIPS without MACH_INGENIC this is better expressed using: depends on MIPS depends on MACH_INGENIC || COMPILE_TEST This way some configuring a mips kernel without INGENIC isn't bothered by this question. As said in the commit message, it is useful on MIPS without MACH_INGENIC, if you want to create a generic kernel that works on more than one MIPS board. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | http://www.pengutronix.de/ |
Re: [PATCH 0/3] watchdog: jz4740: Driver update
Le ven. 9 août 2019 à 19:29, Guenter Roeck a écrit : On Fri, Aug 09, 2019 at 06:55:26PM +0200, Paul Cercueil wrote: Hi Guenter, Le ven. 9 août 2019 à 18:52, Guenter Roeck a écrit : >On Fri, Aug 09, 2019 at 01:59:27PM +0200, Paul Cercueil wrote: >> Hi, >> >> This patchset comes from a bigger patchset that was cut in smaller >> pieces for easier integration to mainline. >> (The patchset was https://lkml.org/lkml/2019/3/27/1837) >> >> The reviews were kept since the code mostly didn't change. The >>exception >> is the use of device_node_to_regmap() in patch 2/3. This function was >> added in a prior patch, now merged in the MIPS tree. >> >> For that reason this patchset is based on the ingenic-tcu-v5.4 branch >>of >> the MIPS tree >> (git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git). >> > >What is the expectation here ? Should the series be sent upstream >through the watchdog tree, or through some other tree ? You can get it through the watchdog tree if you merge the ingenic-tcu-v5.4 branch from the MIPS tree. If you'd rather not do that, I can get it merged through the MIPS tree. I would prefer a merge through the mips tree. Guenter Ok; Can I get some signatures then? ;) Thanks, -Paul
Re: [PATCH 6/7] pwm: jz4740: Make PWM start with the active part
Le ven. 9 août 2019 à 19:10, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : On Fri, Aug 09, 2019 at 02:30:30PM +0200, Paul Cercueil wrote: The PWM will always start with the inactive part. To counter that, when PWM is enabled we switch the configured polarity, and use 'period - duty + 1' as the real duty. Where does the + 1 come from? This looks wrong. (So if duty=0 is requested you use duty = period + 1?) You'd never request duty == 0, would you? Your duty must always be in the inclusive range [1, period] (hardware values, not ns). A duty of 0 is a hardware fault (on the jz4740 it is). If you request duty == 1 (the minimum), then the new duty is equal to (period - 1 + 1) == period, which is the maximum of your range. If you request duty == period (the maximum), then the new duty calculated is equal to (period - period + 1) == 1, which is the minimum of your range. Signed-off-by: Paul Cercueil --- drivers/pwm/pwm-jz4740.c | 22 +- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 85e2110aae4f..8df898429d47 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -121,6 +121,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, *parent_clk = clk_get_parent(clk); unsigned long rate, parent_rate, period, duty; unsigned long long tmp; + bool polarity_inversed; int ret; parent_rate = clk_get_rate(parent_clk); @@ -183,24 +184,27 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* Reset counter to 0 */ regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); - /* Set duty */ - regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); - /* Set period */ regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); + /* + * The PWM will always start with the inactive part. To counter that, + * when PWM is enabled we switch the configured polarity, and use + * 'period - duty + 1' as the real duty. + */ + + /* Set duty */ + regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), period - duty + 1); + Before you set duty first, then period, now you do it the other way round. Is there a good reason? To move it below the comment that explains why we use 'period - duty + 1'. We modify that line anyway, so it's not like it makes the patch much more verbose. /* Set polarity */ - switch (state->polarity) { - case PWM_POLARITY_NORMAL: + polarity_inversed = state->polarity == PWM_POLARITY_INVERSED; + if (!polarity_inversed ^ state->enabled) Why does state->enabled suddenly matter here? The pin stay inactive when the PWM is disabled, but the level of the inactive state depends on the polarity of the pin. So we need to switch the polarity only when the PWM is enabled. regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, 0); - break; - case PWM_POLARITY_INVERSED: + else regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, TCU_TCSR_PWM_INITL_HIGH); - break; - } if (state->enabled) jz4740_pwm_enable(chip, pwm); Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | http://www.pengutronix.de/ |
Re: [PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
Le ven. 9 août 2019 à 19:05, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : On Fri, Aug 09, 2019 at 02:30:28PM +0200, Paul Cercueil wrote: The previous algorithm hardcoded details about how the TCU clocks work. The new algorithm will use clk_round_rate to find the perfect clock rate for the PWM channel. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/pwm/pwm-jz4740.c | 60 +--- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 6ec8794d3b99..f20dc2e19240 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -110,24 +110,56 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); struct clk *clk = pwm_get_chip_data(pwm), *parent_clk = clk_get_parent(clk); - unsigned long rate, period, duty; + unsigned long rate, parent_rate, period, duty; unsigned long long tmp; - unsigned int prescaler = 0; + int ret; - rate = clk_get_rate(parent_clk); - tmp = (unsigned long long)rate * state->period; - do_div(tmp, 10); - period = tmp; + parent_rate = clk_get_rate(parent_clk); + + jz4740_pwm_disable(chip, pwm); - while (period > 0x && prescaler < 6) { - period >>= 2; - rate >>= 2; - ++prescaler; + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ + ret = clk_set_max_rate(clk, parent_rate); What is the purpose of this call? IIUC this limits the allowed range of rates for clk. I assume the idea is to prevent other consumers to change the rate in a way that makes it unsuitable for this pwm. But this only makes sense if you had a notifier for clk changes, doesn't it? I'm confused. Nothing like that. The second call to clk_set_max_rate() might have set a maximum clock rate that's lower than the parent's rate, and we want to undo that. I think this doesn't match the commit log, you didn't even introduced a call to clk_round_rate(). Right, I'll edit the commit message. + if (ret) { + dev_err(chip->dev, "Unable to set max rate: %d\n", ret); + return ret; } - if (prescaler == 6) - return -EINVAL; + ret = clk_set_rate(clk, parent_rate); + if (ret) { + dev_err(chip->dev, "Unable to reset to parent rate (%lu Hz)", + parent_rate); + return ret; + } + + /* + * Limit the clock to a maximum rate that still gives us a period value + * which fits in 16 bits. + */ + tmp = 0xull * NSEC_PER_SEC; + do_div(tmp, state->period); + ret = clk_set_max_rate(clk, tmp); And now you change the maximal rate again? Basically, we start from the maximum clock rate we can get for that PWM - which is the rate of the parent clk - and from that compute the maximum clock rate that we can support that still gives us < 16-bits hardware values for the period and duty. We then pass that computed maximum clock rate to clk_set_max_rate(), which may or may not update the current PWM clock's rate to match the new limits. Finally we read back the PWM clock's rate and compute the period and duty from that. + if (ret) { + dev_err(chip->dev, "Unable to set max rate: %d\n", ret); + return ret; + } + + /* + * Read back the clock rate, as it may have been modified by + * clk_set_max_rate() + */ + rate = clk_get_rate(clk); + + if (rate != parent_rate) + dev_dbg(chip->dev, "PWM clock updated to %lu Hz\n", rate); + + /* Calculate period value */ + tmp = (unsigned long long)rate * state->period; + do_div(tmp, NSEC_PER_SEC); + period = (unsigned long)tmp; + + /* Calculate duty value */ tmp = (unsigned long long)period * state->duty_cycle; do_div(tmp, state->period); duty = period - tmp; @@ -135,14 +167,10 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (duty >= period) duty = period - 1; - jz4740_pwm_disable(chip, pwm); - /* Set abrupt shutdown */ regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); - clk_set_rate(clk, rate); - It's not obvious to me why removing these two lines belong in the current patch. They're not removed, they're both moved up in the function. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | http://www.pengutronix.de/ |
Re: [PATCH 1/7] pwm: jz4740: Obtain regmap from parent node
Hi Uwe, Le ven. 9 août 2019 à 18:51, Uwe =?iso-8859-1?q?Kleine-K=F6nig?= a écrit : On Fri, Aug 09, 2019 at 02:30:25PM +0200, Paul Cercueil wrote: The TCU registers are shared between a handful of drivers, accessing them through the same regmap. While this driver is devicetree-compatible, it is never (as of now) probed from devicetree, so this change does not introduce a ABI problem with current devicetree files. If you adapt the binding also update the binding doc please. It's already updated, in mips-next, so it'll be in the next -rc1. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek nitpick: put your S-o-b line after the other tags you added. --- drivers/pwm/Kconfig | 1 + drivers/pwm/pwm-jz4740.c | 80 ++-- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a7e57516959e..cc4df0ec978a 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -225,6 +225,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" depends on MACH_INGENIC + select MFD_SYSCON help Generic PWM framework driver for Ingenic JZ47xx based machines. diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index f901e8a0d33d..7aea5e0c6e18 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -8,18 +8,20 @@ #include #include #include +#include +#include #include #include #include #include - -#include +#include #define NUM_PWM 8 struct jz4740_pwm_chip { struct pwm_chip chip; struct clk *clk; + struct regmap *map; }; static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) @@ -29,6 +31,8 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { + struct jz4740_pwm_chip *jz = to_jz4740(chip); + /* * Timers 0 and 1 are used for system tasks, so they are unavailable * for use as PWMs. @@ -36,50 +40,53 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) if (pwm->hwpwm < 2) return -EBUSY; - jz4740_timer_start(pwm->hwpwm); + regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); jz4740_timer_start does writel(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_STOP_CLEAR); with #define JZ_REG_TIMER_STOP_CLEAR 0x2C and #define TCU_REG_TSCR0x3c I wonder why the offsets are different. The offset are different because the base is different. return 0; } static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { - jz4740_timer_set_ctrl(pwm->hwpwm, 0); + struct jz4740_pwm_chip *jz = to_jz4740(chip); - jz4740_timer_stop(pwm->hwpwm); + regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); jz4740_timer_set_ctrl writes to offset (((pwm->hwpwm) * 0x10) + 0x3C) and jz4740_timer_stop to offset 0x1c. The regmap_write doesn't do both of them but instead writes to offset 0x3c. I guess it should have been TCU_REG_TSSR ("Timer Stop Set Register") and I didn't notice because the next patch drops this write anyway. I'll do as you suggested in your other reply and swap the two patches if it makes things easier, it'll get rid of this write. So this doesn't seem to be a 1:1 conversion. This either needs fixing, splitting into several patches or a better commit log. Stopping my review here. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | http://www.pengutronix.de/ |
Re: [PATCH 0/3] watchdog: jz4740: Driver update
Hi Guenter, Le ven. 9 août 2019 à 18:52, Guenter Roeck a écrit : On Fri, Aug 09, 2019 at 01:59:27PM +0200, Paul Cercueil wrote: Hi, This patchset comes from a bigger patchset that was cut in smaller pieces for easier integration to mainline. (The patchset was https://lkml.org/lkml/2019/3/27/1837) The reviews were kept since the code mostly didn't change. The exception is the use of device_node_to_regmap() in patch 2/3. This function was added in a prior patch, now merged in the MIPS tree. For that reason this patchset is based on the ingenic-tcu-v5.4 branch of the MIPS tree (git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git). What is the expectation here ? Should the series be sent upstream through the watchdog tree, or through some other tree ? You can get it through the watchdog tree if you merge the ingenic-tcu-v5.4 branch from the MIPS tree. If you'd rather not do that, I can get it merged through the MIPS tree. Thanks, -Paul
[PATCH] clocksource: Add driver for the Ingenic JZ47xx OST
From: Maarten ter Huurne OST is the OS Timer, a 64-bit timer/counter with buffered reading. SoCs before the JZ4770 had (if any) a 32-bit OST; the JZ4770 and JZ4780 have a 64-bit OST. This driver will register both a clocksource and a sched_clock to the system. Signed-off-by: Maarten ter Huurne Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- Hi, This patch comes from a bigger patchset that was cut in smaller pieces for easier integration to mainline. (The patchset was https://lkml.org/lkml/2019/3/27/1837) The only change is the use of device_node_to_regmap(), which was added in a prior patchset now merged in the MIPS tree. For that reason this patch is based on the ingenic-tcu-v5.4 branch of the MIPS tree (git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git). Thanks, -Paul drivers/clocksource/Kconfig | 8 ++ drivers/clocksource/Makefile | 1 + drivers/clocksource/ingenic-ost.c | 221 ++ 3 files changed, 230 insertions(+) create mode 100644 drivers/clocksource/ingenic-ost.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index a9cdc2c4f8bd..3b1503082a23 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -696,4 +696,12 @@ config INGENIC_TIMER help Support for the timer/counter unit of the Ingenic JZ SoCs. +config INGENIC_OST + bool "Ingenic JZ47xx Operating System Timer" + depends on MIPS || COMPILE_TEST + depends on COMMON_CLK + select MFD_SYSCON + help + Support for the OS Timer of the Ingenic JZ4770 or similar SoC. + endmenu diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 4dfe4225ece7..6bc97a6fd229 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o obj-$(CONFIG_H8300_TMR8) += h8300_timer8.o obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o obj-$(CONFIG_H8300_TPU)+= h8300_tpu.o +obj-$(CONFIG_INGENIC_OST) += ingenic-ost.o obj-$(CONFIG_INGENIC_TIMER)+= ingenic-timer.o obj-$(CONFIG_CLKSRC_ST_LPC)+= clksrc_st_lpc.o obj-$(CONFIG_X86_NUMACHIP) += numachip.o diff --git a/drivers/clocksource/ingenic-ost.c b/drivers/clocksource/ingenic-ost.c new file mode 100644 index ..c708d5d7dd15 --- /dev/null +++ b/drivers/clocksource/ingenic-ost.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ47xx SoCs TCU Operating System Timer driver + * + * Copyright (C) 2016 Maarten ter Huurne + * Copyright (C) 2018 Paul Cercueil + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCU_OST_TCSR_MASK 0xffc0 +#define TCU_OST_TCSR_CNT_MDBIT(15) + +#define TCU_OST_CHANNEL15 + +struct ingenic_ost_soc_info { + bool is64bit; +}; + +struct ingenic_ost { + struct regmap *map; + struct clk *clk; + + struct clocksource cs; +}; + +static struct ingenic_ost *ingenic_ost; + +static u64 notrace ingenic_ost_read_cntl(void) +{ + u32 val; + + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTL, ); + + return val; +} + +static u64 notrace ingenic_ost_read_cnth(void) +{ + u32 val; + + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTH, ); + + return val; +} + +static u64 notrace ingenic_ost_clocksource_read64(struct clocksource *cs) +{ + u32 val1, val2; + u64 count, recount; + s64 diff; + + /* +* The buffering of the upper 32 bits of the timer prevents wrong +* results from the bottom 32 bits overflowing due to the timer ticking +* along. However, it does not prevent wrong results from simultaneous +* reads of the timer, which could reset the buffer mid-read. +* Since this kind of wrong read can happen only when the bottom bits +* overflow, there will be minutes between wrong reads, so if we read +* twice in succession, at least one of the reads will be correct. +*/ + + /* Bypass the regmap here as we must return as soon as possible */ + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTL, ); + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTHBUF, ); + count = (u64)val1 | (u64)val2 << 32; + + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTL, ); + regmap_read(ingenic_ost->map, TCU_REG_OST_CNTHBUF, ); + recount = (u64)val1 | (u64)val2 << 32; + + /* +* A wrong read will produce a result that is 1<<32 too high: the bottom +* part from before overflow and the upper part from after overflow. +* Therefore, the lower value of the two reads is the correct value. +*/ + + diff = (s64)(recount - count); + if (unlikely(diff < 0)) +
[PATCH 6/7] pwm: jz4740: Make PWM start with the active part
The PWM will always start with the inactive part. To counter that, when PWM is enabled we switch the configured polarity, and use 'period - duty + 1' as the real duty. Signed-off-by: Paul Cercueil --- drivers/pwm/pwm-jz4740.c | 22 +- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 85e2110aae4f..8df898429d47 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -121,6 +121,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, *parent_clk = clk_get_parent(clk); unsigned long rate, parent_rate, period, duty; unsigned long long tmp; + bool polarity_inversed; int ret; parent_rate = clk_get_rate(parent_clk); @@ -183,24 +184,27 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* Reset counter to 0 */ regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); - /* Set duty */ - regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); - /* Set period */ regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); + /* +* The PWM will always start with the inactive part. To counter that, +* when PWM is enabled we switch the configured polarity, and use +* 'period - duty + 1' as the real duty. +*/ + + /* Set duty */ + regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), period - duty + 1); + /* Set polarity */ - switch (state->polarity) { - case PWM_POLARITY_NORMAL: + polarity_inversed = state->polarity == PWM_POLARITY_INVERSED; + if (!polarity_inversed ^ state->enabled) regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, 0); - break; - case PWM_POLARITY_INVERSED: + else regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_INITL_HIGH, TCU_TCSR_PWM_INITL_HIGH); - break; - } if (state->enabled) jz4740_pwm_enable(chip, pwm); -- 2.21.0.593.g511ec345e18
[PATCH 7/7] pwm: jz4740: document known limitations
From: Uwe Kleine-König The jz4740 PMW implementation doesn't fulfill the (up to now insufficiently documented) requirements of the PWM API. At least document them in the driver. Signed-off-by: Uwe Kleine-König Signed-off-by: Paul Cercueil --- drivers/pwm/pwm-jz4740.c | 4 1 file changed, 4 insertions(+) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 8df898429d47..b331a73a9d5d 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -2,6 +2,10 @@ /* * Copyright (C) 2010, Lars-Peter Clausen * JZ4740 platform PWM support + * + * Limitations: + * - The .apply callback doesn't complete the currently running period before + * reconfiguring the hardware. */ #include -- 2.21.0.593.g511ec345e18
[PATCH 3/7] pwm: jz4740: Drop dependency on MACH_INGENIC
Depending on MACH_INGENIC prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Signed-off-by: Paul Cercueil --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d2557c6fcf65..82a75e0b72e5 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -224,7 +224,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" - depends on MACH_INGENIC + depends on MIPS depends on COMMON_CLK select MFD_SYSCON help -- 2.21.0.593.g511ec345e18
[PATCH 5/7] pwm: jz4740: Allow selection of PWM channels 0 and 1
The TCU channels 0 and 1 were previously reserved for system tasks, and thus unavailable for PWM. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/pwm/pwm-jz4740.c | 19 ++- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index f20dc2e19240..85e2110aae4f 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -28,6 +28,19 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) return container_of(chip, struct jz4740_pwm_chip, chip); } +static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz, + unsigned int channel) +{ + /* Enable all TCU channels for PWM use by default except channels 0/1 */ + u32 pwm_channels_mask = GENMASK(NUM_PWM - 1, 2); + + device_property_read_u32(jz->chip.dev->parent, +"ingenic,pwm-channels-mask", +_channels_mask); + + return !!(pwm_channels_mask & BIT(channel)); +} + static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct jz4740_pwm_chip *jz = to_jz4740(chip); @@ -35,11 +48,7 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) char clk_name[16]; int ret; - /* -* Timers 0 and 1 are used for system tasks, so they are unavailable -* for use as PWMs. -*/ - if (pwm->hwpwm < 2) + if (!jz4740_pwm_can_use_chn(jz, pwm->hwpwm)) return -EBUSY; snprintf(clk_name, sizeof(clk_name), "timer%u", pwm->hwpwm); -- 2.21.0.593.g511ec345e18
[PATCH 2/7] pwm: jz4740: Use clocks from TCU driver
The ingenic-timer "TCU" driver provides us with clocks, that can be (un)gated, reparented or reclocked from devicetree, instead of having these settings hardcoded in this driver. While this driver is devicetree-compatible, it is never (as of now) probed from devicetree, so this change does not introduce a ABI problem with current devicetree files. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/pwm/Kconfig | 1 + drivers/pwm/pwm-jz4740.c | 40 ++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index cc4df0ec978a..d2557c6fcf65 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -225,6 +225,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" depends on MACH_INGENIC + depends on COMMON_CLK select MFD_SYSCON help Generic PWM framework driver for Ingenic JZ47xx based diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 7aea5e0c6e18..6ec8794d3b99 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -20,7 +20,6 @@ struct jz4740_pwm_chip { struct pwm_chip chip; - struct clk *clk; struct regmap *map; }; @@ -32,6 +31,9 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct jz4740_pwm_chip *jz = to_jz4740(chip); + struct clk *clk; + char clk_name[16]; + int ret; /* * Timers 0 and 1 are used for system tasks, so they are unavailable @@ -40,16 +42,29 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) if (pwm->hwpwm < 2) return -EBUSY; - regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); + snprintf(clk_name, sizeof(clk_name), "timer%u", pwm->hwpwm); + + clk = clk_get(chip->dev, clk_name); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + return ret; + } + + pwm_set_chip_data(pwm, clk); return 0; } static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { - struct jz4740_pwm_chip *jz = to_jz4740(chip); + struct clk *clk = pwm_get_chip_data(pwm); - regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); + clk_disable_unprepare(clk); + clk_put(clk); } static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) @@ -93,16 +108,20 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); + struct clk *clk = pwm_get_chip_data(pwm), + *parent_clk = clk_get_parent(clk); + unsigned long rate, period, duty; unsigned long long tmp; - unsigned long period, duty; unsigned int prescaler = 0; - tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period; + rate = clk_get_rate(parent_clk); + tmp = (unsigned long long)rate * state->period; do_div(tmp, 10); period = tmp; while (period > 0x && prescaler < 6) { period >>= 2; + rate >>= 2; ++prescaler; } @@ -122,10 +141,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); - /* Set clock prescale */ - regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), - TCU_TCSR_PRESCALE_MASK, - prescaler << TCU_TCSR_PRESCALE_LSB); + clk_set_rate(clk, rate); /* Reset counter to 0 */ regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); @@ -171,10 +187,6 @@ static int jz4740_pwm_probe(struct platform_device *pdev) if (!jz4740) return -ENOMEM; - jz4740->clk = devm_clk_get(>dev, "ext"); - if (IS_ERR(jz4740->clk)) - return PTR_ERR(jz4740->clk); - jz4740->map = device_node_to_regmap(dev->parent->of_node); if (!jz4740->map) { dev_err(dev, "regmap not found\n"); -- 2.21.0.593.g511ec345e18
[PATCH 4/7] pwm: jz4740: Improve algorithm of clock calculation
The previous algorithm hardcoded details about how the TCU clocks work. The new algorithm will use clk_round_rate to find the perfect clock rate for the PWM channel. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/pwm/pwm-jz4740.c | 60 +--- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 6ec8794d3b99..f20dc2e19240 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -110,24 +110,56 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); struct clk *clk = pwm_get_chip_data(pwm), *parent_clk = clk_get_parent(clk); - unsigned long rate, period, duty; + unsigned long rate, parent_rate, period, duty; unsigned long long tmp; - unsigned int prescaler = 0; + int ret; - rate = clk_get_rate(parent_clk); - tmp = (unsigned long long)rate * state->period; - do_div(tmp, 10); - period = tmp; + parent_rate = clk_get_rate(parent_clk); + + jz4740_pwm_disable(chip, pwm); - while (period > 0x && prescaler < 6) { - period >>= 2; - rate >>= 2; - ++prescaler; + /* Reset the clock to the maximum rate, and we'll reduce it if needed */ + ret = clk_set_max_rate(clk, parent_rate); + if (ret) { + dev_err(chip->dev, "Unable to set max rate: %d\n", ret); + return ret; } - if (prescaler == 6) - return -EINVAL; + ret = clk_set_rate(clk, parent_rate); + if (ret) { + dev_err(chip->dev, "Unable to reset to parent rate (%lu Hz)", + parent_rate); + return ret; + } + + /* +* Limit the clock to a maximum rate that still gives us a period value +* which fits in 16 bits. +*/ + tmp = 0xull * NSEC_PER_SEC; + do_div(tmp, state->period); + ret = clk_set_max_rate(clk, tmp); + if (ret) { + dev_err(chip->dev, "Unable to set max rate: %d\n", ret); + return ret; + } + + /* +* Read back the clock rate, as it may have been modified by +* clk_set_max_rate() +*/ + rate = clk_get_rate(clk); + + if (rate != parent_rate) + dev_dbg(chip->dev, "PWM clock updated to %lu Hz\n", rate); + + /* Calculate period value */ + tmp = (unsigned long long)rate * state->period; + do_div(tmp, NSEC_PER_SEC); + period = (unsigned long)tmp; + + /* Calculate duty value */ tmp = (unsigned long long)period * state->duty_cycle; do_div(tmp, state->period); duty = period - tmp; @@ -135,14 +167,10 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (duty >= period) duty = period - 1; - jz4740_pwm_disable(chip, pwm); - /* Set abrupt shutdown */ regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); - clk_set_rate(clk, rate); - /* Reset counter to 0 */ regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); -- 2.21.0.593.g511ec345e18
[PATCH 1/7] pwm: jz4740: Obtain regmap from parent node
The TCU registers are shared between a handful of drivers, accessing them through the same regmap. While this driver is devicetree-compatible, it is never (as of now) probed from devicetree, so this change does not introduce a ABI problem with current devicetree files. Signed-off-by: Paul Cercueil Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/pwm/Kconfig | 1 + drivers/pwm/pwm-jz4740.c | 80 ++-- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a7e57516959e..cc4df0ec978a 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -225,6 +225,7 @@ config PWM_IMX_TPM config PWM_JZ4740 tristate "Ingenic JZ47xx PWM support" depends on MACH_INGENIC + select MFD_SYSCON help Generic PWM framework driver for Ingenic JZ47xx based machines. diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index f901e8a0d33d..7aea5e0c6e18 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -8,18 +8,20 @@ #include #include #include +#include +#include #include #include #include #include - -#include +#include #define NUM_PWM 8 struct jz4740_pwm_chip { struct pwm_chip chip; struct clk *clk; + struct regmap *map; }; static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) @@ -29,6 +31,8 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { + struct jz4740_pwm_chip *jz = to_jz4740(chip); + /* * Timers 0 and 1 are used for system tasks, so they are unavailable * for use as PWMs. @@ -36,50 +40,53 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) if (pwm->hwpwm < 2) return -EBUSY; - jz4740_timer_start(pwm->hwpwm); + regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); return 0; } static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { - jz4740_timer_set_ctrl(pwm->hwpwm, 0); + struct jz4740_pwm_chip *jz = to_jz4740(chip); - jz4740_timer_stop(pwm->hwpwm); + regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm)); } static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { - uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); + struct jz4740_pwm_chip *jz = to_jz4740(chip); + + /* Enable PWM output */ + regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_EN, TCU_TCSR_PWM_EN); - ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; - jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); - jz4740_timer_enable(pwm->hwpwm); + /* Start counter */ + regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm)); return 0; } static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { - uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); + struct jz4740_pwm_chip *jz = to_jz4740(chip); /* * Set duty > period. This trick allows the TCU channels in TCU2 mode to * properly return to their init level. */ - jz4740_timer_set_duty(pwm->hwpwm, 0x); - jz4740_timer_set_period(pwm->hwpwm, 0x0); + regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), 0x); + regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), 0x0); /* * Disable PWM output. * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the * counter is stopped, while in TCU1 mode the order does not matter. */ - ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; - jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_EN, 0); /* Stop counter */ - jz4740_timer_disable(pwm->hwpwm); + regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm)); } static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, @@ -89,7 +96,6 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long tmp; unsigned long period, duty; unsigned int prescaler = 0; - uint16_t ctrl; tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period; do_div(tmp, 10); @@ -112,26 +118,37 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, jz4740_pwm_disable(chip, pwm); - jz4740_timer_set_count(pwm->hwpwm, 0); - jz4740_timer_set_duty(pwm->hwpwm, duty); - jz4740_timer_set_period(pwm->hwpwm, period); + /* Set abrupt shutdown */ + regmap_update_bits(jz4740->map
[PATCH 0/7] pwm: jz4740: Driver update
Hi, Patches 1-5 come from a bigger patchset that was cut in smaller pieces for easier integration to mainline. (The patchset was https://lkml.org/lkml/2019/3/27/1837) These patches are the exact same as the ones found in the patchset shown above, with the exception of patch [1/7] which now uses device_node_to_regmap(). This function was added in a prior patch, now merged in the MIPS tree. For that reason this patchset is based on the ingenic-tcu-v5.4 branch of the MIPS tree (git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git). Thanks, -Paul
[PATCH 1/3] watchdog: jz4740: Use WDT clock provided by TCU driver
Instead of requesting the "ext" clock and handling the watchdog clock divider and gating in the watchdog driver, we now request and use the "wdt" clock that is supplied by the ingenic-timer "TCU" driver. The major benefit is that the watchdog's clock rate and parent can now be specified from within devicetree, instead of hardcoded in the driver. Also, this driver won't poke anymore into the TCU registers to enable/disable the clock, as this is now handled by the TCU driver. On the bad side, we break the ABI with devicetree - as we now request a different clock. In this very specific case it is still okay, as every Ingenic JZ47xx-based board out there compile the devicetree within the kernel; so it's still time to push breaking changes, in order to get a clean devicetree that won't break once it musn't. Signed-off-by: Paul Cercueil Reviewed-by: Guenter Roeck Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/watchdog/Kconfig | 1 + drivers/watchdog/jz4740_wdt.c | 75 ++- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 8188963a405b..820d8a472310 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1645,6 +1645,7 @@ config INDYDOG config JZ4740_WDT tristate "Ingenic jz4740 SoC hardware watchdog" depends on MACH_JZ4740 || MACH_JZ4780 + depends on COMMON_CLK select WATCHDOG_CORE help Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs. diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index d4a90916dd38..07fbd9d96e84 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -18,19 +18,6 @@ #include #include -#include - -#define JZ_WDT_CLOCK_PCLK 0x1 -#define JZ_WDT_CLOCK_RTC 0x2 -#define JZ_WDT_CLOCK_EXT 0x4 - -#define JZ_WDT_CLOCK_DIV_1(0 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_4(1 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_16 (2 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_64 (3 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_256 (4 << TCU_TCSR_PRESCALE_LSB) -#define JZ_WDT_CLOCK_DIV_1024 (5 << TCU_TCSR_PRESCALE_LSB) - #define DEFAULT_HEARTBEAT 5 #define MAX_HEARTBEAT 2048 @@ -50,7 +37,8 @@ MODULE_PARM_DESC(heartbeat, struct jz4740_wdt_drvdata { struct watchdog_device wdt; void __iomem *base; - struct clk *rtc_clk; + struct clk *clk; + unsigned long clk_rate; }; static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) @@ -65,32 +53,14 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int new_timeout) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - unsigned int rtc_clk_rate; - unsigned int timeout_value; - unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; + u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); u8 tcer; - rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); - - timeout_value = rtc_clk_rate * new_timeout; - while (timeout_value > 0x) { - if (clock_div == JZ_WDT_CLOCK_DIV_1024) { - /* Requested timeout too high; - * use highest possible value. */ - timeout_value = 0x; - break; - } - timeout_value >>= 2; - clock_div += (1 << TCU_TCSR_PRESCALE_LSB); - } - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); - writew(clock_div, drvdata->base + TCU_REG_WDT_TCSR); writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); - writew(clock_div | JZ_WDT_CLOCK_RTC, drvdata->base + TCU_REG_WDT_TCSR); if (tcer & TCU_WDT_TCER_TCEN) writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); @@ -102,11 +72,15 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, static int jz4740_wdt_start(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + int ret; u8 tcer; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + tcer = readb(drvdata->base + TCU_REG_WDT_TCER); - jz4740_timer_enable_watchdog(); jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); /* Start watchdog if it wasn't started already */ @@ -121,7 +95,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); writeb(0x0, drvdata-
[PATCH 3/3] watchdog: jz4740: Drop dependency on MACH_JZ47xx
Depending on MACH_JZ47xx prevent us from creating a generic kernel that works on more than one MIPS board. Instead, we just depend on MIPS being set. Signed-off-by: Paul Cercueil Reviewed-by: Guenter Roeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 78411609048b..90e689cbeed3 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1644,7 +1644,7 @@ config INDYDOG config JZ4740_WDT tristate "Ingenic jz4740 SoC hardware watchdog" - depends on MACH_JZ4740 || MACH_JZ4780 + depends on MIPS depends on COMMON_CLK select WATCHDOG_CORE select MFD_SYSCON -- 2.21.0.593.g511ec345e18
[PATCH 2/3] watchdog: jz4740: Use regmap provided by TCU driver
Since we broke the ABI by changing the clock, the driver was also updated to use the regmap provided by the TCU driver. Signed-off-by: Paul Cercueil Reviewed-by: Guenter Roeck Tested-by: Mathieu Malaterre Tested-by: Artur Rojek --- drivers/watchdog/Kconfig | 1 + drivers/watchdog/jz4740_wdt.c | 36 +++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 820d8a472310..78411609048b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1647,6 +1647,7 @@ config JZ4740_WDT depends on MACH_JZ4740 || MACH_JZ4780 depends on COMMON_CLK select WATCHDOG_CORE + select MFD_SYSCON help Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs. diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index 07fbd9d96e84..bdf9564efa29 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #define DEFAULT_HEARTBEAT 5 #define MAX_HEARTBEAT 2048 @@ -36,7 +38,7 @@ MODULE_PARM_DESC(heartbeat, struct jz4740_wdt_drvdata { struct watchdog_device wdt; - void __iomem *base; + struct regmap *map; struct clk *clk; unsigned long clk_rate; }; @@ -45,7 +47,8 @@ static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); + return 0; } @@ -54,16 +57,16 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); - u8 tcer; + unsigned int tcer; - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); - writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); + regmap_read(drvdata->map, TCU_REG_WDT_TCER, ); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); - writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); - writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); + regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); + regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); if (tcer & TCU_WDT_TCER_TCEN) - writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); wdt_dev->timeout = new_timeout; return 0; @@ -72,20 +75,20 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, static int jz4740_wdt_start(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + unsigned int tcer; int ret; - u8 tcer; ret = clk_prepare_enable(drvdata->clk); if (ret) return ret; - tcer = readb(drvdata->base + TCU_REG_WDT_TCER); + regmap_read(drvdata->map, TCU_REG_WDT_TCER, ); jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); /* Start watchdog if it wasn't started already */ if (!(tcer & TCU_WDT_TCER_TCEN)) - writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); return 0; } @@ -94,7 +97,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); + regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); clk_disable_unprepare(drvdata->clk); return 0; @@ -136,7 +139,6 @@ static int jz4740_wdt_probe(struct platform_device *pdev) struct device *dev = >dev; struct jz4740_wdt_drvdata *drvdata; struct watchdog_device *jz4740_wdt; - struct resource *res; long rate; int ret; @@ -173,9 +175,11 @@ static int jz4740_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(jz4740_wdt, nowayout); watchdog_set_drvdata(jz4740_wdt, drvdata); - drvdata->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(drvdata->base)) - return PTR_ERR(drvdata->base); + drvdata->map = device_node_to_regmap(dev->parent->of_node); + if (!drvdata->map) { + dev_err(dev, "regmap not found\n"); + return -EINVAL; + } return devm_watchdog_register_device(dev, >wdt); } -- 2.21.0.593.g511ec345e18