diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-22 19:30:30 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-22 19:30:30 +0300 |
commit | 5c6bd5de3c2e5bc8a17451e281ed2613375a7fd5 (patch) | |
tree | ec6af5a1dfddef30f92da4a2742bf3da04b520f9 /drivers | |
parent | f7c3bf8fa7e5a8e45f4a8e82be6466157854b59b (diff) | |
parent | 05d013a0366d50f4f0dbebf8c1b22b42020bf49a (diff) | |
download | linux-5c6bd5de3c2e5bc8a17451e281ed2613375a7fd5.tar.xz |
Merge tag 'mips_5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux
Pull MIPS updates from Paul Burton:
"Main MIPS changes:
- boot_mem_map is removed, providing a nice cleanup made possible by
the recent removal of bootmem.
- Some fixes to atomics, in general providing compiler barriers for
smp_mb__{before,after}_atomic plus fixes specific to Loongson CPUs
or MIPS32 systems using cmpxchg64().
- Conversion to the new generic VDSO infrastructure courtesy of
Vincenzo Frascino.
- Removal of undefined behavior in set_io_port_base(), fixing the
behavior of some MIPS kernel configurations when built with recent
clang versions.
- Initial MIPS32 huge page support, functional on at least Ingenic
SoCs.
- pte_special() is now supported for some configurations, allowing
among other things generic fast GUP to be used.
- Miscellaneous fixes & cleanups.
And platform specific changes:
- Major improvements to Ingenic SoC support from Paul Cercueil,
mostly enabled by the inclusion of the new TCU (timer-counter unit)
drivers he's spent a very patient year or so working on. Plus some
fixes for X1000 SoCs from Zhou Yanjie.
- Netgear R6200 v1 systems are now supported by the bcm47xx platform.
- DT updates for BMIPS, Lantiq & Microsemi Ocelot systems"
* tag 'mips_5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux: (89 commits)
MIPS: Detect bad _PFN_SHIFT values
MIPS: Disable pte_special() for MIPS32 with RiXi
MIPS: ralink: deactivate PCI support for SOC_MT7621
mips: compat: vdso: Use legacy syscalls as fallback
MIPS: Drop Loongson _CACHE_* definitions
MIPS: tlbex: Remove cpu_has_local_ebase
MIPS: tlbex: Simplify r3k check
MIPS: Select R3k-style TLB in Kconfig
MIPS: PCI: refactor ioc3 special handling
mips: remove ioremap_cachable
mips/atomic: Fix smp_mb__{before,after}_atomic()
mips/atomic: Fix loongson_llsc_mb() wreckage
mips/atomic: Fix cmpxchg64 barriers
MIPS: Octeon: remove duplicated include from dma-octeon.c
firmware: bcm47xx_nvram: Allow COMPILE_TEST
firmware: bcm47xx_nvram: Correct size_t printf format
MIPS: Treat Loongson Extensions as ASEs
MIPS: Remove dev_err() usage after platform_get_irq()
MIPS: dts: mscc: describe the PTP ready interrupt
MIPS: dts: mscc: describe the PTP register range
...
Diffstat (limited to 'drivers')
28 files changed, 1086 insertions, 2473 deletions
diff --git a/drivers/clk/ingenic/Kconfig b/drivers/clk/ingenic/Kconfig index fe8db93cf21a..1cb489959a99 100644 --- a/drivers/clk/ingenic/Kconfig +++ b/drivers/clk/ingenic/Kconfig @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -menu "Ingenic JZ47xx CGU drivers" +menu "Ingenic SoCs drivers" depends on MIPS config INGENIC_CGU_COMMON @@ -45,4 +45,12 @@ config INGENIC_CGU_JZ4780 If building for a JZ4780 SoC, you want to say Y here. +config INGENIC_TCU_CLK + bool "Ingenic JZ47xx TCU clocks driver" + default MACH_INGENIC + select MFD_SYSCON + help + Support the clocks of the Timer/Counter Unit (TCU) of the Ingenic + JZ47xx SoCs. + endmenu diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile index 250570a809d3..097220b05131 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_TCU_CLK) += tcu.o diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c index 978f32dd424a..4f0e92c877d6 100644 --- a/drivers/clk/ingenic/jz4740-cgu.c +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -229,6 +229,12 @@ static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = { .parents = { JZ4740_CLK_EXT, -1, -1, -1 }, .gate = { CGU_REG_CLKGR, 5 }, }, + + [JZ4740_CLK_TCU] = { + "tcu", CGU_CLK_GATE, + .parents = { JZ4740_CLK_EXT, -1, -1, -1 }, + .gate = { CGU_REG_CLKGR, 1 }, + }, }; static void __init jz4740_cgu_init(struct device_node *np) diff --git a/drivers/clk/ingenic/tcu.c b/drivers/clk/ingenic/tcu.c new file mode 100644 index 000000000000..a1a5f9cb439e --- /dev/null +++ b/drivers/clk/ingenic/tcu.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ47xx SoCs TCU clocks driver + * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clockchips.h> +#include <linux/mfd/ingenic-tcu.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/syscore_ops.h> + +#include <dt-bindings/clock/ingenic,tcu.h> + +/* 8 channels max + watchdog + OST */ +#define TCU_CLK_COUNT 10 + +#undef pr_fmt +#define pr_fmt(fmt) "ingenic-tcu-clk: " fmt + +enum tcu_clk_parent { + TCU_PARENT_PCLK, + TCU_PARENT_RTC, + TCU_PARENT_EXT, +}; + +struct ingenic_soc_info { + unsigned int num_channels; + bool has_ost; + bool has_tcu_clk; +}; + +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; + unsigned int idx; + struct ingenic_tcu *tcu; + const struct ingenic_tcu_clk_info *info; +}; + +struct ingenic_tcu { + const struct ingenic_soc_info *soc_info; + struct regmap *map; + struct clk *clk; + + struct clk_hw_onecell_data *clocks; +}; + +static struct ingenic_tcu *ingenic_tcu; + +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; + struct ingenic_tcu *tcu = tcu_clk->tcu; + + regmap_write(tcu->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; + struct ingenic_tcu *tcu = tcu_clk->tcu; + + regmap_write(tcu->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->tcu->map, TCU_REG_TSR, &value); + + return !(value & BIT(info->gate_bit)); +} + +static bool ingenic_tcu_enable_regs(struct clk_hw *hw) +{ + struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw); + const struct ingenic_tcu_clk_info *info = tcu_clk->info; + struct ingenic_tcu *tcu = tcu_clk->tcu; + bool enabled = false; + + /* + * If the SoC has no global TCU clock, we must ungate the channel's + * clock to be able to access its registers. + * If we have a TCU clock, it will be enabled automatically as it has + * been attached to the regmap. + */ + if (!tcu->clk) { + enabled = !!ingenic_tcu_is_enabled(hw); + regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit)); + } + + return enabled; +} + +static void ingenic_tcu_disable_regs(struct clk_hw *hw) +{ + struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw); + const struct ingenic_tcu_clk_info *info = tcu_clk->info; + struct ingenic_tcu *tcu = tcu_clk->tcu; + + if (!tcu->clk) + regmap_write(tcu->map, TCU_REG_TSSR, 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->tcu->map, info->tcsr_reg, &val); + WARN_ONCE(ret < 0, "Unable to read TCSR %d", 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; + bool was_enabled; + int ret; + + was_enabled = ingenic_tcu_enable_regs(hw); + + ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg, + TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx)); + WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx); + + if (!was_enabled) + ingenic_tcu_disable_regs(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->tcu->map, info->tcsr_reg, &prescale); + WARN_ONCE(ret < 0, "Unable to read TCSR %d", 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; + u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate); + bool was_enabled; + int ret; + + was_enabled = ingenic_tcu_enable_regs(hw); + + ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg, + TCU_TCSR_PRESCALE_MASK, + prescale << TCU_TCSR_PRESCALE_LSB); + WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx); + + if (!was_enabled) + ingenic_tcu_disable_regs(hw); + + 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 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->tcu = tcu; + + /* Reset channel and clock divider, set default parent */ + ingenic_tcu_enable_regs(&tcu_clk->hw); + regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent)); + ingenic_tcu_disable_regs(&tcu_clk->hw); + + err = clk_hw_register(NULL, &tcu_clk->hw); + if (err) { + kfree(tcu_clk); + return err; + } + + clocks->hws[idx] = &tcu_clk->hw; + + return 0; +} + +static const struct ingenic_soc_info jz4740_soc_info = { + .num_channels = 8, + .has_ost = false, + .has_tcu_clk = true, +}; + +static const struct ingenic_soc_info jz4725b_soc_info = { + .num_channels = 6, + .has_ost = true, + .has_tcu_clk = true, +}; + +static const struct ingenic_soc_info jz4770_soc_info = { + .num_channels = 8, + .has_ost = true, + .has_tcu_clk = false, +}; + +static const struct of_device_id ingenic_tcu_of_match[] __initconst = { + { .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, }, + { .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, }, + { .compatible = "ingenic,jz4770-tcu", .data = &jz4770_soc_info, }, + { /* sentinel */ } +}; + +static int __init ingenic_tcu_probe(struct device_node *np) +{ + const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np); + struct ingenic_tcu *tcu; + struct regmap *map; + unsigned int i; + int ret; + + map = device_node_to_regmap(np); + if (IS_ERR(map)) + return PTR_ERR(map); + + tcu = kzalloc(sizeof(*tcu), GFP_KERNEL); + if (!tcu) + return -ENOMEM; + + tcu->map = map; + tcu->soc_info = id->data; + + if (tcu->soc_info->has_tcu_clk) { + tcu->clk = of_clk_get_by_name(np, "tcu"); + if (IS_ERR(tcu->clk)) { + ret = PTR_ERR(tcu->clk); + pr_crit("Cannot get TCU clock\n"); + goto err_free_tcu; + } + + ret = clk_prepare_enable(tcu->clk); + if (ret) { + pr_crit("Unable to enable TCU clock\n"); + goto err_put_clk; + } + } + + tcu->clocks = kzalloc(sizeof(*tcu->clocks) + + sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT, + GFP_KERNEL); + if (!tcu->clocks) { + ret = -ENOMEM; + goto err_clk_disable; + } + + 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("cannot register clock %d\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("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("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("cannot add OF clock provider\n"); + goto err_unregister_ost_clock; + } + + ingenic_tcu = tcu; + + 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); +err_clk_disable: + if (tcu->soc_info->has_tcu_clk) + clk_disable_unprepare(tcu->clk); +err_put_clk: + if (tcu->soc_info->has_tcu_clk) + clk_put(tcu->clk); +err_free_tcu: + kfree(tcu); + return ret; +} + +static int __maybe_unused tcu_pm_suspend(void) +{ + struct ingenic_tcu *tcu = ingenic_tcu; + + if (tcu->clk) + clk_disable(tcu->clk); + + return 0; +} + +static void __maybe_unused tcu_pm_resume(void) +{ + struct ingenic_tcu *tcu = ingenic_tcu; + + if (tcu->clk) + clk_enable(tcu->clk); +} + +static struct syscore_ops __maybe_unused tcu_pm_ops = { + .suspend = tcu_pm_suspend, + .resume = tcu_pm_resume, +}; + +static void __init ingenic_tcu_init(struct device_node *np) +{ + int ret = ingenic_tcu_probe(np); + + if (ret) + pr_crit("Failed to initialize TCU clocks: %d\n", ret); + + if (IS_ENABLED(CONFIG_PM_SLEEP)) + register_syscore_ops(&tcu_pm_ops); +} + +CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-tcu", ingenic_tcu_init); +CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-tcu", ingenic_tcu_init); +CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-tcu", ingenic_tcu_init); diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index a642c23b2fba..f35a53ce8988 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -685,4 +685,15 @@ config MILBEAUT_TIMER help Enables the support for Milbeaut timer driver. +config INGENIC_TIMER + bool "Clocksource/timer using the TCU in Ingenic JZ SoCs" + default MACH_INGENIC + depends on MIPS || COMPILE_TEST + depends on COMMON_CLK + select MFD_SYSCON + select TIMER_OF + select IRQ_DOMAIN + help + Support for the timer/counter unit of the Ingenic JZ SoCs. + endmenu diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 2e7936e7833f..4dfe4225ece7 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_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..4bbdb3d3d0c6 --- /dev/null +++ b/drivers/clocksource/ingenic-timer.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ47xx SoCs TCU IRQ driver + * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/interrupt.h> +#include <linux/mfd/ingenic-tcu.h> +#include <linux/mfd/syscon.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> + +struct ingenic_soc_info { + unsigned int num_channels; +}; + +struct ingenic_tcu { + struct regmap *map; + struct clk *timer_clk, *cs_clk; + 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; + +static u64 notrace ingenic_tcu_timer_read(void) +{ + struct ingenic_tcu *tcu = ingenic_tcu; + unsigned int count; + + regmap_read(tcu->map, TCU_REG_TCNTc(tcu->cs_channel), &count); + + 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 struct clk * __init ingenic_tcu_get_clock(struct device_node *np, int id) +{ + struct of_phandle_args args; + + args.np = np; + args.args_count = 1; + args.args[0] = id; + + return of_clk_get_from_provider(&args); +} + +static int __init ingenic_tcu_timer_init(struct device_node *np, + struct ingenic_tcu *tcu) +{ + unsigned int timer_virq, channel = tcu->timer_channel; + struct irq_domain *domain; + unsigned long rate; + int err; + + tcu->timer_clk = ingenic_tcu_get_clock(np, channel); + if (IS_ERR(tcu->timer_clk)) + return PTR_ERR(tcu->timer_clk); + + err = clk_prepare_enable(tcu->timer_clk); + if (err) + goto err_clk_put; + + rate = clk_get_rate(tcu->timer_clk); + if (!rate) { + err = -EINVAL; + goto err_clk_disable; + } + + domain = irq_find_host(np); + if (!domain) { + err = -ENODEV; + goto err_clk_disable; + } + + timer_virq = irq_create_mapping(domain, 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); +err_clk_put: + clk_put(tcu->timer_clk); + return err; +} + +static int __init ingenic_tcu_clocksource_init(struct device_node *np, + struct ingenic_tcu *tcu) +{ + unsigned int channel = tcu->cs_channel; + struct clocksource *cs = &tcu->cs; + unsigned long rate; + int err; + + tcu->cs_clk = ingenic_tcu_get_clock(np, channel); + if (IS_ERR(tcu->cs_clk)) + return PTR_ERR(tcu->cs_clk); + + err = clk_prepare_enable(tcu->cs_clk); + if (err) + goto err_clk_put; + + 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); +err_clk_put: + clk_put(tcu->cs_clk); + return err; +} + +static const struct ingenic_soc_info jz4740_soc_info = { + .num_channels = 8, +}; + +static const struct ingenic_soc_info jz4725b_soc_info = { + .num_channels = 6, +}; + +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 = &jz4740_soc_info, }, + { /* sentinel */ } +}; + +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 regmap *map; + long rate; + int ret; + + of_node_clear_flag(np, OF_POPULATED); + + map = device_node_to_regmap(np); + if (IS_ERR(map)) + return PTR_ERR(map); + + tcu = kzalloc(sizeof(*tcu), GFP_KERNEL); + if (!tcu) + return -ENOMEM; + + /* Enable all TCU channels for PWM use by default except channels 0/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("%s: Invalid PWM channel mask: 0x%02lx\n", __func__, + tcu->pwm_channels_mask); + ret = -EINVAL; + goto err_free_ingenic_tcu; + } + + tcu->map = map; + 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); + + ret = ingenic_tcu_clocksource_init(np, tcu); + if (ret) { + pr_crit("%s: Unable to init clocksource: %d\n", __func__, ret); + goto err_free_ingenic_tcu; + } + + ret = ingenic_tcu_timer_init(np, tcu); + if (ret) + goto err_tcu_clocksource_cleanup; + + /* Register the sched_clock at the 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: + clocksource_unregister(&tcu->cs); + clk_disable_unprepare(tcu->cs_clk); + clk_put(tcu->cs_clk); +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); + + return 0; +} + +static int __maybe_unused ingenic_tcu_suspend(struct device *dev) +{ + struct ingenic_tcu *tcu = dev_get_drvdata(dev); + + clk_disable(tcu->cs_clk); + clk_disable(tcu->timer_clk); + return 0; +} + +static int __maybe_unused ingenic_tcu_resume(struct device *dev) +{ + struct ingenic_tcu *tcu = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(tcu->timer_clk); + if (ret) + return ret; + + ret = clk_enable(tcu->cs_clk); + if (ret) { + clk_disable(tcu->timer_clk); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops __maybe_unused ingenic_tcu_pm_ops = { + /* _noirq: We want the TCU clocks to be gated last / ungated first */ + .suspend_noirq = ingenic_tcu_suspend, + .resume_noirq = ingenic_tcu_resume, +}; + +static struct platform_driver ingenic_tcu_driver = { + .driver = { + .name = "ingenic-tcu-timer", +#ifdef CONFIG_PM_SLEEP + .pm = &ingenic_tcu_pm_ops, +#endif + .of_match_table = ingenic_tcu_of_match, + }, +}; +builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe); diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 7c511e3db4c8..7af874b69ffb 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -137,12 +137,6 @@ config DMA_BCM2835 select DMA_ENGINE select DMA_VIRTUAL_CHANNELS -config DMA_JZ4740 - tristate "JZ4740 DMA support" - depends on MACH_JZ4740 || COMPILE_TEST - select DMA_ENGINE - select DMA_VIRTUAL_CHANNELS - config DMA_JZ4780 tristate "JZ4780 DMA support" depends on MIPS || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 5bddf6f8790f..f5ce8665e944 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -22,7 +22,6 @@ obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o obj-$(CONFIG_BCM_SBA_RAID) += bcm-sba-raid.o obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o obj-$(CONFIG_DMA_BCM2835) += bcm2835-dma.o -obj-$(CONFIG_DMA_JZ4740) += dma-jz4740.o obj-$(CONFIG_DMA_JZ4780) += dma-jz4780.o obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o obj-$(CONFIG_DMA_SUN4I) += sun4i-dma.o diff --git a/drivers/dma/dma-jz4740.c b/drivers/dma/dma-jz4740.c deleted file mode 100644 index 39c676c47082..000000000000 --- a/drivers/dma/dma-jz4740.c +++ /dev/null @@ -1,623 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2013, Lars-Peter Clausen <lars@metafoo.de> - * JZ4740 DMAC support - */ - -#include <linux/dmaengine.h> -#include <linux/dma-mapping.h> -#include <linux/err.h> -#include <linux/init.h> -#include <linux/list.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/spinlock.h> -#include <linux/irq.h> -#include <linux/clk.h> - -#include "virt-dma.h" - -#define JZ_DMA_NR_CHANS 6 - -#define JZ_REG_DMA_SRC_ADDR(x) (0x00 + (x) * 0x20) -#define JZ_REG_DMA_DST_ADDR(x) (0x04 + (x) * 0x20) -#define JZ_REG_DMA_TRANSFER_COUNT(x) (0x08 + (x) * 0x20) -#define JZ_REG_DMA_REQ_TYPE(x) (0x0C + (x) * 0x20) -#define JZ_REG_DMA_STATUS_CTRL(x) (0x10 + (x) * 0x20) -#define JZ_REG_DMA_CMD(x) (0x14 + (x) * 0x20) -#define JZ_REG_DMA_DESC_ADDR(x) (0x18 + (x) * 0x20) - -#define JZ_REG_DMA_CTRL 0x300 -#define JZ_REG_DMA_IRQ 0x304 -#define JZ_REG_DMA_DOORBELL 0x308 -#define JZ_REG_DMA_DOORBELL_SET 0x30C - -#define JZ_DMA_STATUS_CTRL_NO_DESC BIT(31) -#define JZ_DMA_STATUS_CTRL_DESC_INV BIT(6) -#define JZ_DMA_STATUS_CTRL_ADDR_ERR BIT(4) -#define JZ_DMA_STATUS_CTRL_TRANSFER_DONE BIT(3) -#define JZ_DMA_STATUS_CTRL_HALT BIT(2) -#define JZ_DMA_STATUS_CTRL_COUNT_TERMINATE BIT(1) -#define JZ_DMA_STATUS_CTRL_ENABLE BIT(0) - -#define JZ_DMA_CMD_SRC_INC BIT(23) -#define JZ_DMA_CMD_DST_INC BIT(22) -#define JZ_DMA_CMD_RDIL_MASK (0xf << 16) -#define JZ_DMA_CMD_SRC_WIDTH_MASK (0x3 << 14) -#define JZ_DMA_CMD_DST_WIDTH_MASK (0x3 << 12) -#define JZ_DMA_CMD_INTERVAL_LENGTH_MASK (0x7 << 8) -#define JZ_DMA_CMD_BLOCK_MODE BIT(7) -#define JZ_DMA_CMD_DESC_VALID BIT(4) -#define JZ_DMA_CMD_DESC_VALID_MODE BIT(3) -#define JZ_DMA_CMD_VALID_IRQ_ENABLE BIT(2) -#define JZ_DMA_CMD_TRANSFER_IRQ_ENABLE BIT(1) -#define JZ_DMA_CMD_LINK_ENABLE BIT(0) - -#define JZ_DMA_CMD_FLAGS_OFFSET 22 -#define JZ_DMA_CMD_RDIL_OFFSET 16 -#define JZ_DMA_CMD_SRC_WIDTH_OFFSET 14 -#define JZ_DMA_CMD_DST_WIDTH_OFFSET 12 -#define JZ_DMA_CMD_TRANSFER_SIZE_OFFSET 8 -#define JZ_DMA_CMD_MODE_OFFSET 7 - -#define JZ_DMA_CTRL_PRIORITY_MASK (0x3 << 8) -#define JZ_DMA_CTRL_HALT BIT(3) -#define JZ_DMA_CTRL_ADDRESS_ERROR BIT(2) -#define JZ_DMA_CTRL_ENABLE BIT(0) - -enum jz4740_dma_width { - JZ4740_DMA_WIDTH_32BIT = 0, - JZ4740_DMA_WIDTH_8BIT = 1, - JZ4740_DMA_WIDTH_16BIT = 2, -}; - -enum jz4740_dma_transfer_size { - JZ4740_DMA_TRANSFER_SIZE_4BYTE = 0, - JZ4740_DMA_TRANSFER_SIZE_1BYTE = 1, - JZ4740_DMA_TRANSFER_SIZE_2BYTE = 2, - JZ4740_DMA_TRANSFER_SIZE_16BYTE = 3, - JZ4740_DMA_TRANSFER_SIZE_32BYTE = 4, -}; - -enum jz4740_dma_flags { - JZ4740_DMA_SRC_AUTOINC = 0x2, - JZ4740_DMA_DST_AUTOINC = 0x1, -}; - -enum jz4740_dma_mode { - JZ4740_DMA_MODE_SINGLE = 0, - JZ4740_DMA_MODE_BLOCK = 1, -}; - -struct jz4740_dma_sg { - dma_addr_t addr; - unsigned int len; -}; - -struct jz4740_dma_desc { - struct virt_dma_desc vdesc; - - enum dma_transfer_direction direction; - bool cyclic; - - unsigned int num_sgs; - struct jz4740_dma_sg sg[]; -}; - -struct jz4740_dmaengine_chan { - struct virt_dma_chan vchan; - unsigned int id; - struct dma_slave_config config; - - dma_addr_t fifo_addr; - unsigned int transfer_shift; - - struct jz4740_dma_desc *desc; - unsigned int next_sg; -}; - -struct jz4740_dma_dev { - struct dma_device ddev; - void __iomem *base; - struct clk *clk; - - struct jz4740_dmaengine_chan chan[JZ_DMA_NR_CHANS]; -}; - -static struct jz4740_dma_dev *jz4740_dma_chan_get_dev( - struct jz4740_dmaengine_chan *chan) -{ - return container_of(chan->vchan.chan.device, struct jz4740_dma_dev, - ddev); -} - -static struct jz4740_dmaengine_chan *to_jz4740_dma_chan(struct dma_chan *c) -{ - return container_of(c, struct jz4740_dmaengine_chan, vchan.chan); -} - -static struct jz4740_dma_desc *to_jz4740_dma_desc(struct virt_dma_desc *vdesc) -{ - return container_of(vdesc, struct jz4740_dma_desc, vdesc); -} - -static inline uint32_t jz4740_dma_read(struct jz4740_dma_dev *dmadev, - unsigned int reg) -{ - return readl(dmadev->base + reg); -} - -static inline void jz4740_dma_write(struct jz4740_dma_dev *dmadev, - unsigned reg, uint32_t val) -{ - writel(val, dmadev->base + reg); -} - -static inline void jz4740_dma_write_mask(struct jz4740_dma_dev *dmadev, - unsigned int reg, uint32_t val, uint32_t mask) -{ - uint32_t tmp; - - tmp = jz4740_dma_read(dmadev, reg); - tmp &= ~mask; - tmp |= val; - jz4740_dma_write(dmadev, reg, tmp); -} - -static struct jz4740_dma_desc *jz4740_dma_alloc_desc(unsigned int num_sgs) -{ - return kzalloc(sizeof(struct jz4740_dma_desc) + - sizeof(struct jz4740_dma_sg) * num_sgs, GFP_ATOMIC); -} - -static enum jz4740_dma_width jz4740_dma_width(enum dma_slave_buswidth width) -{ - switch (width) { - case DMA_SLAVE_BUSWIDTH_1_BYTE: - return JZ4740_DMA_WIDTH_8BIT; - case DMA_SLAVE_BUSWIDTH_2_BYTES: - return JZ4740_DMA_WIDTH_16BIT; - case DMA_SLAVE_BUSWIDTH_4_BYTES: - return JZ4740_DMA_WIDTH_32BIT; - default: - return JZ4740_DMA_WIDTH_32BIT; - } -} - -static enum jz4740_dma_transfer_size jz4740_dma_maxburst(u32 maxburst) -{ - if (maxburst <= 1) - return JZ4740_DMA_TRANSFER_SIZE_1BYTE; - else if (maxburst <= 3) - return JZ4740_DMA_TRANSFER_SIZE_2BYTE; - else if (maxburst <= 15) - return JZ4740_DMA_TRANSFER_SIZE_4BYTE; - else if (maxburst <= 31) - return JZ4740_DMA_TRANSFER_SIZE_16BYTE; - - return JZ4740_DMA_TRANSFER_SIZE_32BYTE; -} - -static int jz4740_dma_slave_config_write(struct dma_chan *c, - struct dma_slave_config *config, - enum dma_transfer_direction direction) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - struct jz4740_dma_dev *dmadev = jz4740_dma_chan_get_dev(chan); - enum jz4740_dma_width src_width; - enum jz4740_dma_width dst_width; - enum jz4740_dma_transfer_size transfer_size; - enum jz4740_dma_flags flags; - uint32_t cmd; - - switch (direction) { - case DMA_MEM_TO_DEV: - flags = JZ4740_DMA_SRC_AUTOINC; - transfer_size = jz4740_dma_maxburst(config->dst_maxburst); - chan->fifo_addr = config->dst_addr; - break; - case DMA_DEV_TO_MEM: - flags = JZ4740_DMA_DST_AUTOINC; - transfer_size = jz4740_dma_maxburst(config->src_maxburst); - chan->fifo_addr = config->src_addr; - break; - default: - return -EINVAL; - } - - src_width = jz4740_dma_width(config->src_addr_width); - dst_width = jz4740_dma_width(config->dst_addr_width); - - switch (transfer_size) { - case JZ4740_DMA_TRANSFER_SIZE_2BYTE: - chan->transfer_shift = 1; - break; - case JZ4740_DMA_TRANSFER_SIZE_4BYTE: - chan->transfer_shift = 2; - break; - case JZ4740_DMA_TRANSFER_SIZE_16BYTE: - chan->transfer_shift = 4; - break; - case JZ4740_DMA_TRANSFER_SIZE_32BYTE: - chan->transfer_shift = 5; - break; - default: - chan->transfer_shift = 0; - break; - } - - cmd = flags << JZ_DMA_CMD_FLAGS_OFFSET; - cmd |= src_width << JZ_DMA_CMD_SRC_WIDTH_OFFSET; - cmd |= dst_width << JZ_DMA_CMD_DST_WIDTH_OFFSET; - cmd |= transfer_size << JZ_DMA_CMD_TRANSFER_SIZE_OFFSET; - cmd |= JZ4740_DMA_MODE_SINGLE << JZ_DMA_CMD_MODE_OFFSET; - cmd |= JZ_DMA_CMD_TRANSFER_IRQ_ENABLE; - - jz4740_dma_write(dmadev, JZ_REG_DMA_CMD(chan->id), cmd); - jz4740_dma_write(dmadev, JZ_REG_DMA_STATUS_CTRL(chan->id), 0); - jz4740_dma_write(dmadev, JZ_REG_DMA_REQ_TYPE(chan->id), - config->slave_id); - - return 0; -} - -static int jz4740_dma_slave_config(struct dma_chan *c, - struct dma_slave_config *config) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - - memcpy(&chan->config, config, sizeof(*config)); - return 0; -} - -static int jz4740_dma_terminate_all(struct dma_chan *c) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - struct jz4740_dma_dev *dmadev = jz4740_dma_chan_get_dev(chan); - unsigned long flags; - LIST_HEAD(head); - - spin_lock_irqsave(&chan->vchan.lock, flags); - jz4740_dma_write_mask(dmadev, JZ_REG_DMA_STATUS_CTRL(chan->id), 0, - JZ_DMA_STATUS_CTRL_ENABLE); - chan->desc = NULL; - vchan_get_all_descriptors(&chan->vchan, &head); - spin_unlock_irqrestore(&chan->vchan.lock, flags); - - vchan_dma_desc_free_list(&chan->vchan, &head); - - return 0; -} - -static int jz4740_dma_start_transfer(struct jz4740_dmaengine_chan *chan) -{ - struct jz4740_dma_dev *dmadev = jz4740_dma_chan_get_dev(chan); - dma_addr_t src_addr, dst_addr; - struct virt_dma_desc *vdesc; - struct jz4740_dma_sg *sg; - - jz4740_dma_write_mask(dmadev, JZ_REG_DMA_STATUS_CTRL(chan->id), 0, - JZ_DMA_STATUS_CTRL_ENABLE); - - if (!chan->desc) { - vdesc = vchan_next_desc(&chan->vchan); - if (!vdesc) - return 0; - chan->desc = to_jz4740_dma_desc(vdesc); - chan->next_sg = 0; - } - - if (chan->next_sg == chan->desc->num_sgs) - chan->next_sg = 0; - - sg = &chan->desc->sg[chan->next_sg]; - - if (chan->desc->direction == DMA_MEM_TO_DEV) { - src_addr = sg->addr; - dst_addr = chan->fifo_addr; - } else { - src_addr = chan->fifo_addr; - dst_addr = sg->addr; - } - jz4740_dma_write(dmadev, JZ_REG_DMA_SRC_ADDR(chan->id), src_addr); - jz4740_dma_write(dmadev, JZ_REG_DMA_DST_ADDR(chan->id), dst_addr); - jz4740_dma_write(dmadev, JZ_REG_DMA_TRANSFER_COUNT(chan->id), - sg->len >> chan->transfer_shift); - - chan->next_sg++; - - jz4740_dma_write_mask(dmadev, JZ_REG_DMA_STATUS_CTRL(chan->id), - JZ_DMA_STATUS_CTRL_NO_DESC | JZ_DMA_STATUS_CTRL_ENABLE, - JZ_DMA_STATUS_CTRL_HALT | JZ_DMA_STATUS_CTRL_NO_DESC | - JZ_DMA_STATUS_CTRL_ENABLE); - - jz4740_dma_write_mask(dmadev, JZ_REG_DMA_CTRL, - JZ_DMA_CTRL_ENABLE, - JZ_DMA_CTRL_HALT | JZ_DMA_CTRL_ENABLE); - - return 0; -} - -static void jz4740_dma_chan_irq(struct jz4740_dmaengine_chan *chan) -{ - spin_lock(&chan->vchan.lock); - if (chan->desc) { - if (chan->desc->cyclic) { - vchan_cyclic_callback(&chan->desc->vdesc); - } else { - if (chan->next_sg == chan->desc->num_sgs) { - list_del(&chan->desc->vdesc.node); - vchan_cookie_complete(&chan->desc->vdesc); - chan->desc = NULL; - } - } - } - jz4740_dma_start_transfer(chan); - spin_unlock(&chan->vchan.lock); -} - -static irqreturn_t jz4740_dma_irq(int irq, void *devid) -{ - struct jz4740_dma_dev *dmadev = devid; - uint32_t irq_status; - unsigned int i; - - irq_status = readl(dmadev->base + JZ_REG_DMA_IRQ); - - for (i = 0; i < 6; ++i) { - if (irq_status & (1 << i)) { - jz4740_dma_write_mask(dmadev, - JZ_REG_DMA_STATUS_CTRL(i), 0, - JZ_DMA_STATUS_CTRL_ENABLE | - JZ_DMA_STATUS_CTRL_TRANSFER_DONE); - - jz4740_dma_chan_irq(&dmadev->chan[i]); - } - } - - return IRQ_HANDLED; -} - -static void jz4740_dma_issue_pending(struct dma_chan *c) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - unsigned long flags; - - spin_lock_irqsave(&chan->vchan.lock, flags); - if (vchan_issue_pending(&chan->vchan) && !chan->desc) - jz4740_dma_start_transfer(chan); - spin_unlock_irqrestore(&chan->vchan.lock, flags); -} - -static struct dma_async_tx_descriptor *jz4740_dma_prep_slave_sg( - struct dma_chan *c, struct scatterlist *sgl, - unsigned int sg_len, enum dma_transfer_direction direction, - unsigned long flags, void *context) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - struct jz4740_dma_desc *desc; - struct scatterlist *sg; - unsigned int i; - - desc = jz4740_dma_alloc_desc(sg_len); - if (!desc) - return NULL; - - for_each_sg(sgl, sg, sg_len, i) { - desc->sg[i].addr = sg_dma_address(sg); - desc->sg[i].len = sg_dma_len(sg); - } - - desc->num_sgs = sg_len; - desc->direction = direction; - desc->cyclic = false; - - jz4740_dma_slave_config_write(c, &chan->config, direction); - - return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); -} - -static struct dma_async_tx_descriptor *jz4740_dma_prep_dma_cyclic( - struct dma_chan *c, dma_addr_t buf_addr, size_t buf_len, - size_t period_len, enum dma_transfer_direction direction, - unsigned long flags) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - struct jz4740_dma_desc *desc; - unsigned int num_periods, i; - - if (buf_len % period_len) - return NULL; - - num_periods = buf_len / period_len; - - desc = jz4740_dma_alloc_desc(num_periods); - if (!desc) - return NULL; - - for (i = 0; i < num_periods; i++) { - desc->sg[i].addr = buf_addr; - desc->sg[i].len = period_len; - buf_addr += period_len; - } - - desc->num_sgs = num_periods; - desc->direction = direction; - desc->cyclic = true; - - jz4740_dma_slave_config_write(c, &chan->config, direction); - - return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); -} - -static size_t jz4740_dma_desc_residue(struct jz4740_dmaengine_chan *chan, - struct jz4740_dma_desc *desc, unsigned int next_sg) -{ - struct jz4740_dma_dev *dmadev = jz4740_dma_chan_get_dev(chan); - unsigned int residue, count; - unsigned int i; - - residue = 0; - - for (i = next_sg; i < desc->num_sgs; i++) - residue += desc->sg[i].len; - - if (next_sg != 0) { - count = jz4740_dma_read(dmadev, - JZ_REG_DMA_TRANSFER_COUNT(chan->id)); - residue += count << chan->transfer_shift; - } - - return residue; -} - -static enum dma_status jz4740_dma_tx_status(struct dma_chan *c, - dma_cookie_t cookie, struct dma_tx_state *state) -{ - struct jz4740_dmaengine_chan *chan = to_jz4740_dma_chan(c); - struct virt_dma_desc *vdesc; - enum dma_status status; - unsigned long flags; - - status = dma_cookie_status(c, cookie, state); - if (status == DMA_COMPLETE || !state) - return status; - - spin_lock_irqsave(&chan->vchan.lock, flags); - vdesc = vchan_find_desc(&chan->vchan, cookie); - if (cookie == chan->desc->vdesc.tx.cookie) { - state->residue = jz4740_dma_desc_residue(chan, chan->desc, - chan->next_sg); - } else if (vdesc) { - state->residue = jz4740_dma_desc_residue(chan, - to_jz4740_dma_desc(vdesc), 0); - } else { - state->residue = 0; - } - spin_unlock_irqrestore(&chan->vchan.lock, flags); - - return status; -} - -static void jz4740_dma_free_chan_resources(struct dma_chan *c) -{ - vchan_free_chan_resources(to_virt_chan(c)); -} - -static void jz4740_dma_desc_free(struct virt_dma_desc *vdesc) -{ - kfree(container_of(vdesc, struct jz4740_dma_desc, vdesc)); -} - -#define JZ4740_DMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ - BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)) - -static int jz4740_dma_probe(struct platform_device *pdev) -{ - struct jz4740_dmaengine_chan *chan; - struct jz4740_dma_dev *dmadev; - struct dma_device *dd; - unsigned int i; - struct resource *res; - int ret; - int irq; - - dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL); - if (!dmadev) - return -EINVAL; - - dd = &dmadev->ddev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dmadev->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dmadev->base)) - return PTR_ERR(dmadev->base); - - dmadev->clk = clk_get(&pdev->dev, "dma"); - if (IS_ERR(dmadev->clk)) - return PTR_ERR(dmadev->clk); - - clk_prepare_enable(dmadev->clk); - - dma_cap_set(DMA_SLAVE, dd->cap_mask); - dma_cap_set(DMA_CYCLIC, dd->cap_mask); - dd->device_free_chan_resources = jz4740_dma_free_chan_resources; - dd->device_tx_status = jz4740_dma_tx_status; - dd->device_issue_pending = jz4740_dma_issue_pending; - dd->device_prep_slave_sg = jz4740_dma_prep_slave_sg; - dd->device_prep_dma_cyclic = jz4740_dma_prep_dma_cyclic; - dd->device_config = jz4740_dma_slave_config; - dd->device_terminate_all = jz4740_dma_terminate_all; - dd->src_addr_widths = JZ4740_DMA_BUSWIDTHS; - dd->dst_addr_widths = JZ4740_DMA_BUSWIDTHS; - dd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); - dd->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; - dd->dev = &pdev->dev; - INIT_LIST_HEAD(&dd->channels); - - for (i = 0; i < JZ_DMA_NR_CHANS; i++) { - chan = &dmadev->chan[i]; - chan->id = i; - chan->vchan.desc_free = jz4740_dma_desc_free; - vchan_init(&chan->vchan, dd); - } - - ret = dma_async_device_register(dd); - if (ret) - goto err_clk; - - irq = platform_get_irq(pdev, 0); - ret = request_irq(irq, jz4740_dma_irq, 0, dev_name(&pdev->dev), dmadev); - if (ret) - goto err_unregister; - - platform_set_drvdata(pdev, dmadev); - - return 0; - -err_unregister: - dma_async_device_unregister(dd); -err_clk: - clk_disable_unprepare(dmadev->clk); - return ret; -} - -static void jz4740_cleanup_vchan(struct dma_device *dmadev) -{ - struct jz4740_dmaengine_chan *chan, *_chan; - - list_for_each_entry_safe(chan, _chan, - &dmadev->channels, vchan.chan.device_node) { - list_del(&chan->vchan.chan.device_node); - tasklet_kill(&chan->vchan.task); - } -} - - -static int jz4740_dma_remove(struct platform_device *pdev) -{ - struct jz4740_dma_dev *dmadev = platform_get_drvdata(pdev); - int irq = platform_get_irq(pdev, 0); - - free_irq(irq, dmadev); - - jz4740_cleanup_vchan(&dmadev->ddev); - dma_async_device_unregister(&dmadev->ddev); - clk_disable_unprepare(dmadev->clk); - - return 0; -} - -static struct platform_driver jz4740_dma_driver = { - .probe = jz4740_dma_probe, - .remove = jz4740_dma_remove, - .driver = { - .name = "jz4740-dma", - }, -}; -module_platform_driver(jz4740_dma_driver); - -MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -MODULE_DESCRIPTION("JZ4740 DMA driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/broadcom/Kconfig b/drivers/firmware/broadcom/Kconfig index 64680824f984..d03ed8e43ad7 100644 --- a/drivers/firmware/broadcom/Kconfig +++ b/drivers/firmware/broadcom/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config BCM47XX_NVRAM bool "Broadcom NVRAM driver" - depends on BCM47XX || ARCH_BCM_5301X + depends on BCM47XX || ARCH_BCM_5301X || COMPILE_TEST help Broadcom home routers contain flash partition called "nvram" with all important hardware configuration as well as some minor user setup. diff --git a/drivers/firmware/broadcom/bcm47xx_nvram.c b/drivers/firmware/broadcom/bcm47xx_nvram.c index 77eb74666ecb..6d2820f6aca1 100644 --- a/drivers/firmware/broadcom/bcm47xx_nvram.c +++ b/drivers/firmware/broadcom/bcm47xx_nvram.c @@ -96,7 +96,7 @@ found: nvram_len = size; } if (nvram_len >= NVRAM_SPACE) { - pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", + pr_err("nvram on flash (%zu bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", nvram_len, NVRAM_SPACE - 1); nvram_len = NVRAM_SPACE - 1; } @@ -148,7 +148,7 @@ static int nvram_init(void) header.len > sizeof(header)) { nvram_len = header.len; if (nvram_len >= NVRAM_SPACE) { - pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", + pr_err("nvram on flash (%zu bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n", header.len, NVRAM_SPACE); nvram_len = NVRAM_SPACE - 1; } diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2ca5668bdb62..13a6b4afb4b3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -670,16 +670,6 @@ config SENSORS_IT87 This driver can also be built as a module. If so, the module will be called it87. -config SENSORS_JZ4740 - tristate "Ingenic JZ4740 SoC ADC driver" - depends on MACH_JZ4740 && MFD_JZ4740_ADC - help - If you say yes here you get support for reading adc values from the ADCIN - pin on Ingenic JZ4740 SoC based boards. - - This driver can also be built as a module. If so, the module will be - called jz4740-hwmon. - config SENSORS_JC42 tristate "JEDEC JC42.4 compliant memory module temperature sensors" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c86ce4d3d36b..40c036ea45e6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -85,7 +85,6 @@ obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o -obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c deleted file mode 100644 index bec5befd1d8b..000000000000 --- a/drivers/hwmon/jz4740-hwmon.c +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> - * JZ4740 SoC HWMON driver - */ - -#include <linux/err.h> -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/io.h> - -#include <linux/completion.h> -#include <linux/mfd/core.h> - -#include <linux/hwmon.h> - -struct jz4740_hwmon { - void __iomem *base; - int irq; - const struct mfd_cell *cell; - struct platform_device *pdev; - struct completion read_completion; - struct mutex lock; -}; - -static irqreturn_t jz4740_hwmon_irq(int irq, void *data) -{ - struct jz4740_hwmon *hwmon = data; - - complete(&hwmon->read_completion); - return IRQ_HANDLED; -} - -static ssize_t in0_input_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - struct jz4740_hwmon *hwmon = dev_get_drvdata(dev); - struct platform_device *pdev = hwmon->pdev; - struct completion *completion = &hwmon->read_completion; - long t; - unsigned long val; - int ret; - - mutex_lock(&hwmon->lock); - - reinit_completion(completion); - - enable_irq(hwmon->irq); - hwmon->cell->enable(pdev); - - t = wait_for_completion_interruptible_timeout(completion, HZ); - - if (t > 0) { - val = readw(hwmon->base) & 0xfff; - val = (val * 3300) >> 12; - ret = sprintf(buf, "%lu\n", val); - } else { - ret = t ? t : -ETIMEDOUT; - } - - hwmon->cell->disable(pdev); - disable_irq(hwmon->irq); - - mutex_unlock(&hwmon->lock); - - return ret; -} - -static DEVICE_ATTR_RO(in0_input); - -static struct attribute *jz4740_attrs[] = { - &dev_attr_in0_input.attr, - NULL -}; - -ATTRIBUTE_GROUPS(jz4740); - -static int jz4740_hwmon_probe(struct platform_device *pdev) -{ - int ret; - struct device *dev = &pdev->dev; - struct jz4740_hwmon *hwmon; - struct device *hwmon_dev; - - hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); - if (!hwmon) - return -ENOMEM; - - hwmon->cell = mfd_get_cell(pdev); - - hwmon->irq = platform_get_irq(pdev, 0); - if (hwmon->irq < 0) { - dev_err(&pdev->dev, "Failed to get platform irq: %d\n", - hwmon->irq); - return hwmon->irq; - } - - hwmon->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(hwmon->base)) - return PTR_ERR(hwmon->base); - - hwmon->pdev = pdev; - init_completion(&hwmon->read_completion); - mutex_init(&hwmon->lock); - - ret = devm_request_irq(dev, hwmon->irq, jz4740_hwmon_irq, 0, - pdev->name, hwmon); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); - return ret; - } - disable_irq(hwmon->irq); - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jz4740", hwmon, - jz4740_groups); - return PTR_ERR_OR_ZERO(hwmon_dev); -} - -static struct platform_driver jz4740_hwmon_driver = { - .probe = jz4740_hwmon_probe, - .driver = { - .name = "jz4740-hwmon", - }, -}; - -module_platform_driver(jz4740_hwmon_driver); - -MODULE_DESCRIPTION("JZ4740 SoC HWMON driver"); -MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:jz4740-hwmon"); diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 80e10f4e213a..ccbb8973a324 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -315,6 +315,18 @@ config INGENIC_IRQ depends on MACH_INGENIC default y +config INGENIC_TCU_IRQ + bool "Ingenic JZ47xx TCU interrupt controller" + default MACH_INGENIC + depends on MIPS || COMPILE_TEST + select MFD_SYSCON + select GENERIC_IRQ_CHIP + help + Support for interrupts in the Timer/Counter Unit (TCU) of the Ingenic + JZ47xx SoCs. + + If unsure, say N. + config RENESAS_H8300H_INTC bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 8d0fcec6ab23..cc7c43932f16 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_RENESAS_H8300H_INTC) += irq-renesas-h8300h.o obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o +obj-$(CONFIG_INGENIC_TCU_IRQ) += irq-ingenic-tcu.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o obj-$(CONFIG_MSCC_OCELOT_IRQ) += irq-mscc-ocelot.o diff --git a/drivers/irqchip/irq-ingenic-tcu.c b/drivers/irqchip/irq-ingenic-tcu.c new file mode 100644 index 000000000000..6d05cefe9d79 --- /dev/null +++ b/drivers/irqchip/irq-ingenic-tcu.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ47xx SoCs TCU IRQ driver + * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/mfd/ingenic-tcu.h> +#include <linux/mfd/syscon.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +struct ingenic_tcu { + struct regmap *map; + struct clk *clk; + struct irq_domain *domain; + unsigned int nb_parent_irqs; + u32 parent_irqs[3]; +}; + +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 int __init ingenic_tcu_irq_init(struct device_node *np, + struct device_node *parent) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + struct ingenic_tcu *tcu; + struct regmap *map; + unsigned int i; + int ret, irqs; + + map = device_node_to_regmap(np); + if (IS_ERR(map)) + return PTR_ERR(map); + + tcu = kzalloc(sizeof(*tcu), GFP_KERNEL); + if (!tcu) + return -ENOMEM; + + tcu->map = map; + + irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32)); + if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs)) { + pr_crit("%s: Invalid 'interrupts' property\n", __func__); + ret = -EINVAL; + goto err_free_tcu; + } + + tcu->nb_parent_irqs = irqs; + + tcu->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops, + NULL); + if (!tcu->domain) { + ret = -ENOMEM; + goto err_free_tcu; + } + + ret = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU", + handle_level_irq, 0, + IRQ_NOPROBE | IRQ_LEVEL, 0); + if (ret) { + pr_crit("%s: Invalid 'interrupts' property\n", __func__); + 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]) { + ret = -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); +err_free_tcu: + kfree(tcu); + return ret; +} +IRQCHIP_DECLARE(jz4740_tcu_irq, "ingenic,jz4740-tcu", ingenic_tcu_irq_init); +IRQCHIP_DECLARE(jz4725b_tcu_irq, "ingenic,jz4725b-tcu", ingenic_tcu_irq_init); +IRQCHIP_DECLARE(jz4770_tcu_irq, "ingenic,jz4770-tcu", ingenic_tcu_irq_init); diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index b65e585fc8c6..660723276481 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -40,7 +40,7 @@ static const struct regmap_config syscon_regmap_config = { .reg_stride = 4, }; -static struct syscon *of_syscon_register(struct device_node *np) +static struct syscon *of_syscon_register(struct device_node *np, bool check_clk) { struct clk *clk; struct syscon *syscon; @@ -51,9 +51,6 @@ static struct syscon *of_syscon_register(struct device_node *np) struct regmap_config syscon_config = syscon_regmap_config; struct resource res; - if (!of_device_is_compatible(np, "syscon")) - return ERR_PTR(-EINVAL); - syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); if (!syscon) return ERR_PTR(-ENOMEM); @@ -117,16 +114,18 @@ static struct syscon *of_syscon_register(struct device_node *np) goto err_regmap; } - clk = of_clk_get(np, 0); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - /* clock is optional */ - if (ret != -ENOENT) - goto err_clk; - } else { - ret = regmap_mmio_attach_clk(regmap, clk); - if (ret) - goto err_attach; + if (check_clk) { + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + /* clock is optional */ + if (ret != -ENOENT) + goto err_clk; + } else { + ret = regmap_mmio_attach_clk(regmap, clk); + if (ret) + goto err_attach; + } } syscon->regmap = regmap; @@ -150,7 +149,8 @@ err_map: return ERR_PTR(ret); } -struct regmap *syscon_node_to_regmap(struct device_node *np) +static struct regmap *device_node_get_regmap(struct device_node *np, + bool check_clk) { struct syscon *entry, *syscon = NULL; @@ -165,13 +165,27 @@ struct regmap *syscon_node_to_regmap(struct device_node *np) spin_unlock(&syscon_list_slock); if (!syscon) - syscon = of_syscon_register(np); + syscon = of_syscon_register(np, check_clk); if (IS_ERR(syscon)) return ERR_CAST(syscon); return syscon->regmap; } + +struct regmap *device_node_to_regmap(struct device_node *np) +{ + return device_node_get_regmap(np, false); +} +EXPORT_SYMBOL_GPL(device_node_to_regmap); + +struct regmap *syscon_node_to_regmap(struct device_node *np) +{ + if (!of_device_is_compatible(np, "syscon")) + return ERR_PTR(-EINVAL); + + return device_node_get_regmap(np, true); +} EXPORT_SYMBOL_GPL(syscon_node_to_regmap); struct regmap *syscon_regmap_lookup_by_compatible(const char *s) diff --git a/drivers/mtd/nand/raw/ingenic/Kconfig b/drivers/mtd/nand/raw/ingenic/Kconfig index 66b7cffdb0c2..e30feb56b650 100644 --- a/drivers/mtd/nand/raw/ingenic/Kconfig +++ b/drivers/mtd/nand/raw/ingenic/Kconfig @@ -1,11 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -config MTD_NAND_JZ4740 - tristate "JZ4740 NAND controller" - depends on MACH_JZ4740 || COMPILE_TEST - depends on HAS_IOMEM - help - Enables support for NAND Flash on JZ4740 SoC based boards. - config MTD_NAND_JZ4780 tristate "JZ4780 NAND controller" depends on JZ4780_NEMC diff --git a/drivers/mtd/nand/raw/ingenic/Makefile b/drivers/mtd/nand/raw/ingenic/Makefile index b63d36889263..4c53f5e759c3 100644 --- a/drivers/mtd/nand/raw/ingenic/Makefile +++ b/drivers/mtd/nand/raw/ingenic/Makefile @@ -1,5 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_JZ4780) += ingenic_nand.o ingenic_nand-y += ingenic_nand_drv.o diff --git a/drivers/mtd/nand/raw/ingenic/jz4740_nand.c b/drivers/mtd/nand/raw/ingenic/jz4740_nand.c deleted file mode 100644 index acdf674fcc87..000000000000 --- a/drivers/mtd/nand/raw/ingenic/jz4740_nand.c +++ /dev/null @@ -1,536 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> - * JZ4740 SoC NAND controller driver - */ - -#include <linux/io.h> -#include <linux/ioport.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/rawnand.h> -#include <linux/mtd/partitions.h> - -#include <linux/gpio/consumer.h> - -#include <linux/platform_data/jz4740/jz4740_nand.h> - -#define JZ_REG_NAND_CTRL 0x50 -#define JZ_REG_NAND_ECC_CTRL 0x100 -#define JZ_REG_NAND_DATA 0x104 -#define JZ_REG_NAND_PAR0 0x108 -#define JZ_REG_NAND_PAR1 0x10C -#define JZ_REG_NAND_PAR2 0x110 -#define JZ_REG_NAND_IRQ_STAT 0x114 -#define JZ_REG_NAND_IRQ_CTRL 0x118 -#define JZ_REG_NAND_ERR(x) (0x11C + ((x) << 2)) - -#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) -#define JZ_NAND_ECC_CTRL_ENCODING BIT(3) -#define JZ_NAND_ECC_CTRL_RS BIT(2) -#define JZ_NAND_ECC_CTRL_RESET BIT(1) -#define JZ_NAND_ECC_CTRL_ENABLE BIT(0) - -#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) -#define JZ_NAND_STATUS_PAD_FINISH BIT(4) -#define JZ_NAND_STATUS_DEC_FINISH BIT(3) -#define JZ_NAND_STATUS_ENC_FINISH BIT(2) -#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) -#define JZ_NAND_STATUS_ERROR BIT(0) - -#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1) -#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1) -#define JZ_NAND_CTRL_ASSERT_CHIP_MASK 0xaa - -#define JZ_NAND_MEM_CMD_OFFSET 0x08000 -#define JZ_NAND_MEM_ADDR_OFFSET 0x10000 - -struct jz_nand { - struct nand_chip chip; - void __iomem *base; - struct resource *mem; - - unsigned char banks[JZ_NAND_NUM_BANKS]; - void __iomem *bank_base[JZ_NAND_NUM_BANKS]; - struct resource *bank_mem[JZ_NAND_NUM_BANKS]; - - int selected_bank; - - struct gpio_desc *busy_gpio; - bool is_reading; -}; - -static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd) -{ - return container_of(mtd_to_nand(mtd), struct jz_nand, chip); -} - -static void jz_nand_select_chip(struct nand_chip *chip, int chipnr) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - uint32_t ctrl; - int banknr; - - ctrl = readl(nand->base + JZ_REG_NAND_CTRL); - ctrl &= ~JZ_NAND_CTRL_ASSERT_CHIP_MASK; - - if (chipnr == -1) { - banknr = -1; - } else { - banknr = nand->banks[chipnr] - 1; - chip->legacy.IO_ADDR_R = nand->bank_base[banknr]; - chip->legacy.IO_ADDR_W = nand->bank_base[banknr]; - } - writel(ctrl, nand->base + JZ_REG_NAND_CTRL); - - nand->selected_bank = banknr; -} - -static void jz_nand_cmd_ctrl(struct nand_chip *chip, int dat, - unsigned int ctrl) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - uint32_t reg; - void __iomem *bank_base = nand->bank_base[nand->selected_bank]; - - BUG_ON(nand->selected_bank < 0); - - if (ctrl & NAND_CTRL_CHANGE) { - BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE)); - if (ctrl & NAND_ALE) - bank_base += JZ_NAND_MEM_ADDR_OFFSET; - else if (ctrl & NAND_CLE) - bank_base += JZ_NAND_MEM_CMD_OFFSET; - chip->legacy.IO_ADDR_W = bank_base; - - reg = readl(nand->base + JZ_REG_NAND_CTRL); - if (ctrl & NAND_NCE) - reg |= JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank); - else - reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank); - writel(reg, nand->base + JZ_REG_NAND_CTRL); - } - if (dat != NAND_CMD_NONE) - writeb(dat, chip->legacy.IO_ADDR_W); -} - -static int jz_nand_dev_ready(struct nand_chip *chip) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - return gpiod_get_value_cansleep(nand->busy_gpio); -} - -static void jz_nand_hwctl(struct nand_chip *chip, int mode) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - uint32_t reg; - - writel(0, nand->base + JZ_REG_NAND_IRQ_STAT); - reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); - - reg |= JZ_NAND_ECC_CTRL_RESET; - reg |= JZ_NAND_ECC_CTRL_ENABLE; - reg |= JZ_NAND_ECC_CTRL_RS; - - switch (mode) { - case NAND_ECC_READ: - reg &= ~JZ_NAND_ECC_CTRL_ENCODING; - nand->is_reading = true; - break; - case NAND_ECC_WRITE: - reg |= JZ_NAND_ECC_CTRL_ENCODING; - nand->is_reading = false; - break; - default: - break; - } - - writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); -} - -static int jz_nand_calculate_ecc_rs(struct nand_chip *chip, const uint8_t *dat, - uint8_t *ecc_code) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - uint32_t reg, status; - int i; - unsigned int timeout = 1000; - static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4, - 0x8b, 0xff, 0xb7, 0x6f}; - - if (nand->is_reading) - return 0; - - do { - status = readl(nand->base + JZ_REG_NAND_IRQ_STAT); - } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); - - if (timeout == 0) - return -1; - - reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); - reg &= ~JZ_NAND_ECC_CTRL_ENABLE; - writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); - - for (i = 0; i < 9; ++i) - ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i); - - /* If the written data is completly 0xff, we also want to write 0xff as - * ecc, otherwise we will get in trouble when doing subpage writes. */ - if (memcmp(ecc_code, empty_block_ecc, 9) == 0) - memset(ecc_code, 0xff, 9); - - return 0; -} - -static void jz_nand_correct_data(uint8_t *dat, int index, int mask) -{ - int offset = index & 0x7; - uint16_t data; - - index += (index >> 3); - - data = dat[index]; - data |= dat[index+1] << 8; - - mask ^= (data >> offset) & 0x1ff; - data &= ~(0x1ff << offset); - data |= (mask << offset); - - dat[index] = data & 0xff; - dat[index+1] = (data >> 8) & 0xff; -} - -static int jz_nand_correct_ecc_rs(struct nand_chip *chip, uint8_t *dat, - uint8_t *read_ecc, uint8_t *calc_ecc) -{ - struct jz_nand *nand = mtd_to_jz_nand(nand_to_mtd(chip)); - int i, error_count, index; - uint32_t reg, status, error; - unsigned int timeout = 1000; - - for (i = 0; i < 9; ++i) - writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i); - - reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); - reg |= JZ_NAND_ECC_CTRL_PAR_READY; - writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); - - do { - status = readl(nand->base + JZ_REG_NAND_IRQ_STAT); - } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); - - if (timeout == 0) - return -ETIMEDOUT; - - reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); - reg &= ~JZ_NAND_ECC_CTRL_ENABLE; - writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); - - if (status & JZ_NAND_STATUS_ERROR) { - if (status & JZ_NAND_STATUS_UNCOR_ERROR) - return -EBADMSG; - - error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; - - for (i = 0; i < error_count; ++i) { - error = readl(nand->base + JZ_REG_NAND_ERR(i)); - index = ((error >> 16) & 0x1ff) - 1; - if (index >= 0 && index < 512) - jz_nand_correct_data(dat, index, error & 0x1ff); - } - - return error_count; - } - - return 0; -} - -static int jz_nand_ioremap_resource(struct platform_device *pdev, - const char *name, struct resource **res, void __iomem **base) -{ - int ret; - - *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); - if (!*res) { - dev_err(&pdev->dev, "Failed to get platform %s memory\n", name); - ret = -ENXIO; - goto err; - } - - *res = request_mem_region((*res)->start, resource_size(*res), - pdev->name); - if (!*res) { - dev_err(&pdev->dev, "Failed to request %s memory region\n", name); - ret = -EBUSY; - goto err; - } - - *base = ioremap((*res)->start, resource_size(*res)); - if (!*base) { - dev_err(&pdev->dev, "Failed to ioremap %s memory region\n", name); - ret = -EBUSY; - goto err_release_mem; - } - - return 0; - -err_release_mem: - release_mem_region((*res)->start, resource_size(*res)); -err: - *res = NULL; - *base = NULL; - return ret; -} - -static inline void jz_nand_iounmap_resource(struct resource *res, - void __iomem *base) -{ - iounmap(base); - release_mem_region(res->start, resource_size(res)); -} - -static int jz_nand_detect_bank(struct platform_device *pdev, - struct jz_nand *nand, unsigned char bank, - size_t chipnr, uint8_t *nand_maf_id, - uint8_t *nand_dev_id) -{ - int ret; - char res_name[6]; - uint32_t ctrl; - struct nand_chip *chip = &nand->chip; - struct mtd_info *mtd = nand_to_mtd(chip); - struct nand_memory_organization *memorg; - u8 id[2]; - - memorg = nanddev_get_memorg(&chip->base); - - /* Request I/O resource. */ - sprintf(res_name, "bank%d", bank); - ret = jz_nand_ioremap_resource(pdev, res_name, - &nand->bank_mem[bank - 1], - &nand->bank_base[bank - 1]); - if (ret) - return ret; - - /* Enable chip in bank. */ - ctrl = readl(nand->base + JZ_REG_NAND_CTRL); - ctrl |= JZ_NAND_CTRL_ENABLE_CHIP(bank - 1); - writel(ctrl, nand->base + JZ_REG_NAND_CTRL); - - if (chipnr == 0) { - /* Detect first chip. */ - ret = nand_scan(chip, 1); - if (ret) - goto notfound_id; - - /* Retrieve the IDs from the first chip. */ - nand_select_target(chip, 0); - nand_reset_op(chip); - nand_readid_op(chip, 0, id, sizeof(id)); - *nand_maf_id = id[0]; - *nand_dev_id = id[1]; - } else { - /* Detect additional chip. */ - nand_select_target(chip, chipnr); - nand_reset_op(chip); - nand_readid_op(chip, 0, id, sizeof(id)); - if (*nand_maf_id != id[0] || *nand_dev_id != id[1]) { - ret = -ENODEV; - goto notfound_id; - } - - /* Update size of the MTD. */ - memorg->ntargets++; - mtd->size += nanddev_target_size(&chip->base); - } - - dev_info(&pdev->dev, "Found chip %zu on bank %i\n", chipnr, bank); - return 0; - -notfound_id: - dev_info(&pdev->dev, "No chip found on bank %i\n", bank); - ctrl &= ~(JZ_NAND_CTRL_ENABLE_CHIP(bank - 1)); - writel(ctrl, nand->base + JZ_REG_NAND_CTRL); - jz_nand_iounmap_resource(nand->bank_mem[bank - 1], - nand->bank_base[bank - 1]); - return ret; -} - -static int jz_nand_attach_chip(struct nand_chip *chip) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct device *dev = mtd->dev.parent; - struct jz_nand_platform_data *pdata = dev_get_platdata(dev); - struct platform_device *pdev = to_platform_device(dev); - - if (pdata && pdata->ident_callback) - pdata->ident_callback(pdev, mtd, &pdata->partitions, - &pdata->num_partitions); - - return 0; -} - -static const struct nand_controller_ops jz_nand_controller_ops = { - .attach_chip = jz_nand_attach_chip, -}; - -static int jz_nand_probe(struct platform_device *pdev) -{ - int ret; - struct jz_nand *nand; - struct nand_chip *chip; - struct mtd_info *mtd; - struct jz_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); - size_t chipnr, bank_idx; - uint8_t nand_maf_id = 0, nand_dev_id = 0; - - nand = kzalloc(sizeof(*nand), GFP_KERNEL); - if (!nand) - return -ENOMEM; - - ret = jz_nand_ioremap_resource(pdev, "mmio", &nand->mem, &nand->base); - if (ret) - goto err_free; - - nand->busy_gpio = devm_gpiod_get_optional(&pdev->dev, "busy", GPIOD_IN); - if (IS_ERR(nand->busy_gpio)) { - ret = PTR_ERR(nand->busy_gpio); - dev_err(&pdev->dev, "Failed to request busy gpio %d\n", - ret); - goto err_iounmap_mmio; - } - - chip = &nand->chip; - mtd = nand_to_mtd(chip); - mtd->dev.parent = &pdev->dev; - mtd->name = "jz4740-nand"; - - chip->ecc.hwctl = jz_nand_hwctl; - chip->ecc.calculate = jz_nand_calculate_ecc_rs; - chip->ecc.correct = jz_nand_correct_ecc_rs; - chip->ecc.mode = NAND_ECC_HW_OOB_FIRST; - chip->ecc.size = 512; - chip->ecc.bytes = 9; - chip->ecc.strength = 4; - chip->ecc.options = NAND_ECC_GENERIC_ERASED_CHECK; - - chip->legacy.chip_delay = 50; - chip->legacy.cmd_ctrl = jz_nand_cmd_ctrl; - chip->legacy.select_chip = jz_nand_select_chip; - chip->legacy.dummy_controller.ops = &jz_nand_controller_ops; - - if (nand->busy_gpio) - chip->legacy.dev_ready = jz_nand_dev_ready; - - platform_set_drvdata(pdev, nand); - - /* We are going to autodetect NAND chips in the banks specified in the - * platform data. Although nand_scan_ident() can detect multiple chips, - * it requires those chips to be numbered consecuitively, which is not - * always the case for external memory banks. And a fixed chip-to-bank - * mapping is not practical either, since for example Dingoo units - * produced at different times have NAND chips in different banks. - */ - chipnr = 0; - for (bank_idx = 0; bank_idx < JZ_NAND_NUM_BANKS; bank_idx++) { - unsigned char bank; - - /* If there is no platform data, look for NAND in bank 1, - * which is the most likely bank since it is the only one - * that can be booted from. - */ - bank = pdata ? pdata->banks[bank_idx] : bank_idx ^ 1; - if (bank == 0) - break; - if (bank > JZ_NAND_NUM_BANKS) { - dev_warn(&pdev->dev, - "Skipping non-existing bank: %d\n", bank); - continue; - } - /* The detection routine will directly or indirectly call - * jz_nand_select_chip(), so nand->banks has to contain the - * bank we're checking. - */ - nand->banks[chipnr] = bank; - if (jz_nand_detect_bank(pdev, nand, bank, chipnr, - &nand_maf_id, &nand_dev_id) == 0) - chipnr++; - else - nand->banks[chipnr] = 0; - } - if (chipnr == 0) { - dev_err(&pdev->dev, "No NAND chips found\n"); - goto err_iounmap_mmio; - } - - ret = mtd_device_register(mtd, pdata ? pdata->partitions : NULL, - pdata ? pdata->num_partitions : 0); - - if (ret) { - dev_err(&pdev->dev, "Failed to add mtd device\n"); - goto err_cleanup_nand; - } - - dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n"); - - return 0; - -err_cleanup_nand: - nand_cleanup(chip); - while (chipnr--) { - unsigned char bank = nand->banks[chipnr]; - jz_nand_iounmap_resource(nand->bank_mem[bank - 1], - nand->bank_base[bank - 1]); - } - writel(0, nand->base + JZ_REG_NAND_CTRL); -err_iounmap_mmio: - jz_nand_iounmap_resource(nand->mem, nand->base); -err_free: - kfree(nand); - return ret; -} - -static int jz_nand_remove(struct platform_device *pdev) -{ - struct jz_nand *nand = platform_get_drvdata(pdev); - size_t i; - - nand_release(&nand->chip); - - /* Deassert and disable all chips */ - writel(0, nand->base + JZ_REG_NAND_CTRL); - - for (i = 0; i < JZ_NAND_NUM_BANKS; ++i) { - unsigned char bank = nand->banks[i]; - if (bank != 0) { - jz_nand_iounmap_resource(nand->bank_mem[bank - 1], - nand->bank_base[bank - 1]); - } - } - - jz_nand_iounmap_resource(nand->mem, nand->base); - - kfree(nand); - - return 0; -} - -static struct platform_driver jz_nand_driver = { - .probe = jz_nand_probe, - .remove = jz_nand_remove, - .driver = { - .name = "jz4740-nand", - }, -}; - -module_platform_driver(jz_nand_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC"); -MODULE_ALIAS("platform:jz4740-nand"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 5e448b64393d..c84a7b1caeb6 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -417,17 +417,6 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. -config BATTERY_JZ4740 - tristate "Ingenic JZ4740 battery" - depends on MACH_JZ4740 - depends on MFD_JZ4740_ADC - help - Say Y to enable support for the battery on Ingenic JZ4740 based - boards. - - This driver can be build as a module. If so, the module will be - called jz4740-battery. - config BATTERY_RX51 tristate "Nokia RX-51 (N900) battery driver" depends on TWL4030_MADC diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 96c2b74b36bf..6c7da920ea83 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -58,7 +58,6 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o -obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o diff --git a/drivers/power/supply/jz4740-battery.c b/drivers/power/supply/jz4740-battery.c deleted file mode 100644 index 6366bd61ea9f..000000000000 --- a/drivers/power/supply/jz4740-battery.c +++ /dev/null @@ -1,421 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Battery measurement code for Ingenic JZ SOC. - * - * Copyright (C) 2009 Jiejing Zhang <kzjeef@gmail.com> - * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> - */ - -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/io.h> - -#include <linux/delay.h> -#include <linux/err.h> -#include <linux/gpio.h> -#include <linux/mfd/core.h> -#include <linux/power_supply.h> - -#include <linux/power/jz4740-battery.h> -#include <linux/jz4740-adc.h> - -struct jz_battery { - struct jz_battery_platform_data *pdata; - struct platform_device *pdev; - - void __iomem *base; - - int irq; - int charge_irq; - - const struct mfd_cell *cell; - - int status; - long voltage; - - struct completion read_completion; - - struct power_supply *battery; - struct power_supply_desc battery_desc; - struct delayed_work work; - - struct mutex lock; -}; - -static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy) -{ - return power_supply_get_drvdata(psy); -} - -static irqreturn_t jz_battery_irq_handler(int irq, void *devid) -{ - struct jz_battery *battery = devid; - - complete(&battery->read_completion); - return IRQ_HANDLED; -} - -static long jz_battery_read_voltage(struct jz_battery *battery) -{ - long t; - unsigned long val; - long voltage; - - mutex_lock(&battery->lock); - - reinit_completion(&battery->read_completion); - - enable_irq(battery->irq); - battery->cell->enable(battery->pdev); - - t = wait_for_completion_interruptible_timeout(&battery->read_completion, - HZ); - - if (t > 0) { - val = readw(battery->base) & 0xfff; - - if (battery->pdata->info.voltage_max_design <= 2500000) - val = (val * 78125UL) >> 7UL; - else - val = ((val * 924375UL) >> 9UL) + 33000; - voltage = (long)val; - } else { - voltage = t ? t : -ETIMEDOUT; - } - - battery->cell->disable(battery->pdev); - disable_irq(battery->irq); - - mutex_unlock(&battery->lock); - - return voltage; -} - -static int jz_battery_get_capacity(struct power_supply *psy) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - struct power_supply_info *info = &jz_battery->pdata->info; - long voltage; - int ret; - int voltage_span; - - voltage = jz_battery_read_voltage(jz_battery); - - if (voltage < 0) - return voltage; - - voltage_span = info->voltage_max_design - info->voltage_min_design; - ret = ((voltage - info->voltage_min_design) * 100) / voltage_span; - - if (ret > 100) - ret = 100; - else if (ret < 0) - ret = 0; - - return ret; -} - -static int jz_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, union power_supply_propval *val) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - struct power_supply_info *info = &jz_battery->pdata->info; - long voltage; - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = jz_battery->status; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = jz_battery->pdata->info.technology; - break; - case POWER_SUPPLY_PROP_HEALTH: - voltage = jz_battery_read_voltage(jz_battery); - if (voltage < info->voltage_min_design) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = jz_battery_get_capacity(psy); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = jz_battery_read_voltage(jz_battery); - if (val->intval < 0) - return val->intval; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = info->voltage_max_design; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = info->voltage_min_design; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - default: - return -EINVAL; - } - return 0; -} - -static void jz_battery_external_power_changed(struct power_supply *psy) -{ - struct jz_battery *jz_battery = psy_to_jz_battery(psy); - - mod_delayed_work(system_wq, &jz_battery->work, 0); -} - -static irqreturn_t jz_battery_charge_irq(int irq, void *data) -{ - struct jz_battery *jz_battery = data; - - mod_delayed_work(system_wq, &jz_battery->work, 0); - - return IRQ_HANDLED; -} - -static void jz_battery_update(struct jz_battery *jz_battery) -{ - int status; - long voltage; - bool has_changed = false; - int is_charging; - - if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { - is_charging = gpio_get_value(jz_battery->pdata->gpio_charge); - is_charging ^= jz_battery->pdata->gpio_charge_active_low; - if (is_charging) - status = POWER_SUPPLY_STATUS_CHARGING; - else - status = POWER_SUPPLY_STATUS_NOT_CHARGING; - - if (status != jz_battery->status) { - jz_battery->status = status; - has_changed = true; - } - } - - voltage = jz_battery_read_voltage(jz_battery); - if (voltage >= 0 && abs(voltage - jz_battery->voltage) > 50000) { - jz_battery->voltage = voltage; - has_changed = true; - } - - if (has_changed) - power_supply_changed(jz_battery->battery); -} - -static enum power_supply_property jz_battery_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_PRESENT, -}; - -static void jz_battery_work(struct work_struct *work) -{ - /* Too small interval will increase system workload */ - const int interval = HZ * 30; - struct jz_battery *jz_battery = container_of(work, struct jz_battery, - work.work); - - jz_battery_update(jz_battery); - schedule_delayed_work(&jz_battery->work, interval); -} - -static int jz_battery_probe(struct platform_device *pdev) -{ - int ret = 0; - struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; - struct power_supply_config psy_cfg = {}; - struct jz_battery *jz_battery; - struct power_supply_desc *battery_desc; - struct resource *mem; - - if (!pdata) { - dev_err(&pdev->dev, "No platform_data supplied\n"); - return -ENXIO; - } - - jz_battery = devm_kzalloc(&pdev->dev, sizeof(*jz_battery), GFP_KERNEL); - if (!jz_battery) { - dev_err(&pdev->dev, "Failed to allocate driver structure\n"); - return -ENOMEM; - } - - jz_battery->cell = mfd_get_cell(pdev); - - jz_battery->irq = platform_get_irq(pdev, 0); - if (jz_battery->irq < 0) { - dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); - return jz_battery->irq; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - jz_battery->base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(jz_battery->base)) - return PTR_ERR(jz_battery->base); - - battery_desc = &jz_battery->battery_desc; - battery_desc->name = pdata->info.name; - battery_desc->type = POWER_SUPPLY_TYPE_BATTERY; - battery_desc->properties = jz_battery_properties; - battery_desc->num_properties = ARRAY_SIZE(jz_battery_properties); - battery_desc->get_property = jz_battery_get_property; - battery_desc->external_power_changed = - jz_battery_external_power_changed; - battery_desc->use_for_apm = 1; - - psy_cfg.drv_data = jz_battery; - - jz_battery->pdata = pdata; - jz_battery->pdev = pdev; - - init_completion(&jz_battery->read_completion); - mutex_init(&jz_battery->lock); - - INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work); - - ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name, - jz_battery); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq %d\n", ret); - return ret; - } - disable_irq(jz_battery->irq); - - if (gpio_is_valid(pdata->gpio_charge)) { - ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev)); - if (ret) { - dev_err(&pdev->dev, "charger state gpio request failed.\n"); - goto err_free_irq; - } - ret = gpio_direction_input(pdata->gpio_charge); - if (ret) { - dev_err(&pdev->dev, "charger state gpio set direction failed.\n"); - goto err_free_gpio; - } - - jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge); - - if (jz_battery->charge_irq >= 0) { - ret = request_irq(jz_battery->charge_irq, - jz_battery_charge_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&pdev->dev), jz_battery); - if (ret) { - dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret); - goto err_free_gpio; - } - } - } else { - jz_battery->charge_irq = -1; - } - - if (jz_battery->pdata->info.voltage_max_design <= 2500000) - jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, - JZ_ADC_CONFIG_BAT_MB); - else - jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0); - - jz_battery->battery = power_supply_register(&pdev->dev, battery_desc, - &psy_cfg); - if (IS_ERR(jz_battery->battery)) { - dev_err(&pdev->dev, "power supply battery register failed.\n"); - ret = PTR_ERR(jz_battery->battery); - goto err_free_charge_irq; - } - - platform_set_drvdata(pdev, jz_battery); - schedule_delayed_work(&jz_battery->work, 0); - - return 0; - -err_free_charge_irq: - if (jz_battery->charge_irq >= 0) - free_irq(jz_battery->charge_irq, jz_battery); -err_free_gpio: - if (gpio_is_valid(pdata->gpio_charge)) - gpio_free(jz_battery->pdata->gpio_charge); -err_free_irq: - free_irq(jz_battery->irq, jz_battery); - return ret; -} - -static int jz_battery_remove(struct platform_device *pdev) -{ - struct jz_battery *jz_battery = platform_get_drvdata(pdev); - - cancel_delayed_work_sync(&jz_battery->work); - - if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { - if (jz_battery->charge_irq >= 0) - free_irq(jz_battery->charge_irq, jz_battery); - gpio_free(jz_battery->pdata->gpio_charge); - } - - power_supply_unregister(jz_battery->battery); - - free_irq(jz_battery->irq, jz_battery); - - return 0; -} - -#ifdef CONFIG_PM -static int jz_battery_suspend(struct device *dev) -{ - struct jz_battery *jz_battery = dev_get_drvdata(dev); - - cancel_delayed_work_sync(&jz_battery->work); - jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN; - - return 0; -} - -static int jz_battery_resume(struct device *dev) -{ - struct jz_battery *jz_battery = dev_get_drvdata(dev); - - schedule_delayed_work(&jz_battery->work, 0); - - return 0; -} - -static const struct dev_pm_ops jz_battery_pm_ops = { - .suspend = jz_battery_suspend, - .resume = jz_battery_resume, -}; - -#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops) -#else -#define JZ_BATTERY_PM_OPS NULL -#endif - -static struct platform_driver jz_battery_driver = { - .probe = jz_battery_probe, - .remove = jz_battery_remove, - .driver = { - .name = "jz4740-battery", - .pm = JZ_BATTERY_PM_OPS, - }, -}; - -module_platform_driver(jz_battery_driver); - -MODULE_ALIAS("platform:jz4740-battery"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index 5f83cd715387..1e70e838530e 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -2197,15 +2197,6 @@ config FB_BROADSHEET and could also have been called by other names when coupled with a bridge adapter. -config FB_JZ4740 - tristate "JZ4740 LCD framebuffer support" - depends on FB && MACH_JZ4740 - select FB_SYS_FILLRECT - select FB_SYS_COPYAREA - select FB_SYS_IMAGEBLIT - help - Framebuffer support for the JZ4740 SoC. - config FB_PUV3_UNIGFX tristate "PKUnity v3 Unigfx framebuffer support" depends on FB && UNICORE32 && ARCH_PUV3 diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile index aab7155884ea..aa6352798cf4 100644 --- a/drivers/video/fbdev/Makefile +++ b/drivers/video/fbdev/Makefile @@ -116,7 +116,6 @@ obj-y += omap2/ obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o obj-$(CONFIG_FB_CARMINE) += carminefb.o obj-$(CONFIG_FB_MB862XX) += mb862xx/ -obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o obj-$(CONFIG_FB_PUV3_UNIGFX) += fb-puv3.o obj-$(CONFIG_FB_HYPERV) += hyperv_fb.o obj-$(CONFIG_FB_OPENCORES) += ocfb.o diff --git a/drivers/video/fbdev/jz4740_fb.c b/drivers/video/fbdev/jz4740_fb.c deleted file mode 100644 index 0b6fa25f6924..000000000000 --- a/drivers/video/fbdev/jz4740_fb.c +++ /dev/null @@ -1,690 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> - * JZ4740 SoC LCD framebuffer driver - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/platform_device.h> -#include <linux/pinctrl/consumer.h> - -#include <linux/clk.h> -#include <linux/delay.h> - -#include <linux/console.h> -#include <linux/fb.h> - -#include <linux/dma-mapping.h> - -#include <asm/mach-jz4740/jz4740_fb.h> - -#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_CTRL 0x30 -#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_FID0 0x48 -#define JZ_REG_LCD_CMD0 0x4C -#define JZ_REG_LCD_DA1 0x50 -#define JZ_REG_LCD_SA1 0x54 -#define JZ_REG_LCD_FID1 0x58 -#define JZ_REG_LCD_CMD1 0x5C - -#define JZ_LCD_CFG_SLCD BIT(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_DIR BIT(16) -#define JZ_LCD_CFG_PS_POLARITY BIT(15) -#define JZ_LCD_CFG_CLS_POLARITY BIT(14) -#define JZ_LCD_CFG_SPL_POLARITY BIT(13) -#define JZ_LCD_CFG_REV_POLARITY BIT(12) -#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(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_LOW BIT(8) -#define JZ_LCD_CFG_18_BIT BIT(7) -#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4)) -#define JZ_LCD_CFG_MODE_MASK 0xf - -#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_IRQ BIT(13) -#define JZ_LCD_CTRL_SOF_IRQ BIT(12) -#define JZ_LCD_CTRL_OFU_IRQ BIT(11) -#define JZ_LCD_CTRL_IFU0_IRQ BIT(10) -#define JZ_LCD_CTRL_IFU1_IRQ BIT(9) -#define JZ_LCD_CTRL_DD_IRQ BIT(8) -#define JZ_LCD_CTRL_QDD_IRQ BIT(7) -#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6) -#define JZ_LCD_CTRL_LSB_FISRT BIT(5) -#define JZ_LCD_CTRL_DISABLE BIT(4) -#define JZ_LCD_CTRL_ENABLE BIT(3) -#define JZ_LCD_CTRL_BPP_1 0x0 -#define JZ_LCD_CTRL_BPP_2 0x1 -#define JZ_LCD_CTRL_BPP_4 0x2 -#define JZ_LCD_CTRL_BPP_8 0x3 -#define JZ_LCD_CTRL_BPP_15_16 0x4 -#define JZ_LCD_CTRL_BPP_18_24 0x5 - -#define JZ_LCD_CMD_SOF_IRQ BIT(31) -#define JZ_LCD_CMD_EOF_IRQ BIT(30) -#define JZ_LCD_CMD_ENABLE_PAL BIT(28) - -#define JZ_LCD_SYNC_MASK 0x3ff - -#define JZ_LCD_STATE_DISABLED BIT(0) - -struct jzfb_framedesc { - uint32_t next; - uint32_t addr; - uint32_t id; - uint32_t cmd; -} __packed; - -struct jzfb { - struct fb_info *fb; - struct platform_device *pdev; - void __iomem *base; - struct resource *mem; - struct jz4740_fb_platform_data *pdata; - - size_t vidmem_size; - void *vidmem; - dma_addr_t vidmem_phys; - struct jzfb_framedesc *framedesc; - dma_addr_t framedesc_phys; - - struct clk *ldclk; - struct clk *lpclk; - - unsigned is_enabled:1; - struct mutex lock; - - uint32_t pseudo_palette[16]; -}; - -static const struct fb_fix_screeninfo jzfb_fix = { - .id = "JZ4740 FB", - .type = FB_TYPE_PACKED_PIXELS, - .visual = FB_VISUAL_TRUECOLOR, - .xpanstep = 0, - .ypanstep = 0, - .ywrapstep = 0, - .accel = FB_ACCEL_NONE, -}; - -/* Based on CNVT_TOHW macro from skeletonfb.c */ -static inline uint32_t jzfb_convert_color_to_hw(unsigned val, - struct fb_bitfield *bf) -{ - return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset; -} - -static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green, - unsigned blue, unsigned transp, struct fb_info *fb) -{ - uint32_t color; - - if (regno >= 16) - return -EINVAL; - - color = jzfb_convert_color_to_hw(red, &fb->var.red); - color |= jzfb_convert_color_to_hw(green, &fb->var.green); - color |= jzfb_convert_color_to_hw(blue, &fb->var.blue); - color |= jzfb_convert_color_to_hw(transp, &fb->var.transp); - - ((uint32_t *)(fb->pseudo_palette))[regno] = color; - - return 0; -} - -static int jzfb_get_controller_bpp(struct jzfb *jzfb) -{ - switch (jzfb->pdata->bpp) { - case 18: - case 24: - return 32; - case 15: - return 16; - default: - return jzfb->pdata->bpp; - } -} - -static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb, - struct fb_var_screeninfo *var) -{ - size_t i; - struct fb_videomode *mode = jzfb->pdata->modes; - - for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) { - if (mode->xres == var->xres && mode->yres == var->yres) - return mode; - } - - return NULL; -} - -static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb) -{ - struct jzfb *jzfb = fb->par; - struct fb_videomode *mode; - - if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) && - var->bits_per_pixel != jzfb->pdata->bpp) - return -EINVAL; - - mode = jzfb_get_mode(jzfb, var); - if (mode == NULL) - return -EINVAL; - - fb_videomode_to_var(var, mode); - - switch (jzfb->pdata->bpp) { - case 8: - break; - case 15: - var->red.offset = 10; - var->red.length = 5; - var->green.offset = 6; - var->green.length = 5; - var->blue.offset = 0; - var->blue.length = 5; - break; - case 16: - var->red.offset = 11; - var->red.length = 5; - var->green.offset = 5; - var->green.length = 6; - var->blue.offset = 0; - var->blue.length = 5; - break; - case 18: - var->red.offset = 16; - var->red.length = 6; - var->green.offset = 8; - var->green.length = 6; - var->blue.offset = 0; - var->blue.length = 6; - var->bits_per_pixel = 32; - break; - case 32: - case 24: - var->transp.offset = 24; - var->transp.length = 8; - var->red.offset = 16; - var->red.length = 8; - var->green.offset = 8; - var->green.length = 8; - var->blue.offset = 0; - var->blue.length = 8; - var->bits_per_pixel = 32; - break; - default: - break; - } - - return 0; -} - -static int jzfb_set_par(struct fb_info *info) -{ - struct jzfb *jzfb = info->par; - struct jz4740_fb_platform_data *pdata = jzfb->pdata; - struct fb_var_screeninfo *var = &info->var; - struct fb_videomode *mode; - uint16_t hds, vds; - uint16_t hde, vde; - uint16_t ht, vt; - uint32_t ctrl; - uint32_t cfg; - unsigned long rate; - - mode = jzfb_get_mode(jzfb, var); - if (mode == NULL) - return -EINVAL; - - if (mode == info->mode) - return 0; - - info->mode = mode; - - hds = mode->hsync_len + mode->left_margin; - hde = hds + mode->xres; - ht = hde + mode->right_margin; - - vds = mode->vsync_len + mode->upper_margin; - vde = vds + mode->yres; - vt = vde + mode->lower_margin; - - ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16; - - switch (pdata->bpp) { - case 1: - ctrl |= JZ_LCD_CTRL_BPP_1; - break; - case 2: - ctrl |= JZ_LCD_CTRL_BPP_2; - break; - case 4: - ctrl |= JZ_LCD_CTRL_BPP_4; - break; - case 8: - ctrl |= JZ_LCD_CTRL_BPP_8; - break; - case 15: - ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */ - case 16: - ctrl |= JZ_LCD_CTRL_BPP_15_16; - break; - case 18: - case 24: - case 32: - ctrl |= JZ_LCD_CTRL_BPP_18_24; - break; - default: - break; - } - - cfg = pdata->lcd_type & 0xf; - - if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) - cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW; - - if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) - cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW; - - if (pdata->pixclk_falling_edge) - cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE; - - if (pdata->date_enable_active_low) - cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW; - - if (pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT) - cfg |= JZ_LCD_CFG_18_BIT; - - if (mode->pixclock) { - rate = PICOS2KHZ(mode->pixclock) * 1000; - mode->refresh = rate / vt / ht; - } else { - if (pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL) - rate = mode->refresh * (vt + 2 * mode->xres) * ht; - else - rate = mode->refresh * vt * ht; - - mode->pixclock = KHZ2PICOS(rate / 1000); - } - - mutex_lock(&jzfb->lock); - if (!jzfb->is_enabled) - clk_enable(jzfb->ldclk); - else - ctrl |= JZ_LCD_CTRL_ENABLE; - - switch (pdata->lcd_type) { - case JZ_LCD_TYPE_SPECIAL_TFT_1: - case JZ_LCD_TYPE_SPECIAL_TFT_2: - case JZ_LCD_TYPE_SPECIAL_TFT_3: - writel(pdata->special_tft_config.spl, jzfb->base + JZ_REG_LCD_SPL); - writel(pdata->special_tft_config.cls, jzfb->base + JZ_REG_LCD_CLS); - writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_PS); - writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_REV); - break; - default: - cfg |= JZ_LCD_CFG_PS_DISABLE; - cfg |= JZ_LCD_CFG_CLS_DISABLE; - cfg |= JZ_LCD_CFG_SPL_DISABLE; - cfg |= JZ_LCD_CFG_REV_DISABLE; - break; - } - - writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC); - writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC); - - writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT); - - writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH); - writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV); - - writel(cfg, jzfb->base + JZ_REG_LCD_CFG); - - writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); - - if (!jzfb->is_enabled) - clk_disable_unprepare(jzfb->ldclk); - - mutex_unlock(&jzfb->lock); - - clk_set_rate(jzfb->lpclk, rate); - clk_set_rate(jzfb->ldclk, rate * 3); - - return 0; -} - -static void jzfb_enable(struct jzfb *jzfb) -{ - uint32_t ctrl; - - clk_prepare_enable(jzfb->ldclk); - - pinctrl_pm_select_default_state(&jzfb->pdev->dev); - - writel(0, jzfb->base + JZ_REG_LCD_STATE); - - writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); - - ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); - ctrl |= JZ_LCD_CTRL_ENABLE; - ctrl &= ~JZ_LCD_CTRL_DISABLE; - writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); -} - -static void jzfb_disable(struct jzfb *jzfb) -{ - uint32_t ctrl; - - ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); - ctrl |= JZ_LCD_CTRL_DISABLE; - writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); - do { - ctrl = readl(jzfb->base + JZ_REG_LCD_STATE); - } while (!(ctrl & JZ_LCD_STATE_DISABLED)); - - pinctrl_pm_select_sleep_state(&jzfb->pdev->dev); - - clk_disable_unprepare(jzfb->ldclk); -} - -static int jzfb_blank(int blank_mode, struct fb_info *info) -{ - struct jzfb *jzfb = info->par; - - switch (blank_mode) { - case FB_BLANK_UNBLANK: - mutex_lock(&jzfb->lock); - if (jzfb->is_enabled) { - mutex_unlock(&jzfb->lock); - return 0; - } - - jzfb_enable(jzfb); - jzfb->is_enabled = 1; - - mutex_unlock(&jzfb->lock); - break; - default: - mutex_lock(&jzfb->lock); - if (!jzfb->is_enabled) { - mutex_unlock(&jzfb->lock); - return 0; - } - - jzfb_disable(jzfb); - jzfb->is_enabled = 0; - - mutex_unlock(&jzfb->lock); - break; - } - - return 0; -} - -static int jzfb_alloc_devmem(struct jzfb *jzfb) -{ - int max_videosize = 0; - struct fb_videomode *mode = jzfb->pdata->modes; - int i; - - for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) { - if (max_videosize < mode->xres * mode->yres) - max_videosize = mode->xres * mode->yres; - } - - max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3; - - jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev, - sizeof(*jzfb->framedesc), - &jzfb->framedesc_phys, GFP_KERNEL); - - if (!jzfb->framedesc) - return -ENOMEM; - - jzfb->vidmem_size = PAGE_ALIGN(max_videosize); - jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev, - jzfb->vidmem_size, - &jzfb->vidmem_phys, GFP_KERNEL); - - if (!jzfb->vidmem) - goto err_free_framedesc; - - jzfb->framedesc->next = jzfb->framedesc_phys; - jzfb->framedesc->addr = jzfb->vidmem_phys; - jzfb->framedesc->id = 0xdeafbead; - jzfb->framedesc->cmd = 0; - jzfb->framedesc->cmd |= max_videosize / 4; - - return 0; - -err_free_framedesc: - dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), - jzfb->framedesc, jzfb->framedesc_phys); - return -ENOMEM; -} - -static void jzfb_free_devmem(struct jzfb *jzfb) -{ - dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size, - jzfb->vidmem, jzfb->vidmem_phys); - dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), - jzfb->framedesc, jzfb->framedesc_phys); -} - -static struct fb_ops jzfb_ops = { - .owner = THIS_MODULE, - .fb_check_var = jzfb_check_var, - .fb_set_par = jzfb_set_par, - .fb_blank = jzfb_blank, - .fb_fillrect = sys_fillrect, - .fb_copyarea = sys_copyarea, - .fb_imageblit = sys_imageblit, - .fb_setcolreg = jzfb_setcolreg, -}; - -static int jzfb_probe(struct platform_device *pdev) -{ - int ret; - struct jzfb *jzfb; - struct fb_info *fb; - struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data; - struct resource *mem; - - if (!pdata) { - dev_err(&pdev->dev, "Missing platform data\n"); - return -ENXIO; - } - - fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev); - if (!fb) - return -ENOMEM; - - fb->fbops = &jzfb_ops; - fb->flags = FBINFO_DEFAULT; - - jzfb = fb->par; - jzfb->pdev = pdev; - jzfb->pdata = pdata; - - jzfb->ldclk = devm_clk_get(&pdev->dev, "lcd"); - if (IS_ERR(jzfb->ldclk)) { - ret = PTR_ERR(jzfb->ldclk); - dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret); - goto err_framebuffer_release; - } - - jzfb->lpclk = devm_clk_get(&pdev->dev, "lcd_pclk"); - if (IS_ERR(jzfb->lpclk)) { - ret = PTR_ERR(jzfb->lpclk); - dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret); - goto err_framebuffer_release; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - jzfb->base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(jzfb->base)) { - ret = PTR_ERR(jzfb->base); - goto err_framebuffer_release; - } - - platform_set_drvdata(pdev, jzfb); - - mutex_init(&jzfb->lock); - - fb_videomode_to_modelist(pdata->modes, pdata->num_modes, - &fb->modelist); - fb_videomode_to_var(&fb->var, pdata->modes); - fb->var.bits_per_pixel = pdata->bpp; - jzfb_check_var(&fb->var, fb); - - ret = jzfb_alloc_devmem(jzfb); - if (ret) { - dev_err(&pdev->dev, "Failed to allocate video memory\n"); - goto err_framebuffer_release; - } - - fb->fix = jzfb_fix; - fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8; - fb->fix.mmio_start = mem->start; - fb->fix.mmio_len = resource_size(mem); - fb->fix.smem_start = jzfb->vidmem_phys; - fb->fix.smem_len = fb->fix.line_length * fb->var.yres; - fb->screen_base = jzfb->vidmem; - fb->pseudo_palette = jzfb->pseudo_palette; - - fb_alloc_cmap(&fb->cmap, 256, 0); - - clk_prepare_enable(jzfb->ldclk); - jzfb->is_enabled = 1; - - writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); - - fb->mode = NULL; - jzfb_set_par(fb); - - ret = register_framebuffer(fb); - if (ret) { - dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret); - goto err_free_devmem; - } - - jzfb->fb = fb; - - return 0; - -err_free_devmem: - fb_dealloc_cmap(&fb->cmap); - jzfb_free_devmem(jzfb); -err_framebuffer_release: - framebuffer_release(fb); - return ret; -} - -static int jzfb_remove(struct platform_device *pdev) -{ - struct jzfb *jzfb = platform_get_drvdata(pdev); - - jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb); - - fb_dealloc_cmap(&jzfb->fb->cmap); - jzfb_free_devmem(jzfb); - - framebuffer_release(jzfb->fb); - - return 0; -} - -#ifdef CONFIG_PM - -static int jzfb_suspend(struct device *dev) -{ - struct jzfb *jzfb = dev_get_drvdata(dev); - - console_lock(); - fb_set_suspend(jzfb->fb, 1); - console_unlock(); - - mutex_lock(&jzfb->lock); - if (jzfb->is_enabled) - jzfb_disable(jzfb); - mutex_unlock(&jzfb->lock); - - return 0; -} - -static int jzfb_resume(struct device *dev) -{ - struct jzfb *jzfb = dev_get_drvdata(dev); - clk_prepare_enable(jzfb->ldclk); - - mutex_lock(&jzfb->lock); - if (jzfb->is_enabled) - jzfb_enable(jzfb); - mutex_unlock(&jzfb->lock); - - console_lock(); - fb_set_suspend(jzfb->fb, 0); - console_unlock(); - - return 0; -} - -static const struct dev_pm_ops jzfb_pm_ops = { - .suspend = jzfb_suspend, - .resume = jzfb_resume, - .poweroff = jzfb_suspend, - .restore = jzfb_resume, -}; - -#define JZFB_PM_OPS (&jzfb_pm_ops) - -#else -#define JZFB_PM_OPS NULL -#endif - -static struct platform_driver jzfb_driver = { - .probe = jzfb_probe, - .remove = jzfb_remove, - .driver = { - .name = "jz4740-fb", - .pm = JZFB_PM_OPS, - }, -}; -module_platform_driver(jzfb_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver"); -MODULE_ALIAS("platform:jz4740-fb"); |