diff options
-rw-r--r-- | Documentation/devicetree/bindings/clock/brcm,bcm63xx-clocks.txt | 22 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt | 1 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/clock/silabs,si5341.txt | 162 | ||||
-rw-r--r-- | drivers/clk/Kconfig | 11 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/bcm/Kconfig | 8 | ||||
-rw-r--r-- | drivers/clk/bcm/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/bcm/clk-bcm63xx-gate.c | 238 | ||||
-rw-r--r-- | drivers/clk/clk-lochnagar.c | 205 | ||||
-rw-r--r-- | drivers/clk/clk-si5341.c | 1346 | ||||
-rw-r--r-- | drivers/clk/clk-si544.c | 102 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-mmc-phase.c | 14 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-px30.c | 12 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-rk3228.c | 3 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-rk3288.c | 13 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-rk3328.c | 3 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-rk3368.c | 12 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-rk3399.c | 12 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk.h | 4 | ||||
-rw-r--r-- | include/dt-bindings/clock/rk3228-cru.h | 1 | ||||
-rw-r--r-- | include/dt-bindings/clock/rk3328-cru.h | 1 |
21 files changed, 1997 insertions, 175 deletions
diff --git a/Documentation/devicetree/bindings/clock/brcm,bcm63xx-clocks.txt b/Documentation/devicetree/bindings/clock/brcm,bcm63xx-clocks.txt new file mode 100644 index 000000000000..3041657e2f96 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/brcm,bcm63xx-clocks.txt @@ -0,0 +1,22 @@ +Gated Clock Controller Bindings for MIPS based BCM63XX SoCs + +Required properties: +- compatible: must be one of: + "brcm,bcm3368-clocks" + "brcm,bcm6328-clocks" + "brcm,bcm6358-clocks" + "brcm,bcm6362-clocks" + "brcm,bcm6368-clocks" + "brcm,bcm63268-clocks" + +- reg: Address and length of the register set +- #clock-cells: must be <1> + + +Example: + +clkctl: clock-controller@10000004 { + compatible = "brcm,bcm6328-clocks"; + reg = <0x10000004 0x4>; + #clock-cells = <1>; +}; diff --git a/Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt b/Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt index b8d8ef3bdc5f..52a064c789ee 100644 --- a/Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt +++ b/Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt @@ -40,6 +40,7 @@ Optional properties: input audio clocks from host system. - ln-psia1-mclk, ln-psia2-mclk : Optional input audio clocks from external connector. + - ln-spdif-mclk : Optional input audio clock from SPDIF. - ln-spdif-clkout : Optional input audio clock from SPDIF. - ln-adat-mclk : Optional input audio clock from ADAT. - ln-pmic-32k : On board fixed clock. diff --git a/Documentation/devicetree/bindings/clock/silabs,si5341.txt b/Documentation/devicetree/bindings/clock/silabs,si5341.txt new file mode 100644 index 000000000000..a70c333e4cd4 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/silabs,si5341.txt @@ -0,0 +1,162 @@ +Binding for Silicon Labs Si5341 and Si5340 programmable i2c clock generator. + +Reference +[1] Si5341 Data Sheet + https://www.silabs.com/documents/public/data-sheets/Si5341-40-D-DataSheet.pdf +[2] Si5341 Reference Manual + https://www.silabs.com/documents/public/reference-manuals/Si5341-40-D-RM.pdf + +The Si5341 and Si5340 are programmable i2c clock generators with up to 10 output +clocks. The chip contains a PLL that sources 5 (or 4) multisynth clocks, which +in turn can be directed to any of the 10 (or 4) outputs through a divider. +The internal structure of the clock generators can be found in [2]. + +The driver can be used in "as is" mode, reading the current settings from the +chip at boot, in case you have a (pre-)programmed device. If the PLL is not +configured when the driver probes, it assumes the driver must fully initialize +it. + +The device type, speed grade and revision are determined runtime by probing. + +The driver currently only supports XTAL input mode, and does not support any +fancy input configurations. They can still be programmed into the chip and +the driver will leave them "as is". + +==I2C device node== + +Required properties: +- compatible: shall be one of the following: + "silabs,si5340" - Si5340 A/B/C/D + "silabs,si5341" - Si5341 A/B/C/D +- reg: i2c device address, usually 0x74 +- #clock-cells: from common clock binding; shall be set to 2. + The first value is "0" for outputs, "1" for synthesizers. + The second value is the output or synthesizer index. +- clocks: from common clock binding; list of parent clock handles, + corresponding to inputs. Use a fixed clock for the "xtal" input. + At least one must be present. +- clock-names: One of: "xtal", "in0", "in1", "in2" +- vdd-supply: Regulator node for VDD + +Optional properties: +- vdda-supply: Regulator node for VDDA +- vdds-supply: Regulator node for VDDS +- silabs,pll-m-num, silabs,pll-m-den: Numerator and denominator for PLL + feedback divider. Must be such that the PLL output is in the valid range. For + example, to create 14GHz from a 48MHz xtal, use m-num=14000 and m-den=48. Only + the fraction matters, using 3500 and 12 will deliver the exact same result. + If these are not specified, and the PLL is not yet programmed when the driver + probes, the PLL will be set to 14GHz. +- silabs,reprogram: When present, the driver will always assume the device must + be initialized, and always performs the soft-reset routine. Since this will + temporarily stop all output clocks, don't do this if the chip is generating + the CPU clock for example. +- interrupts: Interrupt for INTRb pin. +- #address-cells: shall be set to 1. +- #size-cells: shall be set to 0. + + +== Child nodes: Outputs == + +The child nodes list the output clocks. + +Each of the clock outputs can be overwritten individually by using a child node. +If a child node for a clock output is not set, the configuration remains +unchanged. + +Required child node properties: +- reg: number of clock output. + +Optional child node properties: +- vdd-supply: Regulator node for VDD for this output. The driver selects default + values for common-mode and amplitude based on the voltage. +- silabs,format: Output format, one of: + 1 = differential (defaults to LVDS levels) + 2 = low-power (defaults to HCSL levels) + 4 = LVCMOS +- silabs,common-mode: Manually override output common mode, see [2] for values +- silabs,amplitude: Manually override output amplitude, see [2] for values +- silabs,synth-master: boolean. If present, this output is allowed to change the + multisynth frequency dynamically. +- silabs,silabs,disable-high: boolean. If set, the clock output is driven HIGH + when disabled, otherwise it's driven LOW. + +==Example== + +/* 48MHz reference crystal */ +ref48: ref48M { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <48000000>; +}; + +i2c-master-node { + /* Programmable clock (for logic) */ + si5341: clock-generator@74 { + reg = <0x74>; + compatible = "silabs,si5341"; + #clock-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&ref48>; + clock-names = "xtal"; + + silabs,pll-m-num = <14000>; /* PLL at 14.0 GHz */ + silabs,pll-m-den = <48>; + silabs,reprogram; /* Chips are not programmed, always reset */ + + out@0 { + reg = <0>; + silabs,format = <1>; /* LVDS 3v3 */ + silabs,common-mode = <3>; + silabs,amplitude = <3>; + silabs,synth-master; + }; + + /* + * Output 6 configuration: + * LVDS 1v8 + */ + out@6 { + reg = <6>; + silabs,format = <1>; /* LVDS 1v8 */ + silabs,common-mode = <13>; + silabs,amplitude = <3>; + }; + + /* + * Output 8 configuration: + * HCSL 3v3 + */ + out@8 { + reg = <8>; + silabs,format = <2>; + silabs,common-mode = <11>; + silabs,amplitude = <3>; + }; + }; +}; + +some-video-node { + /* Standard clock bindings */ + clock-names = "pixel"; + clocks = <&si5341 0 7>; /* Output 7 */ + + /* Set output 7 to use syntesizer 3 as its parent */ + assigned-clocks = <&si5341 0 7>, <&si5341 1 3>; + assigned-clock-parents = <&si5341 1 3>; + /* Set output 7 to 148.5 MHz using a synth frequency of 594 MHz */ + assigned-clock-rates = <148500000>, <594000000>; +}; + +some-audio-node { + clock-names = "i2s-clk"; + clocks = <&si5341 0 0>; + /* + * since output 0 is a synth-master, the synth will be automatically set + * to an appropriate frequency when the audio driver requests another + * frequency. We give control over synth 2 to this output here. + */ + assigned-clocks = <&si5341 0 0>; + assigned-clock-parents = <&si5341 1 2>; +}; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b97974662a1f..cb8f91a1d54f 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -91,6 +91,17 @@ config COMMON_CLK_SCPI This driver uses SCPI Message Protocol to interact with the firmware providing all the clock controls. +config COMMON_CLK_SI5341 + tristate "Clock driver for SiLabs 5341 and 5340 A/B/C/D devices" + depends on I2C + select REGMAP_I2C + help + This driver supports Silicon Labs Si5341 and Si5340 programmable clock + generators. Not all features of these chips are currently supported + by the driver, in particular it only supports XTAL input. The chip can + be pre-programmed to support other configurations and features not yet + implemented in the driver. + config COMMON_CLK_SI5351 tristate "Clock driver for SiLabs 5351A/B/C" depends on I2C diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 9ef4305d55e0..0cad76021297 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o +obj-$(CONFIG_COMMON_CLK_SI5341) += clk-si5341.o obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o obj-$(CONFIG_COMMON_CLK_SI544) += clk-si544.o diff --git a/drivers/clk/bcm/Kconfig b/drivers/clk/bcm/Kconfig index e5497e995cfb..ccef3edfde7a 100644 --- a/drivers/clk/bcm/Kconfig +++ b/drivers/clk/bcm/Kconfig @@ -16,6 +16,14 @@ config CLK_BCM_63XX Enable common clock framework support for Broadcom BCM63xx DSL SoCs based on the ARM architecture +config CLK_BCM_63XX_GATE + bool "Broadcom BCM63xx gated clock support" + depends on BMIPS_GENERIC || COMPILE_TEST + default BMIPS_GENERIC + help + Enable common clock framework support for Broadcom BCM63xx DSL SoCs + based on the MIPS architecture + config CLK_BCM_KONA bool "Broadcom Kona CCU clock support" depends on ARCH_BCM_MOBILE || COMPILE_TEST diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile index 004e9526d6f6..0070ddf6cdd2 100644 --- a/drivers/clk/bcm/Makefile +++ b/drivers/clk/bcm/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_CLK_BCM_63XX) += clk-bcm63xx.o +obj-$(CONFIG_CLK_BCM_63XX_GATE) += clk-bcm63xx-gate.o obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o diff --git a/drivers/clk/bcm/clk-bcm63xx-gate.c b/drivers/clk/bcm/clk-bcm63xx-gate.c new file mode 100644 index 000000000000..9e1dcd43258c --- /dev/null +++ b/drivers/clk/bcm/clk-bcm63xx-gate.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/clk-provider.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct clk_bcm63xx_table_entry { + const char * const name; + u8 bit; + unsigned long flags; +}; + +struct clk_bcm63xx_hw { + void __iomem *regs; + spinlock_t lock; + + struct clk_hw_onecell_data data; +}; + +static const struct clk_bcm63xx_table_entry bcm3368_clocks[] = { + { .name = "mac", .bit = 3, }, + { .name = "tc", .bit = 5, }, + { .name = "us_top", .bit = 6, }, + { .name = "ds_top", .bit = 7, }, + { .name = "acm", .bit = 8, }, + { .name = "spi", .bit = 9, }, + { .name = "usbs", .bit = 10, }, + { .name = "bmu", .bit = 11, }, + { .name = "pcm", .bit = 12, }, + { .name = "ntp", .bit = 13, }, + { .name = "acp_b", .bit = 14, }, + { .name = "acp_a", .bit = 15, }, + { .name = "emusb", .bit = 17, }, + { .name = "enet0", .bit = 18, }, + { .name = "enet1", .bit = 19, }, + { .name = "usbsu", .bit = 20, }, + { .name = "ephy", .bit = 21, }, + { }, +}; + +static const struct clk_bcm63xx_table_entry bcm6328_clocks[] = { + { .name = "phy_mips", .bit = 0, }, + { .name = "adsl_qproc", .bit = 1, }, + { .name = "adsl_afe", .bit = 2, }, + { .name = "adsl", .bit = 3, }, + { .name = "mips", .bit = 4, .flags = CLK_IS_CRITICAL, }, + { .name = "sar", .bit = 5, }, + { .name = "pcm", .bit = 6, }, + { .name = "usbd", .bit = 7, }, + { .name = "usbh", .bit = 8, }, + { .name = "hsspi", .bit = 9, }, + { .name = "pcie", .bit = 10, }, + { .name = "robosw", .bit = 11, }, + { }, +}; + +static const struct clk_bcm63xx_table_entry bcm6358_clocks[] = { + { .name = "enet", .bit = 4, }, + { .name = "adslphy", .bit = 5, }, + { .name = "pcm", .bit = 8, }, + { .name = "spi", .bit = 9, }, + { .name = "usbs", .bit = 10, }, + { .name = "sar", .bit = 11, }, + { .name = "emusb", .bit = 17, }, + { .name = "enet0", .bit = 18, }, + { .name = "enet1", .bit = 19, }, + { .name = "usbsu", .bit = 20, }, + { .name = "ephy", .bit = 21, }, + { }, +}; + +static const struct clk_bcm63xx_table_entry bcm6362_clocks[] = { + { .name = "adsl_qproc", .bit = 1, }, + { .name = "adsl_afe", .bit = 2, }, + { .name = "adsl", .bit = 3, }, + { .name = "mips", .bit = 4, .flags = CLK_IS_CRITICAL, }, + { .name = "wlan_ocp", .bit = 5, }, + { .name = "swpkt_usb", .bit = 7, }, + { .name = "swpkt_sar", .bit = 8, }, + { .name = "sar", .bit = 9, }, + { .name = "robosw", .bit = 10, }, + { .name = "pcm", .bit = 11, }, + { .name = "usbd", .bit = 12, }, + { .name = "usbh", .bit = 13, }, + { .name = "ipsec", .bit = 14, }, + { .name = "spi", .bit = 15, }, + { .name = "hsspi", .bit = 16, }, + { .name = "pcie", .bit = 17, }, + { .name = "fap", .bit = 18, }, + { .name = "phymips", .bit = 19, }, + { .name = "nand", .bit = 20, }, + { }, +}; + +static const struct clk_bcm63xx_table_entry bcm6368_clocks[] = { + { .name = "vdsl_qproc", .bit = 2, }, + { .name = "vdsl_afe", .bit = 3, }, + { .name = "vdsl_bonding", .bit = 4, }, + { .name = "vdsl", .bit = 5, }, + { .name = "phymips", .bit = 6, }, + { .name = "swpkt_usb", .bit = 7, }, + { .name = "swpkt_sar", .bit = 8, }, + { .name = "spi", .bit = 9, }, + { .name = "usbd", .bit = 10, }, + { .name = "sar", .bit = 11, }, + { .name = "robosw", .bit = 12, }, + { .name = "utopia", .bit = 13, }, + { .name = "pcm", .bit = 14, }, + { .name = "usbh", .bit = 15, }, + { .name = "disable_gless", .bit = 16, }, + { .name = "nand", .bit = 17, }, + { .name = "ipsec", .bit = 18, }, + { }, +}; + +static const struct clk_bcm63xx_table_entry bcm63268_clocks[] = { + { .name = "disable_gless", .bit = 0, }, + { .name = "vdsl_qproc", .bit = 1, }, + { .name = "vdsl_afe", .bit = 2, }, + { .name = "vdsl", .bit = 3, }, + { .name = "mips", .bit = 4, .flags = CLK_IS_CRITICAL, }, + { .name = "wlan_ocp", .bit = 5, }, + { .name = "dect", .bit = 6, }, + { .name = "fap0", .bit = 7, }, + { .name = "fap1", .bit = 8, }, + { .name = "sar", .bit = 9, }, + { .name = "robosw", .bit = 10, }, + { .name = "pcm", .bit = 11, }, + { .name = "usbd", .bit = 12, }, + { .name = "usbh", .bit = 13, }, + { .name = "ipsec", .bit = 14, }, + { .name = "spi", .bit = 15, }, + { .name = "hsspi", .bit = 16, }, + { .name = "pcie", .bit = 17, }, + { .name = "phymips", .bit = 18, }, + { .name = "gmac", .bit = 19, }, + { .name = "nand", .bit = 20, }, + { .name = "tbus", .bit = 27, }, + { .name = "robosw250", .bit = 31, }, + { }, +}; + +static int clk_bcm63xx_probe(struct platform_device *pdev) +{ + const struct clk_bcm63xx_table_entry *entry, *table; + struct clk_bcm63xx_hw *hw; + struct resource *r; + u8 maxbit = 0; + int i, ret; + + table = of_device_get_match_data(&pdev->dev); + if (!table) + return -EINVAL; + + for (entry = table; entry->name; entry++) + maxbit = max_t(u8, maxbit, entry->bit); + + hw = devm_kzalloc(&pdev->dev, struct_size(hw, data.hws, maxbit), + GFP_KERNEL); + if (!hw) + return -ENOMEM; + + platform_set_drvdata(pdev, hw); + + spin_lock_init(&hw->lock); + + hw->data.num = maxbit; + for (i = 0; i < maxbit; i++) + hw->data.hws[i] = ERR_PTR(-ENODEV); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hw->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(hw->regs)) + return PTR_ERR(hw->regs); + + for (entry = table; entry->name; entry++) { + struct clk_hw *clk; + + clk = clk_hw_register_gate(&pdev->dev, entry->name, NULL, + entry->flags, hw->regs, entry->bit, + CLK_GATE_BIG_ENDIAN, &hw->lock); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto out_err; + } + + hw->data.hws[entry->bit] = clk; + } + + ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get, + &hw->data); + if (!ret) + return 0; +out_err: + for (i = 0; i < hw->data.num; i++) { + if (!IS_ERR(hw->data.hws[i])) + clk_hw_unregister_gate(hw->data.hws[i]); + } + + return ret; +} + +static int clk_bcm63xx_remove(struct platform_device *pdev) +{ + struct clk_bcm63xx_hw *hw = platform_get_drvdata(pdev); + int i; + + of_clk_del_provider(pdev->dev.of_node); + + for (i = 0; i < hw->data.num; i++) { + if (!IS_ERR(hw->data.hws[i])) + clk_hw_unregister_gate(hw->data.hws[i]); + } + + return 0; +} + +static const struct of_device_id clk_bcm63xx_dt_ids[] = { + { .compatible = "brcm,bcm3368-clocks", .data = &bcm3368_clocks, }, + { .compatible = "brcm,bcm6328-clocks", .data = &bcm6328_clocks, }, + { .compatible = "brcm,bcm6358-clocks", .data = &bcm6358_clocks, }, + { .compatible = "brcm,bcm6362-clocks", .data = &bcm6362_clocks, }, + { .compatible = "brcm,bcm6368-clocks", .data = &bcm6368_clocks, }, + { .compatible = "brcm,bcm63268-clocks", .data = &bcm63268_clocks, }, + { } +}; + +static struct platform_driver clk_bcm63xx = { + .probe = clk_bcm63xx_probe, + .remove = clk_bcm63xx_remove, + .driver = { + .name = "bcm63xx-clock", + .of_match_table = clk_bcm63xx_dt_ids, + }, +}; +builtin_platform_driver(clk_bcm63xx); diff --git a/drivers/clk/clk-lochnagar.c b/drivers/clk/clk-lochnagar.c index a2f31e58ee48..fa8c91758b1d 100644 --- a/drivers/clk/clk-lochnagar.c +++ b/drivers/clk/clk-lochnagar.c @@ -16,7 +16,6 @@ #include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/mfd/lochnagar.h> #include <linux/mfd/lochnagar1_regs.h> #include <linux/mfd/lochnagar2_regs.h> @@ -40,48 +39,46 @@ struct lochnagar_clk { struct lochnagar_clk_priv { struct device *dev; struct regmap *regmap; - enum lochnagar_type type; - - const char **parents; - unsigned int nparents; struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS]; }; -static const char * const lochnagar1_clk_parents[] = { - "ln-none", - "ln-spdif-mclk", - "ln-psia1-mclk", - "ln-psia2-mclk", - "ln-cdc-clkout", - "ln-dsp-clkout", - "ln-pmic-32k", - "ln-gf-mclk1", - "ln-gf-mclk3", - "ln-gf-mclk2", - "ln-gf-mclk4", +#define LN_PARENT(NAME) { .name = NAME, .fw_name = NAME } + +static const struct clk_parent_data lochnagar1_clk_parents[] = { + LN_PARENT("ln-none"), + LN_PARENT("ln-spdif-mclk"), + LN_PARENT("ln-psia1-mclk"), + LN_PARENT("ln-psia2-mclk"), + LN_PARENT("ln-cdc-clkout"), + LN_PARENT("ln-dsp-clkout"), + LN_PARENT("ln-pmic-32k"), + LN_PARENT("ln-gf-mclk1"), + LN_PARENT("ln-gf-mclk3"), + LN_PARENT("ln-gf-mclk2"), + LN_PARENT("ln-gf-mclk4"), }; -static const char * const lochnagar2_clk_parents[] = { - "ln-none", - "ln-cdc-clkout", - "ln-dsp-clkout", - "ln-pmic-32k", - "ln-spdif-mclk", - "ln-clk-12m", - "ln-clk-11m", - "ln-clk-24m", - "ln-clk-22m", - "ln-clk-8m", - "ln-usb-clk-24m", - "ln-gf-mclk1", - "ln-gf-mclk3", - "ln-gf-mclk2", - "ln-psia1-mclk", - "ln-psia2-mclk", - "ln-spdif-clkout", - "ln-adat-mclk", - "ln-usb-clk-12m", +static const struct clk_parent_data lochnagar2_clk_parents[] = { + LN_PARENT("ln-none"), + LN_PARENT("ln-cdc-clkout"), + LN_PARENT("ln-dsp-clkout"), + LN_PARENT("ln-pmic-32k"), + LN_PARENT("ln-spdif-mclk"), + LN_PARENT("ln-clk-12m"), + LN_PARENT("ln-clk-11m"), + LN_PARENT("ln-clk-24m"), + LN_PARENT("ln-clk-22m"), + LN_PARENT("ln-clk-8m"), + LN_PARENT("ln-usb-clk-24m"), + LN_PARENT("ln-gf-mclk1"), + LN_PARENT("ln-gf-mclk3"), + LN_PARENT("ln-gf-mclk2"), + LN_PARENT("ln-psia1-mclk"), + LN_PARENT("ln-psia2-mclk"), + LN_PARENT("ln-spdif-clkout"), + LN_PARENT("ln-adat-mclk"), + LN_PARENT("ln-usb-clk-12m"), }; #define LN1_CLK(ID, NAME, REG) \ @@ -122,6 +119,24 @@ static const struct lochnagar_clk lochnagar2_clks[LOCHNAGAR_NUM_CLOCKS] = { LN2_CLK(SOUNDCARD_MCLK, "ln-soundcard-mclk"), }; +struct lochnagar_config { + const struct clk_parent_data *parents; + int nparents; + const struct lochnagar_clk *clks; +}; + +static const struct lochnagar_config lochnagar1_conf = { + .parents = lochnagar1_clk_parents, + .nparents = ARRAY_SIZE(lochnagar1_clk_parents), + .clks = lochnagar1_clks, +}; + +static const struct lochnagar_config lochnagar2_conf = { + .parents = lochnagar2_clk_parents, + .nparents = ARRAY_SIZE(lochnagar2_clk_parents), + .clks = lochnagar2_clks, +}; + static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw) { return container_of(hw, struct lochnagar_clk, hw); @@ -183,7 +198,7 @@ static u8 lochnagar_clk_get_parent(struct clk_hw *hw) if (ret < 0) { dev_dbg(priv->dev, "Failed to read parent of %s: %d\n", lclk->name, ret); - return priv->nparents; + return hw->init->num_parents; } val &= lclk->src_mask; @@ -198,46 +213,6 @@ static const struct clk_ops lochnagar_clk_ops = { .get_parent = lochnagar_clk_get_parent, }; -static int lochnagar_init_parents(struct lochnagar_clk_priv *priv) -{ - struct device_node *np = priv->dev->of_node; - int i, j; - - switch (priv->type) { - case LOCHNAGAR1: - memcpy(priv->lclks, lochnagar1_clks, sizeof(lochnagar1_clks)); - - priv->nparents = ARRAY_SIZE(lochnagar1_clk_parents); - priv->parents = devm_kmemdup(priv->dev, lochnagar1_clk_parents, - sizeof(lochnagar1_clk_parents), - GFP_KERNEL); - break; - case LOCHNAGAR2: - memcpy(priv->lclks, lochnagar2_clks, sizeof(lochnagar2_clks)); - - priv->nparents = ARRAY_SIZE(lochnagar2_clk_parents); - priv->parents = devm_kmemdup(priv->dev, lochnagar2_clk_parents, - sizeof(lochnagar2_clk_parents), - GFP_KERNEL); - break; - default: - dev_err(priv->dev, "Unknown Lochnagar type: %d\n", priv->type); - return -EINVAL; - } - - if (!priv->parents) - return -ENOMEM; - - for (i = 0; i < priv->nparents; i++) { - j = of_property_match_string(np, "clock-names", - priv->parents[i]); - if (j >= 0) - priv->parents[i] = of_clk_get_parent_name(np, j); - } - - return 0; -} - static struct clk_hw * lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) { @@ -252,16 +227,42 @@ lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) return &priv->lclks[idx].hw; } -static int lochnagar_init_clks(struct lochnagar_clk_priv *priv) +static const struct of_device_id lochnagar_of_match[] = { + { .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf }, + { .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf }, + {} +}; +MODULE_DEVICE_TABLE(of, lochnagar_of_match); + +static int lochnagar_clk_probe(struct platform_device *pdev) { struct clk_init_data clk_init = { .ops = &lochnagar_clk_ops, - .parent_names = priv->parents, - .num_parents = priv->nparents, }; + struct device *dev = &pdev->dev; + struct lochnagar_clk_priv *priv; + const struct of_device_id *of_id; struct lochnagar_clk *lclk; + struct lochnagar_config *conf; int ret, i; + of_id = of_match_device(lochnagar_of_match, dev); + if (!of_id) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->regmap = dev_get_regmap(dev->parent, NULL); + conf = (struct lochnagar_config *)of_id->data; + + memcpy(priv->lclks, conf->clks, sizeof(priv->lclks)); + + clk_init.parent_data = conf->parents; + clk_init.num_parents = conf->nparents; + for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { lclk = &priv->lclks[i]; @@ -273,55 +274,21 @@ static int lochnagar_init_clks(struct lochnagar_clk_priv *priv) lclk->priv = priv; lclk->hw.init = &clk_init; - ret = devm_clk_hw_register(priv->dev, &lclk->hw); + ret = devm_clk_hw_register(dev, &lclk->hw); if (ret) { - dev_err(priv->dev, "Failed to register %s: %d\n", + dev_err(dev, "Failed to register %s: %d\n", lclk->name, ret); return ret; } } - ret = devm_of_clk_add_hw_provider(priv->dev, lochnagar_of_clk_hw_get, - priv); + ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv); if (ret < 0) - dev_err(priv->dev, "Failed to register provider: %d\n", ret); + dev_err(dev, "Failed to register provider: %d\n", ret); return ret; } -static const struct of_device_id lochnagar_of_match[] = { - { .compatible = "cirrus,lochnagar1-clk", .data = (void *)LOCHNAGAR1 }, - { .compatible = "cirrus,lochnagar2-clk", .data = (void *)LOCHNAGAR2 }, - {} -}; -MODULE_DEVICE_TABLE(of, lochnagar_of_match); - -static int lochnagar_clk_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct lochnagar_clk_priv *priv; - const struct of_device_id *of_id; - int ret; - - of_id = of_match_device(lochnagar_of_match, dev); - if (!of_id) - return -EINVAL; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->dev = dev; - priv->regmap = dev_get_regmap(dev->parent, NULL); - priv->type = (enum lochnagar_type)of_id->data; - - ret = lochnagar_init_parents(priv); - if (ret) - return ret; - - return lochnagar_init_clks(priv); -} - static struct platform_driver lochnagar_clk_driver = { .driver = { .name = "lochnagar-clk", diff --git a/drivers/clk/clk-si5341.c b/drivers/clk/clk-si5341.c new file mode 100644 index 000000000000..72424eb7e5f8 --- /dev/null +++ b/drivers/clk/clk-si5341.c @@ -0,0 +1,1346 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Silicon Labs Si5341/Si5340 Clock generator + * Copyright (C) 2019 Topic Embedded Products + * Author: Mike Looijmans <mike.looijmans@topic.nl> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/gcd.h> +#include <linux/math64.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#define SI5341_MAX_NUM_OUTPUTS 10 +#define SI5340_MAX_NUM_OUTPUTS 4 + +#define SI5341_NUM_SYNTH 5 +#define SI5340_NUM_SYNTH 4 + +/* Range of the synthesizer fractional divider */ +#define SI5341_SYNTH_N_MIN 10 +#define SI5341_SYNTH_N_MAX 4095 + +/* The chip can get its input clock from 3 input pins or an XTAL */ + +/* There is one PLL running at 13500–14256 MHz */ +#define SI5341_PLL_VCO_MIN 13500000000ull +#define SI5341_PLL_VCO_MAX 14256000000ull + +/* The 5 frequency synthesizers obtain their input from the PLL */ +struct clk_si5341_synth { + struct clk_hw hw; + struct clk_si5341 *data; + u8 index; +}; +#define to_clk_si5341_synth(_hw) \ + container_of(_hw, struct clk_si5341_synth, hw) + +/* The output stages can be connected to any synth (full mux) */ +struct clk_si5341_output { + struct clk_hw hw; + struct clk_si5341 *data; + u8 index; +}; +#define to_clk_si5341_output(_hw) \ + container_of(_hw, struct clk_si5341_output, hw) + +struct clk_si5341 { + struct clk_hw hw; + struct regmap *regmap; + struct i2c_client *i2c_client; + struct clk_si5341_synth synth[SI5341_NUM_SYNTH]; + struct clk_si5341_output clk[SI5341_MAX_NUM_OUTPUTS]; + struct clk *pxtal; + const char *pxtal_name; + const u16 *reg_output_offset; + const u16 *reg_rdiv_offset; + u64 freq_vco; /* 13500–14256 MHz */ + u8 num_outputs; + u8 num_synth; +}; +#define to_clk_si5341(_hw) container_of(_hw, struct clk_si5341, hw) + +struct clk_si5341_output_config { + u8 out_format_drv_bits; + u8 out_cm_ampl_bits; + bool synth_master; + bool always_on; +}; + +#define SI5341_PAGE 0x0001 +#define SI5341_PN_BASE 0x0002 +#define SI5341_DEVICE_REV 0x0005 +#define SI5341_STATUS 0x000C +#define SI5341_SOFT_RST 0x001C + +/* Input dividers (48-bit) */ +#define SI5341_IN_PDIV(x) (0x0208 + ((x) * 10)) +#define SI5341_IN_PSET(x) (0x020E + ((x) * 10)) + +/* PLL configuration */ +#define SI5341_PLL_M_NUM 0x0235 +#define SI5341_PLL_M_DEN 0x023B + +/* Output configuration */ +#define SI5341_OUT_CONFIG(output) \ + ((output)->data->reg_output_offset[(output)->index]) +#define SI5341_OUT_FORMAT(output) (SI5341_OUT_CONFIG(output) + 1) +#define SI5341_OUT_CM(output) (SI5341_OUT_CONFIG(output) + 2) +#define SI5341_OUT_MUX_SEL(output) (SI5341_OUT_CONFIG(output) + 3) +#define SI5341_OUT_R_REG(output) \ + ((output)->data->reg_rdiv_offset[(output)->index]) + +/* Synthesize N divider */ +#define SI5341_SYNTH_N_NUM(x) (0x0302 + ((x) * 11)) +#define SI5341_SYNTH_N_DEN(x) (0x0308 + ((x) * 11)) +#define SI5341_SYNTH_N_UPD(x) (0x030C + ((x) * 11)) + +/* Synthesizer output enable, phase bypass, power mode */ +#define SI5341_SYNTH_N_CLK_TO_OUTX_EN 0x0A03 +#define SI5341_SYNTH_N_PIBYP 0x0A04 +#define SI5341_SYNTH_N_PDNB 0x0A05 +#define SI5341_SYNTH_N_CLK_DIS 0x0B4A + +#define SI5341_REGISTER_MAX 0xBFF + +/* SI5341_OUT_CONFIG bits */ +#define SI5341_OUT_CFG_PDN BIT(0) +#define SI5341_OUT_CFG_OE BIT(1) +#define SI5341_OUT_CFG_RDIV_FORCE2 BIT(2) + +/* Static configuration (to be moved to firmware) */ +struct si5341_reg_default { + u16 address; + u8 value; +}; + +/* Output configuration registers 0..9 are not quite logically organized */ +static const u16 si5341_reg_output_offset[] = { + 0x0108, + 0x010D, + 0x0112, + 0x0117, + 0x011C, + 0x0121, + 0x0126, + 0x012B, + 0x0130, + 0x013A, +}; + +static const u16 si5340_reg_output_offset[] = { + 0x0112, + 0x0117, + 0x0126, + 0x012B, +}; + +/* The location of the R divider registers */ +static const u16 si5341_reg_rdiv_offset[] = { + 0x024A, + 0x024D, + 0x0250, + 0x0253, + 0x0256, + 0x0259, + 0x025C, + 0x025F, + 0x0262, + 0x0268, +}; +static const u16 si5340_reg_rdiv_offset[] = { + 0x0250, + 0x0253, + 0x025C, + 0x025F, +}; + +/* + * Programming sequence from ClockBuilder, settings to initialize the system + * using only the XTAL input, without pre-divider. + * This also contains settings that aren't mentioned anywhere in the datasheet. + * The "known" settings like synth and output configuration are done later. + */ +static const struct si5341_reg_default si5341_reg_defaults[] = { + { 0x0017, 0x3A }, /* INT mask (disable interrupts) */ + { 0x0018, 0xFF }, /* INT mask */ + { 0x0021, 0x0F }, /* Select XTAL as input */ + { 0x0022, 0x00 }, /* Not in datasheet */ + { 0x002B, 0x02 }, /* SPI config */ + { 0x002C, 0x20 }, /* LOS enable for XTAL */ + { 0x002D, 0x00 }, /* LOS timing */ + { 0x002E, 0x00 }, + { 0x002F, 0x00 }, + { 0x0030, 0x00 }, + { 0x0031, 0x00 }, + { 0x0032, 0x00 }, + { 0x0033, 0x00 }, + { 0x0034, 0x00 }, + { 0x0035, 0x00 }, + { 0x0036, 0x00 }, + { 0x0037, 0x00 }, + { 0x0038, 0x00 }, /* LOS setting (thresholds) */ + { 0x0039, 0x00 }, + { 0x003A, 0x00 }, + { 0x003B, 0x00 }, + { 0x003C, 0x00 }, + { 0x003D, 0x00 }, /* LOS setting (thresholds) end */ + { 0x0041, 0x00 }, /* LOS0_DIV_SEL */ + { 0x0042, 0x00 }, /* LOS1_DIV_SEL */ + { 0x0043, 0x00 }, /* LOS2_DIV_SEL */ + { 0x0044, 0x00 }, /* LOS3_DIV_SEL */ + { 0x009E, 0x00 }, /* Not in datasheet */ + { 0x0102, 0x01 }, /* Enable outputs */ + { 0x013F, 0x00 }, /* Not in datasheet */ + { 0x0140, 0x00 }, /* Not in datasheet */ + { 0x0141, 0x40 }, /* OUT LOS */ + { 0x0202, 0x00 }, /* XAXB_FREQ_OFFSET (=0)*/ + { 0x0203, 0x00 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0206, 0x00 }, /* PXAXB (2^x) */ + { 0x0208, 0x00 }, /* Px divider setting (usually 0) */ + { 0x0209, 0x00 }, + { 0x020A, 0x00 }, + { 0x020B, 0x00 }, + { 0x020C, 0x00 }, + { 0x020D, 0x00 }, + { 0x020E, 0x00 }, + { 0x020F, 0x00 }, + { 0x0210, 0x00 }, + { 0x0211, 0x00 }, + { 0x0212, 0x00 }, + { 0x0213, 0x00 }, + { 0x0214, 0x00 }, + { 0x0215, 0x00 }, + { 0x0216, 0x00 }, + { 0x0217, 0x00 }, + { 0x0218, 0x00 }, + { 0x0219, 0x00 }, + { 0x021A, 0x00 }, + { 0x021B, 0x00 }, + { 0x021C, 0x00 }, + { 0x021D, 0x00 }, + { 0x021E, 0x00 }, + { 0x021F, 0x00 }, + { 0x0220, 0x00 }, + { 0x0221, 0x00 }, + { 0x0222, 0x00 }, + { 0x0223, 0x00 }, + { 0x0224, 0x00 }, + { 0x0225, 0x00 }, + { 0x0226, 0x00 }, + { 0x0227, 0x00 }, + { 0x0228, 0x00 }, + { 0x0229, 0x00 }, + { 0x022A, 0x00 }, + { 0x022B, 0x00 }, + { 0x022C, 0x00 }, + { 0x022D, 0x00 }, + { 0x022E, 0x00 }, + { 0x022F, 0x00 }, /* Px divider setting (usually 0) end */ + { 0x026B, 0x00 }, /* DESIGN_ID (ASCII string) */ + { 0x026C, 0x00 }, + { 0x026D, 0x00 }, + { 0x026E, 0x00 }, + { 0x026F, 0x00 }, + { 0x0270, 0x00 }, + { 0x0271, 0x00 }, + { 0x0272, 0x00 }, /* DESIGN_ID (ASCII string) end */ + { 0x0339, 0x1F }, /* N_FSTEP_MSK */ + { 0x033B, 0x00 }, /* Nx_FSTEPW (Frequency step) */ + { 0x033C, 0x00 }, + { 0x033D, 0x00 }, + { 0x033E, 0x00 }, + { 0x033F, 0x00 }, + { 0x0340, 0x00 }, + { 0x0341, 0x00 }, + { 0x0342, 0x00 }, + { 0x0343, 0x00 }, + { 0x0344, 0x00 }, + { 0x0345, 0x00 }, + { 0x0346, 0x00 }, + { 0x0347, 0x00 }, + { 0x0348, 0x00 }, + { 0x0349, 0x00 }, + { 0x034A, 0x00 }, + { 0x034B, 0x00 }, + { 0x034C, 0x00 }, + { 0x034D, 0x00 }, + { 0x034E, 0x00 }, + { 0x034F, 0x00 }, + { 0x0350, 0x00 }, + { 0x0351, 0x00 }, + { 0x0352, 0x00 }, + { 0x0353, 0x00 }, + { 0x0354, 0x00 }, + { 0x0355, 0x00 }, + { 0x0356, 0x00 }, + { 0x0357, 0x00 }, + { 0x0358, 0x00 }, /* Nx_FSTEPW (Frequency step) end */ + { 0x0359, 0x00 }, /* Nx_DELAY */ + { 0x035A, 0x00 }, + { 0x035B, 0x00 }, + { 0x035C, 0x00 }, + { 0x035D, 0x00 }, + { 0x035E, 0x00 }, + { 0x035F, 0x00 }, + { 0x0360, 0x00 }, + { 0x0361, 0x00 }, + { 0x0362, 0x00 }, /* Nx_DELAY end */ + { 0x0802, 0x00 }, /* Not in datasheet */ + { 0x0803, 0x00 }, /* Not in datasheet */ + { 0x0804, 0x00 }, /* Not in datasheet */ + { 0x090E, 0x02 }, /* XAXB_EXTCLK_EN=0 XAXB_PDNB=1 (use XTAL) */ + { 0x091C, 0x04 }, /* ZDM_EN=4 (Normal mode) */ + { 0x0943, 0x00 }, /* IO_VDD_SEL=0 (0=1v8, use 1=3v3) */ + { 0x0949, 0x00 }, /* IN_EN (disable input clocks) */ + { 0x094A, 0x00 }, /* INx_TO_PFD_EN (disabled) */ + { 0x0A02, 0x00 }, /* Not in datasheet */ + { 0x0B44, 0x0F }, /* PDIV_ENB (datasheet does not mention what it is) */ +}; + +/* Read and interpret a 44-bit followed by a 32-bit value in the regmap */ +static int si5341_decode_44_32(struct regmap *regmap, unsigned int reg, + u64 *val1, u32 *val2) +{ + int err; + u8 r[10]; + + err = regmap_bulk_read(regmap, reg, r, 10); + if (err < 0) + return err; + + *val1 = ((u64)((r[5] & 0x0f) << 8 | r[4]) << 32) | + (get_unaligned_le32(r)); + *val2 = get_unaligned_le32(&r[6]); + + return 0; +} + +static int si5341_encode_44_32(struct regmap *regmap, unsigned int reg, + u64 n_num, u32 n_den) +{ + u8 r[10]; + + /* Shift left as far as possible without overflowing */ + while (!(n_num & BIT_ULL(43)) && !(n_den & BIT(31))) { + n_num <<= 1; + n_den <<= 1; + } + + /* 44 bits (6 bytes) numerator */ + put_unaligned_le32(n_num, r); + r[4] = (n_num >> 32) & 0xff; + r[5] = (n_num >> 40) & 0x0f; + /* 32 bits denominator */ + put_unaligned_le32(n_den, &r[6]); + + /* Program the fraction */ + return regmap_bulk_write(regmap, reg, r, sizeof(r)); +} + +/* VCO, we assume it runs at a constant frequency */ +static unsigned long si5341_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_si5341 *data = to_clk_si5341(hw); + int err; + u64 res; + u64 m_num; + u32 m_den; + unsigned int shift; + + /* Assume that PDIV is not being used, just read the PLL setting */ + err = si5341_decode_44_32(data->regmap, SI5341_PLL_M_NUM, + &m_num, &m_den); + if (err < 0) + return 0; + + if (!m_num || !m_den) + return 0; + + /* + * Though m_num is 64-bit, only the upper bits are actually used. While + * calculating m_num and m_den, they are shifted as far as possible to + * the left. To avoid 96-bit division here, we just shift them back so + * we can do with just 64 bits. + */ + shift = 0; + res = m_num; + while (res & 0xffff00000000ULL) { + ++shift; + res >>= 1; + } + res *= parent_rate; + do_div(res, (m_den >> shift)); + + /* We cannot return the actual frequency in 32 bit, store it locally */ + data->freq_vco = res; + + /* Report kHz since the value is out of range */ + do_div(res, 1000); + + return (unsigned long)res; +} + +static const struct clk_ops si5341_clk_ops = { + .recalc_rate = si5341_clk_recalc_rate, +}; + +/* Synthesizers, there are 5 synthesizers that connect to any of the outputs */ + +/* The synthesizer is on if all power and enable bits are set */ +static int si5341_synth_clk_is_on(struct clk_hw *hw) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + int err; + u32 val; + u8 index = synth->index; + + err = regmap_read(synth->data->regmap, + SI5341_SYNTH_N_CLK_TO_OUTX_EN, &val); + if (err < 0) + return 0; + + if (!(val & BIT(index))) + return 0; + + err = regmap_read(synth->data->regmap, SI5341_SYNTH_N_PDNB, &val); + if (err < 0) + return 0; + + if (!(val & BIT(index))) + return 0; + + /* This bit must be 0 for the synthesizer to receive clock input */ + err = regmap_read(synth->data->regmap, SI5341_SYNTH_N_CLK_DIS, &val); + if (err < 0) + return 0; + + return !(val & BIT(index)); +} + +static void si5341_synth_clk_unprepare(struct clk_hw *hw) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + u8 index = synth->index; /* In range 0..5 */ + u8 mask = BIT(index); + + /* Disable output */ + regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_CLK_TO_OUTX_EN, mask, 0); + /* Power down */ + regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_PDNB, mask, 0); + /* Disable clock input to synth (set to 1 to disable) */ + regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_CLK_DIS, mask, mask); +} + +static int si5341_synth_clk_prepare(struct clk_hw *hw) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + int err; + u8 index = synth->index; + u8 mask = BIT(index); + + /* Power up */ + err = regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_PDNB, mask, mask); + if (err < 0) + return err; + + /* Enable clock input to synth (set bit to 0 to enable) */ + err = regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_CLK_DIS, mask, 0); + if (err < 0) + return err; + + /* Enable output */ + return regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_CLK_TO_OUTX_EN, mask, mask); +} + +/* Synth clock frequency: Fvco * n_den / n_den, with Fvco in 13500-14256 MHz */ +static unsigned long si5341_synth_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + u64 f; + u64 n_num; + u32 n_den; + int err; + + err = si5341_decode_44_32(synth->data->regmap, + SI5341_SYNTH_N_NUM(synth->index), &n_num, &n_den); + if (err < 0) + return err; + + /* + * n_num and n_den are shifted left as much as possible, so to prevent + * overflow in 64-bit math, we shift n_den 4 bits to the right + */ + f = synth->data->freq_vco; + f *= n_den >> 4; + + /* Now we need to to 64-bit division: f/n_num */ + /* And compensate for the 4 bits we dropped */ + f = div64_u64(f, (n_num >> 4)); + + return f; +} + +static long si5341_synth_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + u64 f; + + /* The synthesizer accuracy is such that anything in range will work */ + f = synth->data->freq_vco; + do_div(f, SI5341_SYNTH_N_MAX); + if (rate < f) + return f; + + f = synth->data->freq_vco; + do_div(f, SI5341_SYNTH_N_MIN); + if (rate > f) + return f; + + return rate; +} + +static int si5341_synth_program(struct clk_si5341_synth *synth, + u64 n_num, u32 n_den, bool is_integer) +{ + int err; + u8 index = synth->index; + + err = si5341_encode_44_32(synth->data->regmap, + SI5341_SYNTH_N_NUM(index), n_num, n_den); + + err = regmap_update_bits(synth->data->regmap, + SI5341_SYNTH_N_PIBYP, BIT(index), is_integer ? BIT(index) : 0); + if (err < 0) + return err; + + return regmap_write(synth->data->regmap, + SI5341_SYNTH_N_UPD(index), 0x01); +} + + +static int si5341_synth_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_si5341_synth *synth = to_clk_si5341_synth(hw); + u64 n_num; + u32 n_den; + u32 r; + u32 g; + bool is_integer; + + n_num = synth->data->freq_vco; + n_den = rate; + + /* see if there's an integer solution */ + r = do_div(n_num, rate); + is_integer = (r == 0); + if (is_integer) { + /* Integer divider equal to n_num */ + n_den = 1; + } else { + /* Calculate a fractional solution */ + g = gcd(r, rate); + n_den = rate / g; + n_num *= n_den; + n_num += r / g; + } + + dev_dbg(&synth->data->i2c_client->dev, + "%s(%u): n=0x%llx d=0x%x %s\n", __func__, + synth->index, n_num, n_den, + is_integer ? "int" : "frac"); + + return si5341_synth_program(synth, n_num, n_den, is_integer); +} + +static const struct clk_ops si5341_synth_clk_ops = { + .is_prepared = si5341_synth_clk_is_on, + .prepare = si5341_synth_clk_prepare, + .unprepare = si5341_synth_clk_unprepare, + .recalc_rate = si5341_synth_clk_recalc_rate, + .round_rate = si5341_synth_clk_round_rate, + .set_rate = si5341_synth_clk_set_rate, +}; + +static int si5341_output_clk_is_on(struct clk_hw *hw) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + int err; + u32 val; + + err = regmap_read(output->data->regmap, + SI5341_OUT_CONFIG(output), &val); + if (err < 0) + return err; + + /* Bit 0=PDN, 1=OE so only a value of 0x2 enables the output */ + return (val & 0x03) == SI5341_OUT_CFG_OE; +} + +/* Disables and then powers down the output */ +static void si5341_output_clk_unprepare(struct clk_hw *hw) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + + regmap_update_bits(output->data->regmap, + SI5341_OUT_CONFIG(output), + SI5341_OUT_CFG_OE, 0); + regmap_update_bits(output->data->regmap, + SI5341_OUT_CONFIG(output), + SI5341_OUT_CFG_PDN, SI5341_OUT_CFG_PDN); +} + +/* Powers up and then enables the output */ +static int si5341_output_clk_prepare(struct clk_hw *hw) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + int err; + + err = regmap_update_bits(output->data->regmap, + SI5341_OUT_CONFIG(output), + SI5341_OUT_CFG_PDN, 0); + if (err < 0) + return err; + + return regmap_update_bits(output->data->regmap, + SI5341_OUT_CONFIG(output), + SI5341_OUT_CFG_OE, SI5341_OUT_CFG_OE); +} + +static unsigned long si5341_output_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + int err; + u32 val; + u32 r_divider; + u8 r[3]; + + err = regmap_bulk_read(output->data->regmap, + SI5341_OUT_R_REG(output), r, 3); + if (err < 0) + return err; + + /* Calculate value as 24-bit integer*/ + r_divider = r[2] << 16 | r[1] << 8 | r[0]; + + /* If Rx_REG is zero, the divider is disabled, so return a "0" rate */ + if (!r_divider) + return 0; + + /* Divider is 2*(Rx_REG+1) */ + r_divider += 1; + r_divider <<= 1; + + err = regmap_read(output->data->regmap, + SI5341_OUT_CONFIG(output), &val); + if (err < 0) + return err; + + if (val & SI5341_OUT_CFG_RDIV_FORCE2) + r_divider = 2; + + return parent_rate / r_divider; +} + +static long si5341_output_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long r; + + r = *parent_rate >> 1; + + /* If rate is an even divisor, no changes to parent required */ + if (r && !(r % rate)) + return (long)rate; + + if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { + if (rate > 200000000) { + /* minimum r-divider is 2 */ + r = 2; + } else { + /* Take a parent frequency near 400 MHz */ + r = (400000000u / rate) & ~1; + } + *parent_rate = r * rate; + } else { + /* We cannot change our parent's rate, report what we can do */ + r /= rate; + rate = *parent_rate / (r << 1); + } + + return rate; +} + +static int si5341_output_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + /* Frequency divider is (r_div + 1) * 2 */ + u32 r_div = (parent_rate / rate) >> 1; + int err; + u8 r[3]; + + if (r_div <= 1) + r_div = 0; + else if (r_div >= BIT(24)) + r_div = BIT(24) - 1; + else + --r_div; + + /* For a value of "2", we set the "OUT0_RDIV_FORCE2" bit */ + err = regmap_update_bits(output->data->regmap, + SI5341_OUT_CONFIG(output), + SI5341_OUT_CFG_RDIV_FORCE2, + (r_div == 0) ? SI5341_OUT_CFG_RDIV_FORCE2 : 0); + if (err < 0) + return err; + + /* Always write Rx_REG, because a zero value disables the divider */ + r[0] = r_div ? (r_div & 0xff) : 1; + r[1] = (r_div >> 8) & 0xff; + r[2] = (r_div >> 16) & 0xff; + err = regmap_bulk_write(output->data->regmap, + SI5341_OUT_R_REG(output), r, 3); + + return 0; +} + +static int si5341_output_reparent(struct clk_si5341_output *output, u8 index) +{ + return regmap_update_bits(output->data->regmap, + SI5341_OUT_MUX_SEL(output), 0x07, index); +} + +static int si5341_output_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + + if (index >= output->data->num_synth) + return -EINVAL; + + return si5341_output_reparent(output, index); +} + +static u8 si5341_output_get_parent(struct clk_hw *hw) +{ + struct clk_si5341_output *output = to_clk_si5341_output(hw); + int err; + u32 val; + + err = regmap_read(output->data->regmap, + SI5341_OUT_MUX_SEL(output), &val); + + return val & 0x7; +} + +static const struct clk_ops si5341_output_clk_ops = { + .is_prepared = si5341_output_clk_is_on, + .prepare = si5341_output_clk_prepare, + .unprepare = si5341_output_clk_unprepare, + .recalc_rate = si5341_output_clk_recalc_rate, + .round_rate = si5341_output_clk_round_rate, + .set_rate = si5341_output_clk_set_rate, + .set_parent = si5341_output_set_parent, + .get_parent = si5341_output_get_parent, +}; + +/* + * The chip can be bought in a pre-programmed version, or one can program the + * NVM in the chip to boot up in a preset mode. This routine tries to determine + * if that's the case, or if we need to reset and program everything from + * scratch. Returns negative error, or true/false. + */ +static int si5341_is_programmed_already(struct clk_si5341 *data) +{ + int err; + u8 r[4]; + + /* Read the PLL divider value, it must have a non-zero value */ + err = regmap_bulk_read(data->regmap, SI5341_PLL_M_DEN, + r, ARRAY_SIZE(r)); + if (err < 0) + return err; + + return !!get_unaligned_le32(r); +} + +static struct clk_hw * +of_clk_si5341_get(struct of_phandle_args *clkspec, void *_data) +{ + struct clk_si5341 *data = _data; + unsigned int idx = clkspec->args[1]; + unsigned int group = clkspec->args[0]; + + switch (group) { + case 0: + if (idx >= data->num_outputs) { + dev_err(&data->i2c_client->dev, + "invalid output index %u\n", idx); + return ERR_PTR(-EINVAL); + } + return &data->clk[idx].hw; + case 1: + if (idx >= data->num_synth) { + dev_err(&data->i2c_client->dev, + "invalid synthesizer index %u\n", idx); + return ERR_PTR(-EINVAL); + } + return &data->synth[idx].hw; + case 2: + if (idx > 0) { + dev_err(&data->i2c_client->dev, + "invalid PLL index %u\n", idx); + return ERR_PTR(-EINVAL); + } + return &data->hw; + default: + dev_err(&data->i2c_client->dev, "invalid group %u\n", group); + return ERR_PTR(-EINVAL); + } +} + +static int si5341_probe_chip_id(struct clk_si5341 *data) +{ + int err; + u8 reg[4]; + u16 model; + + err = regmap_bulk_read(data->regmap, SI5341_PN_BASE, reg, + ARRAY_SIZE(reg)); + if (err < 0) { + dev_err(&data->i2c_client->dev, "Failed to read chip ID\n"); + return err; + } + + model = get_unaligned_le16(reg); + + dev_info(&data->i2c_client->dev, "Chip: %x Grade: %u Rev: %u\n", + model, reg[2], reg[3]); + + switch (model) { + case 0x5340: + data->num_outputs = SI5340_MAX_NUM_OUTPUTS; + data->num_synth = SI5340_NUM_SYNTH; + data->reg_output_offset = si5340_reg_output_offset; + data->reg_rdiv_offset = si5340_reg_rdiv_offset; + break; + case 0x5341: + data->num_outputs = SI5341_MAX_NUM_OUTPUTS; + data->num_synth = SI5341_NUM_SYNTH; + data->reg_output_offset = si5341_reg_output_offset; + data->reg_rdiv_offset = si5341_reg_rdiv_offset; + break; + default: + dev_err(&data->i2c_client->dev, "Model '%x' not supported\n", + model); + return -EINVAL; + } + + return 0; +} + +/* Read active settings into the regmap cache for later reference */ +static int si5341_read_settings(struct clk_si5341 *data) +{ + int err; + u8 i; + u8 r[10]; + + err = regmap_bulk_read(data->regmap, SI5341_PLL_M_NUM, r, 10); + if (err < 0) + return err; + + err = regmap_bulk_read(data->regmap, + SI5341_SYNTH_N_CLK_TO_OUTX_EN, r, 3); + if (err < 0) + return err; + + err = regmap_bulk_read(data->regmap, + SI5341_SYNTH_N_CLK_DIS, r, 1); + if (err < 0) + return err; + + for (i = 0; i < data->num_synth; ++i) { + err = regmap_bulk_read(data->regmap, + SI5341_SYNTH_N_NUM(i), r, 10); + if (err < 0) + return err; + } + + for (i = 0; i < data->num_outputs; ++i) { + err = regmap_bulk_read(data->regmap, + data->reg_output_offset[i], r, 4); + if (err < 0) + return err; + + err = regmap_bulk_read(data->regmap, + data->reg_rdiv_offset[i], r, 3); + if (err < 0) + return err; + } + + return 0; +} + +static int si5341_write_multiple(struct clk_si5341 *data, + const struct si5341_reg_default *values, unsigned int num_values) +{ + unsigned int i; + int res; + + for (i = 0; i < num_values; ++i) { + res = regmap_write(data->regmap, + values[i].address, values[i].value); + if (res < 0) { + dev_err(&data->i2c_client->dev, + "Failed to write %#x:%#x\n", + values[i].address, values[i].value); + return res; + } + } + + return 0; +} + +static const struct si5341_reg_default si5341_preamble[] = { + { 0x0B25, 0x00 }, + { 0x0502, 0x01 }, + { 0x0505, 0x03 }, + { 0x0957, 0x1F }, + { 0x0B4E, 0x1A }, +}; + +static int si5341_send_preamble(struct clk_si5341 *data) +{ + int res; + u32 revision; + + /* For revision 2 and up, the values are slightly different */ + res = regmap_read(data->regmap, SI5341_DEVICE_REV, &revision); + if (res < 0) + return res; + + /* Write "preamble" as specified by datasheet */ + res = regmap_write(data->regmap, 0xB24, revision < 2 ? 0xD8 : 0xC0); + if (res < 0) + return res; + res = si5341_write_multiple(data, + si5341_preamble, ARRAY_SIZE(si5341_preamble)); + if (res < 0) + return res; + + /* Datasheet specifies a 300ms wait after sending the preamble */ + msleep(300); + + return 0; +} + +/* Perform a soft reset and write post-amble */ +static int si5341_finalize_defaults(struct clk_si5341 *data) +{ + int res; + u32 revision; + + res = regmap_read(data->regmap, SI5341_DEVICE_REV, &revision); + if (res < 0) + return res; + + dev_dbg(&data->i2c_client->dev, "%s rev=%u\n", __func__, revision); + + res = regmap_write(data->regmap, SI5341_SOFT_RST, 0x01); + if (res < 0) + return res; + + /* Datasheet does not explain these nameless registers */ + res = regmap_write(data->regmap, 0xB24, revision < 2 ? 0xDB : 0xC3); + if (res < 0) + return res; + res = regmap_write(data->regmap, 0x0B25, 0x02); + if (res < 0) + return res; + + return 0; +} + + +static const struct regmap_range si5341_regmap_volatile_range[] = { + regmap_reg_range(0x000C, 0x0012), /* Status */ + regmap_reg_range(0x001C, 0x001E), /* reset, finc/fdec */ + regmap_reg_range(0x00E2, 0x00FE), /* NVM, interrupts, device ready */ + /* Update bits for synth config */ + regmap_reg_range(SI5341_SYNTH_N_UPD(0), SI5341_SYNTH_N_UPD(0)), + regmap_reg_range(SI5341_SYNTH_N_UPD(1), SI5341_SYNTH_N_UPD(1)), + regmap_reg_range(SI5341_SYNTH_N_UPD(2), SI5341_SYNTH_N_UPD(2)), + regmap_reg_range(SI5341_SYNTH_N_UPD(3), SI5341_SYNTH_N_UPD(3)), + regmap_reg_range(SI5341_SYNTH_N_UPD(4), SI5341_SYNTH_N_UPD(4)), +}; + +static const struct regmap_access_table si5341_regmap_volatile = { + .yes_ranges = si5341_regmap_volatile_range, + .n_yes_ranges = ARRAY_SIZE(si5341_regmap_volatile_range), +}; + +/* Pages 0, 1, 2, 3, 9, A, B are valid, so there are 12 pages */ +static const struct regmap_range_cfg si5341_regmap_ranges[] = { + { + .range_min = 0, + .range_max = SI5341_REGISTER_MAX, + .selector_reg = SI5341_PAGE, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 256, + }, +}; + +static const struct regmap_config si5341_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .ranges = si5341_regmap_ranges, + .num_ranges = ARRAY_SIZE(si5341_regmap_ranges), + .max_register = SI5341_REGISTER_MAX, + .volatile_table = &si5341_regmap_volatile, +}; + +static int si5341_dt_parse_dt(struct i2c_client *client, + struct clk_si5341_output_config *config) +{ + struct device_node *child; + struct device_node *np = client->dev.of_node; + u32 num; + u32 val; + + memset(config, 0, sizeof(struct clk_si5341_output_config) * + SI5341_MAX_NUM_OUTPUTS); + + for_each_child_of_node(np, child) { + if (of_property_read_u32(child, "reg", &num)) { + dev_err(&client->dev, "missing reg property of %s\n", + child->name); + goto put_child; + } + + if (num >= SI5341_MAX_NUM_OUTPUTS) { + dev_err(&client->dev, "invalid clkout %d\n", num); + goto put_child; + } + + if (!of_property_read_u32(child, "silabs,format", &val)) { + /* Set cm and ampl conservatively to 3v3 settings */ + switch (val) { + case 1: /* normal differential */ + config[num].out_cm_ampl_bits = 0x33; + break; + case 2: /* low-power differential */ + config[num].out_cm_ampl_bits = 0x13; + break; + case 4: /* LVCMOS */ + config[num].out_cm_ampl_bits = 0x33; + /* Set SI recommended impedance for LVCMOS */ + config[num].out_format_drv_bits |= 0xc0; + break; + default: + dev_err(&client->dev, + "invalid silabs,format %u for %u\n", + val, num); + goto put_child; + } + config[num].out_format_drv_bits &= ~0x07; + config[num].out_format_drv_bits |= val & 0x07; + /* Always enable the SYNC feature */ + config[num].out_format_drv_bits |= 0x08; + } + + if (!of_property_read_u32(child, "silabs,common-mode", &val)) { + if (val > 0xf) { + dev_err(&client->dev, + "invalid silabs,common-mode %u\n", + val); + goto put_child; + } + config[num].out_cm_ampl_bits &= 0xf0; + config[num].out_cm_ampl_bits |= val & 0x0f; + } + + if (!of_property_read_u32(child, "silabs,amplitude", &val)) { + if (val > 0xf) { + dev_err(&client->dev, + "invalid silabs,amplitude %u\n", + val); + goto put_child; + } + config[num].out_cm_ampl_bits &= 0x0f; + config[num].out_cm_ampl_bits |= (val << 4) & 0xf0; + } + + if (of_property_read_bool(child, "silabs,disable-high")) + config[num].out_format_drv_bits |= 0x10; + + config[num].synth_master = + of_property_read_bool(child, "silabs,synth-master"); + + config[num].always_on = + of_property_read_bool(child, "always-on"); + } + + return 0; + +put_child: + of_node_put(child); + return -EINVAL; +} + +/* + * If not pre-configured, calculate and set the PLL configuration manually. + * For low-jitter performance, the PLL should be set such that the synthesizers + * only need integer division. + * Without any user guidance, we'll set the PLL to 14GHz, which still allows + * the chip to generate any frequency on its outputs, but jitter performance + * may be sub-optimal. + */ +static int si5341_initialize_pll(struct clk_si5341 *data) +{ + struct device_node *np = data->i2c_client->dev.of_node; + u32 m_num = 0; + u32 m_den = 0; + + if (of_property_read_u32(np, "silabs,pll-m-num", &m_num)) { + dev_err(&data->i2c_client->dev, + "PLL configuration requires silabs,pll-m-num\n"); + } + if (of_property_read_u32(np, "silabs,pll-m-den", &m_den)) { + dev_err(&data->i2c_client->dev, + "PLL configuration requires silabs,pll-m-den\n"); + } + + if (!m_num || !m_den) { + dev_err(&data->i2c_client->dev, + "PLL configuration invalid, assume 14GHz\n"); + m_den = clk_get_rate(data->pxtal) / 10; + m_num = 1400000000; + } + + return si5341_encode_44_32(data->regmap, + SI5341_PLL_M_NUM, m_num, m_den); +} + +static int si5341_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct clk_si5341 *data; + struct clk_init_data init; + const char *root_clock_name; + const char *synth_clock_names[SI5341_NUM_SYNTH]; + int err; + unsigned int i; + struct clk_si5341_output_config config[SI5341_MAX_NUM_OUTPUTS]; + bool initialization_required; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->i2c_client = client; + + data->pxtal = devm_clk_get(&client->dev, "xtal"); + if (IS_ERR(data->pxtal)) { + if (PTR_ERR(data->pxtal) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_err(&client->dev, "Missing xtal clock input\n"); + } + + err = si5341_dt_parse_dt(client, config); + if (err) + return err; + + if (of_property_read_string(client->dev.of_node, "clock-output-names", + &init.name)) + init.name = client->dev.of_node->name; + root_clock_name = init.name; + + data->regmap = devm_regmap_init_i2c(client, &si5341_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + i2c_set_clientdata(client, data); + + err = si5341_probe_chip_id(data); + if (err < 0) + return err; + + /* "Activate" the xtal (usually a fixed clock) */ + clk_prepare_enable(data->pxtal); + + if (of_property_read_bool(client->dev.of_node, "silabs,reprogram")) { + initialization_required = true; + } else { + err = si5341_is_programmed_already(data); + if (err < 0) + return err; + + initialization_required = !err; + } + + if (initialization_required) { + /* Populate the regmap cache in preparation for "cache only" */ + err = si5341_read_settings(data); + if (err < 0) + return err; + + err = si5341_send_preamble(data); + if (err < 0) + return err; + + /* + * We intend to send all 'final' register values in a single + * transaction. So cache all register writes until we're done + * configuring. + */ + regcache_cache_only(data->regmap, true); + + /* Write the configuration pairs from the firmware blob */ + err = si5341_write_multiple(data, si5341_reg_defaults, + ARRAY_SIZE(si5341_reg_defaults)); + if (err < 0) + return err; + + /* PLL configuration is required */ + err = si5341_initialize_pll(data); + if (err < 0) + return err; + } + + /* Register the PLL */ + data->pxtal_name = __clk_get_name(data->pxtal); + init.parent_names = &data->pxtal_name; + init.num_parents = 1; /* For now, only XTAL input supported */ + init.ops = &si5341_clk_ops; + init.flags = 0; + data->hw.init = &init; + + err = devm_clk_hw_register(&client->dev, &data->hw); + if (err) { + dev_err(&client->dev, "clock registration failed\n"); + return err; + } + + init.num_parents = 1; + init.parent_names = &root_clock_name; + init.ops = &si5341_synth_clk_ops; + for (i = 0; i < data->num_synth; ++i) { + synth_clock_names[i] = devm_kasprintf(&client->dev, GFP_KERNEL, + "%s.N%u", client->dev.of_node->name, i); + init.name = synth_clock_names[i]; + data->synth[i].index = i; + data->synth[i].data = data; + data->synth[i].hw.init = &init; + err = devm_clk_hw_register(&client->dev, &data->synth[i].hw); + if (err) { + dev_err(&client->dev, + "synth N%u registration failed\n", i); + } + } + + init.num_parents = data->num_synth; + init.parent_names = synth_clock_names; + init.ops = &si5341_output_clk_ops; + for (i = 0; i < data->num_outputs; ++i) { + init.name = kasprintf(GFP_KERNEL, "%s.%d", + client->dev.of_node->name, i); + init.flags = config[i].synth_master ? CLK_SET_RATE_PARENT : 0; + data->clk[i].index = i; + data->clk[i].data = data; + data->clk[i].hw.init = &init; + if (config[i].out_format_drv_bits & 0x07) { + regmap_write(data->regmap, + SI5341_OUT_FORMAT(&data->clk[i]), + config[i].out_format_drv_bits); + regmap_write(data->regmap, + SI5341_OUT_CM(&data->clk[i]), + config[i].out_cm_ampl_bits); + } + err = devm_clk_hw_register(&client->dev, &data->clk[i].hw); + kfree(init.name); /* clock framework made a copy of the name */ + if (err) { + dev_err(&client->dev, + "output %u registration failed\n", i); + return err; + } + if (config[i].always_on) + clk_prepare(data->clk[i].hw.clk); + } + + err = of_clk_add_hw_provider(client->dev.of_node, of_clk_si5341_get, + data); + if (err) { + dev_err(&client->dev, "unable to add clk provider\n"); + return err; + } + + if (initialization_required) { + /* Synchronize */ + regcache_cache_only(data->regmap, false); + err = regcache_sync(data->regmap); + if (err < 0) + return err; + + err = si5341_finalize_defaults(data); + if (err < 0) + return err; + } + + /* Free the names, clk framework makes copies */ + for (i = 0; i < data->num_synth; ++i) + devm_kfree(&client->dev, (void *)synth_clock_names[i]); + + return 0; +} + +static const struct i2c_device_id si5341_id[] = { + { "si5340", 0 }, + { "si5341", 1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si5341_id); + +static const struct of_device_id clk_si5341_of_match[] = { + { .compatible = "silabs,si5340" }, + { .compatible = "silabs,si5341" }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_si5341_of_match); + +static struct i2c_driver si5341_driver = { + .driver = { + .name = "si5341", + .of_match_table = clk_si5341_of_match, + }, + .probe = si5341_probe, + .id_table = si5341_id, +}; +module_i2c_driver(si5341_driver); + +MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>"); +MODULE_DESCRIPTION("Si5341 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 64e607f3232a..d9ec9086184d 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -7,6 +7,7 @@ #include <linux/clk-provider.h> #include <linux/delay.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/regmap.h> @@ -50,6 +51,11 @@ /* Lowest frequency synthesizeable using only the HS divider */ #define MIN_HSDIV_FREQ (FVCO_MIN / HS_DIV_MAX) +/* Range and interpretation of the adjustment value */ +#define DELTA_M_MAX 8161512 +#define DELTA_M_FRAC_NUM 19 +#define DELTA_M_FRAC_DEN 20000 + enum si544_speed_grade { si544a, si544b, @@ -71,12 +77,14 @@ struct clk_si544 { * @hs_div: 1st divider, 5..2046, must be even when >33 * @ls_div_bits: 2nd divider, as 2^x, range 0..5 * If ls_div_bits is non-zero, hs_div must be even + * @delta_m: Frequency shift for small -950..+950 ppm changes, 24 bit */ struct clk_si544_muldiv { u32 fb_div_frac; u16 fb_div_int; u16 hs_div; u8 ls_div_bits; + s32 delta_m; }; /* Enables or disables the output driver */ @@ -134,9 +142,30 @@ static int si544_get_muldiv(struct clk_si544 *data, settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | reg[3] << 24; + + err = regmap_bulk_read(data->regmap, SI544_REG_ADPLL_DELTA_M0, reg, 3); + if (err) + return err; + + /* Interpret as 24-bit signed number */ + settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; + settings->delta_m >>= 8; + return 0; } +static int si544_set_delta_m(struct clk_si544 *data, s32 delta_m) +{ + u8 reg[3]; + + reg[0] = delta_m; + reg[1] = delta_m >> 8; + reg[2] = delta_m >> 16; + + return regmap_bulk_write(data->regmap, SI544_REG_ADPLL_DELTA_M0, + reg, 3); +} + static int si544_set_muldiv(struct clk_si544 *data, struct clk_si544_muldiv *settings) { @@ -238,11 +267,15 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, do_div(vco, FXO); settings->fb_div_frac = vco; + /* Reset the frequency adjustment */ + settings->delta_m = 0; + return 0; } /* Calculate resulting frequency given the register settings */ -static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) +static unsigned long si544_calc_center_rate( + const struct clk_si544_muldiv *settings) { u32 d = settings->hs_div * BIT(settings->ls_div_bits); u64 vco; @@ -261,6 +294,25 @@ static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) return vco; } +static unsigned long si544_calc_rate(const struct clk_si544_muldiv *settings) +{ + unsigned long rate = si544_calc_center_rate(settings); + s64 delta = (s64)rate * (DELTA_M_FRAC_NUM * settings->delta_m); + + /* + * The clock adjustment is much smaller than 1 Hz, round to the + * nearest multiple. Apparently div64_s64 rounds towards zero, hence + * check the sign and adjust into the proper direction. + */ + if (settings->delta_m < 0) + delta -= ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; + else + delta += ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; + delta = div64_s64(delta, ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN)); + + return rate + delta; +} + static unsigned long si544_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -279,33 +331,60 @@ static long si544_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct clk_si544 *data = to_clk_si544(hw); - struct clk_si544_muldiv settings; - int err; if (!is_valid_frequency(data, rate)) return -EINVAL; - err = si544_calc_muldiv(&settings, rate); - if (err) - return err; + /* The accuracy is less than 1 Hz, so any rate is possible */ + return rate; +} - return si544_calc_rate(&settings); +/* Calculates the maximum "small" change, 950 * rate / 1000000 */ +static unsigned long si544_max_delta(unsigned long rate) +{ + u64 num = rate; + + num *= DELTA_M_FRAC_NUM; + do_div(num, DELTA_M_FRAC_DEN); + + return num; +} + +static s32 si544_calc_delta(s32 delta, s32 max_delta) +{ + s64 n = (s64)delta * DELTA_M_MAX; + + return div_s64(n, max_delta); } -/* - * Update output frequency for "big" frequency changes - */ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_si544 *data = to_clk_si544(hw); struct clk_si544_muldiv settings; + unsigned long center; + long max_delta; + long delta; unsigned int old_oe_state; int err; if (!is_valid_frequency(data, rate)) return -EINVAL; + /* Try using the frequency adjustment feature for a <= 950ppm change */ + err = si544_get_muldiv(data, &settings); + if (err) + return err; + + center = si544_calc_center_rate(&settings); + max_delta = si544_max_delta(center); + delta = rate - center; + + if (abs(delta) <= max_delta) + return si544_set_delta_m(data, + si544_calc_delta(delta, max_delta)); + + /* Too big for the delta adjustment, need to reprogram */ err = si544_calc_muldiv(&settings, rate); if (err) return err; @@ -321,6 +400,9 @@ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, if (err < 0) return err; + err = si544_set_delta_m(data, settings.delta_m); + if (err < 0) + return err; err = si544_set_muldiv(data, &settings); if (err < 0) diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c index 026a26bb702d..baa73285b953 100644 --- a/drivers/clk/rockchip/clk-mmc-phase.c +++ b/drivers/clk/rockchip/clk-mmc-phase.c @@ -55,29 +55,27 @@ static unsigned long rockchip_mmc_recalc(struct clk_hw *hw, static int rockchip_mmc_get_phase(struct clk_hw *hw) { struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw); - unsigned long rate = clk_get_rate(hw->clk); + unsigned long rate = clk_hw_get_rate(hw); u32 raw_value; u16 degrees; u32 delay_num = 0; /* See the comment for rockchip_mmc_set_phase below */ - if (!rate) { - pr_err("%s: invalid clk rate\n", __func__); + if (!rate) return -EINVAL; - } raw_value = readl(mmc_clock->reg) >> (mmc_clock->shift); degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90; if (raw_value & ROCKCHIP_MMC_DELAY_SEL) { - /* degrees/delaynum * 10000 */ + /* degrees/delaynum * 1000000 */ unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) * - 36 * (rate / 1000000); + 36 * (rate / 10000); delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK); delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET; - degrees += DIV_ROUND_CLOSEST(delay_num * factor, 10000); + degrees += DIV_ROUND_CLOSEST(delay_num * factor, 1000000); } return degrees % 360; @@ -86,7 +84,7 @@ static int rockchip_mmc_get_phase(struct clk_hw *hw) static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees) { struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw); - unsigned long rate = clk_get_rate(hw->clk); + unsigned long rate = clk_hw_get_rate(hw); u8 nineties, remainder; u8 delay_num; u32 raw_value; diff --git a/drivers/clk/rockchip/clk-px30.c b/drivers/clk/rockchip/clk-px30.c index 68d23be18cbc..e92b64d37fb8 100644 --- a/drivers/clk/rockchip/clk-px30.c +++ b/drivers/clk/rockchip/clk-px30.c @@ -803,6 +803,9 @@ static struct rockchip_clk_branch px30_clk_branches[] __initdata = { GATE(ACLK_GIC, "aclk_gic", "aclk_bus_pre", CLK_IGNORE_UNUSED, PX30_CLKGATE_CON(13), 12, GFLAGS), GATE(ACLK_DCF, "aclk_dcf", "aclk_bus_pre", 0, PX30_CLKGATE_CON(13), 15, GFLAGS), + /* aclk_dmac is controlled by sgrf_soc_con1[11]. */ + SGRF_GATE(ACLK_DMAC, "aclk_dmac", "aclk_bus_pre"), + GATE(0, "hclk_bus_niu", "hclk_bus_pre", CLK_IGNORE_UNUSED, PX30_CLKGATE_CON(13), 9, GFLAGS), GATE(0, "hclk_rom", "hclk_bus_pre", CLK_IGNORE_UNUSED, PX30_CLKGATE_CON(13), 14, GFLAGS), GATE(HCLK_PDM, "hclk_pdm", "hclk_bus_pre", 0, PX30_CLKGATE_CON(14), 1, GFLAGS), @@ -966,7 +969,6 @@ static void __init px30_clk_init(struct device_node *np) { struct rockchip_clk_provider *ctx; void __iomem *reg_base; - struct clk *clk; reg_base = of_iomap(np, 0); if (!reg_base) { @@ -981,14 +983,6 @@ static void __init px30_clk_init(struct device_node *np) return; } - /* aclk_dmac is controlled by sgrf_soc_con1[11]. */ - clk = clk_register_fixed_factor(NULL, "aclk_dmac", "aclk_bus_pre", 0, 1, 1); - if (IS_ERR(clk)) - pr_warn("%s: could not register clock aclk_dmac: %ld\n", - __func__, PTR_ERR(clk)); - else - rockchip_clk_add_lookup(ctx, clk, ACLK_DMAC); - rockchip_clk_register_plls(ctx, px30_pll_clks, ARRAY_SIZE(px30_pll_clks), PX30_GRF_SOC_STATUS0); diff --git a/drivers/clk/rockchip/clk-rk3228.c b/drivers/clk/rockchip/clk-rk3228.c index 7176003b5c7c..68bf4f8fd64c 100644 --- a/drivers/clk/rockchip/clk-rk3228.c +++ b/drivers/clk/rockchip/clk-rk3228.c @@ -110,6 +110,7 @@ static struct rockchip_cpuclk_rate_table rk3228_cpuclk_rates[] __initdata = { RK3228_CPUCLK_RATE(1608000000, 1, 7), RK3228_CPUCLK_RATE(1512000000, 1, 7), RK3228_CPUCLK_RATE(1488000000, 1, 5), + RK3228_CPUCLK_RATE(1464000000, 1, 5), RK3228_CPUCLK_RATE(1416000000, 1, 5), RK3228_CPUCLK_RATE(1392000000, 1, 5), RK3228_CPUCLK_RATE(1296000000, 1, 5), @@ -255,7 +256,7 @@ static struct rockchip_clk_branch rk3228_clk_branches[] __initdata = { RK2928_CLKGATE_CON(4), 0, GFLAGS), /* PD_MISC */ - MUX(0, "hdmiphy", mux_hdmiphy_p, CLK_SET_RATE_PARENT, + MUX(SCLK_HDMI_PHY, "hdmiphy", mux_hdmiphy_p, CLK_SET_RATE_PARENT, RK2928_MISC_CON, 13, 1, MFLAGS), MUX(0, "usb480m_phy", mux_usb480m_phy_p, CLK_SET_RATE_PARENT, RK2928_MISC_CON, 14, 1, MFLAGS), diff --git a/drivers/clk/rockchip/clk-rk3288.c b/drivers/clk/rockchip/clk-rk3288.c index 85907f31c63f..8c7a8fa3235c 100644 --- a/drivers/clk/rockchip/clk-rk3288.c +++ b/drivers/clk/rockchip/clk-rk3288.c @@ -122,7 +122,6 @@ static struct rockchip_pll_rate_table rk3288_pll_rates[] = { RK3066_PLL_RATE( 160000000, 1, 80, 12), RK3066_PLL_RATE( 157500000, 1, 105, 16), RK3066_PLL_RATE( 126000000, 1, 84, 16), - RK3066_PLL_RATE( 48000000, 1, 64, 32), { /* sentinel */ }, }; @@ -776,6 +775,9 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { GATE(PCLK_GRF, "pclk_grf", "pclk_pd_alive", CLK_IGNORE_UNUSED, RK3288_CLKGATE_CON(14), 11, GFLAGS), GATE(0, "pclk_alive_niu", "pclk_pd_alive", 0, RK3288_CLKGATE_CON(14), 12, GFLAGS), + /* Watchdog pclk is controlled by RK3288_SGRF_SOC_CON0[1]. */ + SGRF_GATE(PCLK_WDT, "pclk_wdt", "pclk_pd_alive"), + /* pclk_pd_pmu gates */ GATE(PCLK_PMU, "pclk_pmu", "pclk_pd_pmu", CLK_IGNORE_UNUSED, RK3288_CLKGATE_CON(17), 0, GFLAGS), GATE(0, "pclk_intmem1", "pclk_pd_pmu", CLK_IGNORE_UNUSED, RK3288_CLKGATE_CON(17), 1, GFLAGS), @@ -924,7 +926,6 @@ static struct syscore_ops rk3288_clk_syscore_ops = { static void __init rk3288_clk_init(struct device_node *np) { struct rockchip_clk_provider *ctx; - struct clk *clk; rk3288_cru_base = of_iomap(np, 0); if (!rk3288_cru_base) { @@ -939,14 +940,6 @@ static void __init rk3288_clk_init(struct device_node *np) return; } - /* Watchdog pclk is controlled by RK3288_SGRF_SOC_CON0[1]. */ - clk = clk_register_fixed_factor(NULL, "pclk_wdt", "pclk_pd_alive", 0, 1, 1); - if (IS_ERR(clk)) - pr_warn("%s: could not register clock pclk_wdt: %ld\n", - __func__, PTR_ERR(clk)); - else - rockchip_clk_add_lookup(ctx, clk, PCLK_WDT); - rockchip_clk_register_plls(ctx, rk3288_pll_clks, ARRAY_SIZE(rk3288_pll_clks), RK3288_GRF_SOC_STATUS1); diff --git a/drivers/clk/rockchip/clk-rk3328.c b/drivers/clk/rockchip/clk-rk3328.c index 9b03c1abf19c..cc88532662a6 100644 --- a/drivers/clk/rockchip/clk-rk3328.c +++ b/drivers/clk/rockchip/clk-rk3328.c @@ -800,6 +800,9 @@ static struct rockchip_clk_branch rk3328_clk_branches[] __initdata = { GATE(PCLK_SARADC, "pclk_saradc", "pclk_bus", 0, RK3328_CLKGATE_CON(17), 15, GFLAGS), GATE(0, "pclk_pmu", "pclk_bus", CLK_IGNORE_UNUSED, RK3328_CLKGATE_CON(28), 3, GFLAGS), + /* Watchdog pclk is controlled from the secure GRF */ + SGRF_GATE(PCLK_WDT, "pclk_wdt", "pclk_bus"), + GATE(PCLK_USB3PHY_OTG, "pclk_usb3phy_otg", "pclk_phy_pre", 0, RK3328_CLKGATE_CON(28), 1, GFLAGS), GATE(PCLK_USB3PHY_PIPE, "pclk_usb3phy_pipe", "pclk_phy_pre", 0, RK3328_CLKGATE_CON(28), 2, GFLAGS), GATE(PCLK_USB3_GRF, "pclk_usb3_grf", "pclk_phy_pre", CLK_IGNORE_UNUSED, RK3328_CLKGATE_CON(17), 2, GFLAGS), diff --git a/drivers/clk/rockchip/clk-rk3368.c b/drivers/clk/rockchip/clk-rk3368.c index d239bbc2fc77..6ecc17b80ffe 100644 --- a/drivers/clk/rockchip/clk-rk3368.c +++ b/drivers/clk/rockchip/clk-rk3368.c @@ -820,6 +820,9 @@ static struct rockchip_clk_branch rk3368_clk_branches[] __initdata = { GATE(PCLK_GPIO2, "pclk_gpio2", "pclk_pd_alive", 0, RK3368_CLKGATE_CON(22), 2, GFLAGS), GATE(PCLK_GPIO1, "pclk_gpio1", "pclk_pd_alive", 0, RK3368_CLKGATE_CON(22), 1, GFLAGS), + /* Watchdog pclk is controlled by sgrf_soc_con3[7]. */ + SGRF_GATE(PCLK_WDT, "pclk_wdt", "pclk_pd_alive"), + /* * pclk_vio gates * pclk_vio comes from the exactly same source as hclk_vio @@ -871,7 +874,6 @@ static void __init rk3368_clk_init(struct device_node *np) { struct rockchip_clk_provider *ctx; void __iomem *reg_base; - struct clk *clk; reg_base = of_iomap(np, 0); if (!reg_base) { @@ -886,14 +888,6 @@ static void __init rk3368_clk_init(struct device_node *np) return; } - /* Watchdog pclk is controlled by sgrf_soc_con3[7]. */ - clk = clk_register_fixed_factor(NULL, "pclk_wdt", "pclk_pd_alive", 0, 1, 1); - if (IS_ERR(clk)) - pr_warn("%s: could not register clock pclk_wdt: %ld\n", - __func__, PTR_ERR(clk)); - else - rockchip_clk_add_lookup(ctx, clk, PCLK_WDT); - rockchip_clk_register_plls(ctx, rk3368_pll_clks, ARRAY_SIZE(rk3368_pll_clks), RK3368_GRF_SOC_STATUS0); diff --git a/drivers/clk/rockchip/clk-rk3399.c b/drivers/clk/rockchip/clk-rk3399.c index 2322d712ba88..b01496d0e2b8 100644 --- a/drivers/clk/rockchip/clk-rk3399.c +++ b/drivers/clk/rockchip/clk-rk3399.c @@ -1304,6 +1304,9 @@ static struct rockchip_clk_branch rk3399_clk_branches[] __initdata = { GATE(PCLK_PMU_INTR_ARB, "pclk_pmu_intr_arb", "pclk_alive", CLK_IGNORE_UNUSED, RK3399_CLKGATE_CON(31), 9, GFLAGS), GATE(PCLK_SGRF, "pclk_sgrf", "pclk_alive", CLK_IGNORE_UNUSED, RK3399_CLKGATE_CON(31), 10, GFLAGS), + /* Watchdog pclk is controlled by RK3399 SECURE_GRF_SOC_CON3[8]. */ + SGRF_GATE(PCLK_WDT, "pclk_wdt", "pclk_alive"), + GATE(SCLK_MIPIDPHY_REF, "clk_mipidphy_ref", "xin24m", 0, RK3399_CLKGATE_CON(11), 14, GFLAGS), GATE(SCLK_DPHY_PLL, "clk_dphy_pll", "clk_mipidphy_ref", CLK_IGNORE_UNUSED, RK3399_CLKGATE_CON(21), 0, GFLAGS), @@ -1531,7 +1534,6 @@ static void __init rk3399_clk_init(struct device_node *np) { struct rockchip_clk_provider *ctx; void __iomem *reg_base; - struct clk *clk; reg_base = of_iomap(np, 0); if (!reg_base) { @@ -1546,14 +1548,6 @@ static void __init rk3399_clk_init(struct device_node *np) return; } - /* Watchdog pclk is controlled by RK3399 SECURE_GRF_SOC_CON3[8]. */ - clk = clk_register_fixed_factor(NULL, "pclk_wdt", "pclk_alive", 0, 1, 1); - if (IS_ERR(clk)) - pr_warn("%s: could not register clock pclk_wdt: %ld\n", - __func__, PTR_ERR(clk)); - else - rockchip_clk_add_lookup(ctx, clk, PCLK_WDT); - rockchip_clk_register_plls(ctx, rk3399_pll_clks, ARRAY_SIZE(rk3399_pll_clks), -1); diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index 1b5270755431..2a911923cf81 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -820,6 +820,10 @@ struct rockchip_clk_branch { .gate_offset = -1, \ } +/* SGRF clocks are only accessible from secure mode, so not controllable */ +#define SGRF_GATE(_id, cname, pname) \ + FACTOR(_id, cname, pname, 0, 1, 1) + struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np, void __iomem *base, unsigned long nr_clks); void rockchip_clk_of_add_provider(struct device_node *np, diff --git a/include/dt-bindings/clock/rk3228-cru.h b/include/dt-bindings/clock/rk3228-cru.h index 55655ab0a4c4..a0422f62c040 100644 --- a/include/dt-bindings/clock/rk3228-cru.h +++ b/include/dt-bindings/clock/rk3228-cru.h @@ -73,6 +73,7 @@ #define SCLK_WIFI 141 #define SCLK_OTGPHY0 142 #define SCLK_OTGPHY1 143 +#define SCLK_HDMI_PHY 144 /* dclk gates */ #define DCLK_VOP 190 diff --git a/include/dt-bindings/clock/rk3328-cru.h b/include/dt-bindings/clock/rk3328-cru.h index bcaa4559ab1b..6ad54c39f8da 100644 --- a/include/dt-bindings/clock/rk3328-cru.h +++ b/include/dt-bindings/clock/rk3328-cru.h @@ -173,6 +173,7 @@ #define PCLK_DCF 233 #define PCLK_SARADC 234 #define PCLK_ACODECPHY 235 +#define PCLK_WDT 236 /* hclk gates */ #define HCLK_PERI 308 |