rockchip: rk3288: Add i2s pinctrl and clock support

Add support for setting pinctrl and clock for I2S on rk3288. This allows
the sound driver to operate. These settings were created by rkmux.py

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3288.h b/arch/arm/include/asm/arch-rockchip/cru_rk3288.h
index 0475598..e891f20 100644
--- a/arch/arm/include/asm/arch-rockchip/cru_rk3288.h
+++ b/arch/arm/include/asm/arch-rockchip/cru_rk3288.h
@@ -75,6 +75,14 @@
 	MMC0_DIV_MASK		= 0x3f << MMC0_DIV_SHIFT,
 };
 
+/* CRU_CLKSEL8_CON */
+enum {
+	I2S0_FRAC_DENOM_SHIFT	= 0,
+	I2S0_FRAC_DENOM_MASK	= 0xffff << I2S0_FRAC_DENOM_SHIFT,
+	I2S0_FRAC_NUMER_SHIFT	= 16,
+	I2S0_FRAC_NUMER_MASK	= 0xffffu << I2S0_FRAC_NUMER_SHIFT,
+};
+
 /* CRU_CLKSEL12_CON */
 enum {
 	EMMC_PLL_SHIFT		= 0xe,
diff --git a/arch/arm/include/asm/arch-rockchip/grf_rk3288.h b/arch/arm/include/asm/arch-rockchip/grf_rk3288.h
index c235607..7729544 100644
--- a/arch/arm/include/asm/arch-rockchip/grf_rk3288.h
+++ b/arch/arm/include/asm/arch-rockchip/grf_rk3288.h
@@ -561,6 +561,49 @@
 	GPIO5C0_TS0_SYNC,
 };
 
+/* GRF_GPIO6A_IOMUX */
+enum {
+	GPIO6A7_SHIFT		= 0xe,
+	GPIO6A7_MASK		= 1,
+	GPIO6A7_GPIO		= 0,
+	GPIO6A7_I2S_SDO3,
+
+	GPIO6A6_SHIFT		= 0xc,
+	GPIO6A6_MASK		= 1,
+	GPIO6A6_GPIO		= 0,
+	GPIO6A6_I2S_SDO2,
+
+	GPIO6A5_SHIFT		= 0xa,
+	GPIO6A5_MASK		= 1,
+	GPIO6A5_GPIO		= 0,
+	GPIO6A5_I2S_SDO1,
+
+	GPIO6A4_SHIFT		= 8,
+	GPIO6A4_MASK		= 1,
+	GPIO6A4_GPIO		= 0,
+	GPIO6A4_I2S_SDO0,
+
+	GPIO6A3_SHIFT		= 6,
+	GPIO6A3_MASK		= 1,
+	GPIO6A3_GPIO		= 0,
+	GPIO6A3_I2S_SDI,
+
+	GPIO6A2_SHIFT		= 4,
+	GPIO6A2_MASK		= 1,
+	GPIO6A2_GPIO		= 0,
+	GPIO6A2_I2S_LRCKTX,
+
+	GPIO6A1_SHIFT		= 2,
+	GPIO6A1_MASK		= 1,
+	GPIO6A1_GPIO		= 0,
+	GPIO6A1_I2S_LRCKRX,
+
+	GPIO6A0_SHIFT		= 0,
+	GPIO6A0_MASK		= 1,
+	GPIO6A0_GPIO		= 0,
+	GPIO6A0_I2S_SCLK,
+};
+
 /* GRF_GPIO6B_IOMUX */
 enum {
 	GPIO6B3_SHIFT		= 6,
@@ -1042,6 +1085,59 @@
 	RK3288_DPHY_TX0_TURNREQUEST_DIS = 0,
 };
 
+/* GRF_IO_VSEL */
+enum {
+	GPIO1830_V18SEL_SHIFT		= 9,
+	GPIO1830_V18SEL_MASK		= 1,
+	GPIO1830_V18SEL_3_3V		= 0,
+	GPIO1830_V18SEL_1_8V,
+
+	GPIO30_V18SEL_SHIFT	= 8,
+	GPIO30_V18SEL_MASK	= 1,
+	GPIO30_V18SEL_3_3V	= 0,
+	GPIO30_V18SEL_1_8V,
+
+	SDCARD_V18SEL_SHIFT	= 7,
+	SDCARD_V18SEL_MASK	= 1,
+	SDCARD_V18SEL_3_3V	= 0,
+	SDCARD_V18SEL_1_8V,
+
+	AUDIO_V18SEL_SHIFT	= 6,
+	AUDIO_V18SEL_MASK	= 1,
+	AUDIO_V18SEL_3_3V	= 0,
+	AUDIO_V18SEL_1_8V,
+
+	BB_V18SEL_SHIFT		= 5,
+	BB_V18SEL_MASK		= 1,
+	BB_V18SEL_3_3V		= 0,
+	BB_V18SEL_1_8V,
+
+	WIFI_V18SEL_SHIFT	= 4,
+	WIFI_V18SEL_MASK	= 1,
+	WIFI_V18SEL_3_3V	= 0,
+	WIFI_V18SEL_1_8V,
+
+	FLASH1_V18SEL_SHIFT	= 3,
+	FLASH1_V18SEL_MASK	= 1,
+	FLASH1_V18SEL_3_3V	= 0,
+	FLASH1_V18SEL_1_8V,
+
+	FLASH0_V18SEL_SHIFT	= 2,
+	FLASH0_V18SEL_MASK	= 1,
+	FLASH0_V18SEL_3_3V	= 0,
+	FLASH0_V18SEL_1_8V,
+
+	DVP_V18SEL_SHIFT	= 1,
+	DVP_V18SEL_MASK		= 1,
+	DVP_V18SEL_3_3V		= 0,
+	DVP_V18SEL_1_8V,
+
+	LCDC_V18SEL_SHIFT	= 0,
+	LCDC_V18SEL_MASK	= 1,
+	LCDC_V18SEL_3_3V	= 0,
+	LCDC_V18SEL_1_8V,
+};
+
 /* GPIO Bias settings */
 enum GPIO_BIAS {
 	GPIO_BIAS_2MA = 0,
diff --git a/arch/arm/include/asm/arch-rockchip/periph.h b/arch/arm/include/asm/arch-rockchip/periph.h
index 514baf6..2191b7d 100644
--- a/arch/arm/include/asm/arch-rockchip/periph.h
+++ b/arch/arm/include/asm/arch-rockchip/periph.h
@@ -45,6 +45,7 @@
 	PERIPH_ID_HDMI,
 	PERIPH_ID_GMAC,
 	PERIPH_ID_SFC,
+	PERIPH_ID_I2S,
 
 	PERIPH_ID_COUNT,
 
diff --git a/drivers/clk/rockchip/clk_rk3288.c b/drivers/clk/rockchip/clk_rk3288.c
index 4a6e5c7..930c99f 100644
--- a/drivers/clk/rockchip/clk_rk3288.c
+++ b/drivers/clk/rockchip/clk_rk3288.c
@@ -6,6 +6,7 @@
 #include <common.h>
 #include <bitfield.h>
 #include <clk-uclass.h>
+#include <div64.h>
 #include <dm.h>
 #include <dt-structs.h>
 #include <errno.h>
@@ -371,6 +372,50 @@
 
 	return 0;
 }
+
+static u32 rockchip_clk_gcd(u32 a, u32 b)
+{
+	while (b != 0) {
+		int r = b;
+
+		b = a % b;
+		a = r;
+	}
+	return a;
+}
+
+static ulong rockchip_i2s_get_clk(struct rk3288_cru *cru, uint gclk_rate)
+{
+	unsigned long long rate;
+	uint val;
+	int n, d;
+
+	val = readl(&cru->cru_clksel_con[8]);
+	n = (val & I2S0_FRAC_NUMER_MASK) >> I2S0_FRAC_NUMER_SHIFT;
+	d = (val & I2S0_FRAC_DENOM_MASK) >> I2S0_FRAC_DENOM_SHIFT;
+
+	rate = (unsigned long long)gclk_rate * n;
+	do_div(rate, d);
+
+	return (ulong)rate;
+}
+
+static ulong rockchip_i2s_set_clk(struct rk3288_cru *cru, uint gclk_rate,
+				  uint freq)
+{
+	int n, d;
+	int v;
+
+	/* set frac divider */
+	v = rockchip_clk_gcd(gclk_rate, freq);
+	n = gclk_rate / v;
+	d = freq / v;
+	assert(freq == gclk_rate / n * d);
+	writel(d << I2S0_FRAC_NUMER_SHIFT | n << I2S0_FRAC_DENOM_SHIFT,
+	       &cru->cru_clksel_con[8]);
+
+	return rockchip_i2s_get_clk(cru, gclk_rate);
+}
 #endif /* CONFIG_SPL_BUILD */
 
 static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf)
@@ -769,6 +814,9 @@
 		new_rate = rockchip_spi_set_clk(cru, gclk_rate, clk->id, rate);
 		break;
 #ifndef CONFIG_SPL_BUILD
+	case SCLK_I2S0:
+		new_rate = rockchip_i2s_set_clk(cru, gclk_rate, rate);
+		break;
 	case SCLK_MAC:
 		new_rate = rockchip_mac_set_clk(priv->cru, rate);
 		break;
diff --git a/drivers/pinctrl/rockchip/pinctrl_rk3288.c b/drivers/pinctrl/rockchip/pinctrl_rk3288.c
index 3e01cfd..fd19cfb 100644
--- a/drivers/pinctrl/rockchip/pinctrl_rk3288.c
+++ b/drivers/pinctrl/rockchip/pinctrl_rk3288.c
@@ -531,6 +531,17 @@
 		break;
 	}
 }
+
+static void pinctrl_rk3288_i2s_config(struct rk3288_grf *grf)
+{
+	rk_setreg(&grf->gpio6a_iomux, GPIO6A4_I2S_SDO0 << GPIO6A4_SHIFT |
+		  GPIO6A3_I2S_SDI << GPIO6A3_SHIFT |
+		  GPIO6A2_I2S_LRCKTX << GPIO6A2_SHIFT |
+		  GPIO6A1_I2S_LRCKRX << GPIO6A1_SHIFT |
+		  GPIO6A0_I2S_SCLK << GPIO6A0_SHIFT);
+	rk_setreg(&grf->gpio6b_iomux, GPIO6B0_I2S_CLK << GPIO6B0_SHIFT);
+	rk_setreg(&grf->io_vsel, AUDIO_V18SEL_1_8V << AUDIO_V18SEL_SHIFT);
+}
 #endif
 
 static int rk3288_pinctrl_request(struct udevice *dev, int func, int flags)
@@ -567,6 +578,9 @@
 		pinctrl_rk3288_uart_config(priv->grf, func);
 		break;
 #ifndef CONFIG_SPL_BUILD
+	case PERIPH_ID_I2S:
+		pinctrl_rk3288_i2s_config(priv->grf);
+		break;
 	case PERIPH_ID_LCDC0:
 	case PERIPH_ID_LCDC1:
 		pinctrl_rk3288_lcdc_config(priv->grf, func);