Module Name:    src
Committed By:   jmcneill
Date:           Fri Jan  2 21:59:29 UTC 2015

Modified Files:
        src/sys/arch/arm/rockchip: files.rockchip obio.c rockchip_board.c
            rockchip_var.h
Added Files:
        src/sys/arch/arm/rockchip: rockchip_cpufreq.c

Log Message:
Add RK3188/RK3188+ CPU frequency setting support.


To generate a diff of this commit:
cvs rdiff -u -r1.5 -r1.6 src/sys/arch/arm/rockchip/files.rockchip
cvs rdiff -u -r1.8 -r1.9 src/sys/arch/arm/rockchip/obio.c \
    src/sys/arch/arm/rockchip/rockchip_var.h
cvs rdiff -u -r1.10 -r1.11 src/sys/arch/arm/rockchip/rockchip_board.c
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/rockchip/rockchip_cpufreq.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/rockchip/files.rockchip
diff -u src/sys/arch/arm/rockchip/files.rockchip:1.5 src/sys/arch/arm/rockchip/files.rockchip:1.6
--- src/sys/arch/arm/rockchip/files.rockchip:1.5	Tue Dec 30 18:57:36 2014
+++ src/sys/arch/arm/rockchip/files.rockchip	Fri Jan  2 21:59:29 2015
@@ -1,4 +1,4 @@
-#	$NetBSD: files.rockchip,v 1.5 2014/12/30 18:57:36 jmcneill Exp $
+#	$NetBSD: files.rockchip,v 1.6 2015/01/02 21:59:29 jmcneill Exp $
 #
 # Configuration info for Rockchip ARM Peripherals
 #
@@ -12,6 +12,7 @@ file	arch/arm/arm32/arm32_reboot.c
 file	arch/arm/arm32/irq_dispatch.S
 
 file	arch/arm/rockchip/rockchip_board.c
+file	arch/arm/rockchip/rockchip_cpufreq.c
 file	arch/arm/rockchip/rockchip_space.c	obio
 #file	arch/arm/rockchip/rockchip_a4x_space.c	obio
 file	arch/arm/rockchip/rockchip_dma.c

Index: src/sys/arch/arm/rockchip/obio.c
diff -u src/sys/arch/arm/rockchip/obio.c:1.8 src/sys/arch/arm/rockchip/obio.c:1.9
--- src/sys/arch/arm/rockchip/obio.c:1.8	Thu Jan  1 18:59:03 2015
+++ src/sys/arch/arm/rockchip/obio.c	Fri Jan  2 21:59:29 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: obio.c,v 1.8 2015/01/01 18:59:03 jmcneill Exp $	*/
+/*	$NetBSD: obio.c,v 1.9 2015/01/02 21:59:29 jmcneill Exp $	*/
 
 /*
  * Copyright (c) 2001, 2002, 2003 Wasabi Systems, Inc.
@@ -38,7 +38,7 @@
 #include "opt_rockchip.h"
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: obio.c,v 1.8 2015/01/01 18:59:03 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: obio.c,v 1.9 2015/01/02 21:59:29 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -282,6 +282,7 @@ obio_dump_clocks(void)
 	printf("CPU: %u Hz\n", rockchip_cpu_get_rate());
 	printf("AHB: %u Hz\n", rockchip_ahb_get_rate());
 	printf("APB: %u Hz\n", rockchip_apb_get_rate());
+	printf("PCLK_CPU: %u Hz\n", rockchip_pclk_cpu_get_rate());
 	printf("A9PERIPH: %u Hz\n", rockchip_a9periph_get_rate());
 }
 #endif
Index: src/sys/arch/arm/rockchip/rockchip_var.h
diff -u src/sys/arch/arm/rockchip/rockchip_var.h:1.8 src/sys/arch/arm/rockchip/rockchip_var.h:1.9
--- src/sys/arch/arm/rockchip/rockchip_var.h:1.8	Tue Dec 30 17:15:31 2014
+++ src/sys/arch/arm/rockchip/rockchip_var.h	Fri Jan  2 21:59:29 2015
@@ -1,4 +1,4 @@
-/* $NetBSD: rockchip_var.h,v 1.8 2014/12/30 17:15:31 jmcneill Exp $ */
+/* $NetBSD: rockchip_var.h,v 1.9 2015/01/02 21:59:29 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -59,18 +59,20 @@ extern bus_space_handle_t rockchip_core1
 
 void rockchip_bootstrap(void);
 
+void rockchip_cpufreq_init(void);
+
 bool rockchip_is_chip(const char *);
 #define ROCKCHIP_CHIPVER_RK3066		"300A20111111V101"
 #define ROCKCHIP_CHIPVER_RK3188		"310B20121130V100"
 #define ROCKCHIP_CHIPVER_RK3188PLUS	"310B20130131V101"
 
 u_int rockchip_apll_get_rate(void);
-u_int rockchip_apll_set_rate(u_int);
 u_int rockchip_cpll_get_rate(void);
 u_int rockchip_gpll_get_rate(void);
 u_int rockchip_cpu_get_rate(void);
 u_int rockchip_ahb_get_rate(void);
 u_int rockchip_apb_get_rate(void);
+u_int rockchip_pclk_cpu_get_rate(void);
 u_int rockchip_a9periph_get_rate(void);
 u_int rockchip_mmc0_get_rate(void);
 u_int rockchip_mmc0_set_div(u_int);

Index: src/sys/arch/arm/rockchip/rockchip_board.c
diff -u src/sys/arch/arm/rockchip/rockchip_board.c:1.10 src/sys/arch/arm/rockchip/rockchip_board.c:1.11
--- src/sys/arch/arm/rockchip/rockchip_board.c:1.10	Wed Dec 31 18:14:14 2014
+++ src/sys/arch/arm/rockchip/rockchip_board.c	Fri Jan  2 21:59:29 2015
@@ -1,4 +1,4 @@
-/* $NetBSD: rockchip_board.c,v 1.10 2014/12/31 18:14:14 jmcneill Exp $ */
+/* $NetBSD: rockchip_board.c,v 1.11 2015/01/02 21:59:29 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2014 Jared D. McNeill <[email protected]>
@@ -29,7 +29,7 @@
 #include "opt_rockchip.h"
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: rockchip_board.c,v 1.10 2014/12/31 18:14:14 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rockchip_board.c,v 1.11 2015/01/02 21:59:29 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -83,13 +83,6 @@ rockchip_get_cru_bsh(bus_space_handle_t 
 	    ROCKCHIP_CRU_OFFSET, ROCKCHIP_CRU_SIZE, pbsh);
 }
 
-static void
-rockchip_get_grf_bsh(bus_space_handle_t *pbsh)
-{
-	bus_space_subregion(&rockchip_bs_tag, rockchip_core1_bsh,
-	    ROCKCHIP_GRF_OFFSET, ROCKCHIP_GRF_SIZE, pbsh);
-}
-
 static u_int
 rockchip_pll_get_rate(bus_size_t con0_reg, bus_size_t con1_reg)
 {
@@ -134,203 +127,6 @@ rockchip_apll_get_rate(void)
 	return rockchip_pll_get_rate(CRU_APLL_CON0_REG, CRU_APLL_CON1_REG);
 }
 
-struct rk3188_apll_rate {
-	u_int rate;
-	u_int nr, nf, no;
-	u_int core_div, core_periph_div, core_axi_div;
-	u_int aclk_div, hclk_div, pclk_div, ahb2apb_div;
-};
-
-#define RK3188_RATE(_r, _nf, _no, _p, _a, _aclk, _hclk, _pclk, _ahb2apb) \
-	{ .rate = (_r) * 1000000, .nr = 1, .nf = (_nf), .no = (_no),	 \
-	  .core_div = 1, .core_periph_div = (_p), .core_axi_div = (_a),	 \
-	  .aclk_div = (_aclk), .hclk_div = (_hclk), .pclk_div = (_pclk), \
-	  .ahb2apb_div = (_ahb2apb) }
-
-static const struct rk3188_apll_rate rk3188_apll_rates[] = {
-	RK3188_RATE(1608, 67, 1, 8, 4, 4, 2, 4, 2),
-	RK3188_RATE(1416, 59, 1, 8, 4, 4, 2, 4, 2),
-	RK3188_RATE(1200, 50, 1, 8, 4, 4, 2, 4, 2),
-	RK3188_RATE(1008, 42, 1, 8, 4, 3, 2, 4, 2),
-	RK3188_RATE( 816, 68, 2, 8, 4, 3, 2, 4, 2),
-	RK3188_RATE( 600, 50, 2, 4, 4, 3, 2, 4, 2),
-};
-
-#define RK3188_GRF_STATUS0_REG		0x00ac
-#define RK3188_GRF_STATUS0_APLL_LOCK	__BIT(6)
-
-static u_int
-rk3188_apll_set_rate(u_int rate)
-{
-	const struct rk3188_apll_rate *r = NULL;
-	bus_space_tag_t bst = &rockchip_bs_tag;
-	bus_space_handle_t bsh, grf_bsh;
-	uint32_t apll_con0, apll_con1, apll_con2, clksel0_con, clksel1_con;
-	uint32_t reset_mask, reset;
-	u_int cpu_aclk_div_con;
-	const bool rk3188plus_p = rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188PLUS);
-
-	rockchip_get_cru_bsh(&bsh);
-	rockchip_get_grf_bsh(&grf_bsh);
-
-#ifdef ROCKCHIP_CLOCK_DEBUG
-	printf("%s: rate=%u rk3188plus_p=%d\n", __func__, rate, rk3188plus_p);
-#endif
-
-	/* Pick the closest rate (nearest 100MHz increment) */
-	for (int i = 0; i < __arraycount(rk3188_apll_rates); i++) {
-		u_int trate = rate / 1000000;
-		u_int arate = ((rk3188_apll_rates[i].rate / 1000000) + 50)
-		    / 100 * 100;
-		if (arate <= trate) {
-			r = &rk3188_apll_rates[i];
-			break;
-		}
-	}
-	if (r == NULL) {
-#ifdef ROCKCHIP_CLOCK_DEBUG
-		printf("CPU: No matching rate found for %u MHz\n", rate);
-#endif
-		return ENOENT;
-	}
-
-	printf("CPU: Set frequency to %u MHz...\n", r->rate / 1000000);
-
-	if (rk3188plus_p) {
-	    	reset_mask = CRU_PLL_CON3_RESET_MASK;
-		reset = CRU_PLL_CON3_RESET;
-	} else {
-		reset_mask = CRU_PLL_CON3_POWER_DOWN_MASK;
-		reset = CRU_PLL_CON3_POWER_DOWN;
-	}
-
-	apll_con0 = CRU_PLL_CON0_CLKR_MASK | CRU_PLL_CON0_CLKOD_MASK;
-	apll_con0 |= __SHIFTIN(r->no - 1, CRU_PLL_CON0_CLKOD);
-	apll_con0 |= __SHIFTIN(r->nr - 1, CRU_PLL_CON0_CLKR);
-
-	apll_con1 = CRU_PLL_CON1_CLKF_MASK;
-	apll_con1 |= __SHIFTIN(r->nf - 1, CRU_PLL_CON1_CLKF);
-
-	if (rk3188plus_p) {
-		apll_con2 = CRU_PLL_CON2_BWADJ_MASK;
-		apll_con2 |= __SHIFTIN(r->nf >> 1, CRU_PLL_CON2_BWADJ);
-	} else {
-		apll_con2 = 0;
-	}
-
-	clksel0_con = RK3188_CRU_CLKSEL_CON0_A9_CORE_DIV_CON_MASK |
-		      CRU_CLKSEL_CON0_CORE_PERI_DIV_CON_MASK |
-		      CRU_CLKSEL_CON0_A9_CORE_DIV_CON_MASK;
-	clksel0_con |= __SHIFTIN(r->core_div - 1,
-				 RK3188_CRU_CLKSEL_CON0_A9_CORE_DIV_CON);
-	clksel0_con |= __SHIFTIN(ffs(r->core_periph_div) - 2,
-				 CRU_CLKSEL_CON0_CORE_PERI_DIV_CON);
-	clksel0_con |= __SHIFTIN(r->aclk_div - 1,
-				 CRU_CLKSEL_CON0_A9_CORE_DIV_CON);
-
-	clksel1_con = CRU_CLKSEL_CON1_AHB2APB_PCLKEN_DIV_CON_MASK |
-		      CRU_CLKSEL_CON1_CPU_PCLK_DIV_CON_MASK |
-		      CRU_CLKSEL_CON1_CPU_HCLK_DIV_CON_MASK |
-		      RK3188_CRU_CLKSEL_CON1_CPU_ACLK_DIV_CON_MASK;
-
-	switch (r->core_axi_div) {
-	case 1:	cpu_aclk_div_con = 0; break;
-	case 2: cpu_aclk_div_con = 1; break;
-	case 3: cpu_aclk_div_con = 2; break;
-	case 4: cpu_aclk_div_con = 3; break;
-	case 8: cpu_aclk_div_con = 4; break;
-	default: panic("bad core_axi_div");
-	}
-	clksel1_con |= __SHIFTIN(ffs(r->ahb2apb_div) - 1,
-				 CRU_CLKSEL_CON1_AHB2APB_PCLKEN_DIV_CON);
-	clksel1_con |= __SHIFTIN(ffs(r->hclk_div) - 1,
-				 CRU_CLKSEL_CON1_CPU_HCLK_DIV_CON);
-	clksel1_con |= __SHIFTIN(ffs(r->pclk_div) - 1,
-				 CRU_CLKSEL_CON1_CPU_PCLK_DIV_CON);
-	clksel1_con |= __SHIFTIN(cpu_aclk_div_con,
-				 RK3188_CRU_CLKSEL_CON1_CPU_ACLK_DIV_CON);
-
-#ifdef ROCKCHIP_CLOCK_DEBUG
-	printf("before: APLL_CON0: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_APLL_CON0_REG));
-	printf("before: APLL_CON1: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_APLL_CON1_REG));
-	printf("before: CLKSEL0_CON: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_CLKSEL_CON_REG(0)));
-	printf("before: CLKSEL1_CON: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_CLKSEL_CON_REG(1)));
-#endif
-
-	bus_space_write_4(bst, bsh, CRU_MODE_CON_REG,
-	    CRU_MODE_CON_APLL_WORK_MODE_MASK |
-	    __SHIFTIN(CRU_MODE_CON_APLL_WORK_MODE_SLOW,
-		      CRU_MODE_CON_APLL_WORK_MODE));
-
-	/* Power down */
-	bus_space_write_4(bst, bsh, CRU_APLL_CON3_REG, reset_mask | reset);
-
-	/* Update APLL regs */
-	bus_space_write_4(bst, bsh, CRU_APLL_CON0_REG, apll_con0);
-	bus_space_write_4(bst, bsh, CRU_APLL_CON1_REG, apll_con1);
-	if (apll_con2)
-		bus_space_write_4(bst, bsh, CRU_APLL_CON2_REG, apll_con2);
-
-	for (volatile int i = 5000; i >= 0; i--)
-		;
-
-	/* Power up */
-	bus_space_write_4(bst, bsh, CRU_APLL_CON3_REG, reset_mask);
-
-	/* Wait for PLL lock */
-	printf("CPU: Waiting for PLL lock...\n");
-	for (volatile int i = 50000; i >= 0; i--)
-		;
-	int retry = ROCKCHIP_REF_FREQ;
-	while (--retry > 0) {
-		uint32_t status = bus_space_read_4(bst, grf_bsh,
-		    RK3188_GRF_STATUS0_REG);
-		if (status & RK3188_GRF_STATUS0_APLL_LOCK)
-			break;
-		for (volatile int i = 1000; i >= 0; i--)
-			;
-	}
-	printf("CPU: PLL lock %s\n", retry == 0 ? "timeout!" : "OK");
-
-	/* Update CLKSEL regs */
-	bus_space_write_4(bst, bsh, CRU_CLKSEL_CON_REG(0), clksel0_con);
-	bus_space_write_4(bst, bsh, CRU_CLKSEL_CON_REG(1), clksel1_con);
-
-	/* Slow -> Normal mode */
-	bus_space_write_4(bst, bsh, CRU_MODE_CON_REG,
-	    CRU_MODE_CON_APLL_WORK_MODE_MASK |
-	    __SHIFTIN(CRU_MODE_CON_APLL_WORK_MODE_NORMAL,
-		      CRU_MODE_CON_APLL_WORK_MODE));
-
-#ifdef ROCKCHIP_CLOCK_DEBUG
-	printf("after: APLL_CON0: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_APLL_CON0_REG));
-	printf("after: APLL_CON1: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_APLL_CON1_REG));
-	printf("after: CLKSEL0_CON: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_CLKSEL_CON_REG(0)));
-	printf("after: CLKSEL1_CON: %#x\n",
-	    bus_space_read_4(bst, bsh, CRU_CLKSEL_CON_REG(1)));
-#endif
-
-	return 0;
-}
-
-u_int
-rockchip_apll_set_rate(u_int rate)
-{
-	if (rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188) ||
-	    rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188PLUS)) {
-		return rk3188_apll_set_rate(rate);
-	}
-
-	return ENODEV;
-}
-
 u_int
 rockchip_cpu_get_rate(void)
 {
@@ -384,6 +180,32 @@ rockchip_a9periph_get_rate(void)
 }
 
 u_int
+rockchip_pclk_cpu_get_rate(void)
+{
+	bus_space_tag_t bst = &rockchip_bs_tag;
+	bus_space_handle_t bsh;
+	uint32_t clksel_con1;
+	u_int core_axi_div, pclk_div;
+
+	rockchip_get_cru_bsh(&bsh);
+
+	clksel_con1 = bus_space_read_4(bst, bsh, CRU_CLKSEL_CON_REG(1));
+	switch (__SHIFTOUT(clksel_con1,
+			   RK3188_CRU_CLKSEL_CON1_CPU_ACLK_DIV_CON)) {
+	case 0: core_axi_div = 1; break;
+	case 1: core_axi_div = 2; break;
+	case 2: core_axi_div = 3; break;
+	case 3: core_axi_div = 4; break;
+	case 4: core_axi_div = 8; break;
+	default: return EINVAL;
+	}
+	pclk_div = 1 << __SHIFTOUT(clksel_con1,
+				   CRU_CLKSEL_CON1_CPU_PCLK_DIV_CON);
+
+	return rockchip_cpu_get_rate() / (core_axi_div * pclk_div);
+}
+
+u_int
 rockchip_ahb_get_rate(void)
 {
 	bus_space_tag_t bst = &rockchip_bs_tag;
@@ -481,7 +303,7 @@ u_int
 rockchip_i2c_get_rate(u_int port)
 {
 	if (port == 0 || port == 1) {
-		return rockchip_a9periph_get_rate();
+		return rockchip_pclk_cpu_get_rate();
 	} else {
 		return rockchip_apb_get_rate();
 	}

Added files:

Index: src/sys/arch/arm/rockchip/rockchip_cpufreq.c
diff -u /dev/null src/sys/arch/arm/rockchip/rockchip_cpufreq.c:1.1
--- /dev/null	Fri Jan  2 21:59:29 2015
+++ src/sys/arch/arm/rockchip/rockchip_cpufreq.c	Fri Jan  2 21:59:29 2015
@@ -0,0 +1,443 @@
+/* $NetBSD: rockchip_cpufreq.c,v 1.1 2015/01/02 21:59:29 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2015 Jared D. McNeill <[email protected]>
+ * All rights reserved.
+ *
+ * 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 THE AUTHOR ``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 THE AUTHOR 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.
+ */
+
+#include "locators.h"
+#include "act8846pm.h"
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: rockchip_cpufreq.c,v 1.1 2015/01/02 21:59:29 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/atomic.h>
+#include <sys/kmem.h>
+#include <sys/xcall.h>
+#include <sys/sysctl.h>
+
+#include <arm/rockchip/rockchip_reg.h>
+#include <arm/rockchip/rockchip_crureg.h>
+#include <arm/rockchip/rockchip_var.h>
+
+#include <dev/i2c/act8846.h>
+
+#include <arm/cortex/a9tmr_var.h>
+
+static bus_space_tag_t bst = &rockchip_bs_tag;
+static bus_space_handle_t cru_bsh;
+static bus_space_handle_t grf_bsh;
+
+static u_int cpufreq_busy;
+static struct sysctllog *cpufreq_log;
+static int cpufreq_node_target, cpufreq_node_current, cpufreq_node_available;
+static u_int (*cpufreq_set_rate)(u_int);
+static u_int (*cpufreq_get_rate)(void);
+static size_t (*cpufreq_get_available)(u_int *, size_t);
+
+#define ROCKCHIP_CPUFREQ_MAX	8
+
+static void	rockchip_cpufreq_cb(void *, void *);
+static int	rockchip_cpufreq_freq_helper(SYSCTLFN_PROTO);
+static char 	rockchip_cpufreq_available[ROCKCHIP_CPUFREQ_MAX * 5];
+
+static u_int	rk3188_cpu_set_rate(u_int);
+static u_int	rk3188_cpu_get_rate(void);
+static size_t	rk3188_cpu_get_available(u_int *, size_t);
+
+void
+rockchip_cpufreq_init(void)
+{
+	const struct sysctlnode *node, *cpunode, *freqnode;
+	u_int availfreq[ROCKCHIP_CPUFREQ_MAX];
+	size_t nfreq;
+	int error;
+
+	bus_space_subregion(bst, rockchip_core1_bsh, ROCKCHIP_CRU_OFFSET,
+	    ROCKCHIP_CRU_SIZE, &cru_bsh);
+	bus_space_subregion(bst, rockchip_core1_bsh, ROCKCHIP_GRF_OFFSET,
+	    ROCKCHIP_GRF_SIZE, &grf_bsh);
+
+	if (rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188) ||
+	    rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188PLUS)) {
+		cpufreq_set_rate = &rk3188_cpu_set_rate;
+		cpufreq_get_rate = &rk3188_cpu_get_rate;
+		cpufreq_get_available = &rk3188_cpu_get_available;
+	}
+
+	if (cpufreq_set_rate == NULL ||
+	    cpufreq_get_rate == NULL ||
+	    cpufreq_get_available == NULL)
+		return;
+
+	nfreq = cpufreq_get_available(availfreq, ROCKCHIP_CPUFREQ_MAX);
+	if (nfreq == 0)
+		return;
+
+	KASSERT(nfreq <= ROCKCHIP_CPUFREQ_MAX);
+
+	for (int i = 0; i < nfreq; i++) {
+		char buf[6];
+		snprintf(buf, sizeof(buf), i ? " %u" : "%u", availfreq[i]);
+		strcat(rockchip_cpufreq_available, buf);
+	}
+
+	error = sysctl_createv(&cpufreq_log, 0, NULL, &node,
+	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
+	    NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	error = sysctl_createv(&cpufreq_log, 0, &node, &cpunode,
+	    0, CTLTYPE_NODE, "cpu", NULL,
+	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	error = sysctl_createv(&cpufreq_log, 0, &cpunode, &freqnode,
+	    0, CTLTYPE_NODE, "frequency", NULL,
+	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+
+	error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
+	    rockchip_cpufreq_freq_helper, 0, NULL, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	cpufreq_node_target = node->sysctl_num;
+
+	error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "current", NULL,
+	    rockchip_cpufreq_freq_helper, 0, NULL, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	cpufreq_node_current = node->sysctl_num;
+
+	error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
+	    0, CTLTYPE_STRING, "available", NULL,
+	    NULL, 0, rockchip_cpufreq_available, 0,
+	    CTL_CREATE, CTL_EOL);
+	if (error)
+		goto sysctl_failed;
+	cpufreq_node_available = node->sysctl_num;
+
+	return;
+
+sysctl_failed:
+	aprint_error("cpufreq: couldn't create sysctl nodes (%d)\n", error);
+	sysctl_teardown(&cpufreq_log);
+}
+
+static void
+rockchip_cpufreq_cb(void *arg1, void *arg2)
+{
+	struct cpu_info *ci = curcpu();
+
+	ci->ci_data.cpu_cc_freq = cpufreq_get_rate() * 1000000;
+}
+
+static int
+rockchip_cpufreq_freq_helper(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node;
+	int fq, oldfq = 0, error;
+	uint64_t xc;
+
+	node = *rnode;
+	node.sysctl_data = &fq;
+
+	fq = cpufreq_get_rate();
+	if (rnode->sysctl_num == cpufreq_node_target)
+		oldfq = fq;
+
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	if (fq == oldfq || rnode->sysctl_num != cpufreq_node_target)
+		return 0;
+
+	if (atomic_cas_uint(&cpufreq_busy, 0, 1) != 0)
+		return EBUSY;
+
+	error = cpufreq_set_rate(fq);
+	if (error == 0) {
+		xc = xc_broadcast(0, rockchip_cpufreq_cb, NULL, NULL);
+		xc_wait(xc);
+
+		pmf_event_inject(NULL, PMFE_SPEED_CHANGED);
+	}
+
+	atomic_dec_uint(&cpufreq_busy);
+
+	return error;
+}
+
+/*
+ * RK3188 / RK3188+
+ */
+struct rk3188_apll_rate {
+	u_int rate;
+	u_int nr, nf, no;
+	u_int core_div, core_periph_div, core_axi_div;
+	u_int aclk_div, hclk_div, pclk_div, ahb2apb_div;
+	u_int voltage;
+};
+
+#define RK3188_RATE(_r, _nf, _no, _p, _a, _aclk, _hclk, _pclk, _ahb2apb, _v) \
+	{ .rate = (_r) * 1000000, .nr = 1, .nf = (_nf), .no = (_no),     \
+	  .core_div = 1, .core_periph_div = (_p), .core_axi_div = (_a),  \
+	  .aclk_div = (_aclk), .hclk_div = (_hclk), .pclk_div = (_pclk), \
+	  .ahb2apb_div = (_ahb2apb), .voltage = (_v) }
+
+static const struct rk3188_apll_rate rk3188_apll_rates[] = {
+	RK3188_RATE(1608, 67, 1, 8, 4, 4, 2, 4, 2, 1350),
+	RK3188_RATE(1416, 59, 1, 8, 4, 4, 2, 4, 2, 1250),
+	RK3188_RATE(1200, 50, 1, 8, 4, 4, 2, 4, 2, 1200),
+	RK3188_RATE(1008, 42, 1, 8, 4, 3, 2, 4, 2, 1075),
+	RK3188_RATE( 816, 68, 2, 8, 4, 3, 2, 4, 2, 1000),
+	RK3188_RATE( 600, 50, 2, 4, 4, 3, 2, 4, 2, 1000),
+};
+
+#define RK3188_GRF_STATUS0_REG          0x00ac
+#define RK3188_GRF_STATUS0_APLL_LOCK    __BIT(6)
+
+static size_t
+rk3188_cpu_get_available(u_int *pavail, size_t maxavail)
+{
+	u_int n;
+
+	KASSERT(__arraycount(rk3188_apll_rates) <= maxavail);
+
+	for (n = 0; n < __arraycount(rk3188_apll_rates); n++) {
+		pavail[n] = rk3188_apll_rates[n].rate / 1000000;
+	}
+
+	return __arraycount(rk3188_apll_rates);
+}
+
+static u_int
+rk3188_cpu_get_rate(void)
+{
+	return rockchip_cpu_get_rate() / 1000000;
+}
+
+static u_int
+rk3188_cpu_set_rate(u_int rate)
+{
+	const struct rk3188_apll_rate *r = NULL;
+	uint32_t apll_con0, apll_con1, apll_con2, clksel0_con, clksel1_con;
+	uint32_t reset_mask, reset;
+	u_int cpu_aclk_div_con;
+	const bool rk3188plus_p = rockchip_is_chip(ROCKCHIP_CHIPVER_RK3188PLUS);
+	device_t pmic;
+	u_int old_rate = rk3188_cpu_get_rate();
+	u_int new_rate;
+#if NACT8846PM > 0
+	struct act8846_ctrl *dcdc3;
+#endif
+
+#ifdef ROCKCHIP_CLOCK_DEBUG
+	printf("%s: rate=%u rk3188plus_p=%d\n", __func__, rate, rk3188plus_p);
+#endif
+
+	pmic = device_find_by_driver_unit("act8846pm", 0);
+	if (pmic == NULL) {
+		printf("%s: no PMIC driver found\n", __func__);
+		return ENXIO;
+	}
+
+	/* Pick the closest rate (nearest 100MHz increment) */
+	for (int i = 0; i < __arraycount(rk3188_apll_rates); i++) {
+		u_int arate = ((rk3188_apll_rates[i].rate / 1000000) + 50)
+		    / 100 * 100;
+		if (arate <= rate) {
+			r = &rk3188_apll_rates[i];
+			break;
+		}
+	}
+	if (r == NULL) {
+#ifdef ROCKCHIP_CLOCK_DEBUG
+		printf("CPU: No matching rate found for %u MHz\n", rate);
+#endif
+		return EINVAL;
+	}
+
+#if NACT8846PM > 0
+	dcdc3 = act8846_lookup(pmic, "DCDC3");
+	KASSERT(dcdc3 != NULL);
+#endif
+
+#ifdef ROCKCHIP_CLOCK_DEBUG
+	printf("%s: Set frequency to %u MHz...\n", __func__, r->rate);
+#endif
+
+	new_rate = r->rate / 1000000;
+	if (new_rate > old_rate) {
+#if NACT8846PM > 0
+		act8846_set_voltage(dcdc3, r->voltage, r->voltage);
+#endif
+	}
+
+	if (rk3188plus_p) {
+	    	reset_mask = CRU_PLL_CON3_RESET_MASK;
+		reset = CRU_PLL_CON3_RESET;
+	} else {
+		reset_mask = CRU_PLL_CON3_POWER_DOWN_MASK;
+		reset = CRU_PLL_CON3_POWER_DOWN;
+	}
+
+	apll_con0 = CRU_PLL_CON0_CLKR_MASK | CRU_PLL_CON0_CLKOD_MASK;
+	apll_con0 |= __SHIFTIN(r->no - 1, CRU_PLL_CON0_CLKOD);
+	apll_con0 |= __SHIFTIN(r->nr - 1, CRU_PLL_CON0_CLKR);
+
+	apll_con1 = CRU_PLL_CON1_CLKF_MASK;
+	apll_con1 |= __SHIFTIN(r->nf - 1, CRU_PLL_CON1_CLKF);
+
+	if (rk3188plus_p) {
+		apll_con2 = CRU_PLL_CON2_BWADJ_MASK;
+		apll_con2 |= __SHIFTIN(r->nf >> 1, CRU_PLL_CON2_BWADJ);
+	} else {
+		apll_con2 = 0;
+	}
+
+	clksel0_con = RK3188_CRU_CLKSEL_CON0_A9_CORE_DIV_CON_MASK |
+		      CRU_CLKSEL_CON0_CORE_PERI_DIV_CON_MASK |
+		      CRU_CLKSEL_CON0_A9_CORE_DIV_CON_MASK;
+	clksel0_con |= __SHIFTIN(r->core_div - 1,
+				 RK3188_CRU_CLKSEL_CON0_A9_CORE_DIV_CON);
+	clksel0_con |= __SHIFTIN(ffs(r->core_periph_div) - 2,
+				 CRU_CLKSEL_CON0_CORE_PERI_DIV_CON);
+	clksel0_con |= __SHIFTIN(r->aclk_div - 1,
+				 CRU_CLKSEL_CON0_A9_CORE_DIV_CON);
+
+	clksel1_con = CRU_CLKSEL_CON1_AHB2APB_PCLKEN_DIV_CON_MASK |
+		      CRU_CLKSEL_CON1_CPU_PCLK_DIV_CON_MASK |
+		      CRU_CLKSEL_CON1_CPU_HCLK_DIV_CON_MASK |
+		      RK3188_CRU_CLKSEL_CON1_CPU_ACLK_DIV_CON_MASK;
+
+	switch (r->core_axi_div) {
+	case 1:	cpu_aclk_div_con = 0; break;
+	case 2: cpu_aclk_div_con = 1; break;
+	case 3: cpu_aclk_div_con = 2; break;
+	case 4: cpu_aclk_div_con = 3; break;
+	case 8: cpu_aclk_div_con = 4; break;
+	default: panic("bad core_axi_div");
+	}
+	clksel1_con |= __SHIFTIN(ffs(r->ahb2apb_div) - 1,
+				 CRU_CLKSEL_CON1_AHB2APB_PCLKEN_DIV_CON);
+	clksel1_con |= __SHIFTIN(ffs(r->hclk_div) - 1,
+				 CRU_CLKSEL_CON1_CPU_HCLK_DIV_CON);
+	clksel1_con |= __SHIFTIN(ffs(r->pclk_div) - 1,
+				 CRU_CLKSEL_CON1_CPU_PCLK_DIV_CON);
+	clksel1_con |= __SHIFTIN(cpu_aclk_div_con,
+				 RK3188_CRU_CLKSEL_CON1_CPU_ACLK_DIV_CON);
+
+#ifdef ROCKCHIP_CLOCK_DEBUG
+	printf("before: APLL_CON0: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_APLL_CON0_REG));
+	printf("before: APLL_CON1: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_APLL_CON1_REG));
+	printf("before: CLKSEL0_CON: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(0)));
+	printf("before: CLKSEL1_CON: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(1)));
+#endif
+
+	bus_space_write_4(bst, cru_bsh, CRU_MODE_CON_REG,
+	    CRU_MODE_CON_APLL_WORK_MODE_MASK |
+	    __SHIFTIN(CRU_MODE_CON_APLL_WORK_MODE_SLOW,
+		      CRU_MODE_CON_APLL_WORK_MODE));
+
+	/* Power down */
+	bus_space_write_4(bst, cru_bsh, CRU_APLL_CON3_REG, reset_mask | reset);
+
+	/* Update APLL regs */
+	bus_space_write_4(bst, cru_bsh, CRU_APLL_CON0_REG, apll_con0);
+	bus_space_write_4(bst, cru_bsh, CRU_APLL_CON1_REG, apll_con1);
+	if (apll_con2)
+		bus_space_write_4(bst, cru_bsh, CRU_APLL_CON2_REG, apll_con2);
+
+	for (volatile int i = 5000; i >= 0; i--)
+		;
+
+	/* Power up */
+	bus_space_write_4(bst, cru_bsh, CRU_APLL_CON3_REG, reset_mask);
+
+	/* Wait for PLL lock */
+#ifdef ROCKCHIP_CLOCK_DEBUG
+	printf("%s: Waiting for PLL lock...\n", __func__);
+#endif
+	for (volatile int i = 50000; i >= 0; i--)
+		;
+	int retry = ROCKCHIP_REF_FREQ;
+	while (--retry > 0) {
+		uint32_t status = bus_space_read_4(bst, grf_bsh,
+		    RK3188_GRF_STATUS0_REG);
+		if (status & RK3188_GRF_STATUS0_APLL_LOCK)
+			break;
+		for (volatile int i = 1000; i >= 0; i--)
+			;
+	}
+	if (retry == 0)
+		printf("%s: PLL lock timeout\n", __func__);
+
+	/* Update CLKSEL regs */
+	bus_space_write_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(0), clksel0_con);
+	bus_space_write_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(1), clksel1_con);
+
+	/* Slow -> Normal mode */
+	bus_space_write_4(bst, cru_bsh, CRU_MODE_CON_REG,
+	    CRU_MODE_CON_APLL_WORK_MODE_MASK |
+	    __SHIFTIN(CRU_MODE_CON_APLL_WORK_MODE_NORMAL,
+		      CRU_MODE_CON_APLL_WORK_MODE));
+
+#ifdef ROCKCHIP_CLOCK_DEBUG
+	printf("after: APLL_CON0: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_APLL_CON0_REG));
+	printf("after: APLL_CON1: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_APLL_CON1_REG));
+	printf("after: CLKSEL0_CON: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(0)));
+	printf("after: CLKSEL1_CON: %#x\n",
+	    bus_space_read_4(bst, cru_bsh, CRU_CLKSEL_CON_REG(1)));
+#endif
+
+	a9tmr_update_freq(rockchip_a9periph_get_rate());
+
+#if NACT8846PM > 0
+	if (new_rate < old_rate) {
+		act8846_set_voltage(dcdc3, r->voltage, r->voltage);
+	}
+#endif
+
+	return 0;
+}

Reply via email to