summaryrefslogtreecommitdiff
path: root/drivers/clk/imx/clk-imx6sx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/imx/clk-imx6sx.c')
-rw-r--r--drivers/clk/imx/clk-imx6sx.c174
1 files changed, 160 insertions, 14 deletions
diff --git a/drivers/clk/imx/clk-imx6sx.c b/drivers/clk/imx/clk-imx6sx.c
index c4685c01929a..274c31cf61a2 100644
--- a/drivers/clk/imx/clk-imx6sx.c
+++ b/drivers/clk/imx/clk-imx6sx.c
@@ -10,13 +10,18 @@
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
+#include <linux/imx_sema4.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/types.h>
+#include <soc/imx/gpc.h>
+#include <soc/imx/src.h>
#include "clk.h"
+#define CCM_CCGR_OFFSET(index) (index * 2)
+
static const char *step_sels[] = { "osc", "pll2_pfd2_396m", };
static const char *pll1_sw_sels[] = { "pll1_sys", "step", };
static const char *periph_pre_sels[] = { "pll2_bus", "pll2_pfd2_396m", "pll2_pfd0_352m", "pll2_198m", };
@@ -83,6 +88,12 @@ static const char *pll7_bypass_sels[] = { "pll7", "pll7_bypass_src", };
static struct clk_hw **hws;
static struct clk_hw_onecell_data *clk_hw_data;
+struct imx_sema4_mutex *amp_power_mutex;
+
+static int clks_shared[MAX_SHARED_CLK_NUMBER];
+
+struct imx_shared_mem *shared_mem;
+static unsigned int shared_mem_paddr, shared_mem_size;
static const struct clk_div_table clk_enet_ref_table[] = {
{ .val = 0, .div = 20, },
@@ -116,18 +127,43 @@ static u32 share_count_ssi3;
static u32 share_count_sai1;
static u32 share_count_sai2;
-static const int uart_clk_ids[] __initconst = {
- IMX6SX_CLK_UART_IPG,
- IMX6SX_CLK_UART_SERIAL,
-};
+/*
+ * As IMX6SX_CLK_M4_PRE_SEL is NOT a glitchless MUX, so when
+ * M4 is trying to change its clk parent, need to ask A9 to
+ * help do it, and M4 must be hold in wfi. To avoid glitch
+ * occur, need to gate M4 clk first before switching its parent.
+ */
+void imx6sx_set_m4_highfreq(bool high_freq)
+{
+ static struct clk *m4_high_freq_sel;
+
+ imx_gpc_hold_m4_in_sleep();
+
+ clk_disable_unprepare(hws[IMX6SX_CLK_M4]->clk);
+ clk_set_parent(hws[IMX6SX_CLK_M4_SEL]->clk,
+ hws[IMX6SX_CLK_LDB_DI0]->clk);
+
+ if (high_freq) {
+ /* FIXME: m4_high_freq_sel possible used without intialization? */
+ clk_set_parent(hws[IMX6SX_CLK_M4_PRE_SEL]->clk,
+ m4_high_freq_sel);
+ } else {
+ m4_high_freq_sel = clk_get_parent(hws[IMX6SX_CLK_M4_PRE_SEL]->clk);
+ clk_set_parent(hws[IMX6SX_CLK_M4_PRE_SEL]->clk,
+ hws[IMX6SX_CLK_OSC]->clk);
+ }
+
+ clk_set_parent(hws[IMX6SX_CLK_M4_SEL]->clk,
+ hws[IMX6SX_CLK_M4_PRE_SEL]->clk);
+ clk_prepare_enable(hws[IMX6SX_CLK_M4]->clk);
-static struct clk **uart_clks[ARRAY_SIZE(uart_clk_ids) + 1] __initdata;
+ imx_gpc_release_m4_in_sleep();
+}
static void __init imx6sx_clocks_init(struct device_node *ccm_node)
{
struct device_node *np;
void __iomem *base;
- int i;
clk_hw_data = kzalloc(struct_size(clk_hw_data, hws,
IMX6SX_CLK_CLK_END), GFP_KERNEL);
@@ -189,7 +225,7 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
clk_set_parent(hws[IMX6SX_PLL6_BYPASS]->clk, hws[IMX6SX_CLK_PLL6]->clk);
clk_set_parent(hws[IMX6SX_PLL7_BYPASS]->clk, hws[IMX6SX_CLK_PLL7]->clk);
- hws[IMX6SX_CLK_PLL1_SYS] = imx_clk_hw_gate("pll1_sys", "pll1_bypass", base + 0x00, 13);
+ hws[IMX6SX_CLK_PLL1_SYS] = imx_clk_hw_fixed_factor("pll1_sys", "pll1_bypass", 1, 1);
hws[IMX6SX_CLK_PLL2_BUS] = imx_clk_hw_gate("pll2_bus", "pll2_bypass", base + 0x30, 13);
hws[IMX6SX_CLK_PLL3_USB_OTG] = imx_clk_hw_gate("pll3_usb_otg", "pll3_bypass", base + 0x10, 13);
hws[IMX6SX_CLK_PLL4_AUDIO] = imx_clk_hw_gate("pll4_audio", "pll4_bypass", base + 0x70, 13);
@@ -405,7 +441,7 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
hws[IMX6SX_CLK_GPT_BUS] = imx_clk_hw_gate2("gpt_bus", "perclk", base + 0x6c, 20);
hws[IMX6SX_CLK_GPT_SERIAL] = imx_clk_hw_gate2("gpt_serial", "perclk", base + 0x6c, 22);
hws[IMX6SX_CLK_GPU] = imx_clk_hw_gate2("gpu", "gpu_core_podf", base + 0x6c, 26);
- hws[IMX6SX_CLK_OCRAM_S] = imx_clk_hw_gate2("ocram_s", "ahb", base + 0x6c, 28);
+ hws[IMX6SX_CLK_OCRAM_S] = imx_clk_hw_gate2_flags("ocram_s", "ahb", base + 0x6c, 28, CLK_IS_CRITICAL);
hws[IMX6SX_CLK_CANFD] = imx_clk_hw_gate2("canfd", "can_podf", base + 0x6c, 30);
/* CCGR2 */
@@ -489,13 +525,59 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
hws[IMX6SX_CLK_CKO1] = imx_clk_hw_gate("cko1", "cko1_podf", base + 0x60, 7);
hws[IMX6SX_CLK_CKO2] = imx_clk_hw_gate("cko2", "cko2_podf", base + 0x60, 24);
+ /* get those shared clk nodes if M4 is active */
+ if (imx_src_is_m4_enabled()) {
+ u32 num;
+
+ of_property_read_u32(np, "fsl,shared-clks-number", &num);
+ if (num > MAX_SHARED_CLK_NUMBER)
+ pr_err("clk: shared clk nodes exceed the max number!\n");
+ of_property_read_u32_array(np, "fsl,shared-clks-index",
+ clks_shared, num);
+ if (of_property_read_u32(np, "fsl,shared-mem-addr",
+ &shared_mem_paddr))
+ pr_err("clk: fsl,shared-mem-addr NOT found!\n");
+ if (of_property_read_u32(np, "fsl,shared-mem-size",
+ &shared_mem_size))
+ pr_err("clk: fsl,shared-mem-size NOT found!\n");
+ }
+
/* mask handshake of mmdc */
imx_mmdc_mask_handshake(base, 0);
imx_check_clk_hws(hws, IMX6SX_CLK_CLK_END);
+ /*
+ * QSPI2/GPMI_IO share the same clock source but with the
+ * different gate, need explicitely gate the QSPI2 & GPMI_IO
+ * during the clock init phase according to the SOC design.
+ */
+ if (!imx_src_is_m4_enabled()) {
+ writel_relaxed(readl_relaxed(base + 0x78) &
+ ~(3 << CCM_CCGR_OFFSET(5)), base + 0x78);
+ writel_relaxed(readl_relaxed(base + 0x78) &
+ ~(3 << CCM_CCGR_OFFSET(14)), base + 0x78);
+ }
+
+
of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_hw_data);
+ /*
+ * As some of the modules need to access ocotp in MSL,
+ * need to make sure ocotp clk(CCM_CCGR2_CG6) is enabled
+ * during MSL, as on i.MX6SX, accessing OCOTP registers
+ * needs its clk on, it will be disabled by clk late
+ * init and managed by ocotp driver.
+ */
+ writel_relaxed(readl_relaxed(base + 0x70) | 1 << 12, base + 0x70);
+
+ /* maintain M4 usecount */
+ if (imx_src_is_m4_enabled())
+ clk_prepare_enable(hws[IMX6SX_CLK_M4]->clk);
+
+ /* set perclk to from OSC */
+ clk_set_parent(hws[IMX6SX_CLK_PERCLK_SEL]->clk, hws[IMX6SX_CLK_OSC]->clk);
+
if (IS_ENABLED(CONFIG_USB_MXS_PHY)) {
clk_prepare_enable(hws[IMX6SX_CLK_USBPHY1_GATE]->clk);
clk_prepare_enable(hws[IMX6SX_CLK_USBPHY2_GATE]->clk);
@@ -527,7 +609,7 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
clk_set_rate(hws[IMX6SX_CLK_PLL4_AUDIO_DIV]->clk, 393216000);
clk_set_parent(hws[IMX6SX_CLK_SPDIF_SEL]->clk, hws[IMX6SX_CLK_PLL4_AUDIO_DIV]->clk);
- clk_set_rate(hws[IMX6SX_CLK_SPDIF_PODF]->clk, 98304000);
+ clk_set_rate(hws[IMX6SX_CLK_SPDIF_PODF]->clk, 24576000);
clk_set_parent(hws[IMX6SX_CLK_AUDIO_SEL]->clk, hws[IMX6SX_CLK_PLL3_USB_OTG]->clk);
clk_set_rate(hws[IMX6SX_CLK_AUDIO_PODF]->clk, 24000000);
@@ -542,6 +624,12 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
clk_set_parent(hws[IMX6SX_CLK_ESAI_SEL]->clk, hws[IMX6SX_CLK_PLL4_AUDIO_DIV]->clk);
clk_set_rate(hws[IMX6SX_CLK_ESAI_PODF]->clk, 24576000);
+ /* Set the UART parent if needed. */
+ if (uart_from_osc)
+ clk_set_parent(hws[IMX6SX_CLK_UART_SEL]->clk, hws[IMX6SX_CLK_OSC]->clk);
+ else
+ clk_set_parent(hws[IMX6SX_CLK_UART_SEL]->clk, hws[IMX6SX_CLK_PLL3_80M]->clk);
+
/* Set parent clock for vadc */
clk_set_parent(hws[IMX6SX_CLK_VID_SEL]->clk, hws[IMX6SX_CLK_PLL3_USB_OTG]->clk);
@@ -551,16 +639,74 @@ static void __init imx6sx_clocks_init(struct device_node *ccm_node)
/* Update gpu clock from default 528M to 720M */
clk_set_parent(hws[IMX6SX_CLK_GPU_CORE_SEL]->clk, hws[IMX6SX_CLK_PLL3_PFD0]->clk);
clk_set_parent(hws[IMX6SX_CLK_GPU_AXI_SEL]->clk, hws[IMX6SX_CLK_PLL3_PFD0]->clk);
+ if (!imx_src_is_m4_enabled())
+ /* default parent of can_sel clock is invalid, manually set it here */
+ clk_set_parent(hws[IMX6SX_CLK_CAN_SEL]->clk, hws[IMX6SX_CLK_PLL3_60M]->clk);
clk_set_parent(hws[IMX6SX_CLK_QSPI1_SEL]->clk, hws[IMX6SX_CLK_PLL2_BUS]->clk);
clk_set_parent(hws[IMX6SX_CLK_QSPI2_SEL]->clk, hws[IMX6SX_CLK_PLL2_BUS]->clk);
- for (i = 0; i < ARRAY_SIZE(uart_clk_ids); i++) {
- int index = uart_clk_ids[i];
+ imx_register_uart_clocks();
+}
+CLK_OF_DECLARE(imx6sx, "fsl,imx6sx-ccm", imx6sx_clocks_init);
+
+int imx_update_shared_mem(struct clk_hw *hw, bool enable)
+{
+ int i;
- uart_clks[i] = &hws[index]->clk;
+ for (i = 0; i < ARRAY_SIZE(clks_shared); i++) {
+ if (shared_mem->imx_clk[i].self == hw->clk)
+ break;
}
- imx_register_uart_clocks(uart_clks);
+ if (i >= ARRAY_SIZE(clks_shared))
+ return 1;
+
+ /* update ca9 clk status in shared memory */
+ if (enable)
+ shared_mem->imx_clk[i].ca9_enabled = 1;
+ else
+ shared_mem->imx_clk[i].ca9_enabled = 0;
+
+ if (shared_mem->imx_clk[i].cm4_enabled == 0)
+ return 1;
+
+ return 0;
}
-CLK_OF_DECLARE(imx6sx, "fsl,imx6sx-ccm", imx6sx_clocks_init);
+
+static int __init imx_amp_power_init(void)
+{
+ int i;
+ void __iomem *shared_mem_base;
+
+ if (!(imx_src_is_m4_enabled() && clk_on_imx6sx()))
+ return 0;
+
+ amp_power_mutex = imx_sema4_mutex_create(0, MCC_POWER_SHMEM_NUMBER);
+
+ shared_mem_base = ioremap_nocache(shared_mem_paddr, shared_mem_size);
+
+ if (!amp_power_mutex) {
+ pr_err("Failed to create sema4 mutex!\n");
+ return 0;
+ }
+
+ shared_mem = (struct imx_shared_mem *)shared_mem_base;
+
+ for (i = 0; i < ARRAY_SIZE(clks_shared); i++) {
+ shared_mem->imx_clk[i].self = hws[clks_shared[i]]->clk;
+ shared_mem->imx_clk[i].ca9_enabled = 1;
+ pr_debug("%d: name %s, addr 0x%x\n", i,
+ __clk_get_name(shared_mem->imx_clk[i].self),
+ (u32)&(shared_mem->imx_clk[i]));
+ }
+ /* enable amp power management */
+ shared_mem->ca9_valid = SHARED_MEM_MAGIC_NUMBER;
+
+ pr_info("A9-M4 sema4 num %d, A9-M4 magic number 0x%x - 0x%x.\n",
+ amp_power_mutex->gate_num, shared_mem->ca9_valid,
+ shared_mem->cm4_valid);
+
+ return 0;
+}
+late_initcall(imx_amp_power_init);