/*	$NetBSD: imx7_ccm.c,v 1.2 2016/10/20 16:50:11 ryo Exp $	*/

/*
 * Copyright (c) 2010-2012, 2014  Genetec Corporation.  All rights reserved.
 * Written by Hashimoto Kenichi for Genetec Corporation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GENETEC CORPORATION ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL GENETEC CORPORATION
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Clock Controller Module (CCM) for i.MX7
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: imx7_ccm.c,v 1.2 2016/10/20 16:50:11 ryo Exp $");

#include "opt_imx.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/cpufreq.h>
#include <sys/malloc.h>
#include <sys/param.h>

#include <machine/cpu.h>
#include <arm/imx/imx7_ccmvar.h>
#include <arm/imx/imx7_ccmreg.h>
#include <arm/imx/imx7var.h>
#include <arm/imx/imx7reg.h>

#ifdef CCM_DEBUG
int ccm_debug = 1;
#endif

struct imxccm_softc {
	device_t sc_dev;
	bus_space_tag_t sc_iot;
	bus_space_handle_t sc_ioh;
	bus_space_handle_t sc_ioh_analog;

	/* for sysctl */
	struct sysctllog *sc_log;
	int sc_sysctlnode_arm_pll;
	int sc_sysctlnode_sys_pll;
	int sc_sysctlnode_enet_pll;
	int sc_sysctlnode_audio_pll;
	int sc_sysctlnode_video_pll;
	int sc_sysctlnode_ddr_pll;
	int sc_sysctlnode_usb_pll;

	int sc_sysctlnode_arm_a7_clk;
	int sc_sysctlnode_arm_m4_clk;
};

const struct imx7clkroottbl {
	int clksrc[8];
	uint32_t targetroot;
	int type;
#define CLKTYPE_CORE		0
#define CLKTYPE_DRAM		1
#define CLKTYPE_DRAM_PHYM	2
#define CLKTYPE_IP		3
#define CLKTYPE_AHB		4
#define CLKTYPE_BUS		5
} imx7clkroottbl[] = {
	[IMX7CLK_ARM_A7_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ARM_A7_CLK_ROOT,
		.type = CLKTYPE_CORE,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ARM_PLL,
			IMX7CLK_ENET_PLL_DIV2, IMX7CLK_DDR_PLL,
			IMX7CLK_SYS_PLL, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_AUDIO_PLL, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_ARM_M4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ARM_M4_CLK_ROOT,
		.type = CLKTYPE_BUS,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_MAIN_AXI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_MAIN_AXI_CLK_ROOT,
		.type = CLKTYPE_BUS,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD1,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV4,
			IMX7CLK_SYS_PLL_PFD5, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_DISP_AXI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_DISP_AXI_CLK_ROOT,
		.type = CLKTYPE_BUS,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD1,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV4,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_ENET_AXI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET_AXI_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV4,
			IMX7CLK_SYS_PLL_DIV2, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD4
		}
	},
	[IMX7CLK_NAND_USDHC_BUS_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_NAND_USDHC_BUS_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_AHB_CLK_ROOT,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD2_DIV2, IMX7CLK_SYS_PLL_PFD6,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_AUDIO_PLL
		}
	},
	[IMX7CLK_AHB_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_AHB_CLK_ROOT,
		.type = CLKTYPE_AHB,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_USB_PLL,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_IPG_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_IPG_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_AHB_CLK_ROOT, -1,
			-1, -1,
			-1, -1,
			-1, -1
		}
	},
	[IMX7CLK_DRAM_PHYM_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_DRAM_PHYM_CLK_ROOT,
		.type = CLKTYPE_DRAM_PHYM,
		.clksrc = {
			IMX7CLK_DDR_PLL, IMX7CLK_DRAM_PHYM_ALT_CLK_ROOT,
			-1, -1,
			-1, -1,
			-1, -1
		}
	},
	[IMX7CLK_DRAM_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_DRAM_CLK_ROOT,
		.type = CLKTYPE_DRAM,
		.clksrc = {
			IMX7CLK_DDR_PLL, IMX7CLK_DRAM_ALT_CLK_ROOT,
			-1, -1,
			-1, -1,
			-1, -1
		}
	},
	[IMX7CLK_DRAM_PHYM_ALT_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_DRAM_PHYM_ALT_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_SYS_PLL, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD7,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_DRAM_ALT_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_DRAM_ALT_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_SYS_PLL, IMX7CLK_ENET_PLL_DIV4,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_AUDIO_PLL, IMX7CLK_SYS_PLL_PFD2
		}
	},
	[IMX7CLK_USB_HSIC_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_USB_HSIC_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD3,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_SYS_PLL_PFD5,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_PCIE_CTRL_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PCIE_CTRL_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV4,
			IMX7CLK_SYS_PLL_DIV2, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD1, IMX7CLK_SYS_PLL_PFD6
		}
	},
	[IMX7CLK_PCIE_PHY_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PCIE_PHY_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_ENET_PLL_DIV2, IMX7CLK_EXT_CLK1,
			IMX7CLK_EXT_CLK2, IMX7CLK_EXT_CLK3,
			IMX7CLK_EXT_CLK4, IMX7CLK_SYS_PLL_PFD0
		}
	},
	[IMX7CLK_EPDC_PIXEL_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_EPDC_PIXEL_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD1,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL,
			IMX7CLK_SYS_PLL_PFD5, IMX7CLK_SYS_PLL_PFD6,
			IMX7CLK_SYS_PLL_PFD7, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_LCDIF_PIXEL_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_LCDIF_PIXEL_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD5,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_EXT_CLK3,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_MIPI_DSI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_MIPI_DSI_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD5,
			IMX7CLK_SYS_PLL_PFD3, IMX7CLK_SYS_PLL,
			IMX7CLK_SYS_PLL_PFD0_DIV2, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_AUDIO_PLL
		}
	},
	[IMX7CLK_MIPI_CSI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_MIPI_CSI_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_SYS_PLL_PFD3, IMX7CLK_SYS_PLL,
			IMX7CLK_SYS_PLL_PFD0_DIV2, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_AUDIO_PLL
		}
	},
	[IMX7CLK_MIPI_DPHY_REF_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_MIPI_DPHY_REF_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL_PFD5,
			IMX7CLK_REF_1M, IMX7CLK_EXT_CLK2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_EXT_CLK3
		}
	},
	[IMX7CLK_SAI1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_SAI1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_EXT_CLK2
		}
	},
	[IMX7CLK_SAI2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_SAI2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_EXT_CLK2
		}
	},
	[IMX7CLK_SAI3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_SAI3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_EXT_CLK3
		}
	},
	[IMX7CLK_ENET1_REF_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET1_REF_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV8,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_ENET_PLL_DIV40,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_EXT_CLK4
		}
	},
	[IMX7CLK_ENET1_TIME_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET1_TIME_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK1,
			IMX7CLK_EXT_CLK2, IMX7CLK_EXT_CLK3,
			IMX7CLK_EXT_CLK4, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_ENET2_REF_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET2_REF_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV8,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_ENET_PLL_DIV40,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_EXT_CLK4
		}
	},
	[IMX7CLK_ENET2_TIME_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET2_TIME_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK1,
			IMX7CLK_EXT_CLK2, IMX7CLK_EXT_CLK3,
			IMX7CLK_EXT_CLK4, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_ENET_PHY_REF_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ENET_PHY_REF_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV40,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_ENET_PLL_DIV8,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_SYS_PLL_PFD3
		}
	},
	[IMX7CLK_EIM_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_EIM_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD2, IMX7CLK_SYS_PLL_PFD3,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_NAND_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_NAND_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_SYS_PLL_PFD3, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_QSPI_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_QSPI_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD3, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_USDHC1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_USDHC1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_USDHC2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_USDHC2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_USDHC3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_USDHC3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD0,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_SYS_PLL_PFD2,
			IMX7CLK_SYS_PLL_PFD6, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_CAN1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_CAN1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_USB_PLL,
			IMX7CLK_EXT_CLK1, IMX7CLK_EXT_CLK4
		}
	},
	[IMX7CLK_CAN2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_CAN2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_SYS_PLL,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_USB_PLL,
			IMX7CLK_EXT_CLK1, IMX7CLK_EXT_CLK3
		}
	},
	[IMX7CLK_I2C1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_I2C1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD2_DIV2
		}
	},
	[IMX7CLK_I2C2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_I2C2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD2_DIV2
		}
	},
	[IMX7CLK_I2C3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_I2C3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD2_DIV2
		}
	},
	[IMX7CLK_I2C4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_I2C4_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_ENET_PLL_DIV20, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_AUDIO_PLL, IMX7CLK_VIDEO_PLL,
			IMX7CLK_USB_PLL, IMX7CLK_SYS_PLL_PFD2_DIV2
		}
	},
	[IMX7CLK_UART1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK3, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART4_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK3, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART5_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART5_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART6_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART6_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK3, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_UART7_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_UART7_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_EXT_CLK4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_ECSPI1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ECSPI1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_SYS_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_ECSPI2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ECSPI2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_SYS_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_ECSPI3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ECSPI3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_SYS_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_ECSPI4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_ECSPI4_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV25, IMX7CLK_SYS_PLL_DIV4,
			IMX7CLK_SYS_PLL, IMX7CLK_SYS_PLL_PFD4,
			IMX7CLK_ENET_PLL_DIV4, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_PWM1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PWM1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK1,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_PWM2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PWM2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK1,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_PWM3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PWM3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_PWM4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_PWM4_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK2,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_FLEXTIMER1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_FLEXTIMER1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK3,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_FLEXTIMER2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_FLEXTIMER2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK3,
			IMX7CLK_REF_1M, IMX7CLK_VIDEO_PLL
		}
	},
	[IMX7CLK_SIM1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_SIM1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_USB_PLL, IMX7CLK_AUDIO_PLL,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_SIM2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_SIM2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_USB_PLL, IMX7CLK_VIDEO_PLL,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_SYS_PLL_PFD7
		}
	},
	[IMX7CLK_GPT1_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_GPT1_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_PFD0, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_VIDEO_PLL, IMX7CLK_REF_1M,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK1
		}
	},
	[IMX7CLK_GPT2_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_GPT2_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_PFD0, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_VIDEO_PLL, IMX7CLK_REF_1M,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK2
		}
	},
	[IMX7CLK_GPT3_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_GPT3_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_PFD0, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_VIDEO_PLL, IMX7CLK_REF_1M,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK3
		}
	},
	[IMX7CLK_GPT4_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_GPT4_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_ENET_PLL_DIV10,
			IMX7CLK_SYS_PLL_PFD0, IMX7CLK_ENET_PLL_DIV25,
			IMX7CLK_VIDEO_PLL, IMX7CLK_REF_1M,
			IMX7CLK_AUDIO_PLL, IMX7CLK_EXT_CLK4
		}
	},
	[IMX7CLK_TRACE_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_TRACE_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_USB_PLL,
			IMX7CLK_EXT_CLK2, IMX7CLK_EXT_CLK3
		}
	},
	[IMX7CLK_WDOG_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_WDOG_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_USB_PLL,
			IMX7CLK_REF_1M, IMX7CLK_SYS_PLL_PFD1_DIV2
		}
	},
	[IMX7CLK_CSI_MCLK_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_CSI_MCLK_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_AUDIO_MCLK_CLK_ROOT] = {
		.targetroot = CCM_CLKROOT_AUDIO_MCLK_CLK_ROOT,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_PFD2_DIV2,
			IMX7CLK_SYS_PLL_DIV4, IMX7CLK_DDR_PLL_DIV2,
			IMX7CLK_ENET_PLL_DIV8, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_USB_PLL
		}
	},
	[IMX7CLK_CCM_CLKO1] = {
		.targetroot = CCM_CLKROOT_CCM_CLKO1,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL,
			IMX7CLK_SYS_PLL_DIV2, IMX7CLK_SYS_PLL_PFD0_DIV2,
			IMX7CLK_SYS_PLL_PFD3, IMX7CLK_ENET_PLL_DIV2,
			IMX7CLK_DDR_PLL_DIV2, IMX7CLK_REF_1M
		}
	},
	[IMX7CLK_CCM_CLKO2] = {
		.targetroot = CCM_CLKROOT_CCM_CLKO2,
		.type = CLKTYPE_IP,
		.clksrc = {
			IMX7CLK_OSC_24M, IMX7CLK_SYS_PLL_DIV2,
			IMX7CLK_SYS_PLL_PFD0, IMX7CLK_SYS_PLL_PFD1_DIV2,
			IMX7CLK_SYS_PLL_PFD4, IMX7CLK_AUDIO_PLL,
			IMX7CLK_VIDEO_PLL, IMX7CLK_OSC_32K
		}
	}
};

struct imxccm_softc *ccm_softc;

static int imxccm_match(device_t, cfdata_t, void *);
static void imxccm_attach(device_t, device_t, void *);

static int imxccm_sysctl_freq_helper(SYSCTLFN_PROTO);
static int imxccm_sysctl_setup(struct imxccm_softc *);

CFATTACH_DECL_NEW(imxccm, sizeof(struct imxccm_softc),
    imxccm_match, imxccm_attach, NULL, NULL);

/* ARGSUSED */
static int
imxccm_match(device_t parent __unused, cfdata_t cfdata __unused, void *aux)
{
	struct axi_attach_args *aa = aux;

	if (ccm_softc != NULL)
		return 0;

	if (aa->aa_addr == IMX7_AIPS_BASE + AIPS1_CCM_BASE)
		return 1;

	return 0;
}

/* ARGSUSED */
static void
imxccm_attach(device_t parent __unused, device_t self, void *aux)
{
	struct imxccm_softc * const sc = device_private(self);
	struct axi_attach_args *aa = aux;
	bus_space_tag_t iot = aa->aa_iot;

	ccm_softc = sc;
	sc->sc_dev = self;
	sc->sc_iot = iot;
	sc->sc_log = NULL;

	if (bus_space_map(iot, IMX7_AIPS_BASE + AIPS1_CCM_BASE,
	    AIPS1_CCM_SIZE, 0, &sc->sc_ioh)) {
		aprint_error(": can't map CCM registers\n");
		return;
	}

	if (bus_space_map(iot, IMX7_AIPS_BASE + AIPS1_CCM_ANALOG_BASE,
	    AIPS1_CCM_ANALOG_SIZE, 0, &sc->sc_ioh_analog)) {
		aprint_error(": can't map CCM_ANALOG registers\n");
		bus_space_unmap(iot, sc->sc_ioh, AIPS1_CCM_SIZE);
		return;
	}

	aprint_normal(": Clock Control Module\n");
	aprint_naive("\n");

	imxccm_sysctl_setup(sc);

	aprint_verbose_dev(self, "ARM_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_ARM_PLL));
	aprint_verbose_dev(self, "SYS_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_SYS_PLL));
	aprint_verbose_dev(self, "ENET_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_ENET_PLL));
	aprint_verbose_dev(self, "AUDIO_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_AUDIO_PLL));
	aprint_verbose_dev(self, "VIDEO_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_VIDEO_PLL));
	aprint_verbose_dev(self, "DDR_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_DDR_PLL));
	aprint_verbose_dev(self, "USB_PLL clock=%d\n",
	    imx7_get_clock(IMX7CLK_USB_PLL));
	aprint_verbose_dev(self, "AXI clock=%d\n",
	    imx7_get_clock(IMX7CLK_MAIN_AXI_CLK_ROOT));
	aprint_verbose_dev(self, "ENET AXI clock=%d\n",
	    imx7_get_clock(IMX7CLK_ENET_AXI_CLK_ROOT));

#ifdef CCM_DEBUG
#define CLOCK_PRINT(name)	\
	printf(# name " clock=%d\n", imx7_get_clock(name))

	CLOCK_PRINT(IMX7CLK_OSC_24M);

	CLOCK_PRINT(IMX7CLK_ARM_PLL);
	CLOCK_PRINT(IMX7CLK_SYS_PLL);
	CLOCK_PRINT(IMX7CLK_ENET_PLL);
	CLOCK_PRINT(IMX7CLK_AUDIO_PLL);
	CLOCK_PRINT(IMX7CLK_VIDEO_PLL);
	CLOCK_PRINT(IMX7CLK_DDR_PLL);
	CLOCK_PRINT(IMX7CLK_USB_PLL);

	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD0);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD1);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD2);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD3);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD4);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD5);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD6);
	CLOCK_PRINT(IMX7CLK_SYS_PLL_PFD7);

	CLOCK_PRINT(IMX7CLK_ARM_A7_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_ARM_M4_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_MAIN_AXI_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_ENET_AXI_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_AHB_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_IPG_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_DRAM_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_DRAM_ALT_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_USB_HSIC_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_NAND_USDHC_BUS_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_USDHC1_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_USDHC2_CLK_ROOT);
	CLOCK_PRINT(IMX7CLK_USDHC3_CLK_ROOT);
#endif
}

static int
imxccm_sysctl_setup(struct imxccm_softc *sc)
{
	const struct sysctlnode *node, *imxnode, *freqnode, *pllnode;
	int rv;

	rv = sysctl_createv(&sc->sc_log, 0, NULL, &node,
	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
	    NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
	if (rv != 0)
		goto fail;

	rv = sysctl_createv(&sc->sc_log, 0, &node, &imxnode,
	    0, CTLTYPE_NODE, "imx7", NULL,
	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;

	rv = sysctl_createv(&sc->sc_log, 0, &imxnode, &freqnode,
	    0, CTLTYPE_NODE, "frequency", NULL,
	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;

	rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &pllnode,
	    0, CTLTYPE_NODE, "pll", NULL,
	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    CTLFLAG_READWRITE, CTLTYPE_INT, "arm",
	    SYSCTL_DESCR("frequency of ARM clock (ARM_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_arm_pll = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "system",
	    SYSCTL_DESCR("frequency of system clock (SYS_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_sys_pll = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "enet",
	    SYSCTL_DESCR("frequency of enet clock (ENET_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_enet_pll = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "audio",
	    SYSCTL_DESCR("frequency of audio clock (AUDIO_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_audio_pll = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "video",
	    SYSCTL_DESCR("frequency of video clock (VIDEO_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_video_pll  = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "ddr",
	    SYSCTL_DESCR("frequency of DDR clock (DDR_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_ddr_pll = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node,
	    0, CTLTYPE_INT, "usb",
	    SYSCTL_DESCR("frequency of USB clock (USB_PLL)"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_usb_pll = node->sysctl_num;


	rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node,
	    CTLFLAG_READWRITE, CTLTYPE_INT, "arm_a7",
	    SYSCTL_DESCR("frequency of ARM A7 Root clock"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_arm_a7_clk = node->sysctl_num;

	rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node,
	    CTLFLAG_READWRITE, CTLTYPE_INT, "arm_m4",
	    SYSCTL_DESCR("frequency of ARM M4 Root clock"),
	    imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
	if (rv != 0)
		goto fail;
	sc->sc_sysctlnode_arm_m4_clk = node->sysctl_num;


	return 0;

 fail:
	aprint_error_dev(sc->sc_dev, "cannot initialize sysctl (err=%d)\n", rv);

	sysctl_teardown(&sc->sc_log);
	sc->sc_log = NULL;

	return -1;
}

static int
imxccm_sysctl_freq_helper(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct imxccm_softc *sc;
	int value, ovalue, err;

	node = *rnode;
	sc = node.sysctl_data;

	/* for sysctl read */
	if (rnode->sysctl_num == sc->sc_sysctlnode_arm_pll)
		value = imx7_get_clock(IMX7CLK_ARM_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_sys_pll)
		value = imx7_get_clock(IMX7CLK_SYS_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_enet_pll)
		value = imx7_get_clock(IMX7CLK_ENET_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_audio_pll)
		value = imx7_get_clock(IMX7CLK_AUDIO_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_video_pll)
		value = imx7_get_clock(IMX7CLK_VIDEO_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_ddr_pll)
		value = imx7_get_clock(IMX7CLK_DDR_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_usb_pll)
		value = imx7_get_clock(IMX7CLK_USB_PLL);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_arm_a7_clk)
		value = imx7_get_clock(IMX7CLK_ARM_A7_CLK_ROOT);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_arm_m4_clk)
		value = imx7_get_clock(IMX7CLK_ARM_M4_CLK_ROOT);
	else
		return EOPNOTSUPP;

#ifdef SYSCTL_BY_MHZ
	value /= 1000 * 1000;	/* Hz -> MHz */
#endif
	ovalue = value;

	node.sysctl_data = &value;
	err = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (err != 0 || newp == NULL)
		return err;

	/* for sysctl write */
	if (value == ovalue)
		return 0;

#ifdef SYSCTL_BY_MHZ
	value *= 1000 * 1000;	/* MHz -> Hz */
#endif

	if (rnode->sysctl_num == sc->sc_sysctlnode_arm_pll)
		return imx7_set_clock(IMX7CLK_ARM_PLL, value);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_arm_a7_clk)
		return imx7_set_clock(IMX7CLK_ARM_A7_CLK_ROOT, value);
	else if (rnode->sysctl_num == sc->sc_sysctlnode_arm_m4_clk)
		return imx7_set_clock(IMX7CLK_ARM_M4_CLK_ROOT, value);

	return EOPNOTSUPP;
}


uint32_t
imx7_ccm_read(uint32_t reg)
{
	if (ccm_softc == NULL)
		return 0;

	return bus_space_read_4(ccm_softc->sc_iot, ccm_softc->sc_ioh, reg);
}

void
imx7_ccm_write(uint32_t reg, uint32_t val)
{
	if (ccm_softc == NULL)
		return;

	bus_space_write_4(ccm_softc->sc_iot, ccm_softc->sc_ioh, reg, val);
}

uint32_t
imx7_ccm_analog_read(uint32_t reg)
{
	if (ccm_softc == NULL)
		return 0;

	return bus_space_read_4(ccm_softc->sc_iot, ccm_softc->sc_ioh_analog,
	    reg);
}

void
imx7_ccm_analog_write(uint32_t reg, uint32_t val)
{
	if (ccm_softc == NULL)
		return;

	bus_space_write_4(ccm_softc->sc_iot, ccm_softc->sc_ioh_analog, reg,
	    val);
}

static uint64_t
rootclk(int clk, bool nodiv)
{
	uint32_t d, v;
	uint64_t freq;

	/* read CCM_TARGET_ROOTxx */
	v = imx7_ccm_read(imx7clkroottbl[clk].targetroot);

	if ((v & CCM_TARGET_ROOT_ENABLE) == 0)
		return 0;

	d = __SHIFTOUT(v, CCM_TARGET_ROOT_MUX);
	freq = imx7_get_clock(imx7clkroottbl[clk].clksrc[d]);

#ifdef CCM_DEBUG
	if (ccm_debug) {
		printf("TARGET_ROOT[%08x]: value=%08x, mux=%d, freq=%llu\n",
		    imx7clkroottbl[clk].targetroot, v, d, freq);
	}
#endif

	if (nodiv)
		return freq;

	/* eval TARGET_ROOT[PRE_PODF] */
	if ((imx7clkroottbl[clk].type != CLKTYPE_CORE) &&
	    (imx7clkroottbl[clk].type != CLKTYPE_DRAM) &&
	    (imx7clkroottbl[clk].type != CLKTYPE_DRAM_PHYM)) {
		d = __SHIFTOUT(v, CCM_TARGET_ROOT_PRE_PODF) + 1;
		freq /= d;
#ifdef CCM_DEBUG
		if (ccm_debug) {
			printf("TARGET_ROOT[%08x]: pre_podf=%d, freq=%llu\n",
			    imx7clkroottbl[clk].targetroot, d, freq);
		}
#endif
	}

	/* eval TARGET_ROOT[POST_PODF] */
	if (clk != IMX7CLK_DRAM_PHYM_CLK_ROOT) {
		d = __SHIFTOUT(v, CCM_TARGET_ROOT_POST_PODF) + 1;
		if (clk == IMX7CLK_DRAM_CLK_ROOT)
			d &= 7;
		freq /= d;
#ifdef CCM_DEBUG
		if (ccm_debug) {
			printf("TARGET_ROOT[%08x]: post_podf=%d, freq=%llu\n",
			    imx7clkroottbl[clk].targetroot, d, freq);
		}
#endif
	}

	/* eval TARGET_ROOT[AUTO_PODF] (from u-boot source, not datasheet) */
	if ((imx7clkroottbl[clk].type != CLKTYPE_AHB) &&
	    (imx7clkroottbl[clk].type != CLKTYPE_BUS)) {
		if (v & CCM_TARGET_ROOT_AUTO_ENABLE) {
			d = __SHIFTOUT(v, CCM_TARGET_ROOT_AUTO_PODF) + 1;
			freq /= d;
#ifdef CCM_DEBUG
			if (ccm_debug) {
				printf("TARGET_ROOT[%08x]: "
				    "auto_podf=%d, freq=%llu\n",
				    imx7clkroottbl[clk].targetroot, d, freq);
			}
#endif
		}
	}

	return freq;
}

/* see also iMX7 Reference Manual 5.2.6.1 Input Clocks */
uint32_t
imx7_get_clock(enum imx7_clock clk)
{
	uint32_t d, denom, num, v;
	uint64_t freq = 0;

	if (ccm_softc == NULL)
		return 0;

	switch (clk) {
	case IMX7CLK_OSC_24M:
		freq = IMX7_OSC_FREQ;	/* fixed clock */
		break;
	case IMX7CLK_REF_1M:
		freq = IMX7_OSC_FREQ / 24;	/* fixed clock */
		break;
	case IMX7CLK_OSC_32K:
		freq = IMX7_OSC_FREQ / 768;	/* fixed clock */
		break;

	case IMX7CLK_ARM_PLL:
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_ARM);
		freq = IMX7_OSC_FREQ *
		    __SHIFTOUT(v, CCM_ANALOG_PLL_ARM_DIV_SELECT) / 2;
		break;

	case IMX7CLK_DDR_PLL:
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_DDR);
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_DDR_DIV_SELECT);
		num = imx7_ccm_analog_read(CCM_ANALOG_PLL_DDR_NUM);
		denom = imx7_ccm_analog_read(CCM_ANALOG_PLL_DDR_DENOM);
		freq = (uint64_t)IMX7_OSC_FREQ * (d + num / denom);
		if (v & CCM_ANALOG_PLL_DDR_DIV2_ENABLE_CLK)
			freq /= 2;
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_DDR_TEST_DIV_SELECT);
		switch (d) {
		case 0:
			freq /= 4;
			break;
		case 1:
			freq /= 2;
			break;
		case 2:
		case 3:
			break;
		}
		break;
	case IMX7CLK_DDR_PLL_DIV2:
		freq = imx7_get_clock(IMX7CLK_DDR_PLL) / 2;
		break;

	case IMX7CLK_SYS_PLL:
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_480);
		freq = IMX7_OSC_FREQ *
		    ((v & CCM_ANALOG_PLL_480_DIV_SELECT) ? 22 : 20);
		break;
	case IMX7CLK_SYS_PLL_DIV2:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL) / 2;
		break;
	case IMX7CLK_SYS_PLL_DIV4:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL) / 4;
		break;
	case IMX7CLK_SYS_PLL_PFD0:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480A);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480A_PDF0_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD0_DIV2:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL_PFD0) / 2;
		break;
	case IMX7CLK_SYS_PLL_PFD1:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480A);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480A_PDF1_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD1_DIV2:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL_PFD1) / 2;
		break;
	case IMX7CLK_SYS_PLL_PFD2:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480A);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480A_PDF2_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD2_DIV2:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL_PFD2) / 2;
		break;
	case IMX7CLK_SYS_PLL_PFD3:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480A);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480A_PDF3_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD4:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480B);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480B_PDF4_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD5:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480B);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480B_PDF5_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD6:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480B);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480B_PDF6_FRAC);
		break;
	case IMX7CLK_SYS_PLL_PFD7:
		freq = imx7_get_clock(IMX7CLK_SYS_PLL);
		v = imx7_ccm_analog_read(CCM_ANALOG_PFD_480B);
		freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480B_PDF7_FRAC);
		break;

	case IMX7CLK_ENET_PLL:
		freq = IMX7_ENET_PLL_CLK;	/* fixed clock */
		break;
	case IMX7CLK_ENET_PLL_DIV2:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 2;
		break;
	case IMX7CLK_ENET_PLL_DIV4:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 4;
		break;
	case IMX7CLK_ENET_PLL_DIV8:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 8;
		break;
	case IMX7CLK_ENET_PLL_DIV10:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 10;
		break;
	case IMX7CLK_ENET_PLL_DIV20:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 20;
		break;
	case IMX7CLK_ENET_PLL_DIV25:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 25;
		break;
	case IMX7CLK_ENET_PLL_DIV40:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 40;
		break;
	case IMX7CLK_ENET_PLL_DIV50:
		freq = imx7_get_clock(IMX7CLK_ENET_PLL) / 50;
		break;

	case IMX7CLK_USB_PLL:
		freq = IMX7_USB_PLL_CLK;	/* fixed clock */
		break;
	case IMX7CLK_AUDIO_PLL:
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_AUDIO);
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_AUDIO_DIV_SELECT);
		num = imx7_ccm_analog_read(CCM_ANALOG_PLL_AUDIO_NUM);
		denom = imx7_ccm_analog_read(CCM_ANALOG_PLL_AUDIO_DENOM);
		freq = (uint64_t)IMX7_OSC_FREQ * (d + num / denom);
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_AUDIO_TEST_DIV_SELECT);
		switch (d) {
		case 0:
			freq /= 4;
			break;
		case 1:
			freq /= 2;
			break;
		case 2:
		case 3:
			break;
		}
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT);
		switch (d) {
		case 0:
		case 2:
			break;
		case 1:
			freq /= 2;
			break;
		case 3:
			freq /= 4;
			break;
		}
		break;
	case IMX7CLK_VIDEO_PLL:
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_VIDEO);
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_VIDEO_DIV_SELECT);
		num = imx7_ccm_analog_read(CCM_ANALOG_PLL_VIDEO_NUM);
		denom = imx7_ccm_analog_read(CCM_ANALOG_PLL_VIDEO_DENOM);
		freq = (uint64_t)IMX7_OSC_FREQ * (d + num / denom);
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_VIDEO_TEST_DIV_SELECT);
		switch (d) {
		case 0:
			freq /= 4;
			break;
		case 1:
			freq /= 2;
			break;
		case 2:
		case 3:
			break;
		}
		d = __SHIFTOUT(v, CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT);
		switch (d) {
		case 0:
		case 2:
			break;
		case 1:
			freq /= 2;
			break;
		case 3:
			freq /= 4;
			break;
		}
		break;

	case IMX7CLK_ARM_A7_CLK_ROOT:
	case IMX7CLK_ARM_M4_CLK_ROOT:
	case IMX7CLK_MAIN_AXI_CLK_ROOT:
	case IMX7CLK_DISP_AXI_CLK_ROOT:
	case IMX7CLK_ENET_AXI_CLK_ROOT:
	case IMX7CLK_NAND_USDHC_BUS_CLK_ROOT:
	case IMX7CLK_AHB_CLK_ROOT:
	case IMX7CLK_IPG_CLK_ROOT:
	case IMX7CLK_DRAM_PHYM_CLK_ROOT:
	case IMX7CLK_DRAM_CLK_ROOT:
	case IMX7CLK_DRAM_PHYM_ALT_CLK_ROOT:
	case IMX7CLK_DRAM_ALT_CLK_ROOT:
	case IMX7CLK_USB_HSIC_CLK_ROOT:
	case IMX7CLK_PCIE_CTRL_CLK_ROOT:
	case IMX7CLK_PCIE_PHY_CLK_ROOT:
	case IMX7CLK_EPDC_PIXEL_CLK_ROOT:
	case IMX7CLK_LCDIF_PIXEL_CLK_ROOT:
	case IMX7CLK_MIPI_DSI_CLK_ROOT:
	case IMX7CLK_MIPI_CSI_CLK_ROOT:
	case IMX7CLK_MIPI_DPHY_REF_CLK_ROOT:
	case IMX7CLK_SAI1_CLK_ROOT:
	case IMX7CLK_SAI2_CLK_ROOT:
	case IMX7CLK_SAI3_CLK_ROOT:
	case IMX7CLK_ENET1_REF_CLK_ROOT:
	case IMX7CLK_ENET1_TIME_CLK_ROOT:
	case IMX7CLK_ENET2_REF_CLK_ROOT:
	case IMX7CLK_ENET2_TIME_CLK_ROOT:
	case IMX7CLK_ENET_PHY_REF_CLK_ROOT:
	case IMX7CLK_EIM_CLK_ROOT:
	case IMX7CLK_NAND_CLK_ROOT:
	case IMX7CLK_QSPI_CLK_ROOT:
	case IMX7CLK_USDHC1_CLK_ROOT:
	case IMX7CLK_USDHC2_CLK_ROOT:
	case IMX7CLK_USDHC3_CLK_ROOT:
	case IMX7CLK_CAN1_CLK_ROOT:
	case IMX7CLK_CAN2_CLK_ROOT:
	case IMX7CLK_I2C1_CLK_ROOT:
	case IMX7CLK_I2C2_CLK_ROOT:
	case IMX7CLK_I2C3_CLK_ROOT:
	case IMX7CLK_I2C4_CLK_ROOT:
	case IMX7CLK_UART1_CLK_ROOT:
	case IMX7CLK_UART2_CLK_ROOT:
	case IMX7CLK_UART3_CLK_ROOT:
	case IMX7CLK_UART4_CLK_ROOT:
	case IMX7CLK_UART5_CLK_ROOT:
	case IMX7CLK_UART6_CLK_ROOT:
	case IMX7CLK_UART7_CLK_ROOT:
	case IMX7CLK_ECSPI1_CLK_ROOT:
	case IMX7CLK_ECSPI2_CLK_ROOT:
	case IMX7CLK_ECSPI3_CLK_ROOT:
	case IMX7CLK_ECSPI4_CLK_ROOT:
	case IMX7CLK_PWM1_CLK_ROOT:
	case IMX7CLK_PWM2_CLK_ROOT:
	case IMX7CLK_PWM3_CLK_ROOT:
	case IMX7CLK_PWM4_CLK_ROOT:
	case IMX7CLK_FLEXTIMER1_CLK_ROOT:
	case IMX7CLK_FLEXTIMER2_CLK_ROOT:
	case IMX7CLK_SIM1_CLK_ROOT:
	case IMX7CLK_SIM2_CLK_ROOT:
	case IMX7CLK_GPT1_CLK_ROOT:
	case IMX7CLK_GPT2_CLK_ROOT:
	case IMX7CLK_GPT3_CLK_ROOT:
	case IMX7CLK_GPT4_CLK_ROOT:
	case IMX7CLK_TRACE_CLK_ROOT:
	case IMX7CLK_WDOG_CLK_ROOT:
	case IMX7CLK_CSI_MCLK_CLK_ROOT:
	case IMX7CLK_AUDIO_MCLK_CLK_ROOT:
	case IMX7CLK_CCM_CLKO1:
	case IMX7CLK_CCM_CLKO2:
		freq = rootclk(clk, false);
		break;

	default:
		aprint_error_dev(ccm_softc->sc_dev,
		    "%s: clockid %d: not supported\n", __func__, clk);
		return 0;
	}

	return freq;
}

/*
 * resolve two clock divisors d3 and d6.
 * freq = maxfreq / d3(3bit) / d6(6bit)
 */
static void
getrootclkdiv(uint32_t maxfreq, uint32_t freq, uint32_t *d3p, uint32_t *d6p)
{
	uint32_t c_dif, c_d3, c_d6;
	uint32_t dif, d3, d6;
	uint32_t lim, base, f;

	c_dif = 0xffffffff;
	c_d3 = c_d6 = 0;
	for (d3 = 0; d3 < 8; d3++) {
		base = 0;
		for (lim = 64; lim != 0; lim >>= 1) {
			d6 = (lim >> 1) + base;
			f = maxfreq / (d3 + 1) / (d6 + 1);
			if (freq < f) {
				base = d6 + 1;
				lim--;
			}
			dif = (freq > f) ? freq - f : f - freq;
			if (c_dif > dif) {
				c_dif = dif;
				c_d3 = d3;
				c_d6 = d6;
			}
		}
	}
	*d3p = c_d3;
	*d6p = c_d6;
}

int
imx7_set_clock(enum imx7_clock clk, uint32_t freq)
{
	uint32_t v, x;
	uint32_t d3, d6, maxfreq;

	if (ccm_softc == NULL)
		return 0;

	switch (clk) {
	case IMX7CLK_ARM_PLL:
		x = (uint64_t)freq * 2 / IMX7_OSC_FREQ;
		v = imx7_ccm_analog_read(CCM_ANALOG_PLL_ARM);
		v &= ~CCM_ANALOG_PLL_ARM_DIV_SELECT;
		v |=  __SHIFTIN(x, CCM_ANALOG_PLL_ARM_DIV_SELECT);
		imx7_ccm_analog_write(CCM_ANALOG_PLL_ARM, v);
		break;

	/* core type clock */
	case IMX7CLK_ARM_A7_CLK_ROOT:
		maxfreq = rootclk(clk, true);
		getrootclkdiv(maxfreq, freq, &d3, &d6);
		v = imx7_ccm_read(imx7clkroottbl[clk].targetroot);
		/* core type clocks use AUTO_PODF and POST_PODF */
		v &= ~CCM_TARGET_ROOT_AUTO_PODF;
		v |= __SHIFTIN(d3, CCM_TARGET_ROOT_AUTO_PODF);
		v |= CCM_TARGET_ROOT_AUTO_ENABLE;
		v &= ~CCM_TARGET_ROOT_POST_PODF;
		v |= __SHIFTIN(d6, CCM_TARGET_ROOT_POST_PODF);
		imx7_ccm_write(imx7clkroottbl[clk].targetroot, v);
		break;

	/* bus type clocks */
	case IMX7CLK_ARM_M4_CLK_ROOT:
	case IMX7CLK_MAIN_AXI_CLK_ROOT:
	case IMX7CLK_DISP_AXI_CLK_ROOT:
		maxfreq = rootclk(clk, true);
		getrootclkdiv(maxfreq, freq, &d3, &d6);
		/* bus type clocks use PRE_PODF and POST_PODF */
		v = imx7_ccm_read(imx7clkroottbl[clk].targetroot);
		v &= ~CCM_TARGET_ROOT_PRE_PODF;
		v |= __SHIFTIN(d3, CCM_TARGET_ROOT_PRE_PODF);
		v &= ~CCM_TARGET_ROOT_POST_PODF;
		v |= __SHIFTIN(d6, CCM_TARGET_ROOT_POST_PODF);
		imx7_ccm_write(imx7clkroottbl[clk].targetroot, v);
		break;

	default:
		aprint_error_dev(ccm_softc->sc_dev,
		    "%s: clockid %d: not supported\n", __func__, clk);
		return EINVAL;
	}

	return 0;
}

int
imx7_pll_power(uint32_t pllreg, int on)
{
	uint32_t v;
	int timeout;

	switch (pllreg) {
	case CCM_ANALOG_PLL_ENET:
		v = imx7_ccm_analog_read(pllreg);
		if (on)
			v &= ~CCM_ANALOG_PLL_ENET_POWERDOWN;
		else
			v |= CCM_ANALOG_PLL_ENET_POWERDOWN;
		imx7_ccm_analog_write(pllreg, v);

		for (timeout = 100000; timeout > 0; timeout--) {
			if (imx7_ccm_analog_read(pllreg) &
			    CCM_ANALOG_PLL_ENET_LOCK)
				break;
		}
		if (timeout <= 0)
			break;

		if (on) {
			v &= ~CCM_ANALOG_PLL_ENET_BYPASS;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_500MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_250MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_125MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_100MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_50MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_40MHZ;
			v |= CCM_ANALOG_PLL_ENET_ENABLE_CLK_25MHZ;
		} else {
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_500MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_250MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_125MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_100MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_50MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_40MHZ;
			v &= ~CCM_ANALOG_PLL_ENET_ENABLE_CLK_25MHZ;
		}
		imx7_ccm_analog_write(pllreg, v);
		return 0;

	case CCM_ANALOG_PLL_ARM:
	case CCM_ANALOG_PLL_DDR:
	case CCM_ANALOG_PLL_480:
	case CCM_ANALOG_PLL_AUDIO:
	case CCM_ANALOG_PLL_VIDEO:
	default:
		break;
	}

	return -1;
}
