mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 09:19:51 +00:00
![Harvey Hunt](/assets/img/avatar_default.png)
The JZ4780's MSC clock divider registers multiply the clock divider by 2. This means that MMC devices run at half their expected speed. Add the ability to divide the clock divider in order to solve this. Signed-off-by: Harvey Hunt <harvey.hunt@imgtec.com> Cc: Paul Burton <paul.burton@imgtec.com> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: linux-clk@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
303 lines
7.5 KiB
C
303 lines
7.5 KiB
C
/*
|
|
* Ingenic JZ4740 SoC CGU driver
|
|
*
|
|
* Copyright (c) 2015 Imagination Technologies
|
|
* Author: Paul Burton <paul.burton@imgtec.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that 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/of.h>
|
|
#include <dt-bindings/clock/jz4740-cgu.h>
|
|
#include <asm/mach-jz4740/clock.h>
|
|
#include "cgu.h"
|
|
|
|
/* CGU register offsets */
|
|
#define CGU_REG_CPCCR 0x00
|
|
#define CGU_REG_LCR 0x04
|
|
#define CGU_REG_CPPCR 0x10
|
|
#define CGU_REG_CLKGR 0x20
|
|
#define CGU_REG_SCR 0x24
|
|
#define CGU_REG_I2SCDR 0x60
|
|
#define CGU_REG_LPCDR 0x64
|
|
#define CGU_REG_MSCCDR 0x68
|
|
#define CGU_REG_UHCCDR 0x6c
|
|
#define CGU_REG_SSICDR 0x74
|
|
|
|
/* bits within a PLL control register */
|
|
#define PLLCTL_M_SHIFT 23
|
|
#define PLLCTL_M_MASK (0x1ff << PLLCTL_M_SHIFT)
|
|
#define PLLCTL_N_SHIFT 18
|
|
#define PLLCTL_N_MASK (0x1f << PLLCTL_N_SHIFT)
|
|
#define PLLCTL_OD_SHIFT 16
|
|
#define PLLCTL_OD_MASK (0x3 << PLLCTL_OD_SHIFT)
|
|
#define PLLCTL_STABLE (1 << 10)
|
|
#define PLLCTL_BYPASS (1 << 9)
|
|
#define PLLCTL_ENABLE (1 << 8)
|
|
|
|
/* bits within the LCR register */
|
|
#define LCR_SLEEP (1 << 0)
|
|
|
|
/* bits within the CLKGR register */
|
|
#define CLKGR_UDC (1 << 11)
|
|
|
|
static struct ingenic_cgu *cgu;
|
|
|
|
static const s8 pll_od_encoding[4] = {
|
|
0x0, 0x1, -1, 0x3,
|
|
};
|
|
|
|
static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = {
|
|
|
|
/* External clocks */
|
|
|
|
[JZ4740_CLK_EXT] = { "ext", CGU_CLK_EXT },
|
|
[JZ4740_CLK_RTC] = { "rtc", CGU_CLK_EXT },
|
|
|
|
[JZ4740_CLK_PLL] = {
|
|
"pll", CGU_CLK_PLL,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.pll = {
|
|
.reg = CGU_REG_CPPCR,
|
|
.m_shift = 23,
|
|
.m_bits = 9,
|
|
.m_offset = 2,
|
|
.n_shift = 18,
|
|
.n_bits = 5,
|
|
.n_offset = 2,
|
|
.od_shift = 16,
|
|
.od_bits = 2,
|
|
.od_max = 4,
|
|
.od_encoding = pll_od_encoding,
|
|
.stable_bit = 10,
|
|
.bypass_bit = 9,
|
|
.enable_bit = 8,
|
|
},
|
|
},
|
|
|
|
/* Muxes & dividers */
|
|
|
|
[JZ4740_CLK_PLL_HALF] = {
|
|
"pll half", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 21, 1, 1, -1, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_CCLK] = {
|
|
"cclk", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_HCLK] = {
|
|
"hclk", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_PCLK] = {
|
|
"pclk", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_MCLK] = {
|
|
"mclk", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_LCD] = {
|
|
"lcd", CGU_CLK_DIV | CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
|
|
.div = { CGU_REG_CPCCR, 16, 1, 5, 22, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 10 },
|
|
},
|
|
|
|
[JZ4740_CLK_LCD_PCLK] = {
|
|
"lcd_pclk", CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
|
|
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
|
|
},
|
|
|
|
[JZ4740_CLK_I2S] = {
|
|
"i2s", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
|
|
.mux = { CGU_REG_CPCCR, 31, 1 },
|
|
.div = { CGU_REG_I2SCDR, 0, 1, 8, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 6 },
|
|
},
|
|
|
|
[JZ4740_CLK_SPI] = {
|
|
"spi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL, -1, -1 },
|
|
.mux = { CGU_REG_SSICDR, 31, 1 },
|
|
.div = { CGU_REG_SSICDR, 0, 1, 4, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 4 },
|
|
},
|
|
|
|
[JZ4740_CLK_MMC] = {
|
|
"mmc", CGU_CLK_DIV | CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
|
|
.div = { CGU_REG_MSCCDR, 0, 1, 5, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 7 },
|
|
},
|
|
|
|
[JZ4740_CLK_UHC] = {
|
|
"uhc", CGU_CLK_DIV | CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
|
|
.div = { CGU_REG_UHCCDR, 0, 1, 4, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 14 },
|
|
},
|
|
|
|
[JZ4740_CLK_UDC] = {
|
|
"udc", CGU_CLK_MUX | CGU_CLK_DIV,
|
|
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
|
|
.mux = { CGU_REG_CPCCR, 29, 1 },
|
|
.div = { CGU_REG_CPCCR, 23, 1, 6, -1, -1, -1 },
|
|
.gate = { CGU_REG_SCR, 6 },
|
|
},
|
|
|
|
/* Gate-only clocks */
|
|
|
|
[JZ4740_CLK_UART0] = {
|
|
"uart0", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 0 },
|
|
},
|
|
|
|
[JZ4740_CLK_UART1] = {
|
|
"uart1", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 15 },
|
|
},
|
|
|
|
[JZ4740_CLK_DMA] = {
|
|
"dma", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 12 },
|
|
},
|
|
|
|
[JZ4740_CLK_IPU] = {
|
|
"ipu", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 13 },
|
|
},
|
|
|
|
[JZ4740_CLK_ADC] = {
|
|
"adc", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 8 },
|
|
},
|
|
|
|
[JZ4740_CLK_I2C] = {
|
|
"i2c", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 3 },
|
|
},
|
|
|
|
[JZ4740_CLK_AIC] = {
|
|
"aic", CGU_CLK_GATE,
|
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
|
.gate = { CGU_REG_CLKGR, 5 },
|
|
},
|
|
};
|
|
|
|
static void __init jz4740_cgu_init(struct device_node *np)
|
|
{
|
|
int retval;
|
|
|
|
cgu = ingenic_cgu_new(jz4740_cgu_clocks,
|
|
ARRAY_SIZE(jz4740_cgu_clocks), np);
|
|
if (!cgu) {
|
|
pr_err("%s: failed to initialise CGU\n", __func__);
|
|
return;
|
|
}
|
|
|
|
retval = ingenic_cgu_register_clocks(cgu);
|
|
if (retval)
|
|
pr_err("%s: failed to register CGU Clocks\n", __func__);
|
|
}
|
|
CLK_OF_DECLARE(jz4740_cgu, "ingenic,jz4740-cgu", jz4740_cgu_init);
|
|
|
|
void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode)
|
|
{
|
|
uint32_t lcr = readl(cgu->base + CGU_REG_LCR);
|
|
|
|
switch (mode) {
|
|
case JZ4740_WAIT_MODE_IDLE:
|
|
lcr &= ~LCR_SLEEP;
|
|
break;
|
|
|
|
case JZ4740_WAIT_MODE_SLEEP:
|
|
lcr |= LCR_SLEEP;
|
|
break;
|
|
}
|
|
|
|
writel(lcr, cgu->base + CGU_REG_LCR);
|
|
}
|
|
|
|
void jz4740_clock_udc_disable_auto_suspend(void)
|
|
{
|
|
uint32_t clkgr = readl(cgu->base + CGU_REG_CLKGR);
|
|
|
|
clkgr &= ~CLKGR_UDC;
|
|
writel(clkgr, cgu->base + CGU_REG_CLKGR);
|
|
}
|
|
EXPORT_SYMBOL_GPL(jz4740_clock_udc_disable_auto_suspend);
|
|
|
|
void jz4740_clock_udc_enable_auto_suspend(void)
|
|
{
|
|
uint32_t clkgr = readl(cgu->base + CGU_REG_CLKGR);
|
|
|
|
clkgr |= CLKGR_UDC;
|
|
writel(clkgr, cgu->base + CGU_REG_CLKGR);
|
|
}
|
|
EXPORT_SYMBOL_GPL(jz4740_clock_udc_enable_auto_suspend);
|
|
|
|
#define JZ_CLOCK_GATE_UART0 BIT(0)
|
|
#define JZ_CLOCK_GATE_TCU BIT(1)
|
|
#define JZ_CLOCK_GATE_DMAC BIT(12)
|
|
|
|
void jz4740_clock_suspend(void)
|
|
{
|
|
uint32_t clkgr, cppcr;
|
|
|
|
clkgr = readl(cgu->base + CGU_REG_CLKGR);
|
|
clkgr |= JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0;
|
|
writel(clkgr, cgu->base + CGU_REG_CLKGR);
|
|
|
|
cppcr = readl(cgu->base + CGU_REG_CPPCR);
|
|
cppcr &= ~BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.enable_bit);
|
|
writel(cppcr, cgu->base + CGU_REG_CPPCR);
|
|
}
|
|
|
|
void jz4740_clock_resume(void)
|
|
{
|
|
uint32_t clkgr, cppcr, stable;
|
|
|
|
cppcr = readl(cgu->base + CGU_REG_CPPCR);
|
|
cppcr |= BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.enable_bit);
|
|
writel(cppcr, cgu->base + CGU_REG_CPPCR);
|
|
|
|
stable = BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.stable_bit);
|
|
do {
|
|
cppcr = readl(cgu->base + CGU_REG_CPPCR);
|
|
} while (!(cppcr & stable));
|
|
|
|
clkgr = readl(cgu->base + CGU_REG_CLKGR);
|
|
clkgr &= ~JZ_CLOCK_GATE_TCU;
|
|
clkgr &= ~JZ_CLOCK_GATE_DMAC;
|
|
clkgr &= ~JZ_CLOCK_GATE_UART0;
|
|
writel(clkgr, cgu->base + CGU_REG_CLKGR);
|
|
}
|