[2/2] clk: Add support for Renesas PhiClock clock generator

Message ID 20221115192625.9410-3-alexander.helms.jy@renesas.com
State New
Headers
Series Add support for Renesas PhiClock 9FGV1006 clock generator |

Commit Message

Alex Helms Nov. 15, 2022, 7:26 p.m. UTC
  Add clock device driver for Renesas PhiClock clock generator. The device
takes 1 input clock and has 1 ref out and 2 clock outputs that are the
same frequency. This driver is for the 9FGV1006 device. It supports
changing the frequency with or without spread spectrum enabled, and gating
each output clock.

Signed-off-by: Alex Helms <alexander.helms.jy@renesas.com>
---
 MAINTAINERS                |   1 +
 drivers/clk/Kconfig        |   9 +
 drivers/clk/Makefile       |   1 +
 drivers/clk/clk-phiclock.c | 729 +++++++++++++++++++++++++++++++++++++
 4 files changed, 740 insertions(+)
 create mode 100644 drivers/clk/clk-phiclock.c
  

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 7eabe930b..0a8b429e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17645,6 +17645,7 @@  RENESAS PHICLOCK CLOCK DRIVER
 M:	Alex Helms <alexander.helms.jy@renesas.com>
 S:	Maintained
 F:	Documentation/devicetree/bindings/clock/renesas,phiclock.yaml
+F:	drivers/clk/clk-phiclock.c
 
 RESET CONTROLLER FRAMEWORK
 M:	Philipp Zabel <p.zabel@pengutronix.de>
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index d79905f3e..6bcdde0f0 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -386,6 +386,15 @@  config COMMON_CLK_VC7
 	  Renesas Versaclock7 is a family of configurable clock generator
 	  and jitter attenuator ICs with fractional and integer dividers.
 
+config COMMON_CLK_PHICLOCK
+	tristate "Clock driver for Renesas PhiClock devices"
+	depends on I2C
+	depends on OF
+	select REGMAP_I2C
+	help
+	  Renesas PhiClock is a clock generator with a fractional divider
+	  and spread spectrum support.
+
 config COMMON_CLK_STM32MP135
 	def_bool COMMON_CLK && MACH_STM32MP13
 	help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e3ca0d058..6b12861f7 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -52,6 +52,7 @@  obj-$(CONFIG_ARCH_NPCM7XX)	    	+= clk-npcm7xx.o
 obj-$(CONFIG_ARCH_NSPIRE)		+= clk-nspire.o
 obj-$(CONFIG_COMMON_CLK_OXNAS)		+= clk-oxnas.o
 obj-$(CONFIG_COMMON_CLK_PALMAS)		+= clk-palmas.o
+obj-$(CONFIG_COMMON_CLK_PHICLOCK)	+= clk-phiclock.o
 obj-$(CONFIG_CLK_LS1028A_PLLDIG)	+= clk-plldig.o
 obj-$(CONFIG_COMMON_CLK_PWM)		+= clk-pwm.o
 obj-$(CONFIG_CLK_QORIQ)			+= clk-qoriq.o
diff --git a/drivers/clk/clk-phiclock.c b/drivers/clk/clk-phiclock.c
new file mode 100644
index 000000000..cff1547e6
--- /dev/null
+++ b/drivers/clk/clk-phiclock.c
@@ -0,0 +1,729 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common clock framework driver for the PhiClock 9FGV1006 clock generator.
+ *
+ * Copyright (c) 2022 Renesas Electronics Corporation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/swab.h>
+
+/* VCO range is 2.3GHz to 2.6GHz */
+#define PHICLOCK_FVCO_MIN		2300000000UL
+#define PHICLOCK_FVCO_MAX		2600000000UL
+
+/* FOUT range is 10MHz to 325MHz */
+#define PHICLOCK_FOUT_MIN		10000000UL
+#define PHICLOCK_FOUT_MAX		325000000UL
+
+/* FPFD max frequency is 160MHz */
+#define PHICLOCK_FPFD_MAX		160000000UL
+
+#define PHICLOCK_NUM_CLKS		4
+#define PHICLOCK_REF0			0
+#define PHICLOCK_OUT			1
+#define PHICLOCK_OUT0			2
+#define PHICLOCK_OUT1			3
+
+#define PHICLOCK_FBFRAC_BITS		16
+#define PHICLOCK_FBFRAC_DIVISOR		BIT(PHICLOCK_FBFRAC_BITS)
+
+#define PHICLOCK_FBDIV_MIN		12
+#define PHICLOCK_FBDIV_MAX		255
+
+#define PHICLOCK_OUTDIV_MIN		8
+#define PHICLOCK_OUTDIV_MAX		4095
+
+#define PHICLOCK_REG_REF0		0x01
+#define PHICLOCK_REG_OUT0		0x05
+#define PHICLOCK_REG_OUT1		0x0b
+#define PHICLOCK_REG_SS_CNFG_PERIOD	0x10
+#define PHICLOCK_REG_FBINT		0x12
+#define PHICLOCK_REG_FBFRAC		0x13
+#define PHICLOCK_REG_SS_STEP		0x15
+#define PHICLOCK_REG_FBCNFG		0x18
+#define PHICLOCK_REG_CALIBRATION	0x1a
+#define PHICLOCK_REG_OUTDIV		0x21
+#define PHICLOCK_REG_DOUBLER		0x25
+
+#define PHICLOCK_REF_EN_MASK		0x40
+#define PHICLOCK_OUT_EN_MASK		0x80
+#define PHICLOCK_FBMODE_MASK		0x02
+#define PHICLOCK_OUTDIV_11_8_MASK	0xf0
+#define PHICLOCK_DOUBLER_MASK		0x20
+#define PHICLOCK_VCO_CAL_MASK		0x80
+#define PHICLOCK_SS_EN_MASK		0x80
+#define PHICLOCK_SS_PERIOD_11_8_MASK	0x0f
+
+enum phiclock_pll_mode {
+	PLL_MODE_FRAC,
+	PLL_MODE_INT,
+};
+
+enum phiclock_ss_direction {
+	SS_DOWN,
+	SS_CENTER,
+};
+
+enum phiclock_model {
+	PHICLOCK_9FGV1006,
+};
+
+struct phiclock_data;
+
+struct phiclock_clk_hw {
+	struct clk_hw hw;
+	struct clk_init_data init;
+	struct phiclock_data *phiclock;
+	unsigned int idx;
+	u8 enabled;
+};
+
+struct phiclock_data {
+	struct regmap *regmap;
+	struct i2c_client *i2c_client;
+	struct clk *xin_clkin_clk;
+	struct phiclock_clk_hw hw[PHICLOCK_NUM_CLKS];
+	enum phiclock_model model;
+	enum phiclock_ss_direction ss_direction;
+	u16 ss_amount;
+	u16 ss_modulation;
+	u32 fvco;
+	u32 fout;
+	u8 doubler;
+	u8 fbint;
+	u16 fbfrac;
+	u16 outdiv;
+	u8 ss_en;
+	u16 ss_period;
+	u16 ss_step;
+};
+
+static inline struct phiclock_clk_hw *to_phiclock_clk(struct clk_hw *hw)
+{
+	return container_of(hw, struct phiclock_clk_hw, hw);
+}
+
+static int phiclock_get_en_reg(unsigned int idx, unsigned int *reg, unsigned int *mask)
+{
+	switch (idx) {
+	case PHICLOCK_REF0:
+		*reg = PHICLOCK_REG_REF0;
+		*mask = PHICLOCK_REF_EN_MASK;
+		break;
+	case PHICLOCK_OUT0:
+		*reg = PHICLOCK_REG_OUT0;
+		*mask = PHICLOCK_OUT_EN_MASK;
+		break;
+	case PHICLOCK_OUT1:
+		*reg = PHICLOCK_REG_OUT1;
+		*mask = PHICLOCK_OUT_EN_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static u32 phiclock_calc_fvco(struct phiclock_data *phiclock)
+{
+	u64 fref, fvco;
+	u8 doubler;
+
+	doubler = phiclock->doubler ? 2 : 1;
+	fref = clk_get_rate(phiclock->xin_clkin_clk) * doubler;
+
+	fvco = 2 * ((fref * phiclock->fbint) +
+		div_u64(fref * phiclock->fbfrac, PHICLOCK_FBFRAC_DIVISOR));
+
+	return (u32)fvco;
+}
+
+static int phiclock_get_divs(struct phiclock_data *phiclock, u8 *doubler, u8 *fbint, u16 *fbfrac,
+			     u16 *outdiv)
+{
+	int ret;
+	unsigned int tmp;
+	u8 reg[3];
+
+	ret = regmap_bulk_read(phiclock->regmap, PHICLOCK_REG_FBINT, reg, 3);
+	if (ret)
+		return ret;
+
+	*fbint = reg[0];
+	*fbfrac = (reg[1] << 8) + reg[2];
+
+	ret = regmap_bulk_read(phiclock->regmap, PHICLOCK_REG_OUTDIV, reg, 2);
+	if (ret)
+		return ret;
+
+	*outdiv = ((reg[1] & PHICLOCK_OUTDIV_11_8_MASK) << 8) + reg[0];
+	if (*outdiv < PHICLOCK_OUTDIV_MIN)
+		*outdiv = PHICLOCK_OUTDIV_MIN;
+	if (*outdiv > PHICLOCK_OUTDIV_MAX)
+		*outdiv = PHICLOCK_OUTDIV_MAX;
+
+	ret = regmap_read(phiclock->regmap, PHICLOCK_REG_DOUBLER, &tmp);
+	if (ret)
+		return ret;
+
+	*doubler = (tmp & PHICLOCK_DOUBLER_MASK) >> 5;
+
+	pr_debug("doubler: %u, fbint: %u, fbfrac: %u, outdiv: %u\n",
+		 *doubler, *fbint, *fbfrac, *outdiv);
+
+	return ret;
+}
+
+static int phiclock_get_ss(struct phiclock_data *phiclock, u8 *ss_en, u16 *ss_period, u16 *ss_step)
+{
+	int ret;
+	u8 reg[2];
+
+	*ss_en = 0;
+	*ss_period = 0;
+	*ss_step = 0;
+
+	ret = regmap_bulk_read(phiclock->regmap, PHICLOCK_REG_SS_CNFG_PERIOD, reg, 2);
+	if (ret)
+		return ret;
+
+	*ss_en = !!(reg[0] & PHICLOCK_SS_EN_MASK);
+	*ss_period = ((reg[0] & PHICLOCK_SS_PERIOD_11_8_MASK) << 8) + reg[1];
+
+	ret = regmap_bulk_read(phiclock->regmap, PHICLOCK_REG_SS_STEP, reg, 2);
+	if (ret)
+		return ret;
+
+	*ss_step = (reg[0] << 8) + reg[1];
+
+	pr_debug("ss_en: %u, ss_period: %u, ss_step: %u\n", *ss_en, *ss_period, *ss_step);
+
+	return ret;
+}
+
+static int phiclock_get_defaults(struct phiclock_data *phiclock)
+{
+	int ret;
+	unsigned int ref0, out0, out1;
+
+	ret = phiclock_get_divs(phiclock, &phiclock->doubler, &phiclock->fbint, &phiclock->fbfrac,
+				&phiclock->outdiv);
+	if (ret)
+		return ret;
+
+	phiclock->fvco = phiclock_calc_fvco(phiclock);
+	phiclock->fout = phiclock->fvco / phiclock->outdiv;
+
+	ret = phiclock_get_ss(phiclock, &phiclock->ss_en, &phiclock->ss_period, &phiclock->ss_step);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(phiclock->regmap, PHICLOCK_REG_REF0, &ref0);
+	ret = regmap_read(phiclock->regmap, PHICLOCK_REG_OUT0, &out0);
+	ret = regmap_read(phiclock->regmap, PHICLOCK_REG_OUT1, &out1);
+	if (ret)
+		return ret;
+
+	ref0 = (ref0 & PHICLOCK_REF_EN_MASK) >> __ffs(PHICLOCK_REF_EN_MASK);
+	out0 = (out0 & PHICLOCK_OUT_EN_MASK) >> __ffs(PHICLOCK_OUT_EN_MASK);
+	out1 = (out1 & PHICLOCK_OUT_EN_MASK) >> __ffs(PHICLOCK_OUT_EN_MASK);
+	phiclock->hw[PHICLOCK_REF0].enabled = ref0;
+	phiclock->hw[PHICLOCK_OUT0].enabled = out0;
+	phiclock->hw[PHICLOCK_OUT1].enabled = out1;
+
+	pr_debug("doubler: %u, fbint: %u, fbfrac: %u, outdiv: %u, fvco: %u, fout: %u\n",
+		 phiclock->doubler, phiclock->fbint, phiclock->fbfrac, phiclock->outdiv,
+		 phiclock->fvco, phiclock->fout);
+
+	pr_debug("ref0_en: %u, out0_en: %u, out1_en: %u\n", ref0, out0, out1);
+
+	pr_debug("ss_en: %u, ss_period: %u, ss_step: %u\n",
+		 phiclock->ss_en, phiclock->ss_period, phiclock->ss_step);
+
+	return ret;
+}
+
+static int phiclock_calc_divs(const unsigned long frequency, const struct phiclock_data *phiclock,
+			      u8 *doubler, u8 *fbint, u16 *fbfrac, u16 *outdiv, u32 *fout,
+			      u32 *fvco, u16 *ss_period, u16 *ss_step)
+{
+	u16 outdivstart;
+	u32 fpfd;
+	unsigned long fin;
+	bool found = false, allow_frac = false;
+	bool ss_en = phiclock->ss_en && phiclock->ss_amount && phiclock->ss_modulation;
+
+	if (frequency == 0)
+		return -EINVAL;
+
+	if (phiclock->fbint == 0)
+		return -EINVAL;
+
+	fin = clk_get_rate(phiclock->xin_clkin_clk);
+	*doubler = 2 * fin <= PHICLOCK_FPFD_MAX ? 1 : 0;
+	*fbint = PHICLOCK_FBDIV_MIN;
+	*fbfrac = 0;
+	*outdiv = PHICLOCK_OUTDIV_MIN;
+	*fout = PHICLOCK_FOUT_MIN;
+	*fvco = *outdiv * frequency;
+
+	fpfd = fin * (*doubler + 1);
+	outdivstart = 1 + (PHICLOCK_FVCO_MIN / frequency);
+
+	if (ss_en)
+		allow_frac = true;
+
+retry:
+	for (*outdiv = outdivstart; *outdiv <= PHICLOCK_OUTDIV_MAX; ++(*outdiv)) {
+		*fvco = *outdiv * frequency;
+		if (*fvco > PHICLOCK_FVCO_MAX) {
+			allow_frac = true;
+			goto retry;
+		}
+
+		*fbint = (*fvco >> 1) / fpfd;
+		*fbfrac = (*fvco >> 1) % fpfd;
+
+		if (*fbint < PHICLOCK_FBDIV_MIN)
+			*fbint = PHICLOCK_FBDIV_MIN;
+		if (*fbint > PHICLOCK_FBDIV_MAX)
+			*fbint = PHICLOCK_FBDIV_MAX;
+
+		if (*fbfrac == 0) {
+			found = true;
+			break;
+		}
+
+		if (allow_frac) {
+			*fbfrac = 1 + div_u64(((u64)*fvco >> 1) % fpfd << PHICLOCK_FBFRAC_BITS,
+					      fpfd);
+			found = true;
+			break;
+		}
+	}
+
+	if (!found)
+		return -EINVAL;
+
+	if (*fvco < PHICLOCK_FVCO_MIN || *fvco > PHICLOCK_FVCO_MAX)
+		return -EINVAL;
+
+	if (ss_en) {
+		unsigned int ss_amount = phiclock->ss_amount;
+
+		if (phiclock->ss_direction == SS_CENTER) {
+			u64 num, den;
+			u32 rem;
+
+			num = (u64)*fbint * PHICLOCK_FBFRAC_DIVISOR + *fbfrac;
+			num *= 10000 + phiclock->ss_amount;
+			den = 10000 * PHICLOCK_FBFRAC_DIVISOR;
+			*fbint = div_u64_rem(num, den, &rem);
+			*fbfrac = rem / 10000;
+
+			/* center spread requires peak-peak */
+			ss_amount *= 2;
+		}
+
+		*ss_period = fpfd / (4 * phiclock->ss_modulation);
+		*ss_step = (u16)div_u64((ss_amount * (u64)*fbint) << 24, 20000 * *ss_period);
+	}
+
+	*fvco = 2 * (((u64)fpfd * *fbint) + div_u64((u64)fpfd * *fbfrac, PHICLOCK_FBFRAC_DIVISOR));
+	*fout = *fvco / *outdiv;
+
+	if (*fout < PHICLOCK_FOUT_MIN || *fout > PHICLOCK_FOUT_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int phiclock_update_frequency(struct phiclock_data *phiclock)
+{
+	int ret;
+	enum phiclock_pll_mode pll_mode;
+	u8 reg[3];
+
+	pll_mode = phiclock->fbfrac == 0 ? PLL_MODE_INT : PLL_MODE_FRAC;
+
+	if (phiclock->ss_period == 0 && phiclock->ss_step == 0)
+		phiclock->ss_en = 0;
+
+	reg[0] = phiclock->fbint;
+	reg[1] = (phiclock->fbfrac >> 8) & 0xff;
+	reg[2] = phiclock->fbfrac & 0xff;
+	ret = regmap_bulk_write(phiclock->regmap, PHICLOCK_REG_FBINT, reg, 3);
+
+	reg[0] = phiclock->outdiv & 0xff;
+	reg[1] = (phiclock->outdiv & PHICLOCK_OUTDIV_11_8_MASK) >> 4;
+	ret = regmap_bulk_write(phiclock->regmap, PHICLOCK_REG_OUTDIV, reg, 2);
+
+	ret = regmap_write_bits(phiclock->regmap, PHICLOCK_REG_FBCNFG, PHICLOCK_FBMODE_MASK,
+				pll_mode << __ffs(PHICLOCK_FBMODE_MASK));
+
+	ret = regmap_write_bits(phiclock->regmap, PHICLOCK_REG_DOUBLER, PHICLOCK_DOUBLER_MASK,
+				phiclock->doubler << __ffs(PHICLOCK_DOUBLER_MASK));
+
+	ret = regmap_write_bits(phiclock->regmap, PHICLOCK_REG_SS_CNFG_PERIOD, PHICLOCK_SS_EN_MASK,
+				phiclock->ss_en << __ffs(PHICLOCK_SS_EN_MASK));
+
+	reg[0] = phiclock->ss_en << __ffs(PHICLOCK_SS_EN_MASK);
+	reg[0] += ((phiclock->ss_period >> 8) & PHICLOCK_SS_PERIOD_11_8_MASK);
+	reg[1] = phiclock->ss_period & 0xff;
+	ret = regmap_bulk_write(phiclock->regmap, PHICLOCK_REG_SS_CNFG_PERIOD, reg, 2);
+
+	reg[0] = (phiclock->ss_step >> 8) & 0xff;
+	reg[1] = phiclock->ss_step & 0xff;
+	ret = regmap_bulk_write(phiclock->regmap, PHICLOCK_REG_SS_STEP, reg, 2);
+
+	/* VCO calibration to apply the changes, cal starts on the 0->1 transition */
+	ret = regmap_write_bits(phiclock->regmap, PHICLOCK_REG_CALIBRATION, PHICLOCK_VCO_CAL_MASK,
+				0 << __ffs(PHICLOCK_VCO_CAL_MASK));
+	ret = regmap_write_bits(phiclock->regmap, PHICLOCK_REG_CALIBRATION, PHICLOCK_VCO_CAL_MASK,
+				1 << __ffs(PHICLOCK_VCO_CAL_MASK));
+
+	return ret;
+}
+
+static int phiclock_set_frequency(struct phiclock_data *phiclock, unsigned long frequency)
+{
+	int ret;
+
+	ret = phiclock_calc_divs(frequency, phiclock,
+				 &phiclock->doubler, &phiclock->fbint, &phiclock->fbfrac,
+				 &phiclock->outdiv, &phiclock->fout, &phiclock->fvco,
+				 &phiclock->ss_period, &phiclock->ss_step);
+	if (ret)
+		return ret;
+
+	pr_debug("doubler: %u, fbint: %u, fbfrac: %u, outdiv: %u, fvco: %u, fout: %u\n",
+		 phiclock->doubler, phiclock->fbint, phiclock->fbfrac, phiclock->outdiv,
+		 phiclock->fvco, phiclock->fout);
+
+	if (phiclock->ss_en)
+		pr_debug("ss_period: %u, ss_step: %u\n",
+			 phiclock->ss_period, phiclock->ss_step);
+
+	ret = phiclock_update_frequency(phiclock);
+
+	return ret;
+}
+
+static unsigned long phiclock_clk_recalc_rate(struct clk_hw *hw, unsigned long rate)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+	int ret;
+	u8 doubler, fbint;
+	u16 fbfrac, outdiv;
+	u32 fvco, fout;
+
+	ret = phiclock_get_divs(phiclock, &doubler, &fbint, &fbfrac, &outdiv);
+	if (ret) {
+		dev_err(&phiclock->i2c_client->dev, "recalc rate error\n");
+		return 0;
+	}
+
+	fvco = phiclock_calc_fvco(phiclock);
+	fout = fvco / outdiv;
+
+	return fout;
+}
+
+static long phiclock_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *parent_rate)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+	int ret;
+	u8 doubler, fbint, ss_en;
+	u16 fbfrac, outdiv, ss_period, ss_step;
+	u32 fout, fvco;
+
+	if (!rate)
+		return 0;
+
+	/* Temporarily disable spread spectrum for rounding. */
+	ss_en = phiclock->ss_en;
+	phiclock->ss_en = 0;
+	ret = phiclock_calc_divs(rate, phiclock, &doubler, &fbint, &fbfrac, &outdiv, &fout, &fvco,
+				 &ss_period, &ss_step);
+	phiclock->ss_en = ss_en;
+	if (ret) {
+		dev_err(&phiclock->i2c_client->dev, "round rate error\n");
+		return 0;
+	}
+
+	return fout;
+}
+
+static int phiclock_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+
+	if (rate < PHICLOCK_FOUT_MIN || rate > PHICLOCK_FOUT_MAX) {
+		dev_err(&phiclock->i2c_client->dev, "requested frequency %lu Hz is out of range\n",
+			rate);
+		return -EINVAL;
+	}
+
+	return phiclock_set_frequency(phiclock, rate);
+}
+
+static int phiclock_clk_prepare(struct clk_hw *hw)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+	int err;
+	unsigned int reg, mask;
+
+	pr_debug("prepare %s", clk_hw_get_name(hw));
+
+	err = phiclock_get_en_reg(clk_hw->idx, &reg, &mask);
+	if (err)
+		return err;
+
+	clk_hw->enabled = 1;
+	err = regmap_update_bits(phiclock->regmap, reg, mask, mask);
+	return err;
+}
+
+static void phiclock_clk_unprepare(struct clk_hw *hw)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+	int err;
+	unsigned int reg, mask;
+
+	pr_debug("unprepare %s", clk_hw_get_name(hw));
+
+	err = phiclock_get_en_reg(clk_hw->idx, &reg, &mask);
+	if (!err) {
+		clk_hw->enabled = 0;
+		regmap_update_bits(phiclock->regmap, reg, mask, 0);
+	}
+}
+
+static int phiclock_clk_is_prepared(struct clk_hw *hw)
+{
+	struct phiclock_clk_hw *clk_hw = to_phiclock_clk(hw);
+	struct phiclock_data *phiclock = clk_hw->phiclock;
+	int err;
+	unsigned int reg, mask, val = 0;
+
+	pr_debug("is_prepared %s", clk_hw_get_name(hw));
+
+	err = phiclock_get_en_reg(clk_hw->idx, &reg, &mask);
+	if (err)
+		return err;
+
+	err = regmap_read(phiclock->regmap, reg, &val);
+	if (err)
+		return err;
+
+	return !!(val & mask);
+}
+
+static const struct i2c_device_id phiclock_i2c_id[] = {
+	{ "9fgv1006", PHICLOCK_9FGV1006 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, phiclock_i2c_id);
+
+static const struct regmap_config phiclock_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x25,
+	.cache_type = REGCACHE_RBTREE
+};
+
+struct phiclock_clk {
+	const char *name;
+	int parent_index;
+	const struct clk_ops ops;
+};
+
+static const struct phiclock_clk phiclock_clks[PHICLOCK_NUM_CLKS] = {
+	[PHICLOCK_REF0] = {
+		.name = "phiclock_ref0",
+		.parent_index = -1,
+		.ops = {
+			.prepare	= phiclock_clk_prepare,
+			.unprepare	= phiclock_clk_unprepare,
+			.is_prepared	= phiclock_clk_is_prepared,
+		},
+	},
+	[PHICLOCK_OUT] = {
+		.name = "phiclock_out",
+		.parent_index = -1,
+		.ops = {
+			.recalc_rate	= phiclock_clk_recalc_rate,
+			.round_rate	= phiclock_clk_round_rate,
+			.set_rate	= phiclock_clk_set_rate,
+		},
+	},
+	[PHICLOCK_OUT0] = {
+		.name = "phiclock_out0",
+		.parent_index = PHICLOCK_OUT,
+		.ops = {
+			.prepare	= phiclock_clk_prepare,
+			.unprepare	= phiclock_clk_unprepare,
+			.is_prepared	= phiclock_clk_is_prepared,
+		},
+	},
+	[PHICLOCK_OUT1] = {
+		.name = "phiclock_out1",
+		.parent_index = PHICLOCK_OUT,
+		.ops = {
+			.prepare	= phiclock_clk_prepare,
+			.unprepare	= phiclock_clk_unprepare,
+			.is_prepared	= phiclock_clk_is_prepared,
+		},
+	},
+};
+
+static struct clk_hw *phiclock_of_clk_get(struct of_phandle_args *clkspec, void *data)
+{
+	struct phiclock_data *driver_data = data;
+	unsigned int idx = clkspec->args[0];
+
+	return &driver_data->hw[idx].hw;
+}
+
+static int phiclock_probe(struct i2c_client *client)
+{
+	struct phiclock_data *phiclock;
+	struct device *dev = &client->dev;
+	const char *xin_clkin_name, *spread_spectrum_type_name;
+	unsigned int i;
+	u32 ss_amount = 0, ss_modulation = 0;
+	enum phiclock_ss_direction ss_direction = SS_DOWN;
+	int ret;
+
+	phiclock = devm_kzalloc(dev, sizeof(*phiclock), GFP_KERNEL);
+	if (!phiclock)
+		return -ENOMEM;
+
+	phiclock->xin_clkin_clk = devm_clk_get(dev, "xin-clkin");
+	if (IS_ERR(phiclock->xin_clkin_clk)) {
+		return dev_err_probe(dev, PTR_ERR(phiclock->xin_clkin_clk),
+				     "xin-clkin not specified\n");
+	}
+
+	xin_clkin_name = __clk_get_name(phiclock->xin_clkin_clk);
+
+	phiclock->regmap = devm_regmap_init_i2c(client, &phiclock_regmap_config);
+	if (IS_ERR(phiclock->regmap)) {
+		return dev_err_probe(dev, PTR_ERR(phiclock->regmap),
+				     "failed to initialize register map\n");
+	}
+
+	i2c_set_clientdata(client, phiclock);
+	phiclock->i2c_client = client;
+	phiclock->ss_amount = ss_amount;
+	phiclock->ss_modulation = ss_modulation;
+	phiclock->ss_direction = ss_direction;
+
+	ret = phiclock_get_defaults(phiclock);
+	if (ret) {
+		dev_err(dev, "error getting defaults\n");
+		return ret;
+	}
+
+	for (i = 0; i < PHICLOCK_NUM_CLKS; i++) {
+		int parent_index = phiclock_clks[i].parent_index;
+		const char *name;
+
+		if (of_property_read_string_index(dev->of_node,
+						  "clock-output-names", i, &name) == 0) {
+			phiclock->hw[i].init.name = name;
+		} else {
+			phiclock->hw[i].init.name = phiclock_clks[i].name;
+		}
+
+		phiclock->hw[i].idx = i;
+		phiclock->hw[i].init.ops = &phiclock_clks[i].ops;
+		phiclock->hw[i].init.num_parents = 1;
+		phiclock->hw[i].init.flags = 0;
+
+		if (parent_index > 0) {
+			phiclock->hw[i].init.parent_names = &phiclock->hw[parent_index].init.name;
+			phiclock->hw[i].init.flags |= CLK_SET_RATE_PARENT;
+		} else {
+			phiclock->hw[i].init.parent_names = &xin_clkin_name;
+		}
+
+		phiclock->hw[i].hw.init = &phiclock->hw[i].init;
+		phiclock->hw[i].phiclock = phiclock;
+
+		ret = devm_clk_hw_register(dev, &phiclock->hw[i].hw);
+		if (ret < 0) {
+			return dev_err_probe(dev, ret, "failed to register %s clock\n",
+					     phiclock->hw[i].init.name);
+		}
+	}
+
+	if (of_property_read_u32(dev->of_node, "renesas,ss-amount-percent", &ss_amount) == 0)
+		phiclock->ss_amount = ss_amount;
+
+	if (of_property_read_u32(dev->of_node, "renesas,ss-modulation-hz", &ss_modulation) == 0)
+		phiclock->ss_modulation = ss_modulation;
+
+	if (of_property_read_string(dev->of_node, "renesas,ss-direction",
+				    &spread_spectrum_type_name) == 0) {
+		if (strcmp(spread_spectrum_type_name, "center") == 0)
+			phiclock->ss_direction = SS_CENTER;
+		else
+			phiclock->ss_direction = SS_DOWN;
+	}
+
+	ret = devm_of_clk_add_hw_provider(dev, phiclock_of_clk_get, phiclock);
+	if (ret) {
+		dev_err(dev, "unable to add clk provider\n");
+		return ret;
+	}
+
+	dev_info(dev, "registered, current frequency %u Hz\n", phiclock->fout);
+
+	return ret;
+}
+
+static void phiclock_remove(struct i2c_client *client)
+{
+	of_clk_del_provider(client->dev.of_node);
+}
+
+static const struct of_device_id phiclock_of_match[] = {
+	{ .compatible = "renesas,9fgv1006" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, phiclock_of_match);
+
+static struct i2c_driver phiclock_i2c_driver = {
+	.driver = {
+		.name = "phiclock",
+		.of_match_table = phiclock_of_match,
+	},
+	.probe_new = phiclock_probe,
+	.remove = phiclock_remove,
+	.id_table = phiclock_i2c_id,
+};
+module_i2c_driver(phiclock_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alex Helms <alexander.helms.jy@renesas.com");
+MODULE_DESCRIPTION("Renesas PhiClock Clock Generator Driver");