// SPDX-License-Identifier: GPL-2.0-only
/*
* Purna Chandra Mandal,<purna.mandal@microchip.com>
* Copyright (C) 2015 Microchip Technology Inc. All rights reserved.
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.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