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@xxxxxxxxxxx> --- 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 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@xxxxxxxxxxx> S: Maintained F: Documentation/devicetree/bindings/clock/renesas,phiclock.yaml +F: drivers/clk/clk-phiclock.c RESET CONTROLLER FRAMEWORK M: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx> 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, ®, &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, ®, &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, ®, &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@xxxxxxxxxxx"); +MODULE_DESCRIPTION("Renesas PhiClock Clock Generator Driver"); -- 2.25.1