diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-05-19 10:02:26 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-05-19 10:02:26 -0700 |
commit | 07b75260ebc2c789724c594d7eaf0194fa47b3be (patch) | |
tree | d88b770bca479789e688d95e50aacd5d09b59b21 /drivers | |
parent | 0efacbbaee1e94e9942da0912f5b46ffd45a74bd (diff) | |
parent | 6e4ad1b413604b9130bdbe532aafdbd47ff5318e (diff) |
Merge branch 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus
Pull MIPS updates from Ralf Baechle:
"This is the main pull request for MIPS for 4.7. Here's the summary of
the changes:
- ATH79: Support for DTB passuing using the UHI boot protocol
- ATH79: Remove support for builtin DTB.
- ATH79: Add zboot debug serial support.
- ATH79: Add initial support for Dragino MS14 (Dragine 2), Onion Omega
and DPT-Module.
- ATH79: Update devicetree clock support for AR9132 and AR9331.
- ATH79: Cleanup the DT code.
- ATH79: Support newer SOCs in ath79_ddr_ctrl_init.
- ATH79: Fix regression in PCI window initialization.
- BCM47xx: Move SPROM driver to drivers/firmware/
- BCM63xx: Enable partition parser in defconfig.
- BMIPS: BMIPS5000 has I cache filing from D cache
- BMIPS: BMIPS: Add cpu-feature-overrides.h
- BMIPS: Add Whirlwind support
- BMIPS: Adjust mips-hpt-frequency for BCM7435
- BMIPS: Remove maxcpus from BCM97435SVMB DTS
- BMIPS: Add missing 7038 L1 register cells to BCM7435
- BMIPS: Various tweaks to initialization code.
- BMIPS: Enable partition parser in defconfig.
- BMIPS: Cache tweaks.
- BMIPS: Add UART, I2C and SATA devices to DT.
- BMIPS: Add BCM6358 and BCM63268support
- BMIPS: Add device tree example for BCM6358.
- BMIPS: Improve Improve BCM6328 and BCM6368 device trees
- Lantiq: Add support for device tree file from boot loader
- Lantiq: Allow build with no built-in DT.
- Loongson 3: Reserve 32MB for RS780E integrated GPU.
- Loongson 3: Fix build error after ld-version.sh modification
- Loongson 3: Move chipset ACPI code from drivers to arch.
- Loongson 3: Speedup irq processing.
- Loongson 3: Add basic Loongson 3A support.
- Loongson 3: Set cache flush handlers to nop.
- Loongson 3: Invalidate special TLBs when needed.
- Loongson 3: Fast TLB refill handler.
- MT7620: Fallback strategy for invalid syscfg0.
- Netlogic: Fix CP0_EBASE redefinition warnings
- Octeon: Initialization fixes
- Octeon: Add DTS files for the D-Link DSR-1000N and EdgeRouter Lite
- Octeon: Enable add Octeon-drivers in cavium_octeon_defconfig
- Octeon: Correctly handle endian-swapped initramfs images.
- Octeon: Support CN73xx, CN75xx and CN78xx.
- Octeon: Remove dead code from cvmx-sysinfo.
- Octeon: Extend number of supported CPUs past 32.
- Octeon: Remove some code limiting NR_IRQS to 255.
- Octeon: Simplify octeon_irq_ciu_gpio_set_type.
- Octeon: Mark some functions __init in smp.c
- Octeon: Octeon: Add Octeon III CN7xxx interface detection
- PIC32: Add serial driver and bindings for it.
- PIC32: Add PIC32 deadman timer driver and bindings.
- PIC32: Add PIC32 clock timer driver and bindings.
- Pistachio: Determine SoC revision during boot
- Sibyte: Fix Kconfig dependencies of SIBYTE_BUS_WATCHER.
- Sibyte: Strip redundant comments from bcm1480_regs.h.
- Panic immediately if panic_on_oops is set.
- module: fix incorrect IS_ERR_VALUE macro usage.
- module: Make consistent use of pr_*
- Remove no longer needed work_on_cpu() call.
- Remove CONFIG_IPV6_PRIVACY from defconfigs.
- Fix registers of non-crashing CPUs in dumps.
- Handle MIPSisms in new vmcore_elf32_check_arch.
- Select CONFIG_HANDLE_DOMAIN_IRQ and make it work.
- Allow RIXI to be used on non-R2 or R6 cores.
- Reserve nosave data for hibernation
- Fix siginfo.h to use strict POSIX types.
- Don't unwind user mode with EVA.
- Fix watchpoint restoration
- Ptrace watchpoints for R6.
- Sync icache when it fills from dcache
- I6400 I-cache fills from dcache.
- Various MSA fixes.
- Cleanup MIPS_CPU_* definitions.
- Signal: Move generic copy_siginfo to signal.h
- Signal: Fix uapi include in exported asm/siginfo.h
- Timer fixes for sake of KVM.
- XPA TLB refill fixes.
- Treat perf counter feature
- Update John Crispin's email address
- Add PIC32 watchdog and bindings.
- Handle R10000 LL/SC bug in set_pte()
- cpufreq: Various fixes for Longson1.
- R6: Fix R2 emulation.
- mathemu: Cosmetic fix to ADDIUPC emulation, plenty of other small fixes
- ELF: ABI and FP fixes.
- Allow for relocatable kernel and use that to support KASLR.
- Fix CPC_BASE_ADDR mask
- Plenty fo smp-cps, CM, R6 and M6250 fixes.
- Make reset_control_ops const.
- Fix kernel command line handling of leading whitespace.
- Cleanups to cache handling.
- Add brcm, bcm6345-l1-intc device tree bindings.
- Use generic clkdev.h header
- Remove CLK_IS_ROOT usage.
- Misc small cleanups.
- CM: Fix compilation error when !MIPS_CM
- oprofile: Fix a preemption issue
- Detect DSP ASE v3 support:1"
* 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus: (275 commits)
MIPS: pic32mzda: fix getting timer clock rate.
MIPS: ath79: fix regression in PCI window initialization
MIPS: ath79: make ath79_ddr_ctrl_init() compatible for newer SoCs
MIPS: Fix VZ probe gas errors with binutils <2.24
MIPS: perf: Fix I6400 event numbers
MIPS: DEC: Export `ioasic_ssr_lock' to modules
MIPS: MSA: Fix a link error on `_init_msa_upper' with older GCC
MIPS: CM: Fix compilation error when !MIPS_CM
MIPS: Fix genvdso error on rebuild
USB: ohci-jz4740: Remove obsolete driver
MIPS: JZ4740: Probe OHCI platform device via DT
MIPS: JZ4740: Qi LB60: Remove support for AVT2 variant
MIPS: pistachio: Determine SoC revision during boot
MIPS: BMIPS: Adjust mips-hpt-frequency for BCM7435
mips: mt7620: fallback to SDRAM when syscfg0 does not have a valid value for the memory type
MIPS: Prevent "restoration" of MSA context in non-MSA kernels
MIPS: cevt-r4k: Dynamically calculate min_delta_ns
MIPS: malta-time: Take seconds into account
MIPS: malta-time: Start GIC count before syncing to RTC
MIPS: Force CPUs to lose FP context during mode switches
...
Diffstat (limited to 'drivers')
28 files changed, 3915 insertions, 475 deletions
diff --git a/drivers/bus/brcmstb_gisb.c b/drivers/bus/brcmstb_gisb.c index f364fa4d24eb..72fe0a5a8bf3 100644 --- a/drivers/bus/brcmstb_gisb.c +++ b/drivers/bus/brcmstb_gisb.c @@ -30,6 +30,10 @@ #include <asm/signal.h> #endif +#ifdef CONFIG_MIPS +#include <asm/traps.h> +#endif + #define ARB_ERR_CAP_CLEAR (1 << 0) #define ARB_ERR_CAP_STATUS_TIMEOUT (1 << 12) #define ARB_ERR_CAP_STATUS_TEA (1 << 11) @@ -238,6 +242,29 @@ static int brcmstb_bus_error_handler(unsigned long addr, unsigned int fsr, } #endif +#ifdef CONFIG_MIPS +static int brcmstb_bus_error_handler(struct pt_regs *regs, int is_fixup) +{ + int ret = 0; + struct brcmstb_gisb_arb_device *gdev; + u32 cap_status; + + list_for_each_entry(gdev, &brcmstb_gisb_arb_device_list, next) { + cap_status = gisb_read(gdev, ARB_ERR_CAP_STATUS); + + /* Invalid captured address, bail out */ + if (!(cap_status & ARB_ERR_CAP_STATUS_VALID)) { + is_fixup = 1; + goto out; + } + + ret |= brcmstb_gisb_arb_decode_addr(gdev, "bus error"); + } +out: + return is_fixup ? MIPS_BE_FIXUP : MIPS_BE_FATAL; +} +#endif + static irqreturn_t brcmstb_gisb_timeout_handler(int irq, void *dev_id) { brcmstb_gisb_arb_decode_addr(dev_id, "timeout"); @@ -355,6 +382,9 @@ static int __init brcmstb_gisb_arb_probe(struct platform_device *pdev) hook_fault_code(22, brcmstb_bus_error_handler, SIGBUS, 0, "imprecise external abort"); #endif +#ifdef CONFIG_MIPS + board_be_handler = brcmstb_bus_error_handler; +#endif dev_info(&pdev->dev, "registered mem: %p, irqs: %d, %d\n", gdev->base, timeout_irq, tea_irq); diff --git a/drivers/bus/mips_cdmm.c b/drivers/bus/mips_cdmm.c index 1c543effe062..cad49bc38b3e 100644 --- a/drivers/bus/mips_cdmm.c +++ b/drivers/bus/mips_cdmm.c @@ -599,8 +599,8 @@ BUILD_PERDEV_HELPER(cpu_up) /* int mips_cdmm_cpu_up_helper(...) */ * mips_cdmm_bus_down() - Tear down the CDMM bus. * @data: Pointer to unsigned int CPU number. * - * This work_on_cpu callback function is executed on a given CPU to call the - * CDMM driver cpu_down callback for all devices on that CPU. + * This function is executed on the hotplugged CPU and calls the CDMM + * driver cpu_down callback for all devices on that CPU. */ static long mips_cdmm_bus_down(void *data) { @@ -630,7 +630,9 @@ static long mips_cdmm_bus_down(void *data) * CDMM devices on that CPU, or to call the CDMM driver cpu_up callback for all * devices already discovered on that CPU. * - * It is used during initialisation and when CPUs are brought online. + * It is used as work_on_cpu callback function during + * initialisation. When CPUs are brought online the function is + * invoked directly on the hotplugged CPU. */ static long mips_cdmm_bus_up(void *data) { @@ -677,10 +679,10 @@ static int mips_cdmm_cpu_notify(struct notifier_block *nb, switch (action & ~CPU_TASKS_FROZEN) { case CPU_ONLINE: case CPU_DOWN_FAILED: - work_on_cpu(cpu, mips_cdmm_bus_up, &cpu); + mips_cdmm_bus_up(&cpu); break; case CPU_DOWN_PREPARE: - work_on_cpu(cpu, mips_cdmm_bus_down, &cpu); + mips_cdmm_bus_down(&cpu); break; default: return NOTIFY_DONE; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c45554957499..90518cd7fc9c 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -197,6 +197,9 @@ config COMMON_CLK_PXA ---help--- Support for the Marvell PXA SoC. +config COMMON_CLK_PIC32 + def_bool COMMON_CLK && MACH_PIC32 + source "drivers/clk/bcm/Kconfig" source "drivers/clk/hisilicon/Kconfig" source "drivers/clk/mvebu/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 46869d696e4d..18e64bbeeaf4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_ARCH_MXC) += imx/ obj-$(CONFIG_MACH_INGENIC) += ingenic/ obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ +obj-$(CONFIG_MACH_PIC32) += microchip/ ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_ARCH_MMP) += mmp/ endif diff --git a/drivers/clk/microchip/Makefile b/drivers/clk/microchip/Makefile new file mode 100644 index 000000000000..2152f418106a --- /dev/null +++ b/drivers/clk/microchip/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_COMMON_CLK_PIC32) += clk-core.o +obj-$(CONFIG_PIC32MZDA) += clk-pic32mzda.o diff --git a/drivers/clk/microchip/clk-core.c b/drivers/clk/microchip/clk-core.c new file mode 100644 index 000000000000..ca85cea17839 --- /dev/null +++ b/drivers/clk/microchip/clk-core.c @@ -0,0 +1,1031 @@ +/* + * Purna Chandra Mandal,<purna.mandal@microchip.com> + * Copyright (C) 2015 Microchip Technology Inc. All rights reserved. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <asm/mach-pic32/pic32.h> +#include <asm/traps.h> + +#include "clk-core.h" + +/* OSCCON Reg fields */ +#define OSC_CUR_MASK 0x07 +#define OSC_CUR_SHIFT 12 +#define OSC_NEW_MASK 0x07 +#define OSC_NEW_SHIFT 8 +#define OSC_SWEN BIT(0) + +/* SPLLCON Reg fields */ +#define PLL_RANGE_MASK 0x07 +#define PLL_RANGE_SHIFT 0 +#define PLL_ICLK_MASK 0x01 +#define PLL_ICLK_SHIFT 7 +#define PLL_IDIV_MASK 0x07 +#define PLL_IDIV_SHIFT 8 +#define PLL_ODIV_MASK 0x07 +#define PLL_ODIV_SHIFT 24 +#define PLL_MULT_MASK 0x7F +#define PLL_MULT_SHIFT 16 +#define PLL_MULT_MAX 128 +#define PLL_ODIV_MIN 1 +#define PLL_ODIV_MAX 5 + +/* Peripheral Bus Clock Reg Fields */ +#define PB_DIV_MASK 0x7f +#define PB_DIV_SHIFT 0 +#define PB_DIV_READY BIT(11) +#define PB_DIV_ENABLE BIT(15) +#define PB_DIV_MAX 128 +#define PB_DIV_MIN 0 + +/* Reference Oscillator Control Reg fields */ +#define REFO_SEL_MASK 0x0f +#define REFO_SEL_SHIFT 0 +#define REFO_ACTIVE BIT(8) +#define REFO_DIVSW_EN BIT(9) +#define REFO_OE BIT(12) +#define REFO_ON BIT(15) +#define REFO_DIV_SHIFT 16 +#define REFO_DIV_MASK 0x7fff + +/* Reference Oscillator Trim Register Fields */ +#define REFO_TRIM_REG 0x10 +#define REFO_TRIM_MASK 0x1ff +#define REFO_TRIM_SHIFT 23 +#define REFO_TRIM_MAX 511 + +/* Mux Slew Control Register fields */ +#define SLEW_BUSY BIT(0) +#define SLEW_DOWNEN BIT(1) +#define SLEW_UPEN BIT(2) +#define SLEW_DIV 0x07 +#define SLEW_DIV_SHIFT 8 +#define SLEW_SYSDIV 0x0f +#define SLEW_SYSDIV_SHIFT 20 + +/* Clock Poll Timeout */ +#define LOCK_TIMEOUT_US USEC_PER_MSEC + +/* SoC specific clock needed during SPLL clock rate switch */ +static struct clk_hw *pic32_sclk_hw; + +/* add instruction pipeline delay while CPU clock is in-transition. */ +#define cpu_nop5() \ +do { \ + __asm__ __volatile__("nop"); \ + __asm__ __volatile__("nop"); \ + __asm__ __volatile__("nop"); \ + __asm__ __volatile__("nop"); \ + __asm__ __volatile__("nop"); \ +} while (0) + +/* Perpheral bus clocks */ +struct pic32_periph_clk { + struct clk_hw hw; + void __iomem *ctrl_reg; + struct pic32_clk_common *core; +}; + +#define clkhw_to_pbclk(_hw) container_of(_hw, struct pic32_periph_clk, hw) + +static int pbclk_is_enabled(struct clk_hw *hw) +{ + struct pic32_periph_clk *pb = clkhw_to_pbclk(hw); + + return readl(pb->ctrl_reg) & PB_DIV_ENABLE; +} + +static int pbclk_enable(struct clk_hw *hw) +{ + struct pic32_periph_clk *pb = clkhw_to_pbclk(hw); + + writel(PB_DIV_ENABLE, PIC32_SET(pb->ctrl_reg)); + return 0; +} + +static void pbclk_disable(struct clk_hw *hw) +{ + struct pic32_periph_clk *pb = clkhw_to_pbclk(hw); + + writel(PB_DIV_ENABLE, PIC32_CLR(pb->ctrl_reg)); +} + +static unsigned long calc_best_divided_rate(unsigned long rate, + unsigned long parent_rate, + u32 divider_max, + u32 divider_min) +{ + unsigned long divided_rate, divided_rate_down, best_rate; + unsigned long div, div_up; + + /* eq. clk_rate = parent_rate / divider. + * + * Find best divider to produce closest of target divided rate. + */ + div = parent_rate / rate; + div = clamp_val(div, divider_min, divider_max); + div_up = clamp_val(div + 1, divider_min, divider_max); + + divided_rate = parent_rate / div; + divided_rate_down = parent_rate / div_up; + if (abs(rate - divided_rate_down) < abs(rate - divided_rate)) + best_rate = divided_rate_down; + else + best_rate = divided_rate; + + return best_rate; +} + +static inline u32 pbclk_read_pbdiv(struct pic32_periph_clk *pb) +{ + return ((readl(pb->ctrl_reg) >> PB_DIV_SHIFT) & PB_DIV_MASK) + 1; +} + +static unsigned long pbclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pic32_periph_clk *pb = clkhw_to_pbclk(hw); + + return parent_rate / pbclk_read_pbdiv(pb); +} + +static long pbclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return calc_best_divided_rate(rate, *parent_rate, + PB_DIV_MAX, PB_DIV_MIN); +} + +static int pbclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pic32_periph_clk *pb = clkhw_to_pbclk(hw); + unsigned long flags; + u32 v, div; + int err; + + /* check & wait for DIV_READY */ + err = readl_poll_timeout(pb->ctrl_reg, v, v & PB_DIV_READY, + 1, LOCK_TIMEOUT_US); + if (err) + return err; + + /* calculate clkdiv and best rate */ + div = DIV_ROUND_CLOSEST(parent_rate, rate); + + spin_lock_irqsave(&pb->core->reg_lock, flags); + + /* apply new div */ + v = readl(pb->ctrl_reg); + v &= ~PB_DIV_MASK; + v |= (div - 1); + + pic32_syskey_unlock(); + + writel(v, pb->ctrl_reg); + + spin_unlock_irqrestore(&pb->core->reg_lock, flags); + + /* wait again, for pbdivready */ + err = readl_poll_timeout_atomic(pb->ctrl_reg, v, v & PB_DIV_READY, + 1, LOCK_TIMEOUT_US); + if (err) + return err; + + /* confirm that new div is applied correctly */ + return (pbclk_read_pbdiv(pb) == div) ? 0 : -EBUSY; +} + +const struct clk_ops pic32_pbclk_ops = { + .enable = pbclk_enable, + .disable = pbclk_disable, + .is_enabled = pbclk_is_enabled, + .recalc_rate = pbclk_recalc_rate, + .round_rate = pbclk_round_rate, + .set_rate = pbclk_set_rate, +}; + +struct clk *pic32_periph_clk_register(const struct pic32_periph_clk_data *desc, + struct pic32_clk_common *core) +{ + struct pic32_periph_clk *pbclk; + struct clk *clk; + + pbclk = devm_kzalloc(core->dev, sizeof(*pbclk), GFP_KERNEL); + if (!pbclk) + return ERR_PTR(-ENOMEM); + + pbclk->hw.init = &desc->init_data; + pbclk->core = core; + pbclk->ctrl_reg = desc->ctrl_reg + core->iobase; + + clk = devm_clk_register(core->dev, &pbclk->hw); + if (IS_ERR(clk)) { + dev_err(core->dev, "%s: clk_register() failed\n", __func__); + devm_kfree(core->dev, pbclk); + } + + return clk; +} + +/* Reference oscillator operations */ +struct pic32_ref_osc { + struct clk_hw hw; + void __iomem *ctrl_reg; + const u32 *parent_map; + struct pic32_clk_common *core; +}; + +#define clkhw_to_refosc(_hw) container_of(_hw, struct pic32_ref_osc, hw) + +static int roclk_is_enabled(struct clk_hw *hw) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + + return readl(refo->ctrl_reg) & REFO_ON; +} + +static int roclk_enable(struct clk_hw *hw) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + + writel(REFO_ON | REFO_OE, PIC32_SET(refo->ctrl_reg)); + return 0; +} + +static void roclk_disable(struct clk_hw *hw) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + + writel(REFO_ON | REFO_OE, PIC32_CLR(refo->ctrl_reg)); +} + +static void roclk_init(struct clk_hw *hw) +{ + /* initialize clock in disabled state */ + roclk_disable(hw); +} + +static u8 roclk_get_parent(struct clk_hw *hw) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + u32 v, i; + + v = (readl(refo->ctrl_reg) >> REFO_SEL_SHIFT) & REFO_SEL_MASK; + + if (!refo->parent_map) + return v; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) + if (refo->parent_map[i] == v) + return i; + + return -EINVAL; +} + +static unsigned long roclk_calc_rate(unsigned long parent_rate, + u32 rodiv, u32 rotrim) +{ + u64 rate64; + + /* fout = fin / [2 * {div + (trim / 512)}] + * = fin * 512 / [1024 * div + 2 * trim] + * = fin * 256 / (512 * div + trim) + * = (fin << 8) / ((div << 9) + trim) + */ + if (rotrim) { + rodiv = (rodiv << 9) + rotrim; + rate64 = parent_rate; + rate64 <<= 8; + do_div(rate64, rodiv); + } else if (rodiv) { + rate64 = parent_rate / (rodiv << 1); + } else { + rate64 = parent_rate; + } + return rate64; +} + +static void roclk_calc_div_trim(unsigned long rate, + unsigned long parent_rate, + u32 *rodiv_p, u32 *rotrim_p) +{ + u32 div, rotrim, rodiv; + u64 frac; + + /* Find integer approximation of floating-point arithmetic. + * fout = fin / [2 * {rodiv + (rotrim / 512)}] ... (1) + * i.e. fout = fin / 2 * DIV + * whereas DIV = rodiv + (rotrim / 512) + * + * Since kernel does not perform floating-point arithmatic so + * (rotrim/512) will be zero. And DIV & rodiv will result same. + * + * ie. fout = (fin * 256) / [(512 * rodiv) + rotrim] ... from (1) + * ie. rotrim = ((fin * 256) / fout) - (512 * DIV) + */ + if (parent_rate <= rate) { + div = 0; + frac = 0; + rodiv = 0; + rotrim = 0; + } else { + div = parent_rate / (rate << 1); + frac = parent_rate; + frac <<= 8; + do_div(frac, rate); + frac -= (u64)(div << 9); + + rodiv = (div > REFO_DIV_MASK) ? REFO_DIV_MASK : div; + rotrim = (frac >= REFO_TRIM_MAX) ? REFO_TRIM_MAX : frac; + } + + if (rodiv_p) + *rodiv_p = rodiv; + + if (rotrim_p) + *rotrim_p = rotrim; +} + +static unsigned long roclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + u32 v, rodiv, rotrim; + + /* get rodiv */ + v = readl(refo->ctrl_reg); + rodiv = (v >> REFO_DIV_SHIFT) & REFO_DIV_MASK; + + /* get trim */ + v = readl(refo->ctrl_reg + REFO_TRIM_REG); + rotrim = (v >> REFO_TRIM_SHIFT) & REFO_TRIM_MASK; + + return roclk_calc_rate(parent_rate, rodiv, rotrim); +} + +static long roclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 rotrim, rodiv; + + /* calculate dividers for new rate */ + roclk_calc_div_trim(rate, *parent_rate, &rodiv, &rotrim); + + /* caclulate new rate (rounding) based on new rodiv & rotrim */ + return roclk_calc_rate(*parent_rate, rodiv, rotrim); +} + +static int roclk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *parent_clk, *best_parent_clk = NULL; + unsigned int i, delta, best_delta = -1; + unsigned long parent_rate, best_parent_rate = 0; + unsigned long best = 0, nearest_rate; + + /* find a parent which can generate nearest clkrate >= rate */ + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + /* get parent */ + parent_clk = clk_hw_get_parent_by_index(hw, i); + if (!parent_clk) + continue; + + /* skip if parent runs slower than target rate */ + parent_rate = clk_hw_get_rate(parent_clk); + if (req->rate > parent_rate) + continue; + + nearest_rate = roclk_round_rate(hw, req->rate, &parent_rate); + delta = abs(nearest_rate - req->rate); + if ((nearest_rate >= req->rate) && (delta < best_delta)) { + best_parent_clk = parent_clk; + best_parent_rate = parent_rate; + best = nearest_rate; + best_delta = delta; + + if (delta == 0) + break; + } + } + + /* if no match found, retain old rate */ + if (!best_parent_clk) { + pr_err("%s:%s, no parent found for rate %lu.\n", + __func__, clk_hw_get_name(hw), req->rate); + return clk_hw_get_rate(hw); + } + + pr_debug("%s,rate %lu, best_parent(%s, %lu), best %lu, delta %d\n", + clk_hw_get_name(hw), req->rate, + clk_hw_get_name(best_parent_clk), best_parent_rate, + best, best_delta); + + if (req->best_parent_rate) + req->best_parent_rate = best_parent_rate; + + if (req->best_parent_hw) + req->best_parent_hw = best_parent_clk; + + return best; +} + +static int roclk_set_parent(struct clk_hw *hw, u8 index) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + unsigned long flags; + u32 v; + int err; + + if (refo->parent_map) + index = refo->parent_map[index]; + + /* wait until ACTIVE bit is zero or timeout */ + err = readl_poll_timeout(refo->ctrl_reg, v, !(v & REFO_ACTIVE), + 1, LOCK_TIMEOUT_US); + if (err) { + pr_err("%s: poll failed, clk active\n", clk_hw_get_name(hw)); + return err; + } + + spin_lock_irqsave(&refo->core->reg_lock, flags); + + pic32_syskey_unlock(); + + /* calculate & apply new */ + v = readl(refo->ctrl_reg); + v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT); + v |= index << REFO_SEL_SHIFT; + + writel(v, refo->ctrl_reg); + + spin_unlock_irqrestore(&refo->core->reg_lock, flags); + + return 0; +} + +static int roclk_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 index) +{ + struct pic32_ref_osc *refo = clkhw_to_refosc(hw); + unsigned long flags; + u32 trim, rodiv, v; + int err; + + /* calculate new rodiv & rotrim for new rate */ + roclk_calc_div_trim(rate, parent_rate, &rodiv, &trim); + + pr_debug("parent_rate = %lu, rate = %lu, div = %d, trim = %d\n", + parent_rate, rate, rodiv, trim); + + /* wait till source change is active */ + err = readl_poll_timeout(refo->ctrl_reg, v, + !(v & (REFO_ACTIVE | REFO_DIVSW_EN)), + 1, LOCK_TIMEOUT_US); + if (err) { + pr_err("%s: poll timedout, clock is still active\n", __func__); + return err; + } + + spin_lock_irqsave(&refo->core->reg_lock, flags); + v = readl(refo->ctrl_reg); + + pic32_syskey_unlock(); + + /* apply parent, if required */ + if (refo->parent_map) + index = refo->parent_map[index]; + + v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT); + v |= index << REFO_SEL_SHIFT; + + /* apply RODIV */ + v &= ~(REFO_DIV_MASK << REFO_DIV_SHIFT); + v |= rodiv << REFO_DIV_SHIFT; + writel(v, refo->ctrl_reg); + + /* apply ROTRIM */ + v = readl(refo->ctrl_reg + REFO_TRIM_REG); + v &= ~(REFO_TRIM_MASK << REFO_TRIM_SHIFT); + v |= trim << REFO_TRIM_SHIFT; + writel(v, refo->ctrl_reg + REFO_TRIM_REG); + + /* enable & activate divider switching */ + writel(REFO_ON | REFO_DIVSW_EN, PIC32_SET(refo->ctrl_reg)); + + /* wait till divswen is in-progress */ + err = readl_poll_timeout_atomic(refo->ctrl_reg, v, !(v & REFO_DIVSW_EN), + 1, LOCK_TIMEOUT_US); + /* leave the clk gated as it was */ + writel(REFO_ON, PIC32_CLR(refo->ctrl_reg)); + + spin_unlock_irqrestore(&refo->core->reg_lock, flags); + + return err; +} + +static int roclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + u8 index = roclk_get_parent(hw); + + return roclk_set_rate_and_parent(hw, rate, parent_rate, index); +} + +const struct clk_ops pic32_roclk_ops = { + .enable = roclk_enable, + .disable = roclk_disable, + .is_enabled = roclk_is_enabled, + .get_parent = roclk_get_parent, + .set_parent = roclk_set_parent, + .determine_rate = roclk_determine_rate, + .recalc_rate = roclk_recalc_rate, + .set_rate_and_parent = roclk_set_rate_and_parent, + .set_rate = roclk_set_rate, + .init = roclk_init, +}; + +struct clk *pic32_refo_clk_register(const struct pic32_ref_osc_data *data, + struct pic32_clk_common *core) +{ + struct pic32_ref_osc *refo; + struct clk *clk; + + refo = devm_kzalloc(core->dev, sizeof(*refo), GFP_KERNEL); + if (!refo) + return ERR_PTR(-ENOMEM); + + refo->core = core; + refo->hw.init = &data->init_data; + refo->ctrl_reg = data->ctrl_reg + core->iobase; + refo->parent_map = data->parent_map; + + clk = devm_clk_register(core->dev, &refo->hw); + if (IS_ERR(clk)) + dev_err(core->dev, "%s: clk_register() failed\n", __func__); + + return clk; +} + +struct pic32_sys_pll { + struct clk_hw hw; + void __iomem *ctrl_reg; + void __iomem *status_reg; + u32 lock_mask; + u32 idiv; /* PLL iclk divider, treated fixed */ + struct pic32_clk_common *core; +}; + +#define clkhw_to_spll(_hw) container_of(_hw, struct pic32_sys_pll, hw) + +static inline u32 spll_odiv_to_divider(u32 odiv) +{ + odiv = clamp_val(odiv, PLL_ODIV_MIN, PLL_ODIV_MAX); + + return 1 << odiv; +} + +static unsigned long spll_calc_mult_div(struct pic32_sys_pll *pll, + unsigned long rate, + unsigned long parent_rate, + u32 *mult_p, u32 *odiv_p) +{ + u32 mul, div, best_mul = 1, best_div = 1; + unsigned long new_rate, best_rate = rate; + unsigned int best_delta = -1, delta, match_found = 0; + u64 rate64; + + parent_rate /= pll->idiv; + + for (mul = 1; mul <= PLL_MULT_MAX; mul++) { + for (div = PLL_ODIV_MIN; div <= PLL_ODIV_MAX; div++) { + rate64 = parent_rate; + rate64 *= mul; + do_div(rate64, 1 << div); + new_rate = rate64; + delta = abs(rate - new_rate); + if ((new_rate >= rate) && (delta < best_delta)) { + best_delta = delta; + best_rate = new_rate; + best_mul = mul; + best_div = div; + match_found = 1; + } + } + } + + if (!match_found) { + pr_warn("spll: no match found\n"); + return 0; + } + + pr_debug("rate %lu, par_rate %lu/mult %u, div %u, best_rate %lu\n", + rate, parent_rate, best_mul, best_div, best_rate); + + if (mult_p) + *mult_p = best_mul - 1; + + if (odiv_p) + *odiv_p = best_div; + + return best_rate; +} + +static unsigned long spll_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pic32_sys_pll *pll = clkhw_to_spll(hw); + unsigned long pll_in_rate; + u32 mult, odiv, div, v; + u64 rate64; + + v = readl(pll->ctrl_reg); + odiv = ((v >> PLL_ODIV_SHIFT) & PLL_ODIV_MASK); + mult = ((v >> PLL_MULT_SHIFT) & PLL_MULT_MASK) + 1; + div = spll_odiv_to_divider(odiv); + + /* pll_in_rate = parent_rate / idiv + * pll_out_rate = pll_in_rate * mult / div; + */ + pll_in_rate = parent_rate / pll->idiv; + rate64 = pll_in_rate; + rate64 *= mult; + do_div(rate64, div); + + return rate64; +} + +static long spll_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct pic32_sys_pll *pll = clkhw_to_spll(hw); + + return spll_calc_mult_div(pll, rate, *parent_rate, NULL, NULL); +} + +static int spll_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pic32_sys_pll *pll = clkhw_to_spll(hw); + unsigned long ret, flags; + u32 mult, odiv, v; + int err; + |