[v10,04/27] clocksource: Add a new timer-ingenic driver
diff mbox series

Message ID 20190302233413.14813-5-paul@crapouillou.net
State Not Applicable
Headers show
Series
  • Ingenic TCU patchset
Related show

Commit Message

Paul Cercueil March 2, 2019, 11:33 p.m. UTC
This driver handles the TCU (Timer Counter Unit) present on the Ingenic
JZ47xx SoCs, and provides the kernel with a system timer, and optionally
with a clocksource and a sched_clock.

It also provides clocks and interrupt handling to client drivers.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Tested-by: Mathieu Malaterre <malat@debian.org>
Tested-by: Artur Rojek <contact@artur-rojek.eu>
---

Notes:
         v2: Use SPDX identifier for the license
    
         v3: - Move documentation to its own patch
             - Search the devicetree for PWM clients, and use all the TCU
        	   channels that won't be used for PWM
    
         v4: - Add documentation about why we search for PWM clients
             - Verify that the PWM clients are for the TCU PWM driver
    
         v5: Major overhaul. Too many changes to list. Consider it's a new
             patch.
    
         v6: - Add two API functions ingenic_tcu_request_channel and
               ingenic_tcu_release_channel. To be used by the PWM driver to
               request the use of a TCU channel. The driver will now dynamically
               move away the system timer or clocksource to a new TCU channel.
             - The system timer now defaults to channel 0, the clocksource now
               defaults to channel 1 and is no more optional. The
               ingenic,timer-channel and ingenic,clocksource-channel devicetree
               properties are now gone.
             - Fix round_rate / set_rate not calculating the prescale divider
               the same way. This caused problems when (parent_rate / div) would
               give a non-integer result. The behaviour is correct now.
             - The clocksource clock is turned off on suspend now.
    
         v7: Fix section mismatch by using builtin_platform_driver_probe()
    
         v8: - Removed ingenic_tcu_[request,release]_channel, and the mechanism
               to dynamically change the TCU channel of the system timer or
    	   the clocksource.
    	 - The driver's devicetree node can now have two more children
    	   nodes, that correspond to the system timer and clocksource.
    	   For these two, the driver will use the TCU timer that
    	   correspond to the memory resource supplied in their
    	   respective node.
    
         v9: - Removed support for clocksource / timer children devicetree
               nodes. Now, we use a property "ingenic,pwm-channels-mask" to
    	   know which PWM channels are reserved for PWM use and should
    	   not be used as OS timers.
    
        v10: - Use CLK_SET_RATE_UNGATE instead of CLK_SET_RATE_GATE + manually
               un-gating the clock before changing rate. Same for re-parenting.
    	 - Unconditionally create the clocksource and sched_clock even if
    	   the SoC possesses a OS Timer. That gives the choice back to the
    	   user which clocksource should be selected.
    	 - Use subsys_initcall() instead of builtin_platform_driver_probe().
    	   The OS Timer driver calls builtin_platform_driver_probe, which
    	   requires the device to be created before that.
    	 - Cosmetic cleanups

 drivers/clocksource/Kconfig         |  10 +
 drivers/clocksource/Makefile        |   1 +
 drivers/clocksource/ingenic-timer.c | 903 ++++++++++++++++++++++++++++++++++++
 drivers/clocksource/ingenic-timer.h |  15 +
 include/linux/mfd/ingenic-tcu.h     |   2 +
 5 files changed, 931 insertions(+)
 create mode 100644 drivers/clocksource/ingenic-timer.c
 create mode 100644 drivers/clocksource/ingenic-timer.h

Comments

Thierry Reding March 4, 2019, 12:22 p.m. UTC | #1
On Sat, Mar 02, 2019 at 08:33:50PM -0300, Paul Cercueil wrote:
[...]
> diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c
[...]
> +struct ingenic_tcu {
> +	const struct ingenic_soc_info *soc_info;
> +	struct regmap *map;
> +	struct clk *clk, *timer_clk, *cs_clk;
> +
> +	struct irq_domain *domain;
> +	unsigned int nb_parent_irqs;
> +	u32 parent_irqs[3];
> +
> +	struct clk_hw_onecell_data *clocks;
> +
> +	unsigned int timer_channel, cs_channel;
> +	struct clock_event_device cevt;
> +	struct clocksource cs;
> +	char name[4];
> +
> +	unsigned long pwm_channels_mask;
> +};
> +
> +static struct ingenic_tcu *ingenic_tcu;

Is there really a need for this? Seems like the only two reasons for why
you keep this around are as pointed out below.

> +void __iomem *ingenic_tcu_base;
> +EXPORT_SYMBOL_GPL(ingenic_tcu_base);

Same here.

> +static u64 notrace ingenic_tcu_timer_read(void)
> +{
> +	unsigned int channel = ingenic_tcu->cs_channel;
> +	u16 count;
> +
> +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));

Can't yo do this via the regmap?

> +
> +	return count;
> +}
> +
> +static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs)
> +{
> +	return ingenic_tcu_timer_read();
> +}

And here you've got access to the struct clocksource *, from which you
should be able to get at struct ingenic_tcu * using container_of.

[...]
> +static int __init ingenic_tcu_init(struct device_node *np)
> +{
> +	const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
> +	const struct ingenic_soc_info *soc_info = id->data;
> +	struct ingenic_tcu *tcu;
> +	struct resource res;
> +	void __iomem *base;
> +	long rate;
> +	int ret;
> +
> +	of_node_clear_flag(np, OF_POPULATED);
> +
> +	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
> +	if (!tcu)
> +		return -ENOMEM;
> +
> +	/* Enable all TCU channels for PWM use by default except channel 0
> +	 * and channel 1.
> +	 */
> +	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, 2);
> +	of_property_read_u32(np, "ingenic,pwm-channels-mask",
> +			     (u32 *)&tcu->pwm_channels_mask);
> +
> +	/* Verify that we have at least two free channels */
> +	if (hweight8(tcu->pwm_channels_mask) > soc_info->num_channels - 2) {
> +		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
> +			tcu->pwm_channels_mask);
> +		return -EINVAL;
> +	}
> +
> +	tcu->soc_info = soc_info;
> +	ingenic_tcu = tcu;
> +
> +	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
> +						 soc_info->num_channels);
> +	tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
> +					     soc_info->num_channels,
> +					     tcu->timer_channel + 1);
> +
> +	base = of_io_request_and_map(np, 0, NULL);
> +	if (IS_ERR(base)) {
> +		ret = PTR_ERR(base);
> +		goto err_free_ingenic_tcu;
> +	}
> +
> +	of_address_to_resource(np, 0, &res);
> +
> +	ingenic_tcu_base = base;
> +
> +	tcu->map = regmap_init_mmio(NULL, base, &ingenic_tcu_regmap_config);
> +	if (IS_ERR(tcu->map)) {
> +		ret = PTR_ERR(tcu->map);
> +		goto err_iounmap;
> +	}
> +
> +	tcu->clk = of_clk_get_by_name(np, "tcu");
> +	if (IS_ERR(tcu->clk)) {
> +		ret = PTR_ERR(tcu->clk);
> +		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
> +		goto err_free_regmap;
> +	}
> +
> +	ret = clk_prepare_enable(tcu->clk);
> +	if (ret) {
> +		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
> +		goto err_clk_put;
> +	}
> +
> +	ret = ingenic_tcu_intc_init(tcu, np);
> +	if (ret)
> +		goto err_clk_disable;
> +
> +	ret = ingenic_tcu_clk_init(tcu, np);
> +	if (ret)
> +		goto err_tcu_intc_cleanup;
> +
> +	ret = ingenic_tcu_clocksource_init(tcu);
> +	if (ret)
> +		goto err_tcu_clk_cleanup;
> +
> +	ret = ingenic_tcu_timer_init(tcu);
> +	if (ret)
> +		goto err_tcu_clocksource_cleanup;
> +
> +	// Register the sched_clock at the very end as there's no way to undo it
> +	rate = clk_get_rate(tcu->cs_clk);
> +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);

Oh wow... so you managed to nicely encapsulate everything and now this
seems to be the only reason why you need to rely on global variables.

That's unfortunate. I suppose we could go and add a void *data parameter
to sched_clock_register() and pass that to the read() function. That way
you could make this completely independent of global variables, but
there are 73 callers of sched_clock_register() and they are spread all
over the place, so sounds a bit daunting to me.

> +
> +	return 0;
> +
> +err_tcu_clocksource_cleanup:
> +	ingenic_tcu_clocksource_cleanup(tcu);
> +err_tcu_clk_cleanup:
> +	ingenic_tcu_clk_cleanup(tcu, np);
> +err_tcu_intc_cleanup:
> +	ingenic_tcu_intc_cleanup(tcu);
> +err_clk_disable:
> +	clk_disable_unprepare(tcu->clk);
> +err_clk_put:
> +	clk_put(tcu->clk);
> +err_free_regmap:
> +	regmap_exit(tcu->map);
> +err_iounmap:
> +	iounmap(base);
> +	release_mem_region(res.start, resource_size(&res));
> +err_free_ingenic_tcu:
> +	kfree(tcu);
> +	return ret;
> +}
> +
> +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  ingenic_tcu_init);
> +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
> +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  ingenic_tcu_init);
> +
> +
> +static int __init ingenic_tcu_probe(struct platform_device *pdev)
> +{
> +	platform_set_drvdata(pdev, ingenic_tcu);

Then there's also this. Oh well... nevermind then.

> +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
> +{
> +	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
> +}
> +EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);

I wonder if we could make this slightly nicer, though. Judging by the
name, this one seems like it would only ever be used by the PWM. If the
PWM is a child of the TCU, could it not pass in a struct device * that
points to its parent into this? Or perhaps pass its own struct device *
and have this function look up the struct ingenic_tcu from that? Should
be something trivial as:

	bool ingenic_tcu_pwm_can_use_channel(struct device *dev, unsigned int channel)
	{
		struct ingenic_tcu *tcu = dev_get_drvdata(dev->parent);

		return !!(tcu->pwm_channels_mask & BIT(channel));
	}

Thierry
Paul Cercueil March 4, 2019, 6:13 p.m. UTC | #2
Hi Thierry,

On Mon, Mar 4, 2019 at 1:22 PM, Thierry Reding 
<thierry.reding@gmail.com> wrote:
> On Sat, Mar 02, 2019 at 08:33:50PM -0300, Paul Cercueil wrote:
> [...]
>>  diff --git a/drivers/clocksource/ingenic-timer.c 
>> b/drivers/clocksource/ingenic-timer.c
> [...]
>>  +struct ingenic_tcu {
>>  +	const struct ingenic_soc_info *soc_info;
>>  +	struct regmap *map;
>>  +	struct clk *clk, *timer_clk, *cs_clk;
>>  +
>>  +	struct irq_domain *domain;
>>  +	unsigned int nb_parent_irqs;
>>  +	u32 parent_irqs[3];
>>  +
>>  +	struct clk_hw_onecell_data *clocks;
>>  +
>>  +	unsigned int timer_channel, cs_channel;
>>  +	struct clock_event_device cevt;
>>  +	struct clocksource cs;
>>  +	char name[4];
>>  +
>>  +	unsigned long pwm_channels_mask;
>>  +};
>>  +
>>  +static struct ingenic_tcu *ingenic_tcu;
> 
> Is there really a need for this? Seems like the only two reasons for 
> why
> you keep this around are as pointed out below.
> 
>>  +void __iomem *ingenic_tcu_base;
>>  +EXPORT_SYMBOL_GPL(ingenic_tcu_base);
> 
> Same here.
> 
>>  +static u64 notrace ingenic_tcu_timer_read(void)
>>  +{
>>  +	unsigned int channel = ingenic_tcu->cs_channel;
>>  +	u16 count;
>>  +
>>  +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
> 
> Can't yo do this via the regmap?

I could, but for the sched_clock to be precise the function must return
as fast as possible. That's the rationale behind the use of readw() 
here.

That's also the reason why ingenic_tcu_base is global and exported, so
that the OS Timer driver can use it as well.

>>  +
>>  +	return count;
>>  +}
>>  +
>>  +static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource 
>> *cs)
>>  +{
>>  +	return ingenic_tcu_timer_read();
>>  +}
> 
> And here you've got access to the struct clocksource *, from which you
> should be able to get at struct ingenic_tcu * using container_of.
> 
> [...]
>>  +static int __init ingenic_tcu_init(struct device_node *np)
>>  +{
>>  +	const struct of_device_id *id = 
>> of_match_node(ingenic_tcu_of_match, np);
>>  +	const struct ingenic_soc_info *soc_info = id->data;
>>  +	struct ingenic_tcu *tcu;
>>  +	struct resource res;
>>  +	void __iomem *base;
>>  +	long rate;
>>  +	int ret;
>>  +
>>  +	of_node_clear_flag(np, OF_POPULATED);
>>  +
>>  +	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
>>  +	if (!tcu)
>>  +		return -ENOMEM;
>>  +
>>  +	/* Enable all TCU channels for PWM use by default except channel 0
>>  +	 * and channel 1.
>>  +	 */
>>  +	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, 2);
>>  +	of_property_read_u32(np, "ingenic,pwm-channels-mask",
>>  +			     (u32 *)&tcu->pwm_channels_mask);
>>  +
>>  +	/* Verify that we have at least two free channels */
>>  +	if (hweight8(tcu->pwm_channels_mask) > soc_info->num_channels - 
>> 2) {
>>  +		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
>>  +			tcu->pwm_channels_mask);
>>  +		return -EINVAL;
>>  +	}
>>  +
>>  +	tcu->soc_info = soc_info;
>>  +	ingenic_tcu = tcu;
>>  +
>>  +	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
>>  +						 soc_info->num_channels);
>>  +	tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
>>  +					     soc_info->num_channels,
>>  +					     tcu->timer_channel + 1);
>>  +
>>  +	base = of_io_request_and_map(np, 0, NULL);
>>  +	if (IS_ERR(base)) {
>>  +		ret = PTR_ERR(base);
>>  +		goto err_free_ingenic_tcu;
>>  +	}
>>  +
>>  +	of_address_to_resource(np, 0, &res);
>>  +
>>  +	ingenic_tcu_base = base;
>>  +
>>  +	tcu->map = regmap_init_mmio(NULL, base, 
>> &ingenic_tcu_regmap_config);
>>  +	if (IS_ERR(tcu->map)) {
>>  +		ret = PTR_ERR(tcu->map);
>>  +		goto err_iounmap;
>>  +	}
>>  +
>>  +	tcu->clk = of_clk_get_by_name(np, "tcu");
>>  +	if (IS_ERR(tcu->clk)) {
>>  +		ret = PTR_ERR(tcu->clk);
>>  +		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
>>  +		goto err_free_regmap;
>>  +	}
>>  +
>>  +	ret = clk_prepare_enable(tcu->clk);
>>  +	if (ret) {
>>  +		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
>>  +		goto err_clk_put;
>>  +	}
>>  +
>>  +	ret = ingenic_tcu_intc_init(tcu, np);
>>  +	if (ret)
>>  +		goto err_clk_disable;
>>  +
>>  +	ret = ingenic_tcu_clk_init(tcu, np);
>>  +	if (ret)
>>  +		goto err_tcu_intc_cleanup;
>>  +
>>  +	ret = ingenic_tcu_clocksource_init(tcu);
>>  +	if (ret)
>>  +		goto err_tcu_clk_cleanup;
>>  +
>>  +	ret = ingenic_tcu_timer_init(tcu);
>>  +	if (ret)
>>  +		goto err_tcu_clocksource_cleanup;
>>  +
>>  +	// Register the sched_clock at the very end as there's no way to 
>> undo it
>>  +	rate = clk_get_rate(tcu->cs_clk);
>>  +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
> 
> Oh wow... so you managed to nicely encapsulate everything and now this
> seems to be the only reason why you need to rely on global variables.
> 
> That's unfortunate. I suppose we could go and add a void *data 
> parameter
> to sched_clock_register() and pass that to the read() function. That 
> way
> you could make this completely independent of global variables, but
> there are 73 callers of sched_clock_register() and they are spread all
> over the place, so sounds a bit daunting to me.

Yes, that's the main reason behind the use of a global variables.
Is there a way we could introduce another callback, e.g. .read_value(),
that would receive a void *param? Then the current .read() callback
can be deprecated and the 73 callers can be migrated later.

>>  +
>>  +	return 0;
>>  +
>>  +err_tcu_clocksource_cleanup:
>>  +	ingenic_tcu_clocksource_cleanup(tcu);
>>  +err_tcu_clk_cleanup:
>>  +	ingenic_tcu_clk_cleanup(tcu, np);
>>  +err_tcu_intc_cleanup:
>>  +	ingenic_tcu_intc_cleanup(tcu);
>>  +err_clk_disable:
>>  +	clk_disable_unprepare(tcu->clk);
>>  +err_clk_put:
>>  +	clk_put(tcu->clk);
>>  +err_free_regmap:
>>  +	regmap_exit(tcu->map);
>>  +err_iounmap:
>>  +	iounmap(base);
>>  +	release_mem_region(res.start, resource_size(&res));
>>  +err_free_ingenic_tcu:
>>  +	kfree(tcu);
>>  +	return ret;
>>  +}
>>  +
>>  +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  
>> ingenic_tcu_init);
>>  +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", 
>> ingenic_tcu_init);
>>  +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  
>> ingenic_tcu_init);
>>  +
>>  +
>>  +static int __init ingenic_tcu_probe(struct platform_device *pdev)
>>  +{
>>  +	platform_set_drvdata(pdev, ingenic_tcu);
> 
> Then there's also this. Oh well... nevermind then.

The content of ingenic_tcu_platform_init() could be moved inside
ingenic_tcu_init(). But can we get a hold of the struct device before 
the
probe function is called? That would allow to set the drvdata and regmap
without relying on global state.

>>  +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
>>  +{
>>  +	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
>>  +}
>>  +EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);
> 
> I wonder if we could make this slightly nicer, though. Judging by the
> name, this one seems like it would only ever be used by the PWM. If 
> the
> PWM is a child of the TCU, could it not pass in a struct device * that
> points to its parent into this? Or perhaps pass its own struct device 
> *
> and have this function look up the struct ingenic_tcu from that? 
> Should
> be something trivial as:
> 
> 	bool ingenic_tcu_pwm_can_use_channel(struct device *dev, unsigned 
> int channel)
> 	{
> 		struct ingenic_tcu *tcu = dev_get_drvdata(dev->parent);
> 
> 		return !!(tcu->pwm_channels_mask & BIT(channel));
> 	}
> 
> Thierry

Yes, that definitely looks better. Thanks for the hint.

-Paul
kernel test robot March 5, 2019, 3:15 a.m. UTC | #3
Hi Paul,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linus/master]
[also build test WARNING on v5.0]
[cannot apply to next-20190304]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Paul-Cercueil/Ingenic-TCU-patchset/20190305-054726
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-8 (Debian 8.2.0-21) 8.2.0
reproduce:
        # save the attached .config to linux build tree
        make ARCH=x86_64 

All warnings (new ones prefixed by >>):

   In file included from include/linux/printk.h:7,
                    from include/linux/kernel.h:14,
                    from include/linux/clk.h:16,
                    from drivers/clocksource/ingenic-timer.c:8:
   drivers/clocksource/ingenic-timer.c: In function 'ingenic_tcu_clk_init':
>> include/linux/kern_levels.h:5:18: warning: format '%i' expects argument of type 'int', but argument 2 has type 'size_t' {aka 'long unsigned int'} [-Wformat=]
    #define KERN_SOH "\001"  /* ASCII Start Of Header */
                     ^~~~~~
   include/linux/kern_levels.h:10:19: note: in expansion of macro 'KERN_SOH'
    #define KERN_CRIT KERN_SOH "2" /* critical conditions */
                      ^~~~~~~~
   include/linux/printk.h:301:9: note: in expansion of macro 'KERN_CRIT'
     printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
            ^~~~~~~~~
   drivers/clocksource/ingenic-timer.c:436:4: note: in expansion of macro 'pr_crit'
       pr_crit("ingenic-timer: cannot register clock %i\n", i);
       ^~~~~~~
   drivers/clocksource/ingenic-timer.c:436:51: note: format string is defined here
       pr_crit("ingenic-timer: cannot register clock %i\n", i);
                                                     ~^
                                                     %li
--
   In file included from include/linux/printk.h:7,
                    from include/linux/kernel.h:14,
                    from include/linux/clk.h:16,
                    from drivers//clocksource/ingenic-timer.c:8:
   drivers//clocksource/ingenic-timer.c: In function 'ingenic_tcu_clk_init':
>> include/linux/kern_levels.h:5:18: warning: format '%i' expects argument of type 'int', but argument 2 has type 'size_t' {aka 'long unsigned int'} [-Wformat=]
    #define KERN_SOH "\001"  /* ASCII Start Of Header */
                     ^~~~~~
   include/linux/kern_levels.h:10:19: note: in expansion of macro 'KERN_SOH'
    #define KERN_CRIT KERN_SOH "2" /* critical conditions */
                      ^~~~~~~~
   include/linux/printk.h:301:9: note: in expansion of macro 'KERN_CRIT'
     printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
            ^~~~~~~~~
   drivers//clocksource/ingenic-timer.c:436:4: note: in expansion of macro 'pr_crit'
       pr_crit("ingenic-timer: cannot register clock %i\n", i);
       ^~~~~~~
   drivers//clocksource/ingenic-timer.c:436:51: note: format string is defined here
       pr_crit("ingenic-timer: cannot register clock %i\n", i);
                                                     ~^
                                                     %li

vim +5 include/linux/kern_levels.h

314ba352 Joe Perches 2012-07-30  4  
04d2c8c8 Joe Perches 2012-07-30 @5  #define KERN_SOH	"\001"		/* ASCII Start Of Header */
04d2c8c8 Joe Perches 2012-07-30  6  #define KERN_SOH_ASCII	'\001'
04d2c8c8 Joe Perches 2012-07-30  7  

:::::: The code at line 5 was first introduced by commit
:::::: 04d2c8c83d0e3ac5f78aeede51babb3236200112 printk: convert the format for KERN_<LEVEL> to a 2 byte pattern

:::::: TO: Joe Perches <joe@perches.com>
:::::: CC: Linus Torvalds <torvalds@linux-foundation.org>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Thierry Reding March 8, 2019, 10:22 a.m. UTC | #4
On Mon, Mar 04, 2019 at 07:13:05PM +0100, Paul Cercueil wrote:
> Hi Thierry,
> 
> On Mon, Mar 4, 2019 at 1:22 PM, Thierry Reding <thierry.reding@gmail.com>
> wrote:
> > On Sat, Mar 02, 2019 at 08:33:50PM -0300, Paul Cercueil wrote:
> > [...]
> > >  diff --git a/drivers/clocksource/ingenic-timer.c
> > > b/drivers/clocksource/ingenic-timer.c
[...]
> > >  +static u64 notrace ingenic_tcu_timer_read(void)
> > >  +{
> > >  +	unsigned int channel = ingenic_tcu->cs_channel;
> > >  +	u16 count;
> > >  +
> > >  +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
> > 
> > Can't yo do this via the regmap?
> 
> I could, but for the sched_clock to be precise the function must return
> as fast as possible. That's the rationale behind the use of readw() here.
> 
> That's also the reason why ingenic_tcu_base is global and exported, so
> that the OS Timer driver can use it as well.

Is the path through the regmap really significantly slower than the
direct register access? Anyway, if you need the global anyway, might as
well use it.

[...]
> > >  +	// Register the sched_clock at the very end as there's no way to
> > > undo it
> > >  +	rate = clk_get_rate(tcu->cs_clk);
> > >  +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
> > 
> > Oh wow... so you managed to nicely encapsulate everything and now this
> > seems to be the only reason why you need to rely on global variables.
> > 
> > That's unfortunate. I suppose we could go and add a void *data parameter
> > to sched_clock_register() and pass that to the read() function. That way
> > you could make this completely independent of global variables, but
> > there are 73 callers of sched_clock_register() and they are spread all
> > over the place, so sounds a bit daunting to me.
> 
> Yes, that's the main reason behind the use of a global variables.
> Is there a way we could introduce another callback, e.g. .read_value(),
> that would receive a void *param? Then the current .read() callback
> can be deprecated and the 73 callers can be migrated later.

Yeah, I suppose that would be possible. I'll defer to Daniel and Thomas
on this. They may not consider this important enough.

> > >  +
> > >  +	return 0;
> > >  +
> > >  +err_tcu_clocksource_cleanup:
> > >  +	ingenic_tcu_clocksource_cleanup(tcu);
> > >  +err_tcu_clk_cleanup:
> > >  +	ingenic_tcu_clk_cleanup(tcu, np);
> > >  +err_tcu_intc_cleanup:
> > >  +	ingenic_tcu_intc_cleanup(tcu);
> > >  +err_clk_disable:
> > >  +	clk_disable_unprepare(tcu->clk);
> > >  +err_clk_put:
> > >  +	clk_put(tcu->clk);
> > >  +err_free_regmap:
> > >  +	regmap_exit(tcu->map);
> > >  +err_iounmap:
> > >  +	iounmap(base);
> > >  +	release_mem_region(res.start, resource_size(&res));
> > >  +err_free_ingenic_tcu:
> > >  +	kfree(tcu);
> > >  +	return ret;
> > >  +}
> > >  +
> > >  +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",
> > > ingenic_tcu_init);
> > >  +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu",
> > > ingenic_tcu_init);
> > >  +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",
> > > ingenic_tcu_init);
> > >  +
> > >  +
> > >  +static int __init ingenic_tcu_probe(struct platform_device *pdev)
> > >  +{
> > >  +	platform_set_drvdata(pdev, ingenic_tcu);
> > 
> > Then there's also this. Oh well... nevermind then.
> 
> The content of ingenic_tcu_platform_init() could be moved inside
> ingenic_tcu_init(). But can we get a hold of the struct device before the
> probe function is called? That would allow to set the drvdata and regmap
> without relying on global state.

I'm not sure if the driver core is ready at this point. It's likely it
isn't, otherwise there'd be no need for TIMER_OF_DECLARE(), really. I
also vaguely recall looking into this a few years ago because of some
similar work I was but I eventually gave up because I couldn't find a
way that would allow both the code to execute early enough and still
use the regular driver model.

Platform device become available at arch_initcall_sync (3s), while the
TIMER_OF_DECLARE code runs way earlier than any of the initcalls, so I
don't think there's a way to do it. Unless perhaps if the timers can
be initialized later. I'm not sure if that's possible, and might not be
worth it just for the sake of being pedantic.

Thierry
Paul Cercueil March 11, 2019, 8:52 p.m. UTC | #5
Hi Thierry,

Le ven. 8 mars 2019 à 7:22, Thierry Reding <thierry.reding@gmail.com> 
a écrit :
> On Mon, Mar 04, 2019 at 07:13:05PM +0100, Paul Cercueil wrote:
>>  Hi Thierry,
>> 
>>  On Mon, Mar 4, 2019 at 1:22 PM, Thierry Reding 
>> <thierry.reding@gmail.com>
>>  wrote:
>>  > On Sat, Mar 02, 2019 at 08:33:50PM -0300, Paul Cercueil wrote:
>>  > [...]
>>  > >  diff --git a/drivers/clocksource/ingenic-timer.c
>>  > > b/drivers/clocksource/ingenic-timer.c
> [...]
>>  > >  +static u64 notrace ingenic_tcu_timer_read(void)
>>  > >  +{
>>  > >  +	unsigned int channel = ingenic_tcu->cs_channel;
>>  > >  +	u16 count;
>>  > >  +
>>  > >  +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
>>  >
>>  > Can't yo do this via the regmap?
>> 
>>  I could, but for the sched_clock to be precise the function must 
>> return
>>  as fast as possible. That's the rationale behind the use of readw() 
>> here.
>> 
>>  That's also the reason why ingenic_tcu_base is global and exported, 
>> so
>>  that the OS Timer driver can use it as well.
> 
> Is the path through the regmap really significantly slower than the
> direct register access? Anyway, if you need the global anyway, might 
> as
> well use it.

About 10% slower.

> [...]
>>  > >  +	// Register the sched_clock at the very end as there's no 
>> way to
>>  > > undo it
>>  > >  +	rate = clk_get_rate(tcu->cs_clk);
>>  > >  +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
>>  >
>>  > Oh wow... so you managed to nicely encapsulate everything and now 
>> this
>>  > seems to be the only reason why you need to rely on global 
>> variables.
>>  >
>>  > That's unfortunate. I suppose we could go and add a void *data 
>> parameter
>>  > to sched_clock_register() and pass that to the read() function. 
>> That way
>>  > you could make this completely independent of global variables, 
>> but
>>  > there are 73 callers of sched_clock_register() and they are 
>> spread all
>>  > over the place, so sounds a bit daunting to me.
>> 
>>  Yes, that's the main reason behind the use of a global variables.
>>  Is there a way we could introduce another callback, e.g. 
>> .read_value(),
>>  that would receive a void *param? Then the current .read() callback
>>  can be deprecated and the 73 callers can be migrated later.
> 
> Yeah, I suppose that would be possible. I'll defer to Daniel and 
> Thomas
> on this. They may not consider this important enough.
> 
>>  > >  +
>>  > >  +	return 0;
>>  > >  +
>>  > >  +err_tcu_clocksource_cleanup:
>>  > >  +	ingenic_tcu_clocksource_cleanup(tcu);
>>  > >  +err_tcu_clk_cleanup:
>>  > >  +	ingenic_tcu_clk_cleanup(tcu, np);
>>  > >  +err_tcu_intc_cleanup:
>>  > >  +	ingenic_tcu_intc_cleanup(tcu);
>>  > >  +err_clk_disable:
>>  > >  +	clk_disable_unprepare(tcu->clk);
>>  > >  +err_clk_put:
>>  > >  +	clk_put(tcu->clk);
>>  > >  +err_free_regmap:
>>  > >  +	regmap_exit(tcu->map);
>>  > >  +err_iounmap:
>>  > >  +	iounmap(base);
>>  > >  +	release_mem_region(res.start, resource_size(&res));
>>  > >  +err_free_ingenic_tcu:
>>  > >  +	kfree(tcu);
>>  > >  +	return ret;
>>  > >  +}
>>  > >  +
>>  > >  +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",
>>  > > ingenic_tcu_init);
>>  > >  +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu",
>>  > > ingenic_tcu_init);
>>  > >  +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",
>>  > > ingenic_tcu_init);
>>  > >  +
>>  > >  +
>>  > >  +static int __init ingenic_tcu_probe(struct platform_device 
>> *pdev)
>>  > >  +{
>>  > >  +	platform_set_drvdata(pdev, ingenic_tcu);
>>  >
>>  > Then there's also this. Oh well... nevermind then.
>> 
>>  The content of ingenic_tcu_platform_init() could be moved inside
>>  ingenic_tcu_init(). But can we get a hold of the struct device 
>> before the
>>  probe function is called? That would allow to set the drvdata and 
>> regmap
>>  without relying on global state.
> 
> I'm not sure if the driver core is ready at this point. It's likely it
> isn't, otherwise there'd be no need for TIMER_OF_DECLARE(), really. I
> also vaguely recall looking into this a few years ago because of some
> similar work I was but I eventually gave up because I couldn't find a
> way that would allow both the code to execute early enough and still
> use the regular driver model.
> 
> Platform device become available at arch_initcall_sync (3s), while the
> TIMER_OF_DECLARE code runs way earlier than any of the initcalls, so I
> don't think there's a way to do it. Unless perhaps if the timers can
> be initialized later. I'm not sure if that's possible, and might not 
> be
> worth it just for the sake of being pedantic.
> 
> Thierry

I think for the time being I can't really go around using the global 
variables.
Hopefully that's something that can be fixed in the future.

-Paul

Patch
diff mbox series

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index a9e26f6a81a1..d841934e126c 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -634,4 +634,14 @@  config GX6605S_TIMER
 	help
 	  This option enables support for gx6605s SOC's timer.
 
+config INGENIC_TIMER
+	bool "Clocksource/timer using the TCU in Ingenic JZ SoCs"
+	depends on MIPS || COMPILE_TEST
+	depends on COMMON_CLK
+	select TIMER_OF
+	select IRQ_DOMAIN
+	select REGMAP
+	help
+	  Support for the timer/counter unit of the Ingenic JZ SoCs.
+
 endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index cdd210ff89ea..3eb8c544917f 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -76,6 +76,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_TIMER)		+= ingenic-timer.o
 obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
 obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
 obj-$(CONFIG_ATCPIT100_TIMER)		+= timer-atcpit100.o
diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c
new file mode 100644
index 000000000000..9529f6389ea4
--- /dev/null
+++ b/drivers/clocksource/ingenic-timer.c
@@ -0,0 +1,903 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * JZ47xx SoCs TCU IRQ driver
+ * Copyright (C) 2018 Paul Cercueil <paul@crapouillou.net>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/mfd/ingenic-tcu.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sched_clock.h>
+
+#include <dt-bindings/clock/ingenic,tcu.h>
+
+#include "ingenic-timer.h"
+
+/* 8 channels max + watchdog + OST */
+#define TCU_CLK_COUNT	10
+
+enum tcu_clk_parent {
+	TCU_PARENT_PCLK,
+	TCU_PARENT_RTC,
+	TCU_PARENT_EXT,
+};
+
+struct ingenic_soc_info {
+	unsigned char num_channels;
+	bool has_ost;
+};
+
+struct ingenic_tcu_clk_info {
+	struct clk_init_data init_data;
+	u8 gate_bit;
+	u8 tcsr_reg;
+};
+
+struct ingenic_tcu_clk {
+	struct clk_hw hw;
+
+	struct regmap *map;
+	const struct ingenic_tcu_clk_info *info;
+
+	unsigned int idx;
+};
+
+struct ingenic_tcu {
+	const struct ingenic_soc_info *soc_info;
+	struct regmap *map;
+	struct clk *clk, *timer_clk, *cs_clk;
+
+	struct irq_domain *domain;
+	unsigned int nb_parent_irqs;
+	u32 parent_irqs[3];
+
+	struct clk_hw_onecell_data *clocks;
+
+	unsigned int timer_channel, cs_channel;
+	struct clock_event_device cevt;
+	struct clocksource cs;
+	char name[4];
+
+	unsigned long pwm_channels_mask;
+};
+
+static struct ingenic_tcu *ingenic_tcu;
+
+void __iomem *ingenic_tcu_base;
+EXPORT_SYMBOL_GPL(ingenic_tcu_base);
+
+static inline struct ingenic_tcu_clk *to_tcu_clk(struct clk_hw *hw)
+{
+	return container_of(hw, struct ingenic_tcu_clk, hw);
+}
+
+static int ingenic_tcu_enable(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+	regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
+
+	return 0;
+}
+
+static void ingenic_tcu_disable(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+	regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
+}
+
+static int ingenic_tcu_is_enabled(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int value;
+
+	regmap_read(tcu_clk->map, TCU_REG_TSR, &value);
+
+	return !(value & BIT(info->gate_bit));
+}
+
+static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int val = 0;
+	int ret;
+
+	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &val);
+	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+	return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+}
+
+static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	int ret, enabled;
+
+	/* To be able to access TCSR we must ungate the clock supply and we gate
+	 * it again when done.
+	 */
+	enabled = ingenic_tcu_is_enabled(hw);
+	ingenic_tcu_enable(hw);
+
+	ret = regmap_update_bits(tcu_clk->map, info->tcsr_reg,
+				 TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+	if (!enabled)
+		ingenic_tcu_disable(hw);
+
+	return 0;
+}
+
+static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
+		unsigned long parent_rate)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int prescale;
+	int ret;
+
+	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &prescale);
+	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+	prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
+
+	return parent_rate >> (prescale * 2);
+}
+
+static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned long req_rate)
+{
+	u8 prescale;
+
+	for (prescale = 0; prescale < 5; prescale++)
+		if ((rate >> (prescale * 2)) <= req_rate)
+			return prescale;
+
+	return 5; /* /1024 divider */
+}
+
+static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned long req_rate,
+		unsigned long *parent_rate)
+{
+	unsigned long rate = *parent_rate;
+	u8 prescale;
+
+	if (req_rate > rate)
+		return -EINVAL;
+
+	prescale = ingenic_tcu_get_prescale(rate, req_rate);
+
+	return rate >> (prescale * 2);
+}
+
+static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
+		unsigned long parent_rate)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	struct regmap *map = tcu_clk->map;
+	u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
+	int ret;
+
+	ret = regmap_update_bits(map, info->tcsr_reg,
+				 TCU_TCSR_PRESCALE_MASK,
+				 prescale << TCU_TCSR_PRESCALE_LSB);
+	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+	return 0;
+}
+
+static const struct clk_ops ingenic_tcu_clk_ops = {
+	.get_parent	= ingenic_tcu_get_parent,
+	.set_parent	= ingenic_tcu_set_parent,
+
+	.recalc_rate	= ingenic_tcu_recalc_rate,
+	.round_rate	= ingenic_tcu_round_rate,
+	.set_rate	= ingenic_tcu_set_rate,
+
+	.enable		= ingenic_tcu_enable,
+	.disable	= ingenic_tcu_disable,
+	.is_enabled	= ingenic_tcu_is_enabled,
+};
+
+static const char * const ingenic_tcu_timer_parents[] = {
+	[TCU_PARENT_PCLK] = "pclk",
+	[TCU_PARENT_RTC]  = "rtc",
+	[TCU_PARENT_EXT]  = "ext",
+};
+
+#define DEF_TIMER(_name, _gate_bit, _tcsr)				\
+	{								\
+		.init_data = {						\
+			.name = _name,					\
+			.parent_names = ingenic_tcu_timer_parents,	\
+			.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
+			.ops = &ingenic_tcu_clk_ops,			\
+			.flags = CLK_SET_RATE_UNGATE,			\
+		},							\
+		.gate_bit = _gate_bit,					\
+		.tcsr_reg = _tcsr,					\
+	}
+static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
+	[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
+	[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
+	[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
+	[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
+	[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
+	[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
+	[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
+	[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
+};
+
+static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
+					 DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
+static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
+					 DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
+#undef DEF_TIMER
+
+static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
+{
+	struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
+	struct irq_domain *domain = irq_desc_get_handler_data(desc);
+	struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
+	struct regmap *map = gc->private;
+	uint32_t irq_reg, irq_mask;
+	unsigned int i;
+
+	regmap_read(map, TCU_REG_TFR, &irq_reg);
+	regmap_read(map, TCU_REG_TMR, &irq_mask);
+
+	chained_irq_enter(irq_chip, desc);
+
+	irq_reg &= ~irq_mask;
+
+	for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
+		generic_handle_irq(irq_linear_revmap(domain, i));
+
+	chained_irq_exit(irq_chip, desc);
+}
+
+static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.ack, mask);
+	regmap_write(map, ct->regs.enable, mask);
+	*ct->mask_cache |= mask;
+	irq_gc_unlock(gc);
+}
+
+static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.disable, mask);
+	*ct->mask_cache &= ~mask;
+	irq_gc_unlock(gc);
+}
+
+static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.ack, mask);
+	regmap_write(map, ct->regs.disable, mask);
+	irq_gc_unlock(gc);
+}
+
+static u64 notrace ingenic_tcu_timer_read(void)
+{
+	unsigned int channel = ingenic_tcu->cs_channel;
+	u16 count;
+
+	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
+
+	return count;
+}
+
+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)
+{
+	return container_of(evt, struct ingenic_tcu, cevt);
+}
+
+static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
+{
+	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
+
+	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
+
+	return 0;
+}
+
+static int ingenic_tcu_cevt_set_next(unsigned long next,
+				     struct clock_event_device *evt)
+{
+	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
+
+	if (next > 0xffff)
+		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));
+
+	return 0;
+}
+
+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);
+
+	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
+
+	if (evt->event_handler)
+		evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
+			unsigned int idx, enum tcu_clk_parent parent,
+			const struct ingenic_tcu_clk_info *info,
+			struct clk_hw_onecell_data *clocks)
+{
+	struct ingenic_tcu_clk *tcu_clk;
+	int err;
+
+	tcu_clk = kzalloc(sizeof(*tcu_clk), GFP_KERNEL);
+	if (!tcu_clk)
+		return -ENOMEM;
+
+	tcu_clk->hw.init = &info->init_data;
+	tcu_clk->idx = idx;
+	tcu_clk->info = info;
+	tcu_clk->map = tcu->map;
+
+	/* Reset channel and clock divider, set default parent */
+	ingenic_tcu_enable(&tcu_clk->hw);
+	regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
+	ingenic_tcu_disable(&tcu_clk->hw);
+
+	err = clk_hw_register(NULL, &tcu_clk->hw);
+	if (err)
+		goto err_free_tcu_clk;
+
+	err = clk_hw_register_clkdev(&tcu_clk->hw, info->init_data.name, NULL);
+	if (err)
+		goto err_clk_unregister;
+
+	clocks->hws[idx] = &tcu_clk->hw;
+
+	return 0;
+
+err_clk_unregister:
+	clk_hw_unregister(&tcu_clk->hw);
+err_free_tcu_clk:
+	kfree(tcu_clk);
+	return err;
+}
+
+static int __init ingenic_tcu_clk_init(struct ingenic_tcu *tcu,
+				       struct device_node *np)
+{
+	size_t i;
+	int ret;
+
+	tcu->clocks = kzalloc(sizeof(*tcu->clocks) +
+			      sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT,
+			      GFP_KERNEL);
+	if (!tcu->clocks)
+		return -ENOMEM;
+
+	tcu->clocks->num = TCU_CLK_COUNT;
+
+	for (i = 0; i < tcu->soc_info->num_channels; i++) {
+		ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
+						 &ingenic_tcu_clk_info[i],
+						 tcu->clocks);
+		if (ret) {
+			pr_crit("ingenic-timer: cannot register clock %i\n", i);
+			goto err_unregister_timer_clocks;
+		}
+	}
+
+	/* We set EXT as the default parent clock for all the TCU clocks
+	 * except for the watchdog one, where we set the RTC clock as the
+	 * parent. Since the EXT and PCLK are much faster than the RTC clock,
+	 * the watchdog would kick after a maximum time of 5s, and we might
+	 * want a slower kicking time.
+	 */
+	ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
+					 &ingenic_tcu_watchdog_clk_info,
+					 tcu->clocks);
+	if (ret) {
+		pr_crit("ingenic-timer: cannot register watchdog clock\n");
+		goto err_unregister_timer_clocks;
+	}
+
+	if (tcu->soc_info->has_ost) {
+		ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
+						 TCU_PARENT_EXT,
+						 &ingenic_tcu_ost_clk_info,
+						 tcu->clocks);
+		if (ret) {
+			pr_crit("ingenic-timer: cannot register ost clock\n");
+			goto err_unregister_watchdog_clock;
+		}
+	}
+
+	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, tcu->clocks);
+	if (ret) {
+		pr_crit("ingenic-timer: cannot add OF clock provider\n");
+		goto err_unregister_ost_clock;
+	}
+
+	return 0;
+
+err_unregister_ost_clock:
+	if (tcu->soc_info->has_ost)
+		clk_hw_unregister(tcu->clocks->hws[i + 1]);
+err_unregister_watchdog_clock:
+	clk_hw_unregister(tcu->clocks->hws[i]);
+err_unregister_timer_clocks:
+	for (i = 0; i < tcu->clocks->num; i++)
+		if (tcu->clocks->hws[i])
+			clk_hw_unregister(tcu->clocks->hws[i]);
+	kfree(tcu->clocks);
+	return ret;
+}
+
+static void __init ingenic_tcu_clk_cleanup(struct ingenic_tcu *tcu,
+					   struct device_node *np)
+{
+	unsigned int i;
+
+	of_clk_del_provider(np);
+
+	for (i = 0; i < tcu->clocks->num; i++)
+		clk_hw_unregister(tcu->clocks->hws[i]);
+	kfree(tcu->clocks);
+}
+
+static int __init ingenic_tcu_intc_init(struct ingenic_tcu *tcu,
+					struct device_node *np)
+{
+	struct irq_chip_generic *gc;
+	struct irq_chip_type *ct;
+	int err, i, irqs;
+
+	irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32));
+	if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs))
+		return -EINVAL;
+
+	tcu->nb_parent_irqs = irqs;
+
+	tcu->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops,
+					    NULL);
+	if (!tcu->domain)
+		return -ENOMEM;
+
+	err = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
+					     handle_level_irq, 0,
+					     IRQ_NOPROBE | IRQ_LEVEL, 0);
+	if (err)
+		goto out_domain_remove;
+
+	gc = irq_get_domain_generic_chip(tcu->domain, 0);
+	ct = gc->chip_types;
+
+	gc->wake_enabled = IRQ_MSK(32);
+	gc->private = tcu->map;
+
+	ct->regs.disable = TCU_REG_TMSR;
+	ct->regs.enable = TCU_REG_TMCR;
+	ct->regs.ack = TCU_REG_TFCR;
+	ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
+	ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
+	ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
+	ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
+
+	/* Mask all IRQs by default */
+	regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
+
+	/* On JZ4740, timer 0 and timer 1 have their own interrupt line;
+	 * timers 2-7 share one interrupt.
+	 * On SoCs >= JZ4770, timer 5 has its own interrupt line;
+	 * timers 0-4 and 6-7 share one single interrupt.
+	 *
+	 * To keep things simple, we just register the same handler to
+	 * all parent interrupts. The handler will properly detect which
+	 * channel fired the interrupt.
+	 */
+	for (i = 0; i < irqs; i++) {
+		tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
+		if (!tcu->parent_irqs[i]) {
+			err = -EINVAL;
+			goto out_unmap_irqs;
+		}
+
+		irq_set_chained_handler_and_data(tcu->parent_irqs[i],
+						 ingenic_tcu_intc_cascade,
+						 tcu->domain);
+	}
+
+	return 0;
+
+out_unmap_irqs:
+	for (; i > 0; i--)
+		irq_dispose_mapping(tcu->parent_irqs[i - 1]);
+out_domain_remove:
+	irq_domain_remove(tcu->domain);
+	return err;
+}
+
+static void __init ingenic_tcu_intc_cleanup(struct ingenic_tcu *tcu)
+{
+	unsigned int i;
+
+	for (i = 0; i < tcu->nb_parent_irqs; i++)
+		irq_dispose_mapping(tcu->parent_irqs[i]);
+
+	irq_domain_remove(tcu->domain);
+}
+
+static int __init ingenic_tcu_timer_init(struct ingenic_tcu *tcu)
+{
+	unsigned int timer_virq;
+	unsigned long rate;
+	int err;
+
+	tcu->timer_clk = tcu->clocks->hws[tcu->timer_channel]->clk;
+
+	err = clk_prepare_enable(tcu->timer_clk);
+	if (err)
+		return err;
+
+	rate = clk_get_rate(tcu->timer_clk);
+	if (!rate) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	timer_virq = irq_create_mapping(tcu->domain, tcu->timer_channel);
+	if (!timer_virq) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	snprintf(tcu->name, sizeof(tcu->name), "TCU");
+
+	err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
+			  tcu->name, &tcu->cevt);
+	if (err)
+		goto err_irq_dispose_mapping;
+
+	tcu->cevt.cpumask = cpumask_of(smp_processor_id());
+	tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
+	tcu->cevt.name = tcu->name;
+	tcu->cevt.rating = 200;
+	tcu->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown;
+	tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next;
+
+	clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff);
+
+	return 0;
+
+err_irq_dispose_mapping:
+	irq_dispose_mapping(timer_virq);
+err_clk_disable:
+	clk_disable_unprepare(tcu->timer_clk);
+	return err;
+}
+
+static int __init ingenic_tcu_clocksource_init(struct ingenic_tcu *tcu)
+{
+	unsigned int channel = tcu->cs_channel;
+	struct clocksource *cs = &tcu->cs;
+	unsigned long rate;
+	int err;
+
+	tcu->cs_clk = tcu->clocks->hws[channel]->clk;
+
+	err = clk_prepare_enable(tcu->cs_clk);
+	if (err)
+		return err;
+
+	rate = clk_get_rate(tcu->cs_clk);
+	if (!rate) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	/* Reset channel */
+	regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
+			   0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
+
+	/* Reset counter */
+	regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
+	regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
+
+	/* Enable channel */
+	regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
+
+	cs->name = "ingenic-timer";
+	cs->rating = 200;
+	cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
+	cs->mask = CLOCKSOURCE_MASK(16);
+	cs->read = ingenic_tcu_timer_cs_read;
+
+	err = clocksource_register_hz(cs, rate);
+	if (err)
+		goto err_clk_disable;
+
+	return 0;
+
+err_clk_disable:
+	clk_disable_unprepare(tcu->cs_clk);
+	return err;
+}
+
+static void __init ingenic_tcu_clocksource_cleanup(struct ingenic_tcu *tcu)
+{
+	clocksource_unregister(&tcu->cs);
+	clk_disable_unprepare(tcu->cs_clk);
+}
+
+static const struct regmap_config ingenic_tcu_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+};
+
+static const struct ingenic_soc_info jz4740_soc_info = {
+	.num_channels = 8,
+	.has_ost = false,
+};
+
+static const struct ingenic_soc_info jz4725b_soc_info = {
+	.num_channels = 6,
+	.has_ost = true,
+};
+
+static const struct ingenic_soc_info jz4770_soc_info = {
+	.num_channels = 8,
+	.has_ost = true,
+};
+
+static const struct of_device_id ingenic_tcu_of_match[] = {
+	{ .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
+	{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
+	{ .compatible = "ingenic,jz4770-tcu", .data = &jz4770_soc_info, },
+	{ }
+};
+
+static int __init ingenic_tcu_init(struct device_node *np)
+{
+	const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
+	const struct ingenic_soc_info *soc_info = id->data;
+	struct ingenic_tcu *tcu;
+	struct resource res;
+	void __iomem *base;
+	long rate;
+	int ret;
+
+	of_node_clear_flag(np, OF_POPULATED);
+
+	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
+	if (!tcu)
+		return -ENOMEM;
+
+	/* Enable all TCU channels for PWM use by default except channel 0
+	 * and channel 1.
+	 */
+	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, 2);
+	of_property_read_u32(np, "ingenic,pwm-channels-mask",
+			     (u32 *)&tcu->pwm_channels_mask);
+
+	/* Verify that we have at least two free channels */
+	if (hweight8(tcu->pwm_channels_mask) > soc_info->num_channels - 2) {
+		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
+			tcu->pwm_channels_mask);
+		return -EINVAL;
+	}
+
+	tcu->soc_info = soc_info;
+	ingenic_tcu = tcu;
+
+	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
+						 soc_info->num_channels);
+	tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
+					     soc_info->num_channels,
+					     tcu->timer_channel + 1);
+
+	base = of_io_request_and_map(np, 0, NULL);
+	if (IS_ERR(base)) {
+		ret = PTR_ERR(base);
+		goto err_free_ingenic_tcu;
+	}
+
+	of_address_to_resource(np, 0, &res);
+
+	ingenic_tcu_base = base;
+
+	tcu->map = regmap_init_mmio(NULL, base, &ingenic_tcu_regmap_config);
+	if (IS_ERR(tcu->map)) {
+		ret = PTR_ERR(tcu->map);
+		goto err_iounmap;
+	}
+
+	tcu->clk = of_clk_get_by_name(np, "tcu");
+	if (IS_ERR(tcu->clk)) {
+		ret = PTR_ERR(tcu->clk);
+		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
+		goto err_free_regmap;
+	}
+
+	ret = clk_prepare_enable(tcu->clk);
+	if (ret) {
+		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
+		goto err_clk_put;
+	}
+
+	ret = ingenic_tcu_intc_init(tcu, np);
+	if (ret)
+		goto err_clk_disable;
+
+	ret = ingenic_tcu_clk_init(tcu, np);
+	if (ret)
+		goto err_tcu_intc_cleanup;
+
+	ret = ingenic_tcu_clocksource_init(tcu);
+	if (ret)
+		goto err_tcu_clk_cleanup;
+
+	ret = ingenic_tcu_timer_init(tcu);
+	if (ret)
+		goto err_tcu_clocksource_cleanup;
+
+	// Register the sched_clock at the very end as there's no way to undo it
+	rate = clk_get_rate(tcu->cs_clk);
+	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
+
+	return 0;
+
+err_tcu_clocksource_cleanup:
+	ingenic_tcu_clocksource_cleanup(tcu);
+err_tcu_clk_cleanup:
+	ingenic_tcu_clk_cleanup(tcu, np);
+err_tcu_intc_cleanup:
+	ingenic_tcu_intc_cleanup(tcu);
+err_clk_disable:
+	clk_disable_unprepare(tcu->clk);
+err_clk_put:
+	clk_put(tcu->clk);
+err_free_regmap:
+	regmap_exit(tcu->map);
+err_iounmap:
+	iounmap(base);
+	release_mem_region(res.start, resource_size(&res));
+err_free_ingenic_tcu:
+	kfree(tcu);
+	return ret;
+}
+
+TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  ingenic_tcu_init);
+TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
+TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  ingenic_tcu_init);
+
+
+static int __init ingenic_tcu_probe(struct platform_device *pdev)
+{
+	platform_set_drvdata(pdev, ingenic_tcu);
+
+	regmap_attach_dev(&pdev->dev, ingenic_tcu->map,
+			  &ingenic_tcu_regmap_config);
+
+	return devm_of_platform_populate(&pdev->dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ingenic_tcu_suspend(struct device *dev)
+{
+	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
+
+	clk_disable(tcu->cs_clk);
+	clk_disable(tcu->timer_clk);
+	clk_disable(tcu->clk);
+	return 0;
+}
+
+static int ingenic_tcu_resume(struct device *dev)
+{
+	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_enable(tcu->clk);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(tcu->timer_clk);
+	if (ret)
+		goto err_tcu_clk_disable;
+
+	ret = clk_enable(tcu->cs_clk);
+	if (ret)
+		goto err_tcu_timer_clk_disable;
+
+	return 0;
+
+err_tcu_timer_clk_disable:
+	clk_disable(tcu->timer_clk);
+err_tcu_clk_disable:
+	clk_disable(tcu->clk);
+	return ret;
+}
+
+static const struct dev_pm_ops ingenic_tcu_pm_ops = {
+	/* _noirq: We want the TCU clock to be gated last / ungated first */
+	.suspend_noirq = ingenic_tcu_suspend,
+	.resume_noirq  = ingenic_tcu_resume,
+};
+#define INGENIC_TCU_PM_OPS (&ingenic_tcu_pm_ops)
+
+#else
+#define INGENIC_TCU_PM_OPS NULL
+#endif /* CONFIG_PM_SUSPEND */
+
+static struct platform_driver ingenic_tcu_driver = {
+	.driver = {
+		.name	= "ingenic-tcu",
+		.pm	= INGENIC_TCU_PM_OPS,
+		.of_match_table = ingenic_tcu_of_match,
+	},
+};
+
+static int __init ingenic_tcu_platform_init(void)
+{
+	return platform_driver_probe(&ingenic_tcu_driver,
+				     ingenic_tcu_probe);
+}
+subsys_initcall(ingenic_tcu_platform_init);
+
+bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
+{
+	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
+}
+EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);
diff --git a/drivers/clocksource/ingenic-timer.h b/drivers/clocksource/ingenic-timer.h
new file mode 100644
index 000000000000..fa43da836ab6
--- /dev/null
+++ b/drivers/clocksource/ingenic-timer.h
@@ -0,0 +1,15 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
+#define __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
+
+#include <linux/compiler_types.h>
+
+/*
+ * README: For use *ONLY* by the ingenic-ost driver.
+ * Regular drivers which want to access the TCU registers
+ * must have ingenic-timer as parent and retrieve the regmap
+ * doing dev_get_regmap(pdev->dev.parent);
+ */
+extern void __iomem *ingenic_tcu_base;
+
+#endif /* __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__ */
diff --git a/include/linux/mfd/ingenic-tcu.h b/include/linux/mfd/ingenic-tcu.h
index 2083fa20821d..c10b36f3ad64 100644
--- a/include/linux/mfd/ingenic-tcu.h
+++ b/include/linux/mfd/ingenic-tcu.h
@@ -53,4 +53,6 @@ 
 #define TCU_REG_TCNTc(c)	(TCU_REG_TCNT0 + ((c) * TCU_CHANNEL_STRIDE))
 #define TCU_REG_TCSRc(c)	(TCU_REG_TCSR0 + ((c) * TCU_CHANNEL_STRIDE))
 
+bool ingenic_tcu_pwm_can_use_chn(unsigned int channel);
+
 #endif /* __LINUX_MFD_INGENIC_TCU_H_ */