From patchwork Thu Dec 17 15:26:42 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Purna Chandra Mandal X-Patchwork-Id: 558388 X-Patchwork-Delegate: daniel.schwierzeck@googlemail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 89410140134 for ; Fri, 18 Dec 2015 02:37:24 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 677124BE2C; Thu, 17 Dec 2015 16:37:18 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id BDLuPS3yxvod; Thu, 17 Dec 2015 16:37:18 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 1CD6239A4; Thu, 17 Dec 2015 16:37:07 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 0D46E4BE15 for ; Thu, 17 Dec 2015 16:31:55 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id MODgiMG5l-od for ; Thu, 17 Dec 2015 16:31:54 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from email.microchip.com (exsmtp03.microchip.com [198.175.253.49]) by theia.denx.de (Postfix) with ESMTPS id 297334BE14 for ; Thu, 17 Dec 2015 16:31:51 +0100 (CET) Received: from [10.41.20.11] (10.10.76.4) by chn-sv-exch03.mchp-main.com (10.10.76.49) with Microsoft SMTP Server id 14.3.181.6; Thu, 17 Dec 2015 08:28:30 -0700 To: From: Purna Chandra Mandal Message-ID: <5672D432.5070605@microchip.com> Date: Thu, 17 Dec 2015 20:56:42 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.3.0 MIME-Version: 1.0 X-Mailman-Approved-At: Thu, 17 Dec 2015 16:37:01 +0100 Subject: [U-Boot] [PATCH v1 01/14] drivers: clk: Add clock driver for Microchip PIC32 microcontroller X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" This driver implements basic clock modules available in PIC32MZ[DA] platforms. Signed-off-by: Purna Chandra Mandal --- drivers/clk/Makefile | 1 + drivers/clk/clk-pic32.c | 413 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 drivers/clk/clk-pic32.c diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 4a6a4a8..3c84e08 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_CLK) += clk-uclass.o obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o +obj-$(CONFIG_MACH_PIC32) += clk-pic32.o diff --git a/drivers/clk/clk-pic32.c b/drivers/clk/clk-pic32.c new file mode 100644 index 0000000..4ebeee1 --- /dev/null +++ b/drivers/clk/clk-pic32.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2015 Purna Chandra Mandal + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* Primary oscillator */ +#define SYS_POSC_CLK_HZ 24000000 + +/* Fixed clk rate */ +#define SYS_FRC_CLK_HZ 8000000 + +/* PLL */ +#define ICLK_MASK 0x00000080 +#define PLLIDIV_MASK 0x00000007 +#define PLLODIV_MASK 0x00000007 +#define CUROSC_MASK 0x00000007 +#define PLLMUL_MASK 0x0000007F +#define FRCDIV_MASK 0x00000007 + +/* PBCLK */ +#define PBDIV_MASK 0x00000007 + +/* SYSCLK MUX */ +#define SCLK_SRC_FRC1 0 +#define SCLK_SRC_SPLL 1 +#define SCLK_SRC_POSC 2 +#define SCLK_SRC_FRC2 7 + +/* Reference Oscillator Control Reg fields */ +#define REFO_SEL_MASK 0x0f +#define REFO_SEL_SHIFT 0 +#define REFO_ACTIVE 0x0100 +#define REFO_DIVSW_EN 0x0200 +#define REFO_OE 0x1000 +#define REFO_ON 0x8000 +#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 + +#define ROCLK_SRC_SCLK 0x0 +#define ROCLK_SRC_SPLL 0x7 +#define ROCLK_SRC_ROCLKI 0x8 + +/* Memory PLL */ +#define MPLL_IDIV 0x03 +#define MPLL_MULT 0x32 +#define MPLL_ODIV1 0x02 +#define MPLL_ODIV2 0x01 +#define MPLL_VREG_RDY 0x00800000 +#define MPLL_RDY 0x80000000 +#define MPLL_IDIV_SHIFT 0 +#define MPLL_MULT_SHIFT 8 +#define MPLL_ODIV1_SHIFT 24 +#define MPLL_ODIV2_SHIFT 27 + +static ulong pic32_get_pll_rate(void) +{ + ulong plliclk, v; + u32 iclk, idiv, odiv, mult; + + v = readl(SPLLCON); + iclk = (v & ICLK_MASK); + idiv = ((v >> 8) & PLLIDIV_MASK) + 1; + odiv = ((v >> 24) & PLLODIV_MASK); + mult = ((v >> 16) & PLLMUL_MASK) + 1; + + plliclk = iclk ? SYS_FRC_CLK_HZ : SYS_POSC_CLK_HZ; + + if (odiv < 2) + odiv = 2; + else if (odiv < 5) + odiv = (1 << odiv); + else + odiv = 32; + + return ((plliclk / idiv) * mult) / odiv; +} + +static ulong pic32_get_sysclk(void) +{ + ulong hz; + ulong div, frcdiv; + ulong v = readl(OSCCON); + ulong curr_osc; + + /* get clk source */ + v = readl(OSCCON); + curr_osc = (v >> 12) & CUROSC_MASK; + switch (curr_osc) { + case SCLK_SRC_FRC1: + case SCLK_SRC_FRC2: + frcdiv = ((v >> 24) & FRCDIV_MASK); + div = ((1 << frcdiv) + 1) + (128 * (frcdiv == 7)); + hz = SYS_FRC_CLK_HZ / div; + break; + + case SCLK_SRC_SPLL: + hz = pic32_get_pll_rate(); + break; + + case SCLK_SRC_POSC: + hz = SYS_POSC_CLK_HZ; + break; + + default: + hz = 0; + printf("clk: unknown sclk_src.\n"); + break; + } + + return hz; +} + +static ulong pic32_get_pbclk(int bus) +{ + ulong div, clk_freq; + void __iomem *reg; + + clk_freq = pic32_get_sysclk(); + + reg = (void __iomem *)PB1DIV + (bus * 0x10); + div = (readl(reg) & PBDIV_MASK) + 1; + + return clk_freq / div; +} + +static ulong pic32_get_cpuclk(void) +{ + return pic32_get_pbclk(6); +} + +static ulong pic32_set_refclk(int bus, int parent_rate, int rate, int parent_id) +{ + void __iomem *reg; + u32 div, trim, v; + u64 frac; + ulong base; + + /* calculate dividers, + * rate = parent_rate / [2 * (div + (trim / 512))] + */ + if (parent_rate <= rate) { + div = 0; + trim = 0; + } else { + div = parent_rate / (rate << 1); + frac = parent_rate; + frac <<= 8; + do_div(frac, rate); + frac -= (u64)(div << 9); + trim = (frac >= REFO_TRIM_MAX) ? REFO_TRIM_MAX : (u32)frac; + } + + reg = (void __iomem *)(REFO1CON + bus * 0x20); + + /* disable clk */ + writel(REFO_ON | REFO_OE, reg + _CLR_OFFSET); + + /* wait till previous src change is active */ + base = get_timer(0); + for (;;) { + v = readl(reg); + if ((v & (REFO_DIVSW_EN | REFO_ACTIVE)) == 0) + break; + if (get_timer(base) >= get_tbclk()) { + printf("refclk: tmout while active\n"); + break; + } + } + + /* parent_id */ + v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT); + v |= (parent_id << REFO_SEL_SHIFT); + + /* apply rodiv */ + v &= ~(REFO_DIV_MASK << REFO_DIV_SHIFT); + v |= (div << REFO_DIV_SHIFT); + writel(v, reg); + + /* apply trim */ + v = readl(reg + REFO_TRIM_REG); + v &= ~(REFO_TRIM_MASK << REFO_TRIM_SHIFT); + v |= (trim << REFO_TRIM_SHIFT); + writel(v, reg + REFO_TRIM_REG); + + /* enable clk */ + writel(REFO_ON | REFO_OE, reg + _SET_OFFSET); + + /* switch divider */ + writel(REFO_DIVSW_EN, reg + _SET_OFFSET); + + base = get_timer(0); + for (;;) { + if (!(readl(reg) & REFO_DIVSW_EN)) + break; + + if (get_timer(base) >= get_tbclk()) { + printf("refclk: tmout while switching.\n"); + break; + } + } + + return 0; +} + +static ulong pic32_get_refclk(int bus) +{ + void __iomem *reg; + u32 rodiv, rotrim, rosel, v, parent_rate; + u64 rate64; + + reg = (void __iomem *)(REFO1CON + bus * 0x20); + v = readl(reg); + + /* get rosel */ + rosel = (v >> REFO_SEL_SHIFT) & REFO_SEL_MASK; + /* get div */ + rodiv = (v >> REFO_DIV_SHIFT) & REFO_DIV_MASK; + /* get trim */ + v = readl(reg + REFO_TRIM_REG); + rotrim = (v >> REFO_TRIM_SHIFT) & REFO_TRIM_MASK; + + if (!rodiv) + return 0; + + /* calc rate */ + switch (rosel) { + default: + case ROCLK_SRC_SCLK: + parent_rate = pic32_get_cpuclk(); + break; + case ROCLK_SRC_SPLL: + parent_rate = pic32_get_pll_rate(); + break; + } + + /* Calculation + * rate = parent_rate / [2 * (div + (trim / 512))] + */ + if (rotrim) { + rodiv <<= 9; + rodiv += rotrim; + rate64 = parent_rate; + rate64 <<= 8; + do_div(rate64, rodiv); + v = (u32)rate64; + } else { + v = parent_rate / (rodiv << 1); + } + return v; +} + +static void mpll_init(void) +{ + u32 v, mask; + ulong base; + + /* initialize */ + v = (MPLL_IDIV << MPLL_IDIV_SHIFT) | + (MPLL_MULT << MPLL_MULT_SHIFT) | + (MPLL_ODIV1 << MPLL_ODIV1_SHIFT) | + (MPLL_ODIV2 << MPLL_ODIV2_SHIFT); + + writel(v, (void __iomem *)CFGMPLL); + + /* Wait for ready */ + mask = MPLL_RDY | MPLL_VREG_RDY; + base = get_timer(0); + for (;;) { + v = readl((void __iomem *)CFGMPLL); + if ((v & mask) == mask) + break; + + if (get_timer(base) >= get_tbclk()) { + printf("refclk: tmout while waiting MPLL ready.\n"); + break; + } + } +} + +static void pic32_clk_init(struct udevice *dev) +{ + ulong rate, pll_hz = pic32_get_pll_rate(); + const void *blob = gd->fdt_blob; + char propname[] = "microchip,refo%d-frequency12345"; + int i; + + /* Initialize REFOs as not initialized and enabled on reset. */ + for (i = 0; i < 5; i++) { + snprintf(propname, sizeof(propname), + "microchip,refo%d-frequency", i + 1); + + rate = fdtdec_get_int(blob, dev->of_offset, propname, 0); + if (rate) + pic32_set_refclk(i, pll_hz, rate, ROCLK_SRC_SPLL); + } + + /* Memory PLL */ + mpll_init(); +} + +struct pic32_clk_platdata { + u32 clk_id; +}; + +static ulong pic32_clk_get_rate(struct udevice *dev) +{ + return pic32_get_cpuclk(); +} + +static ulong pic32_get_periph_rate(struct udevice *dev, int periph) +{ + ulong rate; + + switch (periph) { + case PB1CLK ... PB7CLK: + rate = pic32_get_pbclk(periph - PB1CLK); + break; + case REF1CLK ... REF5CLK: + rate = pic32_get_refclk(periph - REF1CLK); + break; + case PLLCLK: + rate = pic32_get_pll_rate(); + break; + default: + rate = 0; + break; + } + + return rate; +} + +static ulong pic32_set_periph_rate(struct udevice *dev, int periph, ulong rate) +{ + ulong pll_hz; + + switch (periph) { + case REF1CLK ... REF5CLK: + pll_hz = pic32_get_pll_rate(); + pic32_set_refclk(periph - REF1CLK, + pll_hz, rate, ROCLK_SRC_SPLL); + break; + default: + break; + } + + return rate; +} + +static struct clk_ops pic32_pic32_clk_ops = { + .get_rate = pic32_clk_get_rate, + .set_periph_rate = pic32_set_periph_rate, + .get_periph_rate = pic32_get_periph_rate, +}; + +static int pic32_clk_probe(struct udevice *dev) +{ + struct pic32_clk_platdata *plat = dev_get_platdata(dev); + + if (plat->clk_id != BASECLK) + goto out; + + /* initialize only once */ + plat->clk_id |= 0xd0ae0000; + pic32_clk_init(dev); +out: + return 0; +} + +static int pic32_clk_bind(struct udevice *dev) +{ + struct pic32_clk_platdata *plat = dev_get_platdata(dev); + + if (dev->of_offset == -1) { + plat->clk_id = BASECLK; + return 0; + } + + return 0; +} + +static const struct udevice_id pic32_clk_ids[] = { + { .compatible = "microchip,pic32mzda_clk"}, + {} +}; + +U_BOOT_DRIVER(pic32_clk) = { + .name = "pic32_clk", + .id = UCLASS_CLK, + .of_match = pic32_clk_ids, + .platdata_auto_alloc_size = sizeof(struct pic32_clk_platdata), + .ops = &pic32_pic32_clk_ops, + .bind = pic32_clk_bind, + .probe = pic32_clk_probe, +};