diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:53 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:53 +0800 |
commit | 19f5dd25611fb1b86fa7e847a22b4ee9ca5d72ec (patch) | |
tree | d867c5e95a80fb50deec4a54588af1e413263631 | |
parent | a01dcca55154b587ce3ddacf5234e4c85e965229 (diff) | |
parent | 988d0a7af732d7bca0aff2ffa3d82490ff8c81d6 (diff) |
Merge remote-tracking branch 'origin/clock/qoriq' into clock/next
* origin/clock/qoriq:
clk: ls1028a: Add clock driver for Display output interface
dt/bindings: clk: Add YAML schemas for LS1028A Display Clock bindings
-rw-r--r-- | Documentation/devicetree/bindings/clock/fsl,plldig.yaml | 54 | ||||
-rw-r--r-- | drivers/clk/Kconfig | 10 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-plldig.c | 301 |
4 files changed, 366 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/fsl,plldig.yaml b/Documentation/devicetree/bindings/clock/fsl,plldig.yaml new file mode 100644 index 000000000000..ee5b5c61a471 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/fsl,plldig.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/bindings/clock/fsl,plldig.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP QorIQ Layerscape LS1028A Display PIXEL Clock Binding + +maintainers: + - Wen He <wen.he_1@nxp.com> + +description: | + NXP LS1028A has a clock domain PXLCLK0 used for the Display output + interface in the display core, as implemented in TSMC CLN28HPM PLL. + which generate and offers pixel clocks to Display. + +properties: + compatible: + const: fsl,ls1028a-plldig + + reg: + maxItems: 1 + + '#clock-cells': + const: 0 + + vco-frequency: + $ref: '/schemas/types.yaml#/definitions/uint32' + description: Optional for VCO frequency of the PLL in Hertz. + The VCO frequency of this PLL cannot be changed during runtime + only at startup. Therefore, the output frequencies are very + limited and might not even closely match the requested frequency. + To work around this restriction the user may specify its own + desired VCO frequency for the PLL. The frequency has to be in the + range of 650000000 to 1300000000. + If not set, the default frequency is 1188000000. + +required: + - compatible + - reg + - clocks + - '#clock-cells' + +examples: + # Display PIXEL Clock node: + - | + dpclk: clock-display@f1f0000 { + compatible = "fsl,ls1028a-plldig"; + reg = <0x0 0xf1f0000 0x0 0xffff>; + #clock-cells = <0>; + clocks = <&osc_27m>; + }; + +... diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c44247d0b83e..bbec55c4aa9b 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -218,6 +218,16 @@ config CLK_QORIQ This adds the clock driver support for Freescale QorIQ platforms using common clock framework. +config CLK_LS1028A_PLLDIG + tristate "Clock driver for LS1028A Display output" + depends on ARCH_LAYERSCAPE || COMPILE_TEST + default ARCH_LAYERSCAPE + help + This driver support the Display output interfaces(LCD, DPHY) pixel clocks + of the QorIQ Layerscape LS1028A, as implemented TSMC CLN28HPM PLL. Not all + features of the PLL are currently supported by the driver. By default, + configured bypass mode with this PLL. + config COMMON_CLK_XGENE bool "Clock driver for APM XGene SoC" default ARCH_XGENE diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 0138fb14e6f8..97d1e5bc6de5 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -43,6 +43,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_CLK_LS1028A_PLLDIG) += clk-plldig.o obj-$(CONFIG_COMMON_CLK_PWM) += clk-pwm.o obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o diff --git a/drivers/clk/clk-plldig.c b/drivers/clk/clk-plldig.c new file mode 100644 index 000000000000..365edd30da8c --- /dev/null +++ b/drivers/clk/clk-plldig.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + * + * Clock driver for LS1028A Display output interfaces(LCD, DPHY). + */ + +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/bitfield.h> + +/* PLLDIG register offsets and bit masks */ +#define PLLDIG_REG_PLLSR 0x24 +#define PLLDIG_LOCK_MASK BIT(2) +#define PLLDIG_REG_PLLDV 0x28 +#define PLLDIG_MFD_MASK GENMASK(7, 0) +#define PLLDIG_RFDPHI1_MASK GENMASK(30, 25) +#define PLLDIG_REG_PLLFM 0x2c +#define PLLDIG_SSCGBYP_ENABLE BIT(30) +#define PLLDIG_REG_PLLFD 0x30 +#define PLLDIG_FDEN BIT(30) +#define PLLDIG_FRAC_MASK GENMASK(15, 0) +#define PLLDIG_DTH_MASK GENMASK(17, 16) +#define PLLDIG_DTH_DISABLE 3 +#define PLLDIG_REG_PLLCAL1 0x38 +#define PLLDIG_REG_PLLCAL2 0x3c + +/* Range of the VCO frequencies, in Hz */ +#define PLLDIG_MIN_VCO_FREQ 650000000 +#define PLLDIG_MAX_VCO_FREQ 1300000000 + +/* Range of the output frequencies, in Hz */ +#define PHI1_MIN_FREQ 27000000 +#define PHI1_MAX_FREQ 600000000 + +/* Maximum value of the reduced frequency divider */ +#define MAX_RFDPHI1 63UL + +/* Best value of multiplication factor divider */ +#define PLLDIG_DEFAULT_MFD 44 + +/* + * Denominator part of the fractional part of the + * loop multiplication factor. + */ +#define MFDEN 20480 + +static const struct clk_parent_data parent_data[] = { + {.index = 0}, +}; + +struct clk_plldig { + struct clk_hw hw; + void __iomem *regs; + unsigned int vco_freq; +}; + +#define to_clk_plldig(_hw) container_of(_hw, struct clk_plldig, hw) + +static int plldig_enable(struct clk_hw *hw) +{ + struct clk_plldig *data = to_clk_plldig(hw); + u32 val; + + val = readl(data->regs + PLLDIG_REG_PLLFM); + /* + * Use Bypass mode with PLL off by default, the frequency overshoot + * detector output was disable. SSCG Bypass mode should be enable. + */ + val |= PLLDIG_SSCGBYP_ENABLE; + writel(val, data->regs + PLLDIG_REG_PLLFM); + + return 0; +} + +static void plldig_disable(struct clk_hw *hw) +{ + struct clk_plldig *data = to_clk_plldig(hw); + u32 val; + + val = readl(data->regs + PLLDIG_REG_PLLFM); + + val &= ~PLLDIG_SSCGBYP_ENABLE; + val |= FIELD_PREP(PLLDIG_SSCGBYP_ENABLE, 0x0); + + writel(val, data->regs + PLLDIG_REG_PLLFM); +} + +static int plldig_is_enabled(struct clk_hw *hw) +{ + struct clk_plldig *data = to_clk_plldig(hw); + + return (readl(data->regs + PLLDIG_REG_PLLFM) & + PLLDIG_SSCGBYP_ENABLE); +} + +static unsigned long plldig_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_plldig *data = to_clk_plldig(hw); + u32 val, rfdphi1; + + val = readl(data->regs + PLLDIG_REG_PLLDV); + + /* Check if PLL is bypassed */ + if (val & PLLDIG_SSCGBYP_ENABLE) + return parent_rate; + + rfdphi1 = FIELD_GET(PLLDIG_RFDPHI1_MASK, val); + + /* + * If RFDPHI1 has a value of 1 the VCO frequency is also divided by + * one. + */ + if (!rfdphi1) + rfdphi1 = 1; + + return DIV_ROUND_UP(data->vco_freq, rfdphi1); +} + +static unsigned long plldig_calc_target_div(unsigned long vco_freq, + unsigned long target_rate) +{ + unsigned long div; + + div = DIV_ROUND_CLOSEST(vco_freq, target_rate); + div = max(1UL, div); + div = min(div, MAX_RFDPHI1); + + return div; +} + +static int plldig_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_plldig *data = to_clk_plldig(hw); + unsigned int div; + + if (req->rate < PHI1_MIN_FREQ) + req->rate = PHI1_MIN_FREQ; + if (req->rate > PHI1_MAX_FREQ) + req->rate = PHI1_MAX_FREQ; + + div = plldig_calc_target_div(data->vco_freq, req->rate); + req->rate = DIV_ROUND_UP(data->vco_freq, div); + + return 0; +} + +static int plldig_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_plldig *data = to_clk_plldig(hw); + unsigned int val, cond; + unsigned int rfdphi1; + + if (rate < PHI1_MIN_FREQ) + rate = PHI1_MIN_FREQ; + if (rate > PHI1_MAX_FREQ) + rate = PHI1_MAX_FREQ; + + rfdphi1 = plldig_calc_target_div(data->vco_freq, rate); + + /* update the divider value */ + val = readl(data->regs + PLLDIG_REG_PLLDV); + val &= ~PLLDIG_RFDPHI1_MASK; + val |= FIELD_PREP(PLLDIG_RFDPHI1_MASK, rfdphi1); + writel(val, data->regs + PLLDIG_REG_PLLDV); + + /* delay 200us make sure that old lock state is cleared */ + udelay(200); + + /* Wait until PLL is locked or timeout (maximum 1000 usecs) */ + return readl_poll_timeout_atomic(data->regs + PLLDIG_REG_PLLSR, cond, + cond & PLLDIG_LOCK_MASK, 0, + USEC_PER_MSEC); +} + +static const struct clk_ops plldig_clk_ops = { + .enable = plldig_enable, + .disable = plldig_disable, + .is_enabled = plldig_is_enabled, + .recalc_rate = plldig_recalc_rate, + .determine_rate = plldig_determine_rate, + .set_rate = plldig_set_rate, +}; + +static int plldig_init(struct clk_hw *hw) +{ + struct clk_plldig *data = to_clk_plldig(hw); + struct clk_hw *parent = clk_hw_get_parent(hw); + unsigned long parent_rate = clk_hw_get_rate(parent); + unsigned long val; + unsigned long long lltmp; + unsigned int mfd, fracdiv = 0; + + if (!parent) + return -EINVAL; + + if (data->vco_freq) { + mfd = data->vco_freq / parent_rate; + lltmp = data->vco_freq % parent_rate; + lltmp *= MFDEN; + do_div(lltmp, parent_rate); + fracdiv = lltmp; + } else { + mfd = PLLDIG_DEFAULT_MFD; + data->vco_freq = parent_rate * mfd; + } + + val = FIELD_PREP(PLLDIG_MFD_MASK, mfd); + writel(val, data->regs + PLLDIG_REG_PLLDV); + + if (fracdiv) { + val = FIELD_PREP(PLLDIG_FRAC_MASK, fracdiv); + /* Enable fractional divider */ + val |= PLLDIG_FDEN; + /* Disable dither */ + val |= FIELD_PREP(PLLDIG_DTH_MASK, PLLDIG_DTH_DISABLE); + writel(val, data->regs + PLLDIG_REG_PLLFD); + } + + return 0; +} + +static int plldig_clk_probe(struct platform_device *pdev) +{ + struct clk_plldig *data; + struct resource *mem; + struct device *dev = &pdev->dev; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(data->regs)) + return PTR_ERR(data->regs); + + data->hw.init = CLK_HW_INIT_PARENTS_DATA("dpclk", + parent_data, + &plldig_clk_ops, + 0); + + ret = devm_clk_hw_register(dev, &data->hw); + if (ret) { + dev_err(dev, "failed to register %s clock\n", + dev->of_node->name); + return ret; + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &data->hw); + if (ret) { + dev_err(dev, "unable to add clk provider\n"); + return ret; + } + + /* + * The frequency of the VCO cannot be changed during runtime. + * Therefore, let the user specify a desired frequency. + */ + if (!of_property_read_u32(dev->of_node, "vco-frequency", + &data->vco_freq)) { + if (data->vco_freq < PLLDIG_MIN_VCO_FREQ || + data->vco_freq > PLLDIG_MAX_VCO_FREQ) + return -EINVAL; + } + + return plldig_init(&data->hw); +} + +static const struct of_device_id plldig_clk_id[] = { + { .compatible = "fsl,ls1028a-plldig"}, + { } +}; +MODULE_DEVICE_TABLE(of, plldig_clk_id); + +static struct platform_driver plldig_clk_driver = { + .driver = { + .name = "plldig-clock", + .of_match_table = plldig_clk_id, + }, + .probe = plldig_clk_probe, +}; +module_platform_driver(plldig_clk_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Wen He <wen.he_1@nxp.com>"); +MODULE_DESCRIPTION("LS1028A Display output interface pixel clock driver"); |