From 1d80c14248d6082c91a8a9e3d70cc94c3cc18ecb Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:23 +0200 Subject: clk: sunxi-ng: Add common infrastructure Start our new clock infrastructure by adding the registration code, common structure and common code. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-3-maxime.ripard@free-electrons.com --- drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/sunxi-ng/Kconfig | 3 ++ drivers/clk/sunxi-ng/Makefile | 3 ++ drivers/clk/sunxi-ng/ccu_common.c | 90 +++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_common.h | 85 ++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_mult.h | 15 +++++++ drivers/clk/sunxi-ng/ccu_reset.c | 55 ++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_reset.h | 40 +++++++++++++++++ 9 files changed, 293 insertions(+) create mode 100644 drivers/clk/sunxi-ng/Kconfig create mode 100644 drivers/clk/sunxi-ng/Makefile create mode 100644 drivers/clk/sunxi-ng/ccu_common.c create mode 100644 drivers/clk/sunxi-ng/ccu_common.h create mode 100644 drivers/clk/sunxi-ng/ccu_mult.h create mode 100644 drivers/clk/sunxi-ng/ccu_reset.c create mode 100644 drivers/clk/sunxi-ng/ccu_reset.h (limited to 'drivers/clk') diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 53ddba26578c..8216f47ac37d 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -212,6 +212,7 @@ source "drivers/clk/mvebu/Kconfig" source "drivers/clk/qcom/Kconfig" source "drivers/clk/renesas/Kconfig" source "drivers/clk/samsung/Kconfig" +source "drivers/clk/sunxi-ng/Kconfig" source "drivers/clk/tegra/Kconfig" source "drivers/clk/ti/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index dcc5e698ff6d..7a44a1526d60 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -79,6 +79,7 @@ obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/ obj-$(CONFIG_PLAT_SPEAR) += spear/ obj-$(CONFIG_ARCH_STI) += st/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ +obj-$(CONFIG_ARCH_SUNXI) += sunxi-ng/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += ti/ obj-$(CONFIG_ARCH_U8500) += ux500/ diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig new file mode 100644 index 000000000000..df5b2768d8b4 --- /dev/null +++ b/drivers/clk/sunxi-ng/Kconfig @@ -0,0 +1,3 @@ +config SUNXI_CCU + bool "Clock support for Allwinner SoCs" + default ARCH_SUNXI diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile new file mode 100644 index 000000000000..886d0786ca51 --- /dev/null +++ b/drivers/clk/sunxi-ng/Makefile @@ -0,0 +1,3 @@ +# Common objects +obj-$(CONFIG_SUNXI_CCU) += ccu_common.o +obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o diff --git a/drivers/clk/sunxi-ng/ccu_common.c b/drivers/clk/sunxi-ng/ccu_common.c new file mode 100644 index 000000000000..fc17b5295e16 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_common.c @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Maxime Ripard + * + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "ccu_common.h" +#include "ccu_reset.h" + +static DEFINE_SPINLOCK(ccu_lock); + +void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock) +{ + u32 reg; + + if (!lock) + return; + + WARN_ON(readl_relaxed_poll_timeout(common->base + common->reg, reg, + !(reg & lock), 100, 70000)); +} + +int sunxi_ccu_probe(struct device_node *node, void __iomem *reg, + const struct sunxi_ccu_desc *desc) +{ + struct ccu_reset *reset; + int i, ret; + + for (i = 0; i < desc->num_ccu_clks; i++) { + struct ccu_common *cclk = desc->ccu_clks[i]; + + if (!cclk) + continue; + + cclk->base = reg; + cclk->lock = &ccu_lock; + } + + for (i = 0; i < desc->hw_clks->num ; i++) { + struct clk_hw *hw = desc->hw_clks->hws[i]; + + if (!hw) + continue; + + ret = clk_hw_register(NULL, hw); + if (ret) { + pr_err("Couldn't register clock %s\n", + clk_hw_get_name(hw)); + goto err_clk_unreg; + } + } + + ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, + desc->hw_clks); + if (ret) + goto err_clk_unreg; + + reset = kzalloc(sizeof(*reset), GFP_KERNEL); + reset->rcdev.of_node = node; + reset->rcdev.ops = &ccu_reset_ops; + reset->rcdev.owner = THIS_MODULE; + reset->rcdev.nr_resets = desc->num_resets; + reset->base = reg; + reset->lock = &ccu_lock; + reset->reset_map = desc->resets; + + ret = reset_controller_register(&reset->rcdev); + if (ret) + goto err_of_clk_unreg; + + return 0; + +err_of_clk_unreg: +err_clk_unreg: + return ret; +} diff --git a/drivers/clk/sunxi-ng/ccu_common.h b/drivers/clk/sunxi-ng/ccu_common.h new file mode 100644 index 000000000000..b3d9abfbd721 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_common.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include + +#define CCU_FEATURE_FRACTIONAL BIT(0) +#define CCU_FEATURE_VARIABLE_PREDIV BIT(1) +#define CCU_FEATURE_FIXED_PREDIV BIT(2) +#define CCU_FEATURE_FIXED_POSTDIV BIT(3) + +struct device_node; + +#define CLK_HW_INIT(_name, _parent, _ops, _flags) \ + &(struct clk_init_data) { \ + .flags = _flags, \ + .name = _name, \ + .parent_names = (const char *[]) { _parent }, \ + .num_parents = 1, \ + .ops = _ops, \ + } + +#define CLK_HW_INIT_PARENTS(_name, _parents, _ops, _flags) \ + &(struct clk_init_data) { \ + .flags = _flags, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .ops = _ops, \ + } + +#define CLK_FIXED_FACTOR(_struct, _name, _parent, \ + _div, _mult, _flags) \ + struct clk_fixed_factor _struct = { \ + .div = _div, \ + .mult = _mult, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &clk_fixed_factor_ops, \ + _flags), \ + } + +struct ccu_common { + void __iomem *base; + u16 reg; + + unsigned long features; + spinlock_t *lock; + struct clk_hw hw; +}; + +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw) +{ + return container_of(hw, struct ccu_common, hw); +} + +struct sunxi_ccu_desc { + struct ccu_common **ccu_clks; + unsigned long num_ccu_clks; + + struct clk_hw_onecell_data *hw_clks; + + struct ccu_reset_map *resets; + unsigned long num_resets; +}; + +void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock); + +int sunxi_ccu_probe(struct device_node *node, void __iomem *reg, + const struct sunxi_ccu_desc *desc); + +#endif /* _COMMON_H_ */ diff --git a/drivers/clk/sunxi-ng/ccu_mult.h b/drivers/clk/sunxi-ng/ccu_mult.h new file mode 100644 index 000000000000..609db6610880 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mult.h @@ -0,0 +1,15 @@ +#ifndef _CCU_MULT_H_ +#define _CCU_MULT_H_ + +struct _ccu_mult { + u8 shift; + u8 width; +}; + +#define _SUNXI_CCU_MULT(_shift, _width) \ + { \ + .shift = _shift, \ + .width = _width, \ + } + +#endif /* _CCU_MULT_H_ */ diff --git a/drivers/clk/sunxi-ng/ccu_reset.c b/drivers/clk/sunxi-ng/ccu_reset.c new file mode 100644 index 000000000000..6c31d48783a7 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_reset.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_reset.h" + +static int ccu_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ccu_reset *ccu = rcdev_to_ccu_reset(rcdev); + const struct ccu_reset_map *map = &ccu->reset_map[id]; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(ccu->lock, flags); + + reg = readl(ccu->base + map->reg); + writel(reg & ~map->bit, ccu->base + map->reg); + + spin_unlock_irqrestore(ccu->lock, flags); + + return 0; +} + +static int ccu_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ccu_reset *ccu = rcdev_to_ccu_reset(rcdev); + const struct ccu_reset_map *map = &ccu->reset_map[id]; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(ccu->lock, flags); + + reg = readl(ccu->base + map->reg); + writel(reg | map->bit, ccu->base + map->reg); + + spin_unlock_irqrestore(ccu->lock, flags); + + return 0; +} + +const struct reset_control_ops ccu_reset_ops = { + .assert = ccu_reset_assert, + .deassert = ccu_reset_deassert, +}; diff --git a/drivers/clk/sunxi-ng/ccu_reset.h b/drivers/clk/sunxi-ng/ccu_reset.h new file mode 100644 index 000000000000..36a4679210bd --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_reset.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_RESET_H_ +#define _CCU_RESET_H_ + +#include + +struct ccu_reset_map { + u16 reg; + u32 bit; +}; + + +struct ccu_reset { + void __iomem *base; + struct ccu_reset_map *reset_map; + spinlock_t *lock; + + struct reset_controller_dev rcdev; +}; + +static inline struct ccu_reset *rcdev_to_ccu_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct ccu_reset, rcdev); +} + +extern const struct reset_control_ops ccu_reset_ops; + +#endif /* _CCU_RESET_H_ */ -- cgit v1.2.3 From 89a3dfb787072438f72de95ff3fe7b58213e08c1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:24 +0200 Subject: clk: sunxi-ng: Add fractional lib Some clocks can be switched to a mode called fractional that have two fixed output rate you can choose from. Add a small library to deal with those clocks. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-4-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 9 ++++ drivers/clk/sunxi-ng/Makefile | 3 ++ drivers/clk/sunxi-ng/ccu_frac.c | 110 ++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_frac.h | 53 +++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_frac.c create mode 100644 drivers/clk/sunxi-ng/ccu_frac.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index df5b2768d8b4..3d117d7a3621 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -1,3 +1,12 @@ config SUNXI_CCU bool "Clock support for Allwinner SoCs" default ARCH_SUNXI + +if SUNXI_CCU + +# Base clock types + +config SUNXI_CCU_FRAC + bool + +endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 886d0786ca51..46f417cb682a 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -1,3 +1,6 @@ # Common objects obj-$(CONFIG_SUNXI_CCU) += ccu_common.o obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o + +# Base clock types +obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o diff --git a/drivers/clk/sunxi-ng/ccu_frac.c b/drivers/clk/sunxi-ng/ccu_frac.c new file mode 100644 index 000000000000..5c4b10cd15b5 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_frac.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_frac.h" + +bool ccu_frac_helper_is_enabled(struct ccu_common *common, + struct _ccu_frac *cf) +{ + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return false; + + return !(readl(common->base + common->reg) & cf->enable); +} + +void ccu_frac_helper_enable(struct ccu_common *common, + struct _ccu_frac *cf) +{ + unsigned long flags; + u32 reg; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + writel(reg & ~cf->enable, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); +} + +void ccu_frac_helper_disable(struct ccu_common *common, + struct _ccu_frac *cf) +{ + unsigned long flags; + u32 reg; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + writel(reg | cf->enable, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); +} + +bool ccu_frac_helper_has_rate(struct ccu_common *common, + struct _ccu_frac *cf, + unsigned long rate) +{ + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return false; + + return (cf->rates[0] == rate) || (cf->rates[1] == rate); +} + +unsigned long ccu_frac_helper_read_rate(struct ccu_common *common, + struct _ccu_frac *cf) +{ + u32 reg; + + printk("%s: Read fractional\n", clk_hw_get_name(&common->hw)); + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return 0; + + printk("%s: clock is fractional (rates %lu and %lu)\n", + clk_hw_get_name(&common->hw), cf->rates[0], cf->rates[1]); + + reg = readl(common->base + common->reg); + + printk("%s: clock reg is 0x%x (select is 0x%x)\n", + clk_hw_get_name(&common->hw), reg, cf->select); + + return (reg & cf->select) ? cf->rates[1] : cf->rates[0]; +} + +int ccu_frac_helper_set_rate(struct ccu_common *common, + struct _ccu_frac *cf, + unsigned long rate) +{ + unsigned long flags; + u32 reg, sel; + + if (!(common->features & CCU_FEATURE_FRACTIONAL)) + return -EINVAL; + + if (cf->rates[0] == rate) + sel = 0; + else if (cf->rates[1] == rate) + sel = cf->select; + else + return -EINVAL; + + spin_lock_irqsave(common->lock, flags); + reg = readl(common->base + common->reg); + reg &= ~cf->select; + writel(reg | sel, common->base + common->reg); + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} diff --git a/drivers/clk/sunxi-ng/ccu_frac.h b/drivers/clk/sunxi-ng/ccu_frac.h new file mode 100644 index 000000000000..e4c670b1cdfe --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_frac.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_FRAC_H_ +#define _CCU_FRAC_H_ + +#include + +#include "ccu_common.h" + +struct _ccu_frac { + u32 enable; + u32 select; + + unsigned long rates[2]; +}; + +#define _SUNXI_CCU_FRAC(_enable, _select, _rate1, _rate2) \ + { \ + .enable = _enable, \ + .select = _select, \ + .rates = { _rate1, _rate2 }, \ + } + +bool ccu_frac_helper_is_enabled(struct ccu_common *common, + struct _ccu_frac *cf); +void ccu_frac_helper_enable(struct ccu_common *common, + struct _ccu_frac *cf); +void ccu_frac_helper_disable(struct ccu_common *common, + struct _ccu_frac *cf); + +bool ccu_frac_helper_has_rate(struct ccu_common *common, + struct _ccu_frac *cf, + unsigned long rate); + +unsigned long ccu_frac_helper_read_rate(struct ccu_common *common, + struct _ccu_frac *cf); + +int ccu_frac_helper_set_rate(struct ccu_common *common, + struct _ccu_frac *cf, + unsigned long rate); + +#endif /* _CCU_FRAC_H_ */ -- cgit v1.2.3 From 1a7e7c388df10b2636e4ba18cc29ef740fbea6cc Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:25 +0200 Subject: clk: sunxi-ng: Add gate clock support Some clocks in the Allwinner SoCs clocks unit are just simple gates. Add support for those clocks. Since it's a feature that can also be found in more complex clocks, provide a bunch of helpers that can be reused later on. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-5-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 3 ++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_gate.c | 82 +++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_gate.h | 52 ++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_gate.c create mode 100644 drivers/clk/sunxi-ng/ccu_gate.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 3d117d7a3621..2f135c8a61b6 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -9,4 +9,7 @@ if SUNXI_CCU config SUNXI_CCU_FRAC bool +config SUNXI_CCU_GATE + bool + endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 46f417cb682a..48b885f2d8ff 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o # Base clock types obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o +obj-$(CONFIG_SUNXI_CCU_GATE) += ccu_gate.o diff --git a/drivers/clk/sunxi-ng/ccu_gate.c b/drivers/clk/sunxi-ng/ccu_gate.c new file mode 100644 index 000000000000..8a81f9d4a89f --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_gate.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include + +#include "ccu_gate.h" + +void ccu_gate_helper_disable(struct ccu_common *common, u32 gate) +{ + unsigned long flags; + u32 reg; + + if (!gate) + return; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + writel(reg & ~gate, common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); +} + +static void ccu_gate_disable(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_disable(&cg->common, cg->enable); +} + +int ccu_gate_helper_enable(struct ccu_common *common, u32 gate) +{ + unsigned long flags; + u32 reg; + + if (!gate) + return 0; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + writel(reg | gate, common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} + +static int ccu_gate_enable(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_enable(&cg->common, cg->enable); +} + +int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate) +{ + if (!gate) + return 1; + + return readl(common->base + common->reg) & gate; +} + +static int ccu_gate_is_enabled(struct clk_hw *hw) +{ + struct ccu_gate *cg = hw_to_ccu_gate(hw); + + return ccu_gate_helper_is_enabled(&cg->common, cg->enable); +} + +const struct clk_ops ccu_gate_ops = { + .disable = ccu_gate_disable, + .enable = ccu_gate_enable, + .is_enabled = ccu_gate_is_enabled, +}; diff --git a/drivers/clk/sunxi-ng/ccu_gate.h b/drivers/clk/sunxi-ng/ccu_gate.h new file mode 100644 index 000000000000..4466169bd2d7 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_gate.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_GATE_H_ +#define _CCU_GATE_H_ + +#include + +#include "ccu_common.h" + +struct ccu_gate { + u32 enable; + + struct ccu_common common; +}; + +#define SUNXI_CCU_GATE(_struct, _name, _parent, _reg, _gate, _flags) \ + struct ccu_gate _struct = { \ + .enable = _gate, \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_gate_ops, \ + _flags), \ + } \ + } + +static inline struct ccu_gate *hw_to_ccu_gate(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_gate, common); +} + +void ccu_gate_helper_disable(struct ccu_common *common, u32 gate); +int ccu_gate_helper_enable(struct ccu_common *common, u32 gate); +int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate); + +extern const struct clk_ops ccu_gate_ops; + +#endif /* _CCU_GATE_H_ */ -- cgit v1.2.3 From 2a65ed42dca8721fb7aa397cc3c7321fbb3b7dba Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:26 +0200 Subject: clk: sunxi-ng: Add mux clock support Some clocks in the Allwinner SoCs clocks unit are just muxes. However, those muxes might also be found in some other complicated clocks that would benefit from the code in there to deal with "advanced" features, like pre-dividers. Introduce a set of helpers to reduce the code duplication in such cases. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-6-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 3 + drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_mux.c | 187 +++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_mux.h | 91 ++++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_mux.c create mode 100644 drivers/clk/sunxi-ng/ccu_mux.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 2f135c8a61b6..4699a602e0c1 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -12,4 +12,7 @@ config SUNXI_CCU_FRAC config SUNXI_CCU_GATE bool +config SUNXI_CCU_MUX + bool + endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 48b885f2d8ff..eaa833721245 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o # Base clock types obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o obj-$(CONFIG_SUNXI_CCU_GATE) += ccu_gate.o +obj-$(CONFIG_SUNXI_CCU_MUX) += ccu_mux.o diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c new file mode 100644 index 000000000000..58fc36e7dcce --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mux.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include + +#include "ccu_gate.h" +#include "ccu_mux.h" + +void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common, + struct ccu_mux_internal *cm, + int parent_index, + unsigned long *parent_rate) +{ + u8 prediv = 1; + u32 reg; + + if (!((common->features & CCU_FEATURE_FIXED_PREDIV) || + (common->features & CCU_FEATURE_VARIABLE_PREDIV))) + return; + + reg = readl(common->base + common->reg); + if (parent_index < 0) { + parent_index = reg >> cm->shift; + parent_index &= (1 << cm->width) - 1; + } + + if (common->features & CCU_FEATURE_FIXED_PREDIV) + if (parent_index == cm->fixed_prediv.index) + prediv = cm->fixed_prediv.div; + + if (common->features & CCU_FEATURE_VARIABLE_PREDIV) + if (parent_index == cm->variable_prediv.index) { + u8 div; + + div = reg >> cm->variable_prediv.shift; + div &= (1 << cm->variable_prediv.width) - 1; + prediv = div + 1; + } + + *parent_rate = *parent_rate / prediv; +} + +int ccu_mux_helper_determine_rate(struct ccu_common *common, + struct ccu_mux_internal *cm, + struct clk_rate_request *req, + unsigned long (*round)(struct ccu_mux_internal *, + unsigned long, + unsigned long, + void *), + void *data) +{ + unsigned long best_parent_rate = 0, best_rate = 0; + struct clk_hw *best_parent, *hw = &common->hw; + unsigned int i; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + unsigned long tmp_rate, parent_rate; + struct clk_hw *parent; + + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + ccu_mux_helper_adjust_parent_for_prediv(common, cm, i, + &parent_rate); + + tmp_rate = round(cm, clk_hw_get_rate(parent), req->rate, data); + if (tmp_rate == req->rate) { + best_parent = parent; + best_parent_rate = parent_rate; + best_rate = tmp_rate; + goto out; + } + + if ((req->rate - tmp_rate) < (req->rate - best_rate)) { + best_rate = tmp_rate; + best_parent_rate = parent_rate; + best_parent = parent; + } + } + + if (best_rate == 0) + return -EINVAL; + +out: + req->best_parent_hw = best_parent; + req->best_parent_rate = best_parent_rate; + req->rate = best_rate; + return 0; +} + +u8 ccu_mux_helper_get_parent(struct ccu_common *common, + struct ccu_mux_internal *cm) +{ + u32 reg; + u8 parent; + + reg = readl(common->base + common->reg); + parent = reg >> cm->shift; + parent &= (1 << cm->width) - 1; + + return parent; +} + +int ccu_mux_helper_set_parent(struct ccu_common *common, + struct ccu_mux_internal *cm, + u8 index) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(common->lock, flags); + + reg = readl(common->base + common->reg); + reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift); + writel(reg | (index << cm->shift), common->base + common->reg); + + spin_unlock_irqrestore(common->lock, flags); + + return 0; +} + +static void ccu_mux_disable(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_disable(&cm->common, cm->enable); +} + +static int ccu_mux_enable(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_enable(&cm->common, cm->enable); +} + +static int ccu_mux_is_enabled(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_gate_helper_is_enabled(&cm->common, cm->enable); +} + +static u8 ccu_mux_get_parent(struct clk_hw *hw) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_mux_helper_get_parent(&cm->common, &cm->mux); +} + +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index); +} + +static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_mux *cm = hw_to_ccu_mux(hw); + + ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1, + &parent_rate); + + return parent_rate; +} + +const struct clk_ops ccu_mux_ops = { + .disable = ccu_mux_disable, + .enable = ccu_mux_enable, + .is_enabled = ccu_mux_is_enabled, + + .get_parent = ccu_mux_get_parent, + .set_parent = ccu_mux_set_parent, + + .determine_rate = __clk_mux_determine_rate, + .recalc_rate = ccu_mux_recalc_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h new file mode 100644 index 000000000000..945082631e7d --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mux.h @@ -0,0 +1,91 @@ +#ifndef _CCU_MUX_H_ +#define _CCU_MUX_H_ + +#include + +#include "ccu_common.h" + +struct ccu_mux_internal { + u8 shift; + u8 width; + + struct { + u8 index; + u8 div; + } fixed_prediv; + + struct { + u8 index; + u8 shift; + u8 width; + } variable_prediv; +}; + +#define SUNXI_CLK_MUX(_shift, _width) \ + { \ + .shift = _shift, \ + .width = _width, \ + } + +struct ccu_mux { + u16 reg; + u32 enable; + + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_MUX(_struct, _name, _parents, _reg, _shift, _width, _flags) \ + struct ccu_mux _struct = { \ + .mux = SUNXI_CLK_MUX(_shift, _width), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_mux_ops, \ + _flags), \ + } \ + } + +#define SUNXI_CCU_MUX_WITH_GATE(_struct, _name, _parents, _reg, \ + _shift, _width, _gate, _flags) \ + struct ccu_mux _struct = { \ + .enable = _gate, \ + .mux = SUNXI_CLK_MUX(_shift, _width), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_mux_ops, \ + _flags), \ + } \ + } + +static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_mux, common); +} + +extern const struct clk_ops ccu_mux_ops; + +void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common, + struct ccu_mux_internal *cm, + int parent_index, + unsigned long *parent_rate); +int ccu_mux_helper_determine_rate(struct ccu_common *common, + struct ccu_mux_internal *cm, + struct clk_rate_request *req, + unsigned long (*round)(struct ccu_mux_internal *, + unsigned long, + unsigned long, + void *), + void *data); +u8 ccu_mux_helper_get_parent(struct ccu_common *common, + struct ccu_mux_internal *cm); +int ccu_mux_helper_set_parent(struct ccu_common *common, + struct ccu_mux_internal *cm, + u8 index); + +#endif /* _CCU_MUX_H_ */ -- cgit v1.2.3 From 6f9f7f876ec050ae1c352a6561616fee050dfc42 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:27 +0200 Subject: clk: sunxi-ng: Add phase clock support Add support for the clocks in the CCU that introduce a phase shift from their parent clock. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-7-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 3 + drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_phase.c | 126 +++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_phase.h | 50 ++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_phase.c create mode 100644 drivers/clk/sunxi-ng/ccu_phase.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 4699a602e0c1..3a3bc4368a2a 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -15,4 +15,7 @@ config SUNXI_CCU_GATE config SUNXI_CCU_MUX bool +config SUNXI_CCU_PHASE + bool + endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index eaa833721245..a0c53c56ebfe 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o obj-$(CONFIG_SUNXI_CCU_GATE) += ccu_gate.o obj-$(CONFIG_SUNXI_CCU_MUX) += ccu_mux.o +obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o diff --git a/drivers/clk/sunxi-ng/ccu_phase.c b/drivers/clk/sunxi-ng/ccu_phase.c new file mode 100644 index 000000000000..400c58ad72fd --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_phase.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_phase.h" + +static int ccu_phase_get_phase(struct clk_hw *hw) +{ + struct ccu_phase *phase = hw_to_ccu_phase(hw); + struct clk_hw *parent, *grandparent; + unsigned int parent_rate, grandparent_rate; + u16 step, parent_div; + u32 reg; + u8 delay; + + reg = readl(phase->common.base + phase->common.reg); + delay = (reg >> phase->shift); + delay &= (1 << phase->width) - 1; + + if (!delay) + return 180; + + /* Get our parent clock, it's the one that can adjust its rate */ + parent = clk_hw_get_parent(hw); + if (!parent) + return -EINVAL; + + /* And its rate */ + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + return -EINVAL; + + /* Now, get our parent's parent (most likely some PLL) */ + grandparent = clk_hw_get_parent(parent); + if (!grandparent) + return -EINVAL; + + /* And its rate */ + grandparent_rate = clk_hw_get_rate(grandparent); + if (!grandparent_rate) + return -EINVAL; + + /* Get our parent clock divider */ + parent_div = grandparent_rate / parent_rate; + + step = DIV_ROUND_CLOSEST(360, parent_div); + return delay * step; +} + +static int ccu_phase_set_phase(struct clk_hw *hw, int degrees) +{ + struct ccu_phase *phase = hw_to_ccu_phase(hw); + struct clk_hw *parent, *grandparent; + unsigned int parent_rate, grandparent_rate; + unsigned long flags; + u32 reg; + u8 delay; + + /* Get our parent clock, it's the one that can adjust its rate */ + parent = clk_hw_get_parent(hw); + if (!parent) + return -EINVAL; + + /* And its rate */ + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + return -EINVAL; + + /* Now, get our parent's parent (most likely some PLL) */ + grandparent = clk_hw_get_parent(parent); + if (!grandparent) + return -EINVAL; + + /* And its rate */ + grandparent_rate = clk_hw_get_rate(grandparent); + if (!grandparent_rate) + return -EINVAL; + + if (degrees != 180) { + u16 step, parent_div; + + /* Get our parent divider */ + parent_div = grandparent_rate / parent_rate; + + /* + * We can only outphase the clocks by multiple of the + * PLL's period. + * + * Since our parent clock is only a divider, and the + * formula to get the outphasing in degrees is deg = + * 360 * delta / period + * + * If we simplify this formula, we can see that the + * only thing that we're concerned about is the number + * of period we want to outphase our clock from, and + * the divider set by our parent clock. + */ + step = DIV_ROUND_CLOSEST(360, parent_div); + delay = DIV_ROUND_CLOSEST(degrees, step); + } else { + delay = 0; + } + + spin_lock_irqsave(phase->common.lock, flags); + reg = readl(phase->common.base + phase->common.reg); + reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift); + writel(reg | (delay << phase->shift), + phase->common.base + phase->common.reg); + spin_unlock_irqrestore(phase->common.lock, flags); + + return 0; +} + +const struct clk_ops ccu_phase_ops = { + .get_phase = ccu_phase_get_phase, + .set_phase = ccu_phase_set_phase, +}; diff --git a/drivers/clk/sunxi-ng/ccu_phase.h b/drivers/clk/sunxi-ng/ccu_phase.h new file mode 100644 index 000000000000..75a091a4c565 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_phase.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_PHASE_H_ +#define _CCU_PHASE_H_ + +#include + +#include "ccu_common.h" + +struct ccu_phase { + u8 shift; + u8 width; + + struct ccu_common common; +}; + +#define SUNXI_CCU_PHASE(_struct, _name, _parent, _reg, _shift, _width, _flags) \ + struct ccu_phase _struct = { \ + .shift = _shift, \ + .width = _width, \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_phase_ops, \ + _flags), \ + } \ + } + +static inline struct ccu_phase *hw_to_ccu_phase(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_phase, common); +} + +extern const struct clk_ops ccu_phase_ops; + +#endif /* _CCU_PHASE_H_ */ -- cgit v1.2.3 From e9b93213103fd442ed72802a1c3869f0939c3705 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:28 +0200 Subject: clk: sunxi-ng: Add divider Add support for the various dividers (linear, table or pow-of-two based) found in the CCU. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-8-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 4 ++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_div.c | 136 +++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_div.h | 133 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_div.c create mode 100644 drivers/clk/sunxi-ng/ccu_div.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 3a3bc4368a2a..7e65e776e7a8 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -6,6 +6,10 @@ if SUNXI_CCU # Base clock types +config SUNXI_CCU_DIV + bool + select SUNXI_CCU_MUX + config SUNXI_CCU_FRAC bool diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index a0c53c56ebfe..4bc5b6168560 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_SUNXI_CCU) += ccu_common.o obj-$(CONFIG_SUNXI_CCU) += ccu_reset.o # Base clock types +obj-$(CONFIG_SUNXI_CCU_DIV) += ccu_div.o obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o obj-$(CONFIG_SUNXI_CCU_GATE) += ccu_gate.o obj-$(CONFIG_SUNXI_CCU_MUX) += ccu_mux.o diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c new file mode 100644 index 000000000000..8659b4cb6c20 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_div.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include + +#include "ccu_gate.h" +#include "ccu_div.h" + +static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux, + unsigned long parent_rate, + unsigned long rate, + void *data) +{ + struct ccu_div *cd = data; + unsigned long val; + + /* + * We can't use divider_round_rate that assumes that there's + * several parents, while we might be called to evaluate + * several different parents. + */ + val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, + cd->div.flags); + + return divider_recalc_rate(&cd->common.hw, parent_rate, val, + cd->div.table, cd->div.flags); +} + +static void ccu_div_disable(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_disable(&cd->common, cd->enable); +} + +static int ccu_div_enable(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_enable(&cd->common, cd->enable); +} + +static int ccu_div_is_enabled(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_gate_helper_is_enabled(&cd->common, cd->enable); +} + +static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + unsigned long val; + u32 reg; + + reg = readl(cd->common.base + cd->common.reg); + val = reg >> cd->div.shift; + val &= (1 << cd->div.width) - 1; + + ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, + &parent_rate); + + return divider_recalc_rate(hw, parent_rate, val, cd->div.table, + cd->div.flags); +} + +static int ccu_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_determine_rate(&cd->common, &cd->mux, + req, ccu_div_round_rate, cd); +} + +static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + unsigned long flags; + unsigned long val; + u32 reg; + + ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, + &parent_rate); + + val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, + cd->div.flags); + + spin_lock_irqsave(cd->common.lock, flags); + + reg = readl(cd->common.base + cd->common.reg); + reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift); + + writel(reg | (val << cd->div.shift), + cd->common.base + cd->common.reg); + + spin_unlock_irqrestore(cd->common.lock, flags); + + return 0; +} + +static u8 ccu_div_get_parent(struct clk_hw *hw) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_get_parent(&cd->common, &cd->mux); +} + +static int ccu_div_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_div *cd = hw_to_ccu_div(hw); + + return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index); +} + +const struct clk_ops ccu_div_ops = { + .disable = ccu_div_disable, + .enable = ccu_div_enable, + .is_enabled = ccu_div_is_enabled, + + .get_parent = ccu_div_get_parent, + .set_parent = ccu_div_set_parent, + + .determine_rate = ccu_div_determine_rate, + .recalc_rate = ccu_div_recalc_rate, + .set_rate = ccu_div_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_div.h b/drivers/clk/sunxi-ng/ccu_div.h new file mode 100644 index 000000000000..653ade5769b3 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_div.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_DIV_H_ +#define _CCU_DIV_H_ + +#include + +#include "ccu_common.h" +#include "ccu_mux.h" + +struct _ccu_div { + u8 shift; + u8 width; + + u32 flags; + + struct clk_div_table *table; +}; + +#define _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, _flags) \ + { \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ + .table = _table, \ + } + +#define _SUNXI_CCU_DIV_FLAGS(_shift, _width, _flags) \ + _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, NULL, _flags) + +#define _SUNXI_CCU_DIV_TABLE(_shift, _width, _table) \ + _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, 0) + +#define _SUNXI_CCU_DIV(_shift, _width) \ + _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, NULL, 0) + +struct ccu_div { + u32 enable; + + struct _ccu_div div; + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg, \ + _shift, _width, \ + _table, _gate, _flags) \ + struct ccu_div _struct = { \ + .div = _SUNXI_CCU_DIV_TABLE(_shift, _width, \ + _table), \ + .enable = _gate, \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_div_ops, \ + _flags), \ + } \ + } + + +#define SUNXI_CCU_DIV_TABLE(_struct, _name, _parent, _reg, \ + _shift, _width, \ + _table, _flags) \ + SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg, \ + _shift, _width, _table, 0, \ + _flags) + +#define SUNXI_CCU_M_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + _gate, _flags) \ + struct ccu_div _struct = { \ + .enable = _gate, \ + .div = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .mux = SUNXI_CLK_MUX(_muxshift, _muxwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_div_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_M_WITH_MUX(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + _flags) \ + SUNXI_CCU_M_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + 0, _flags) + + +#define SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg, \ + _mshift, _mwidth, _gate, \ + _flags) \ + struct ccu_div _struct = { \ + .enable = _gate, \ + .div = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_div_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_M(_struct, _name, _parent, _reg, _mshift, _mwidth, \ + _flags) \ + SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg, \ + _mshift, _mwidth, 0, _flags) + +static inline struct ccu_div *hw_to_ccu_div(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_div, common); +} + +extern const struct clk_ops ccu_div_ops; + +#endif /* _CCU_DIV_H_ */ -- cgit v1.2.3 From 2ab836db5097427c3bfa58f9bd7230513c2f3665 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:29 +0200 Subject: clk: sunxi-ng: Add M-P factor clock support Introduce support for the clocks that combine a linear divider and a power-of-two based one. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-9-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 7 ++ drivers/clk/sunxi-ng/Makefile | 3 + drivers/clk/sunxi-ng/ccu_mp.c | 158 ++++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_mp.h | 77 ++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_mp.c create mode 100644 drivers/clk/sunxi-ng/ccu_mp.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 7e65e776e7a8..fc970b5a28c6 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -22,4 +22,11 @@ config SUNXI_CCU_MUX config SUNXI_CCU_PHASE bool +# Multi-factor clocks + +config SUNXI_CCU_MP + bool + select SUNXI_CCU_GATE + select SUNXI_CCU_MUX + endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 4bc5b6168560..501dec9451f4 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -8,3 +8,6 @@ obj-$(CONFIG_SUNXI_CCU_FRAC) += ccu_frac.o obj-$(CONFIG_SUNXI_CCU_GATE) += ccu_gate.o obj-$(CONFIG_SUNXI_CCU_MUX) += ccu_mux.o obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o + +# Multi-factor clocks +obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c new file mode 100644 index 000000000000..cbf33ef5faa9 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mp.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include + +#include "ccu_gate.h" +#include "ccu_mp.h" + +static void ccu_mp_find_best(unsigned long parent, unsigned long rate, + unsigned int max_m, unsigned int max_p, + unsigned int *m, unsigned int *p) +{ + unsigned long best_rate = 0; + unsigned int best_m = 0, best_p = 0; + unsigned int _m, _p; + + for (_p = 0; _p <= max_p; _p++) { + for (_m = 1; _m <= max_m; _m++) { + unsigned long tmp_rate = (parent >> _p) / _m; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_m = _m; + best_p = _p; + } + } + } + + *m = best_m; + *p = best_p; +} + +static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux, + unsigned long parent_rate, + unsigned long rate, + void *data) +{ + struct ccu_mp *cmp = data; + unsigned int m, p; + + ccu_mp_find_best(parent_rate, rate, + 1 << cmp->m.width, (1 << cmp->p.width) - 1, + &m, &p); + + return (parent_rate >> p) / m; +} + +static void ccu_mp_disable(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_disable(&cmp->common, cmp->enable); +} + +static int ccu_mp_enable(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_enable(&cmp->common, cmp->enable); +} + +static int ccu_mp_is_enabled(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_gate_helper_is_enabled(&cmp->common, cmp->enable); +} + +static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + unsigned int m, p; + u32 reg; + + reg = readl(cmp->common.base + cmp->common.reg); + + m = reg >> cmp->m.shift; + m &= (1 << cmp->m.width) - 1; + + p = reg >> cmp->p.shift; + p &= (1 << cmp->p.width) - 1; + + return (parent_rate >> p) / (m + 1); +} + +static int ccu_mp_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_determine_rate(&cmp->common, &cmp->mux, + req, ccu_mp_round_rate, cmp); +} + +static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + unsigned long flags; + unsigned int m, p; + u32 reg; + + ccu_mp_find_best(parent_rate, rate, + 1 << cmp->m.width, (1 << cmp->p.width) - 1, + &m, &p); + + + spin_lock_irqsave(cmp->common.lock, flags); + + reg = readl(cmp->common.base + cmp->common.reg); + reg &= ~GENMASK(cmp->m.width + cmp->m.shift - 1, cmp->m.shift); + reg &= ~GENMASK(cmp->p.width + cmp->p.shift - 1, cmp->p.shift); + + writel(reg | (p << cmp->p.shift) | ((m - 1) << cmp->m.shift), + cmp->common.base + cmp->common.reg); + + spin_unlock_irqrestore(cmp->common.lock, flags); + + return 0; +} + +static u8 ccu_mp_get_parent(struct clk_hw *hw) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_get_parent(&cmp->common, &cmp->mux); +} + +static int ccu_mp_set_parent(struct clk_hw *hw, u8 index) +{ + struct ccu_mp *cmp = hw_to_ccu_mp(hw); + + return ccu_mux_helper_set_parent(&cmp->common, &cmp->mux, index); +} + +const struct clk_ops ccu_mp_ops = { + .disable = ccu_mp_disable, + .enable = ccu_mp_enable, + .is_enabled = ccu_mp_is_enabled, + + .get_parent = ccu_mp_get_parent, + .set_parent = ccu_mp_set_parent, + + .determine_rate = ccu_mp_determine_rate, + .recalc_rate = ccu_mp_recalc_rate, + .set_rate = ccu_mp_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_mp.h b/drivers/clk/sunxi-ng/ccu_mp.h new file mode 100644 index 000000000000..3cf12bf95962 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_mp.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_MP_H_ +#define _CCU_MP_H_ + +#include + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" +#include "ccu_mux.h" + +/* + * struct ccu_mp - Definition of an M-P clock + * + * Clocks based on the formula parent >> P / M + */ +struct ccu_mp { + u32 enable; + + struct _ccu_div m; + struct _ccu_div p; + struct ccu_mux_internal mux; + struct ccu_common common; +}; + +#define SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + _gate, _flags) \ + struct ccu_mp _struct = { \ + .enable = _gate, \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .p = _SUNXI_CCU_DIV(_pshift, _pwidth), \ + .mux = SUNXI_CLK_MUX(_muxshift, _muxwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT_PARENTS(_name, \ + _parents, \ + &ccu_mp_ops, \ + _flags), \ + } \ + } + +#define SUNXI_CCU_MP_WITH_MUX(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + _flags) \ + SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _muxshift, _muxwidth, \ + 0, _flags) + +static inline struct ccu_mp *hw_to_ccu_mp(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_mp, common); +} + +extern const struct clk_ops ccu_mp_ops; + +#endif /* _CCU_MP_H_ */ -- cgit v1.2.3 From adbfb0056e03d556b98ffe93da0d7126bd630096 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:30 +0200 Subject: clk: sunxi-ng: Add N-K-factor clock support Introduce support for clocks that use a combination of two linear multipliers. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-10-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 4 ++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_nk.c | 147 ++++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_nk.h | 71 ++++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_nk.c create mode 100644 drivers/clk/sunxi-ng/ccu_nk.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index fc970b5a28c6..2eb0fc6b41d5 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -24,6 +24,10 @@ config SUNXI_CCU_PHASE # Multi-factor clocks +config SUNXI_CCU_NK + bool + select SUNXI_CCU_GATE + config SUNXI_CCU_MP bool select SUNXI_CCU_GATE diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 501dec9451f4..22951e60f876 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -10,4 +10,5 @@ obj-$(CONFIG_SUNXI_CCU_MUX) += ccu_mux.o obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o # Multi-factor clocks +obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o diff --git a/drivers/clk/sunxi-ng/ccu_nk.c b/drivers/clk/sunxi-ng/ccu_nk.c new file mode 100644 index 000000000000..4470ffc8cf0d --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nk.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_gate.h" +#include "ccu_nk.h" + +void ccu_nk_find_best(unsigned long parent, unsigned long rate, + unsigned int max_n, unsigned int max_k, + unsigned int *n, unsigned int *k) +{ + unsigned long best_rate = 0; + unsigned int best_k = 0, best_n = 0; + unsigned int _k, _n; + + for (_k = 1; _k <= max_k; _k++) { + for (_n = 1; _n <= max_n; _n++) { + unsigned long tmp_rate = parent * _n * _k; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_k = _k; + best_n = _n; + } + } + } + + *k = best_k; + *n = best_n; +} + +static void ccu_nk_disable(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_disable(&nk->common, nk->enable); +} + +static int ccu_nk_enable(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_enable(&nk->common, nk->enable); +} + +static int ccu_nk_is_enabled(struct clk_hw *hw) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + + return ccu_gate_helper_is_enabled(&nk->common, nk->enable); +} + +static unsigned long ccu_nk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + unsigned long rate, n, k; + u32 reg; + + reg = readl(nk->common.base + nk->common.reg); + + n = reg >> nk->n.shift; + n &= (1 << nk->n.width) - 1; + + k = reg >> nk->k.shift; + k &= (1 << nk->k.width) - 1; + + rate = parent_rate * (n + 1) * (k + 1); + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate /= nk->fixed_post_div; + + return rate; +} + +static long ccu_nk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + unsigned int n, k; + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate *= nk->fixed_post_div; + + ccu_nk_find_best(*parent_rate, rate, + 1 << nk->n.width, 1 << nk->k.width, + &n, &k); + + rate = *parent_rate * n * k; + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate = rate / nk->fixed_post_div; + + return rate; +} + +static int ccu_nk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nk *nk = hw_to_ccu_nk(hw); + unsigned long flags; + unsigned int n, k; + u32 reg; + + if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV) + rate = rate * nk->fixed_post_div; + + ccu_nk_find_best(parent_rate, rate, + 1 << nk->n.width, 1 << nk->k.width, + &n, &k); + + spin_lock_irqsave(nk->common.lock, flags); + + reg = readl(nk->common.base + nk->common.reg); + reg &= ~GENMASK(nk->n.width + nk->n.shift - 1, nk->n.shift); + reg &= ~GENMASK(nk->k.width + nk->k.shift - 1, nk->k.shift); + + writel(reg | ((k - 1) << nk->k.shift) | ((n - 1) << nk->n.shift), + nk->common.base + nk->common.reg); + + spin_unlock_irqrestore(nk->common.lock, flags); + + ccu_helper_wait_for_lock(&nk->common, nk->lock); + + return 0; +} + +const struct clk_ops ccu_nk_ops = { + .disable = ccu_nk_disable, + .enable = ccu_nk_enable, + .is_enabled = ccu_nk_is_enabled, + + .recalc_rate = ccu_nk_recalc_rate, + .round_rate = ccu_nk_round_rate, + .set_rate = ccu_nk_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_nk.h b/drivers/clk/sunxi-ng/ccu_nk.h new file mode 100644 index 000000000000..4b52da0c29fe --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nk.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_NK_H_ +#define _CCU_NK_H_ + +#include + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nk - Definition of an N-K clock + * + * Clocks based on the formula parent * N * K + */ +struct ccu_nk { + u16 reg; + u32 enable; + u32 lock; + + struct _ccu_mult n; + struct _ccu_mult k; + + unsigned int fixed_post_div; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NK_WITH_GATE_LOCK_POSTDIV(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _gate, _lock, _postdiv, \ + _flags) \ + struct ccu_nk _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .fixed_post_div = _postdiv, \ + .common = { \ + .reg = _reg, \ + .features = CCU_FEATURE_FIXED_POSTDIV, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nk_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nk *hw_to_ccu_nk(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nk, common); +} + +extern const struct clk_ops ccu_nk_ops; + +#endif /* _CCU_NK_H_ */ -- cgit v1.2.3 From 6174a1e24b0d13f85f64ff570e9d4efc6b0d6287 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:31 +0200 Subject: clk: sunxi-ng: Add N-M-factor clock support Introduce support for clocks that multiply and divide using linear factors. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-11-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 6 +++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_nm.c | 114 ++++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_nm.h | 91 +++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_nm.c create mode 100644 drivers/clk/sunxi-ng/ccu_nm.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 2eb0fc6b41d5..d8a5da270778 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -28,6 +28,12 @@ config SUNXI_CCU_NK bool select SUNXI_CCU_GATE +config SUNXI_CCU_NM + bool + select RATIONAL + select SUNXI_CCU_FRAC + select SUNXI_CCU_GATE + config SUNXI_CCU_MP bool select SUNXI_CCU_GATE diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 22951e60f876..7d2cd2a47a39 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -11,4 +11,5 @@ obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o # Multi-factor clocks obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o +obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o diff --git a/drivers/clk/sunxi-ng/ccu_nm.c b/drivers/clk/sunxi-ng/ccu_nm.c new file mode 100644 index 000000000000..e35ddd8eec8b --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nm.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_frac.h" +#include "ccu_gate.h" +#include "ccu_nm.h" + +static void ccu_nm_disable(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_disable(&nm->common, nm->enable); +} + +static int ccu_nm_enable(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_enable(&nm->common, nm->enable); +} + +static int ccu_nm_is_enabled(struct clk_hw *hw) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + + return ccu_gate_helper_is_enabled(&nm->common, nm->enable); +} + +static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + unsigned long n, m; + u32 reg; + + if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) + return ccu_frac_helper_read_rate(&nm->common, &nm->frac); + + reg = readl(nm->common.base + nm->common.reg); + + n = reg >> nm->n.shift; + n &= (1 << nm->n.width) - 1; + + m = reg >> nm->m.shift; + m &= (1 << nm->m.width) - 1; + + return parent_rate * (n + 1) / (m + 1); +} + +static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + unsigned long n, m; + + rational_best_approximation(rate, *parent_rate, + 1 << nm->n.width, 1 << nm->m.width, + &n, &m); + + return *parent_rate * n / m; +} + +static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nm *nm = hw_to_ccu_nm(hw); + unsigned long flags; + unsigned long n, m; + u32 reg; + + if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) + return ccu_frac_helper_set_rate(&nm->common, &nm->frac, rate); + else + ccu_frac_helper_disable(&nm->common, &nm->frac); + + rational_best_approximation(rate, parent_rate, + 1 << nm->n.width, 1 << nm->m.width, + &n, &m); + + spin_lock_irqsave(nm->common.lock, flags); + + reg = readl(nm->common.base + nm->common.reg); + reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift); + reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift); + + writel(reg | ((m - 1) << nm->m.shift) | ((n - 1) << nm->n.shift), + nm->common.base + nm->common.reg); + + spin_unlock_irqrestore(nm->common.lock, flags); + + ccu_helper_wait_for_lock(&nm->common, nm->lock); + + return 0; +} + +const struct clk_ops ccu_nm_ops = { + .disable = ccu_nm_disable, + .enable = ccu_nm_enable, + .is_enabled = ccu_nm_is_enabled, + + .recalc_rate = ccu_nm_recalc_rate, + .round_rate = ccu_nm_round_rate, + .set_rate = ccu_nm_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_nm.h b/drivers/clk/sunxi-ng/ccu_nm.h new file mode 100644 index 000000000000..0b7bcd33a2df --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nm.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_NM_H_ +#define _CCU_NM_H_ + +#include + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_frac.h" +#include "ccu_mult.h" + +/* + * struct ccu_nm - Definition of an N-M clock + * + * Clocks based on the formula parent * N / M + */ +struct ccu_nm { + u32 enable; + u32 lock; + + struct _ccu_mult n; + struct _ccu_div m; + struct _ccu_frac frac; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _mshift, _mwidth, \ + _frac_en, _frac_sel, \ + _frac_rate_0, _frac_rate_1, \ + _gate, _lock, _flags) \ + struct ccu_nm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .frac = _SUNXI_CCU_FRAC(_frac_en, _frac_sel, \ + _frac_rate_0, \ + _frac_rate_1), \ + .common = { \ + .reg = _reg, \ + .features = CCU_FEATURE_FRACTIONAL, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nm_ops, \ + _flags), \ + }, \ + } + +#define SUNXI_CCU_NM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _mshift, _mwidth, \ + _gate, _lock, _flags) \ + struct ccu_nm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nm_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nm *hw_to_ccu_nm(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nm, common); +} + +extern const struct clk_ops ccu_nm_ops; + +#endif /* _CCU_NM_H_ */ -- cgit v1.2.3 From df6561e60244c0283340286664b0baf67e846599 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:32 +0200 Subject: clk: sunxi-ng: Add N-K-M Factor clock Introduce support for clocks that multiply and divide using two linear multipliers and one linear divider. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-12-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 5 ++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_nkm.c | 153 +++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_nkm.h | 68 ++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_nkm.c create mode 100644 drivers/clk/sunxi-ng/ccu_nkm.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index d8a5da270778..4c571b86bfd1 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -28,6 +28,11 @@ config SUNXI_CCU_NK bool select SUNXI_CCU_GATE +config SUNXI_CCU_NKM + bool + select RATIONAL + select SUNXI_CCU_GATE + config SUNXI_CCU_NM bool select RATIONAL diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 7d2cd2a47a39..2d6fdca0d3cc 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -11,5 +11,6 @@ obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o # Multi-factor clocks obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o +obj-$(CONFIG_SUNXI_CCU_NKM) += ccu_nkm.o obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c new file mode 100644 index 000000000000..2071822b1e9c --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkm.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_gate.h" +#include "ccu_nkm.h" + +struct _ccu_nkm { + unsigned long n, max_n; + unsigned long k, max_k; + unsigned long m, max_m; +}; + +static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nkm *nkm) +{ + unsigned long best_rate = 0; + unsigned long best_n = 0, best_k = 0, best_m = 0; + unsigned long _n, _k, _m; + + for (_k = 1; _k <= nkm->max_k; _k++) { + unsigned long tmp_rate; + + rational_best_approximation(rate / _k, parent, + nkm->max_n, nkm->max_m, &_n, &_m); + + tmp_rate = parent * _n * _k / _m; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_n = _n; + best_k = _k; + best_m = _m; + } + } + + nkm->n = best_n; + nkm->k = best_k; + nkm->m = best_m; +} + +static void ccu_nkm_disable(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_disable(&nkm->common, nkm->enable); +} + +static int ccu_nkm_enable(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_enable(&nkm->common, nkm->enable); +} + +static int ccu_nkm_is_enabled(struct clk_hw *hw) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + + return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable); +} + +static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + unsigned long n, m, k; + u32 reg; + + reg = readl(nkm->common.base + nkm->common.reg); + + n = reg >> nkm->n.shift; + n &= (1 << nkm->n.width) - 1; + + k = reg >> nkm->k.shift; + k &= (1 << nkm->k.width) - 1; + + m = reg >> nkm->m.shift; + m &= (1 << nkm->m.width) - 1; + + return parent_rate * (n + 1) * (k + 1) / (m + 1); +} + +static long ccu_nkm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + struct _ccu_nkm _nkm; + + _nkm.max_n = 1 << nkm->n.width; + _nkm.max_k = 1 << nkm->k.width; + _nkm.max_m = 1 << nkm->m.width; + + ccu_nkm_find_best(*parent_rate, rate, &_nkm); + + return *parent_rate * _nkm.n * _nkm.k / _nkm.m; +} + +static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); + struct _ccu_nkm _nkm; + unsigned long flags; + u32 reg; + + _nkm.max_n = 1 << nkm->n.width; + _nkm.max_k = 1 << nkm->k.width; + _nkm.max_m = 1 << nkm->m.width; + + ccu_nkm_find_best(parent_rate, rate, &_nkm); + + spin_lock_irqsave(nkm->common.lock, flags); + + reg = readl(nkm->common.base + nkm->common.reg); + reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift); + reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift); + reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift); + + reg |= (_nkm.n - 1) << nkm->n.shift; + reg |= (_nkm.k - 1) << nkm->k.shift; + reg |= (_nkm.m - 1) << nkm->m.shift; + + writel(reg, nkm->common.base + nkm->common.reg); + + spin_unlock_irqrestore(nkm->common.lock, flags); + + ccu_helper_wait_for_lock(&nkm->common, nkm->lock); + + return 0; +} + +const struct clk_ops ccu_nkm_ops = { + .disable = ccu_nkm_disable, + .enable = ccu_nkm_enable, + .is_enabled = ccu_nkm_is_enabled, + + .recalc_rate = ccu_nkm_recalc_rate, + .round_rate = ccu_nkm_round_rate, + .set_rate = ccu_nkm_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_nkm.h b/drivers/clk/sunxi-ng/ccu_nkm.h new file mode 100644 index 000000000000..1936ac1c6b37 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkm.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_NKM_H_ +#define _CCU_NKM_H_ + +#include + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nkm - Definition of an N-K-M clock + * + * Clocks based on the formula parent * N * K / M + */ +struct ccu_nkm { + u32 enable; + u32 lock; + + struct _ccu_mult n; + struct _ccu_mult k; + struct _ccu_div m; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NKM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _mshift, _mwidth, \ + _gate, _lock, _flags) \ + struct ccu_nkm _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nkm_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nkm *hw_to_ccu_nkm(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nkm, common); +} + +extern const struct clk_ops ccu_nkm_ops; + +#endif /* _CCU_NKM_H_ */ -- cgit v1.2.3 From 4f728b5db7cb125af71e5da8154ac3b72653d819 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:33 +0200 Subject: clk: sunxi-ng: Add N-K-M-P factor clock Introduce support for clocks that use a combination of two linear multipliers (N and K factors), one linear divider (M) and one power of two divider (P). Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-13-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 5 ++ drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_nkmp.c | 167 ++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_nkmp.h | 71 +++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_nkmp.c create mode 100644 drivers/clk/sunxi-ng/ccu_nkmp.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 4c571b86bfd1..24fc9e4cc546 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -33,6 +33,11 @@ config SUNXI_CCU_NKM select RATIONAL select SUNXI_CCU_GATE +config SUNXI_CCU_NKMP + bool + select RATIONAL + select SUNXI_CCU_GATE + config SUNXI_CCU_NM bool select RATIONAL diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 2d6fdca0d3cc..5c342cc698d2 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -12,5 +12,6 @@ obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o # Multi-factor clocks obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o obj-$(CONFIG_SUNXI_CCU_NKM) += ccu_nkm.o +obj-$(CONFIG_SUNXI_CCU_NKMP) += ccu_nkmp.o obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o diff --git a/drivers/clk/sunxi-ng/ccu_nkmp.c b/drivers/clk/sunxi-ng/ccu_nkmp.c new file mode 100644 index 000000000000..9f2b98e19dc9 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkmp.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "ccu_gate.h" +#include "ccu_nkmp.h" + +struct _ccu_nkmp { + unsigned long n, max_n; + unsigned long k, max_k; + unsigned long m, max_m; + unsigned long p, max_p; +}; + +static void ccu_nkmp_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nkmp *nkmp) +{ + unsigned long best_rate = 0; + unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0; + unsigned long _n, _k, _m, _p; + + for (_k = 1; _k <= nkmp->max_k; _k++) { + for (_p = 0; _p <= nkmp->max_p; _p++) { + unsigned long tmp_rate; + + rational_best_approximation(rate / _k, parent >> _p, + nkmp->max_n, nkmp->max_m, + &_n, &_m); + + tmp_rate = (parent * _n * _k >> _p) / _m; + + if (tmp_rate > rate) + continue; + + if ((rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_n = _n; + best_k = _k; + best_m = _m; + best_p = _p; + } + } + } + + nkmp->n = best_n; + nkmp->k = best_k; + nkmp->m = best_m; + nkmp->p = best_p; +} + +static void ccu_nkmp_disable(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_disable(&nkmp->common, nkmp->enable); +} + +static int ccu_nkmp_enable(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_enable(&nkmp->common, nkmp->enable); +} + +static int ccu_nkmp_is_enabled(struct clk_hw *hw) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + + return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable); +} + +static unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + unsigned long n, m, k, p; + u32 reg; + + reg = readl(nkmp->common.base + nkmp->common.reg); + + n = reg >> nkmp->n.shift; + n &= (1 << nkmp->n.width) - 1; + + k = reg >> nkmp->k.shift; + k &= (1 << nkmp->k.width) - 1; + + m = reg >> nkmp->m.shift; + m &= (1 << nkmp->m.width) - 1; + + p = reg >> nkmp->p.shift; + p &= (1 << nkmp->p.width) - 1; + + return (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1); +} + +static long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + struct _ccu_nkmp _nkmp; + + _nkmp.max_n = 1 << nkmp->n.width; + _nkmp.max_k = 1 << nkmp->k.width; + _nkmp.max_m = 1 << nkmp->m.width; + _nkmp.max_p = (1 << nkmp->p.width) - 1; + + ccu_nkmp_find_best(*parent_rate, rate, + &_nkmp); + + return (*parent_rate * _nkmp.n * _nkmp.k >> _nkmp.p) / _nkmp.m; +} + +static int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); + struct _ccu_nkmp _nkmp; + unsigned long flags; + u32 reg; + + _nkmp.max_n = 1 << nkmp->n.width; + _nkmp.max_k = 1 << nkmp->k.width; + _nkmp.max_m = 1 << nkmp->m.width; + _nkmp.max_p = (1 << nkmp->p.width) - 1; + + ccu_nkmp_find_best(parent_rate, rate, &_nkmp); + + spin_lock_irqsave(nkmp->common.lock, flags); + + reg = readl(nkmp->common.base + nkmp->common.reg); + reg &= ~GENMASK(nkmp->n.width + nkmp->n.shift - 1, nkmp->n.shift); + reg &= ~GENMASK(nkmp->k.width + nkmp->k.shift - 1, nkmp->k.shift); + reg &= ~GENMASK(nkmp->m.width + nkmp->m.shift - 1, nkmp->m.shift); + reg &= ~GENMASK(nkmp->p.width + nkmp->p.shift - 1, nkmp->p.shift); + + reg |= (_nkmp.n - 1) << nkmp->n.shift; + reg |= (_nkmp.k - 1) << nkmp->k.shift; + reg |= (_nkmp.m - 1) << nkmp->m.shift; + reg |= _nkmp.p << nkmp->p.shift; + + writel(reg, nkmp->common.base + nkmp->common.reg); + + spin_unlock_irqrestore(nkmp->common.lock, flags); + + ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock); + + return 0; +} + +const struct clk_ops ccu_nkmp_ops = { + .disable = ccu_nkmp_disable, + .enable = ccu_nkmp_enable, + .is_enabled = ccu_nkmp_is_enabled, + + .recalc_rate = ccu_nkmp_recalc_rate, + .round_rate = ccu_nkmp_round_rate, + .set_rate = ccu_nkmp_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_nkmp.h b/drivers/clk/sunxi-ng/ccu_nkmp.h new file mode 100644 index 000000000000..5adb0c92a614 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkmp.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_NKMP_H_ +#define _CCU_NKMP_H_ + +#include + +#include "ccu_common.h" +#include "ccu_div.h" +#include "ccu_mult.h" + +/* + * struct ccu_nkmp - Definition of an N-K-M-P clock + * + * Clocks based on the formula parent * N * K >> P / M + */ +struct ccu_nkmp { + u32 enable; + u32 lock; + + struct _ccu_mult n; + struct _ccu_mult k; + struct _ccu_div m; + struct _ccu_div p; + + struct ccu_common common; +}; + +#define SUNXI_CCU_NKMP_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ + _nshift, _nwidth, \ + _kshift, _kwidth, \ + _mshift, _mwidth, \ + _pshift, _pwidth, \ + _gate, _lock, _flags) \ + struct ccu_nkmp _struct = { \ + .enable = _gate, \ + .lock = _lock, \ + .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ + .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ + .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ + .p = _SUNXI_CCU_DIV(_pshift, _pwidth), \ + .common = { \ + .reg = _reg, \ + .hw.init = CLK_HW_INIT(_name, \ + _parent, \ + &ccu_nkmp_ops, \ + _flags), \ + }, \ + } + +static inline struct ccu_nkmp *hw_to_ccu_nkmp(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_nkmp, common); +} + +extern const struct clk_ops ccu_nkmp_ops; + +#endif /* _CCU_NKMP_H_ */ -- cgit v1.2.3 From 0577e4853bfb4c65f620fa56d3157692df7f766e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 29 Jun 2016 21:05:34 +0200 Subject: clk: sunxi-ng: Add H3 clocks Add the list of clocks and resets found in the H3 CCU. Signed-off-by: Maxime Ripard Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20160629190535.11855-14-maxime.ripard@free-electrons.com --- drivers/clk/sunxi-ng/Kconfig | 13 + drivers/clk/sunxi-ng/Makefile | 3 + drivers/clk/sunxi-ng/ccu-sun8i-h3.c | 826 ++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu-sun8i-h3.h | 62 +++ 4 files changed, 904 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-h3.c create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-h3.h (limited to 'drivers/clk') diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 24fc9e4cc546..41e53e8d4d41 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig @@ -49,4 +49,17 @@ config SUNXI_CCU_MP select SUNXI_CCU_GATE select SUNXI_CCU_MUX +# SoC Drivers + +config SUN8I_H3_CCU + bool "Support for the Allwinner H3 CCU" + select SUNXI_CCU_DIV + select SUNXI_CCU_NK + select SUNXI_CCU_NKM + select SUNXI_CCU_NKMP + select SUNXI_CCU_NM + select SUNXI_CCU_MP + select SUNXI_CCU_PHASE + default ARCH_SUN8I + endif diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 5c342cc698d2..633ce642ffae 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -15,3 +15,6 @@ obj-$(CONFIG_SUNXI_CCU_NKM) += ccu_nkm.o obj-$(CONFIG_SUNXI_CCU_NKMP) += ccu_nkmp.o obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o + +# SoC support +obj-$(CONFIG_SUN8I_H3_CCU) += ccu-sun8i-h3.o diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-h3.c b/drivers/clk/sunxi-ng/ccu-sun8i-h3.c new file mode 100644 index 000000000000..bcc0a95549d3 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu-sun8i-h3.c @@ -0,0 +1,826 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "ccu_common.h" +#include "ccu_reset.h" + +#include "ccu_div.h" +#include "ccu_gate.h" +#include "ccu_mp.h" +#include "ccu_mult.h" +#include "ccu_nk.h" +#include "ccu_nkm.h" +#include "ccu_nkmp.h" +#include "ccu_nm.h" +#include "ccu_phase.h" + +#include "ccu-sun8i-h3.h" + +static SUNXI_CCU_NKMP_WITH_GATE_LOCK(pll_cpux_clk, "pll-cpux", + "osc24M", 0x000, + 8, 5, /* N */ + 4, 2, /* K */ + 0, 2, /* M */ + 16, 2, /* P */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +/* + * The Audio PLL is supposed to have 4 outputs: 3 fixed factors from + * the base (2x, 4x and 8x), and one variable divider (the one true + * pll audio). + * + * We don't have any need for the variable divider for now, so we just + * hardcode it to match with the clock names + */ +#define SUN8I_H3_PLL_AUDIO_REG 0x008 + +static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base", + "osc24M", 0x008, + 8, 7, /* N */ + 0, 5, /* M */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video_clk, "pll-video", + "osc24M", 0x0010, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_ve_clk, "pll-ve", + "osc24M", 0x0018, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static SUNXI_CCU_NKM_WITH_GATE_LOCK(pll_ddr_clk, "pll-ddr", + "osc24M", 0x020, + 8, 5, /* N */ + 4, 2, /* K */ + 0, 2, /* M */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static SUNXI_CCU_NK_WITH_GATE_LOCK_POSTDIV(pll_periph0_clk, "pll-periph0", + "osc24M", 0x028, + 8, 5, /* N */ + 4, 2, /* K */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 2, /* post-div */ + 0); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_gpu_clk, "pll-gpu", + "osc24M", 0x0038, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static SUNXI_CCU_NK_WITH_GATE_LOCK_POSTDIV(pll_periph1_clk, "pll-periph1", + "osc24M", 0x044, + 8, 5, /* N */ + 4, 2, /* K */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 2, /* post-div */ + 0); + +static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_de_clk, "pll-de", + "osc24M", 0x0048, + 8, 7, /* N */ + 0, 4, /* M */ + BIT(24), /* frac enable */ + BIT(25), /* frac select */ + 270000000, /* frac rate 0 */ + 297000000, /* frac rate 1 */ + BIT(31), /* gate */ + BIT(28), /* lock */ + 0); + +static const char * const cpux_parents[] = { "osc32k", "osc24M", + "pll-cpux" , "pll-cpux" }; +static SUNXI_CCU_MUX(cpux_clk, "cpux", cpux_parents, + 0x050, 16, 2, CLK_IS_CRITICAL); + +static SUNXI_CCU_M(axi_clk, "axi", "cpux", 0x050, 0, 2, 0); + +static const char * const ahb1_parents[] = { "osc32k", "osc24M", + "axi" , "pll-periph0" }; +static struct ccu_div ahb1_clk = { + .div = _SUNXI_CCU_DIV_FLAGS(4, 2, CLK_DIVIDER_POWER_OF_TWO), + + .mux = { + .shift = 12, + .width = 2, + + .variable_prediv = { + .index = 3, + .shift = 6, + .width = 2, + }, + }, + + .common = { + .reg = 0x054, + .features = CCU_FEATURE_VARIABLE_PREDIV, + .hw.init = CLK_HW_INIT_PARENTS("ahb1", + ahb1_parents, + &ccu_div_ops, + 0), + }, +}; + +static struct clk_div_table apb1_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 8 }, + { /* Sentinel */ }, +}; +static SUNXI_CCU_DIV_TABLE(apb1_clk, "apb1", "ahb1", + 0x054, 8, 2, apb1_div_table, 0); + +static const char * const apb2_parents[] = { "osc32k", "osc24M", + "pll-periph0" , "pll-periph0" }; +static SUNXI_CCU_MP_WITH_MUX(apb2_clk, "apb2", apb2_parents, 0x058, + 0, 5, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + 0); + +static const char * const ahb2_parents[] = { "ahb1" , "pll-periph0" }; +static struct ccu_mux ahb2_clk = { + .mux = { + .shift = 0, + .width = 1, + + .fixed_prediv = { + .index = 1, + .div = 2, + }, + }, + + .common = { + .reg = 0x05c, + .features = CCU_FEATURE_FIXED_PREDIV, + .hw.init = CLK_HW_INIT_PARENTS("ahb2", + ahb2_parents, + &ccu_mux_ops, + 0), + }, +}; + +static SUNXI_CCU_GATE(bus_ce_clk, "bus-ce", "ahb1", + 0x060, BIT(5), 0); +static SUNXI_CCU_GATE(bus_dma_clk, "bus-dma", "ahb1", + 0x060, BIT(6), 0); +static SUNXI_CCU_GATE(bus_mmc0_clk, "bus-mmc0", "ahb1", + 0x060, BIT(8), 0); +static SUNXI_CCU_GATE(bus_mmc1_clk, "bus-mmc1", "ahb1", + 0x060, BIT(9), 0); +static SUNXI_CCU_GATE(bus_mmc2_clk, "bus-mmc2", "ahb1", + 0x060, BIT(10), 0); +static SUNXI_CCU_GATE(bus_nand_clk, "bus-nand", "ahb1", + 0x060, BIT(13), 0); +static SUNXI_CCU_GATE(bus_dram_clk, "bus-dram", "ahb1", + 0x060, BIT(14), 0); +static SUNXI_CCU_GATE(bus_emac_clk, "bus-emac", "ahb2", + 0x060, BIT(17), 0); +static SUNXI_CCU_GATE(bus_ts_clk, "bus-ts", "ahb1", + 0x060, BIT(18), 0); +static SUNXI_CCU_GATE(bus_hstimer_clk, "bus-hstimer", "ahb1", + 0x060, BIT(19), 0); +static SUNXI_CCU_GATE(bus_spi0_clk, "bus-spi0", "ahb1", + 0x060, BIT(20), 0); +static SUNXI_CCU_GATE(bus_spi1_clk, "bus-spi1", "ahb1", + 0x060, BIT(21), 0); +static SUNXI_CCU_GATE(bus_otg_clk, "bus-otg", "ahb1", + 0x060, BIT(23), 0); +static SUNXI_CCU_GATE(bus_ehci0_clk, "bus-ehci0", "ahb1", + 0x060, BIT(24), 0); +static SUNXI_CCU_GATE(bus_ehci1_clk, "bus-ehci1", "ahb2", + 0x060, BIT(25), 0); +static SUNXI_CCU_GATE(bus_ehci2_clk, "bus-ehci2", "ahb2", + 0x060, BIT(26), 0); +static SUNXI_CCU_GATE(bus_ehci3_clk, "bus-ehci3", "ahb2", + 0x060, BIT(27), 0); +static SUNXI_CCU_GATE(bus_ohci0_clk, "bus-ohci0", "ahb1", + 0x060, BIT(28), 0); +static SUNXI_CCU_GATE(bus_ohci1_clk, "bus-ohci1", "ahb2", + 0x060, BIT(29), 0); +static SUNXI_CCU_GATE(bus_ohci2_clk, "bus-ohci2", "ahb2", + 0x060, BIT(30), 0); +static SUNXI_CCU_GATE(bus_ohci3_clk, "bus-ohci3", "ahb2", + 0x060, BIT(31), 0); + +static SUNXI_CCU_GATE(bus_ve_clk, "bus-ve", "ahb1", + 0x064, BIT(0), 0); +static SUNXI_CCU_GATE(bus_tcon0_clk, "bus-tcon0", "ahb1", + 0x064, BIT(3), 0); +static SUNXI_CCU_GATE(bus_tcon1_clk, "bus-tcon1", "ahb1", + 0x064, BIT(4), 0); +static SUNXI_CCU_GATE(bus_deinterlace_clk, "bus-deinterlace", "ahb1", + 0x064, BIT(5), 0); +static SUNXI_CCU_GATE(bus_csi_clk, "bus-csi", "ahb1", + 0x064, BIT(8), 0); +static SUNXI_CCU_GATE(bus_tve_clk, "bus-tve", "ahb1", + 0x064, BIT(9), 0); +static SUNXI_CCU_GATE(bus_hdmi_clk, "bus-hdmi", "ahb1", + 0x064, BIT(11), 0); +static SUNXI_CCU_GATE(bus_de_clk, "bus-de", "ahb1", + 0x064, BIT(12), 0); +static SUNXI_CCU_GATE(bus_gpu_clk, "bus-gpu", "ahb1", + 0x064, BIT(20), 0); +static SUNXI_CCU_GATE(bus_msgbox_clk, "bus-msgbox", "ahb1", + 0x064, BIT(21), 0); +static SUNXI_CCU_GATE(bus_spinlock_clk, "bus-spinlock", "ahb1", + 0x064, BIT(22), 0); + +static SUNXI_CCU_GATE(bus_codec_clk, "bus-codec", "apb1", + 0x068, BIT(0), 0); +static SUNXI_CCU_GATE(bus_spdif_clk, "bus-spdif", "apb1", + 0x068, BIT(1), 0); +static SUNXI_CCU_GATE(bus_pio_clk, "bus-pio", "apb1", + 0x068, BIT(5), 0); +static SUNXI_CCU_GATE(bus_ths_clk, "bus-ths", "apb1", + 0x068, BIT(8), 0); +static SUNXI_CCU_GATE(bus_i2s0_clk, "bus-i2s0", "apb1", + 0x068, BIT(12), 0); +static SUNXI_CCU_GATE(bus_i2s1_clk, "bus-i2s1", "apb1", + 0x068, BIT(13), 0); +static SUNXI_CCU_GATE(bus_i2s2_clk, "bus-i2s2", "apb1", + 0x068, BIT(14), 0); + +static SUNXI_CCU_GATE(bus_i2c0_clk, "bus-i2c0", "apb2", + 0x06c, BIT(0), 0); +static SUNXI_CCU_GATE(bus_i2c1_clk, "bus-i2c1", "apb2", + 0x06c, BIT(1), 0); +static SUNXI_CCU_GATE(bus_i2c2_clk, "bus-i2c2", "apb2", + 0x06c, BIT(2), 0); +static SUNXI_CCU_GATE(bus_uart0_clk, "bus-uart0", "apb2", + 0x06c, BIT(16), 0); +static SUNXI_CCU_GATE(bus_uart1_clk, "bus-uart1", "apb2", + 0x06c, BIT(17), 0); +static SUNXI_CCU_GATE(bus_uart2_clk, "bus-uart2", "apb2", + 0x06c, BIT(18), 0); +static SUNXI_CCU_GATE(bus_uart3_clk, "bus-uart3", "apb2", + 0x06c, BIT(19), 0); +static SUNXI_CCU_GATE(bus_scr_clk, "bus-scr", "apb2", + 0x06c, BIT(20), 0); + +static SUNXI_CCU_GATE(bus_ephy_clk, "bus-ephy", "ahb1", + 0x070, BIT(0), 0); +static SUNXI_CCU_GATE(bus_dbg_clk, "bus-dbg", "ahb1", + 0x070, BIT(7), 0); + +static struct clk_div_table ths_div_table[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 6 }, +}; +static SUNXI_CCU_DIV_TABLE_WITH_GATE(ths_clk, "ths", "osc24M", + 0x074, 0, 2, ths_div_table, BIT(31), 0); + +static const char * const mod0_default_parents[] = { "osc24M", "pll-periph0", + "pll-periph1" }; +static SUNXI_CCU_MP_WITH_MUX_GATE(nand_clk, "nand", mod0_default_parents, 0x080, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc0_clk, "mmc0", mod0_default_parents, 0x088, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_PHASE(mmc0_sample_clk, "mmc0_sample", "mmc0", + 0x088, 20, 3, 0); +static SUNXI_CCU_PHASE(mmc0_output_clk, "mmc0_output", "mmc0", + 0x088, 8, 3, 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc1_clk, "mmc1", mod0_default_parents, 0x08c, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_PHASE(mmc1_sample_clk, "mmc1_sample", "mmc1", + 0x08c, 20, 3, 0); +static SUNXI_CCU_PHASE(mmc1_output_clk, "mmc1_output", "mmc1", + 0x08c, 8, 3, 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(mmc2_clk, "mmc2", mod0_default_parents, 0x090, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_PHASE(mmc2_sample_clk, "mmc2_sample", "mmc2", + 0x090, 20, 3, 0); +static SUNXI_CCU_PHASE(mmc2_output_clk, "mmc2_output", "mmc2", + 0x090, 8, 3, 0); + +static const char * const ts_parents[] = { "osc24M", "pll-periph0", }; +static SUNXI_CCU_MP_WITH_MUX_GATE(ts_clk, "ts", ts_parents, 0x098, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(ce_clk, "ce", mod0_default_parents, 0x09c, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(spi0_clk, "spi0", mod0_default_parents, 0x0a0, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static SUNXI_CCU_MP_WITH_MUX_GATE(spi1_clk, "spi1", mod0_default_parents, 0x0a4, + 0, 4, /* M */ + 16, 2, /* P */ + 24, 2, /* mux */ + BIT(31), /* gate */ + 0); + +static const char * const i2s_parents[] = { "pll-audio-8x", "pll-audio-4x", + "pll-audio-2x", "pll-audio" }; +static SUNXI_CCU_MUX_WITH_GATE(i2s0_clk, "i2s0", i2s_parents, + 0x0b0, 16, 2, BIT(31), 0); + +static SUNXI_CCU_MUX_WITH_GATE(i2s1_clk, "i2s1", i2s_parents, + 0x0b4, 16, 2, BIT(31), 0); + +static SUNXI_CCU_MUX_WITH_GATE(i2s2_clk, "i2s2", i2s_parents, + 0x0b8, 16, 2, BIT(31), 0); + +static SUNXI_CCU_M_WITH_GATE(spdif_clk, "spdif", "pll-audio", + 0x0c0, 0, 4, BIT(31), 0); + +static SUNXI_CCU_GATE(usb_phy0_clk, "usb-phy0", "osc24M", + 0x0cc, BIT(8), 0); +static SUNXI_CCU_GATE(usb_phy1_clk, "usb-phy1", "osc24M", + 0x0cc, BIT(9), 0); +static SUNXI_CCU_GATE(usb_phy2_clk, "usb-phy2", "osc24M", + 0x0cc, BIT(10), 0); +static SUNXI_CCU_GATE(usb_phy3_clk, "usb-phy3", "osc24M", + 0x0cc, BIT(11), 0); +static SUNXI_CCU_GATE(usb_ohci0_clk, "usb-ohci0", "osc24M", + 0x0cc, BIT(16), 0); +static SUNXI_CCU_GATE(usb_ohci1_clk, "usb-ohci1", "osc24M", + 0x0cc, BIT(17), 0); +static SUNXI_CCU_GATE(usb_ohci2_clk, "usb-ohci2", "osc24M", + 0x0cc, BIT(18), 0); +static SUNXI_CCU_GATE(usb_ohci3_clk, "usb-ohci3", "osc24M", + 0x0cc, BIT(19), 0); + +static const char * const dram_parents[] = { "pll-ddr", "pll-periph0-2x" }; +static SUNXI_CCU_M_WITH_MUX(dram_clk, "dram", dram_parents, + 0x0f4, 0, 4, 20, 2, CLK_IS_CRITICAL); + +static SUNXI_CCU_GATE(dram_ve_clk, "dram-ve", "dram", + 0x100, BIT(0), 0); +static SUNXI_CCU_GATE(dram_csi_clk, "dram-csi", "dram", + 0x100, BIT(1), 0); +static SUNXI_CCU_GATE(dram_deinterlace_clk, "dram-deinterlace", "dram", + 0x100, BIT(2), 0); +static SUNXI_CCU_GATE(dram_ts_clk, "dram-ts", "dram", + 0x100, BIT(3), 0); + +static const char * const de_parents[] = { "pll-periph0-2x", "pll-de" }; +static SUNXI_CCU_M_WITH_MUX_GATE(de_clk, "de", de_parents, + 0x104, 0, 4, 24, 3, BIT(31), 0); + +static const char * const tcon_parents[] = { "pll-video" }; +static SUNXI_CCU_M_WITH_MUX_GATE(tcon_clk, "tcon", tcon_parents, + 0x118, 0, 4, 24, 3, BIT(31), 0); + +static const char * const tve_parents[] = { "pll-de", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(tve_clk, "tve", tve_parents, + 0x120, 0, 4, 24, 3, BIT(31), 0); + +static const char * const deinterlace_parents[] = { "pll-periph0", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(deinterlace_clk, "deinterlace", deinterlace_parents, + 0x124, 0, 4, 24, 3, BIT(31), 0); + +static SUNXI_CCU_GATE(csi_misc_clk, "csi-misc", "osc24M", + 0x130, BIT(31), 0); + +static const char * const csi_sclk_parents[] = { "pll-periph0", "pll-periph1" }; +static SUNXI_CCU_M_WITH_MUX_GATE(csi_sclk_clk, "csi-sclk", csi_sclk_parents, + 0x134, 16, 4, 24, 3, BIT(31), 0); + +static const char * const csi_mclk_parents[] = { "osc24M", "pll-video", "pll-periph0" }; +static SUNXI_CCU_M_WITH_MUX_GATE(csi_mclk_clk, "csi-mclk", csi_mclk_parents, + 0x134, 0, 5, 8, 3, BIT(15), 0); + +static SUNXI_CCU_M_WITH_GATE(ve_clk, "ve", "pll-ve", + 0x13c, 16, 3, BIT(31), 0); + +static SUNXI_CCU_GATE(ac_dig_clk, "ac-dig", "pll-audio", + 0x140, BIT(31), 0); +static SUNXI_CCU_GATE(avs_clk, "avs", "osc24M", + 0x144, BIT(31), 0); + +static const char * const hdmi_parents[] = { "pll-video" }; +static SUNXI_CCU_M_WITH_MUX_GATE(hdmi_clk, "hdmi", hdmi_parents, + 0x150, 0, 4, 24, 2, BIT(31), 0); + +static SUNXI_CCU_GATE(hdmi_ddc_clk, "hdmi-ddc", "osc24M", + 0x154, BIT(31), 0); + +static const char * const mbus_parents[] = { "osc24M", "pll-periph0-2x", "pll-ddr" }; +static SUNXI_CCU_M_WITH_MUX_GATE(mbus_clk, "mbus", mbus_parents, + 0x15c, 0, 3, 24, 2, BIT(31), CLK_IS_CRITICAL); + +static SUNXI_CCU_M_WITH_GATE(gpu_clk, "gpu", "pll-gpu", + 0x1a0, 0, 3, BIT(31), 0); + +static struct ccu_common *sun8i_h3_ccu_clks[] = { + &pll_cpux_clk.common, + &pll_audio_base_clk.common, + &pll_video_clk.common, + &pll_ve_clk.common, + &pll_ddr_clk.common, + &pll_periph0_clk.common, + &pll_gpu_clk.common, + &pll_periph1_clk.common, + &pll_de_clk.common, + &cpux_clk.common, + &axi_clk.common, + &ahb1_clk.common, + &apb1_clk.common, + &apb2_clk.common, + &ahb2_clk.common, + &bus_ce_clk.common, + &bus_dma_clk.common, + &bus_mmc0_clk.common, + &bus_mmc1_clk.common, + &bus_mmc2_clk.common, + &bus_nand_clk.common, + &bus_dram_clk.common, + &bus_emac_clk.common, + &bus_ts_clk.common, + &bus_hstimer_clk.common, + &bus_spi0_clk.common, + &bus_spi1_clk.common, + &bus_otg_clk.common, + &bus_ehci0_clk.common, + &bus_ehci1_clk.common, + &bus_ehci2_clk.common, + &bus_ehci3_clk.common, + &bus_ohci0_clk.common, + &bus_ohci1_clk.common, + &bus_ohci2_clk.common, + &bus_ohci3_clk.common, + &bus_ve_clk.common, + &bus_tcon0_clk.common, + &bus_tcon1_clk.common, + &bus_deinterlace_clk.common, + &bus_csi_clk.common, + &bus_tve_clk.common, + &bus_hdmi_clk.common, + &bus_de_clk.common, + &bus_gpu_clk.common, + &bus_msgbox_clk.common, + &bus_spinlock_clk.common, + &bus_codec_clk.common, + &bus_spdif_clk.common, + &bus_pio_clk.common, + &bus_ths_clk.common, + &bus_i2s0_clk.common, + &bus_i2s1_clk.common, + &bus_i2s2_clk.common, + &bus_i2c0_clk.common, + &bus_i2c1_clk.common, + &bus_i2c2_clk.common, + &bus_uart0_clk.common, + &bus_uart1_clk.common, + &bus_uart2_clk.common, + &bus_uart3_clk.common, + &bus_scr_clk.common, + &bus_ephy_clk.common, + &bus_dbg_clk.common, + &ths_clk.common, + &nand_clk.common, + &mmc0_clk.common, + &mmc0_sample_clk.common, + &mmc0_output_clk.common, + &mmc1_clk.common, + &mmc1_sample_clk.common, + &mmc1_output_clk.common, + &mmc2_clk.common, + &mmc2_sample_clk.common, + &mmc2_output_clk.common, + &ts_clk.common, + &ce_clk.common, + &spi0_clk.common, + &spi1_clk.common, + &i2s0_clk.common, + &i2s1_clk.common, + &i2s2_clk.common, + &spdif_clk.common, + &usb_phy0_clk.common, + &usb_phy1_clk.common, + &usb_phy2_clk.common, + &usb_phy3_clk.common, + &usb_ohci0_clk.common, + &usb_ohci1_clk.common, + &usb_ohci2_clk.common, + &usb_ohci3_clk.common, + &dram_clk.common, + &dram_ve_clk.common, + &dram_csi_clk.common, + &dram_deinterlace_clk.common, + &dram_ts_clk.common, + &de_clk.common, + &tcon_clk.common, + &tve_clk.common, + &deinterlace_clk.common, + &csi_misc_clk.common, + &csi_sclk_clk.common, + &csi_mclk_clk.common, + &ve_clk.common, + &ac_dig_clk.common, + &avs_clk.common, + &hdmi_clk.common, + &hdmi_ddc_clk.common, + &mbus_clk.common, + &gpu_clk.common, +}; + +/* We hardcode the divider to 4 for now */ +static CLK_FIXED_FACTOR(pll_audio_clk, "pll-audio", + "pll-audio-base", 4, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_2x_clk, "pll-audio-2x", + "pll-audio-base", 2, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_4x_clk, "pll-audio-4x", + "pll-audio-base", 1, 1, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_audio_8x_clk, "pll-audio-8x", + "pll-audio-base", 1, 2, CLK_SET_RATE_PARENT); +static CLK_FIXED_FACTOR(pll_periph0_2x_clk, "pll-periph0-2x", + "pll-periph0", 1, 2, 0); + +static struct clk_hw_onecell_data sun8i_h3_hw_clks = { + .hws = { + [CLK_PLL_CPUX] = &pll_cpux_clk.common.hw, + [CLK_PLL_AUDIO_BASE] = &pll_audio_base_clk.common.hw, + [CLK_PLL_AUDIO] = &pll_audio_clk.hw, + [CLK_PLL_AUDIO_2X] = &pll_audio_2x_clk.hw, + [CLK_PLL_AUDIO_4X] = &pll_audio_4x_clk.hw, + [CLK_PLL_AUDIO_8X] = &pll_audio_8x_clk.hw, + [CLK_PLL_VIDEO] = &pll_video_clk.common.hw, + [CLK_PLL_VE] = &pll_ve_clk.common.hw, + [CLK_PLL_DDR] = &pll_ddr_clk.common.hw, + [CLK_PLL_PERIPH0] = &pll_periph0_clk.common.hw, + [CLK_PLL_PERIPH0_2X] = &pll_periph0_2x_clk.hw, + [CLK_PLL_GPU] = &pll_gpu_clk.common.hw, + [CLK_PLL_PERIPH1] = &pll_periph1_clk.common.hw, + [CLK_PLL_DE] = &pll_de_clk.common.hw, + [CLK_CPUX] = &cpux_clk.common.hw, + [CLK_AXI] = &axi_clk.common.hw, + [CLK_AHB1] = &ahb1_clk.common.hw, + [CLK_APB1] = &apb1_clk.common.hw, + [CLK_APB2] = &apb2_clk.common.hw, + [CLK_AHB2] = &ahb2_clk.common.hw, + [CLK_BUS_CE] = &bus_ce_clk.common.hw, + [CLK_BUS_DMA] = &bus_dma_clk.common.hw, + [CLK_BUS_MMC0] = &bus_mmc0_clk.common.hw, + [CLK_BUS_MMC1] = &bus_mmc1_clk.common.hw, + [CLK_BUS_MMC2] = &bus_mmc2_clk.common.hw, + [CLK_BUS_NAND] = &bus_nand_clk.common.hw, + [CLK_BUS_DRAM] = &bus_dram_clk.common.hw, + [CLK_BUS_EMAC] = &bus_emac_clk.common.hw, + [CLK_BUS_TS] = &bus_ts_clk.common.hw, + [CLK_BUS_HSTIMER] = &bus_hstimer_clk.common.hw, + [CLK_BUS_SPI0] = &bus_spi0_clk.common.hw, + [CLK_BUS_SPI1] = &bus_spi1_clk.common.hw, + [CLK_BUS_OTG] = &bus_otg_clk.common.hw, + [CLK_BUS_EHCI0] = &bus_ehci0_clk.common.hw, + [CLK_BUS_EHCI1] = &bus_ehci1_clk.common.hw, + [CLK_BUS_EHCI2] = &bus_ehci2_clk.common.hw, + [CLK_BUS_EHCI3] = &bus_ehci3_clk.common.hw, + [CLK_BUS_OHCI0] = &bus_ohci0_clk.common.hw, + [CLK_BUS_OHCI1] = &bus_ohci1_clk.common.hw, + [CLK_BUS_OHCI2] = &bus_ohci2_clk.common.hw, + [CLK_BUS_OHCI3] = &bus_ohci3_clk.common.hw, + [CLK_BUS_VE] = &bus_ve_clk.common.hw, + [CLK_BUS_TCON0] = &bus_tcon0_clk.common.hw, + [CLK_BUS_TCON1] = &bus_tcon1_clk.common.hw, + [CLK_BUS_DEINTERLACE] = &bus_deinterlace_clk.common.hw, + [CLK_BUS_CSI] = &bus_csi_clk.common.hw, + [CLK_BUS_TVE] = &bus_tve_clk.common.hw, + [CLK_BUS_HDMI] = &bus_hdmi_clk.common.hw, + [CLK_BUS_DE] = &bus_de_clk.common.hw, + [CLK_BUS_GPU] = &bus_gpu_clk.common.hw, + [CLK_BUS_MSGBOX] = &bus_msgbox_clk.common.hw, + [CLK_BUS_SPINLOCK] = &bus_spinlock_clk.common.hw, + [CLK_BUS_CODEC] = &bus_codec_clk.common.hw, + [CLK_BUS_SPDIF] = &bus_spdif_clk.common.hw, + [CLK_BUS_PIO] = &bus_pio_clk.common.hw, + [CLK_BUS_THS] = &bus_ths_clk.common.hw, + [CLK_BUS_I2S0] = &bus_i2s0_clk.common.hw, + [CLK_BUS_I2S1] = &bus_i2s1_clk.common.hw, + [CLK_BUS_I2S2] = &bus_i2s2_clk.common.hw, + [CLK_BUS_I2C0] = &bus_i2c0_clk.common.hw, + [CLK_BUS_I2C1] = &bus_i2c1_clk.common.hw, + [CLK_BUS_I2C2] = &bus_i2c2_clk.common.hw, + [CLK_BUS_UART0] = &bus_uart0_clk.common.hw, + [CLK_BUS_UART1] = &bus_uart1_clk.common.hw, + [CLK_BUS_UART2] = &bus_uart2_clk.common.hw, + [CLK_BUS_UART3] = &bus_uart3_clk.common.hw, + [CLK_BUS_SCR] = &bus_scr_clk.common.hw, + [CLK_BUS_EPHY] = &bus_ephy_clk.common.hw, + [CLK_BUS_DBG] = &bus_dbg_clk.common.hw, + [CLK_THS] = &ths_clk.common.hw, + [CLK_NAND] = &nand_clk.common.hw, + [CLK_MMC0] = &mmc0_clk.common.hw, + [CLK_MMC0_SAMPLE] = &mmc0_sample_clk.common.hw, + [CLK_MMC0_OUTPUT] = &mmc0_output_clk.common.hw, + [CLK_MMC1] = &mmc1_clk.common.hw, + [CLK_MMC1_SAMPLE] = &mmc1_sample_clk.common.hw, + [CLK_MMC1_OUTPUT] = &mmc1_output_clk.common.hw, + [CLK_MMC2] = &mmc2_clk.common.hw, + [CLK_MMC2_SAMPLE] = &mmc2_sample_clk.common.hw, + [CLK_MMC2_OUTPUT] = &mmc2_output_clk.common.hw, + [CLK_TS] = &ts_clk.common.hw, + [CLK_CE] = &ce_clk.common.hw, + [CLK_SPI0] = &spi0_clk.common.hw, + [CLK_SPI1] = &spi1_clk.common.hw, + [CLK_I2S0] = &i2s0_clk.common.hw, + [CLK_I2S1] = &i2s1_clk.common.hw, + [CLK_I2S2] = &i2s2_clk.common.hw, + [CLK_SPDIF] = &spdif_clk.common.hw, + [CLK_USB_PHY0] = &usb_phy0_clk.common.hw, + [CLK_USB_PHY1] = &usb_phy1_clk.common.hw, + [CLK_USB_PHY2] = &usb_phy2_clk.common.hw, + [CLK_USB_PHY3] = &usb_phy3_clk.common.hw, + [CLK_USB_OHCI0] = &usb_ohci0_clk.common.hw, + [CLK_USB_OHCI1] = &usb_ohci1_clk.common.hw, + [CLK_USB_OHCI2] = &usb_ohci2_clk.common.hw, + [CLK_USB_OHCI3] = &usb_ohci3_clk.common.hw, + [CLK_DRAM] = &dram_clk.common.hw, + [CLK_DRAM_VE] = &dram_ve_clk.common.hw, + [CLK_DRAM_CSI] = &dram_csi_clk.common.hw, + [CLK_DRAM_DEINTERLACE] = &dram_deinterlace_clk.common.hw, + [CLK_DRAM_TS] = &dram_ts_clk.common.hw, + [CLK_DE] = &de_clk.common.hw, + [CLK_TCON0] = &tcon_clk.common.hw, + [CLK_TVE] = &tve_clk.common.hw, + [CLK_DEINTERLACE] = &deinterlace_clk.common.hw, + [CLK_CSI_MISC] = &csi_misc_clk.common.hw, + [CLK_CSI_SCLK] = &csi_sclk_clk.common.hw, + [CLK_CSI_MCLK] = &csi_mclk_clk.common.hw, + [CLK_VE] = &ve_clk.common.hw, + [CLK_AC_DIG] = &ac_dig_clk.common.hw, + [CLK_AVS] = &avs_clk.common.hw, + [CLK_HDMI] = &hdmi_clk.common.hw, + [CLK_HDMI_DDC] = &hdmi_ddc_clk.common.hw, + [CLK_MBUS] = &mbus_clk.common.hw, + [CLK_GPU] = &gpu_clk.common.hw, + }, + .num = CLK_NUMBER, +}; + +static struct ccu_reset_map sun8i_h3_ccu_resets[] = { + [RST_USB_PHY0] = { 0x0cc, BIT(0) }, + [RST_USB_PHY1] = { 0x0cc, BIT(1) }, + [RST_USB_PHY2] = { 0x0cc, BIT(2) }, + [RST_USB_PHY3] = { 0x0cc, BIT(3) }, + + [RST_MBUS] = { 0x0fc, BIT(31) }, + + [RST_BUS_CE] = { 0x2c0, BIT(5) }, + [RST_BUS_DMA] = { 0x2c0, BIT(6) }, + [RST_BUS_MMC0] = { 0x2c0, BIT(8) }, + [RST_BUS_MMC1] = { 0x2c0, BIT(9) }, + [RST_BUS_MMC2] = { 0x2c0, BIT(10) }, + [RST_BUS_NAND] = { 0x2c0, BIT(13) }, + [RST_BUS_DRAM] = { 0x2c0, BIT(14) }, + [RST_BUS_EMAC] = { 0x2c0, BIT(17) }, + [RST_BUS_TS] = { 0x2c0, BIT(18) }, + [RST_BUS_HSTIMER] = { 0x2c0, BIT(19) }, + [RST_BUS_SPI0] = { 0x2c0, BIT(20) }, + [RST_BUS_SPI1] = { 0x2c0, BIT(21) }, + [RST_BUS_OTG] = { 0x2c0, BIT(23) }, + [RST_BUS_EHCI0] = { 0x2c0, BIT(24) }, + [RST_BUS_EHCI1] = { 0x2c0, BIT(25) }, + [RST_BUS_EHCI2] = { 0x2c0, BIT(26) }, + [RST_BUS_EHCI3] = { 0x2c0, BIT(27) }, + [RST_BUS_OHCI0] = { 0x2c0, BIT(28) }, + [RST_BUS_OHCI1] = { 0x2c0, BIT(29) }, + [RST_BUS_OHCI2] = { 0x2c0, BIT(30) }, + [RST_BUS_OHCI3] = { 0x2c0, BIT(31) }, + + [RST_BUS_VE] = { 0x2c4, BIT(0) }, + [RST_BUS_TCON0] = { 0x2c4, BIT(3) }, + [RST_BUS_TCON1] = { 0x2c4, BIT(4) }, + [RST_BUS_DEINTERLACE] = { 0x2c4, BIT(5) }, + [RST_BUS_CSI] = { 0x2c4, BIT(8) }, + [RST_BUS_TVE] = { 0x2c4, BIT(9) }, + [RST_BUS_HDMI0] = { 0x2c4, BIT(10) }, + [RST_BUS_HDMI1] = { 0x2c4, BIT(11) }, + [RST_BUS_DE] = { 0x2c4, BIT(12) }, + [RST_BUS_GPU] = { 0x2c4, BIT(20) }, + [RST_BUS_MSGBOX] = { 0x2c4, BIT(21) }, + [RST_BUS_SPINLOCK] = { 0x2c4, BIT(22) }, + [RST_BUS_DBG] = { 0x2c4, BIT(31) }, + + [RST_BUS_EPHY] = { 0x2c8, BIT(2) }, + + [RST_BUS_CODEC] = { 0x2d0, BIT(0) }, + [RST_BUS_SPDIF] = { 0x2d0, BIT(1) }, + [RST_BUS_THS] = { 0x2d0, BIT(8) }, + [RST_BUS_I2S0] = { 0x2d0, BIT(12) }, + [RST_BUS_I2S1] = { 0x2d0, BIT(13) }, + [RST_BUS_I2S2] = { 0x2d0, BIT(14) }, + + [RST_BUS_I2C0] = { 0x2d4, BIT(0) }, + [RST_BUS_I2C1] = { 0x2d4, BIT(1) }, + [RST_BUS_I2C2] = { 0x2d4, BIT(2) }, + [RST_BUS_UART0] = { 0x2d4, BIT(16) }, + [RST_BUS_UART1] = { 0x2d4, BIT(17) }, + [RST_BUS_UART2] = { 0x2d4, BIT(18) }, + [RST_BUS_UART3] = { 0x2d4, BIT(19) }, + [RST_BUS_SCR] = { 0x2d4, BIT(20) }, +}; + +static const struct sunxi_ccu_desc sun8i_h3_ccu_desc = { + .ccu_clks = sun8i_h3_ccu_clks, + .num_ccu_clks = ARRAY_SIZE(sun8i_h3_ccu_clks), + + .hw_clks = &sun8i_h3_hw_clks, + + .resets = sun8i_h3_ccu_resets, + .num_resets = ARRAY_SIZE(sun8i_h3_ccu_resets), +}; + +static void __init sun8i_h3_ccu_setup(struct device_node *node) +{ + void __iomem *reg; + u32 val; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("%s: Could not map the clock registers\n", + of_node_full_name(node)); + return; + } + + /* Force the PLL-Audio-1x divider to 4 */ + val = readl(reg + SUN8I_H3_PLL_AUDIO_REG); + val &= ~GENMASK(4, 0); + writel(val | 3, reg + SUN8I_H3_PLL_AUDIO_REG); + + sunxi_ccu_probe(node, reg, &sun8i_h3_ccu_desc); +} +CLK_OF_DECLARE(sun8i_h3_ccu, "allwinner,sun8i-h3-ccu", + sun8i_h3_ccu_setup); diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-h3.h b/drivers/clk/sunxi-ng/ccu-sun8i-h3.h new file mode 100644 index 000000000000..78be712c7487 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu-sun8i-h3.h @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Maxime Ripard + * + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_SUN8I_H3_H_ +#define _CCU_SUN8I_H3_H_ + +#include +#include + +#define CLK_PLL_CPUX 0 +#define CLK_PLL_AUDIO_BASE 1 +#define CLK_PLL_AUDIO 2 +#define CLK_PLL_AUDIO_2X 3 +#define CLK_PLL_AUDIO_4X 4 +#define CLK_PLL_AUDIO_8X 5 +#define CLK_PLL_VIDEO 6 +#define CLK_PLL_VE 7 +#define CLK_PLL_DDR 8 +#define CLK_PLL_PERIPH0 9 +#define CLK_PLL_PERIPH0_2X 10 +#define CLK_PLL_GPU 11 +#define CLK_PLL_PERIPH1 12 +#define CLK_PLL_DE 13 + +/* The CPUX clock is exported */ + +#define CLK_AXI 15 +#define CLK_AHB1 16 +#define CLK_APB1 17 +#define CLK_APB2 18 +#define CLK_AHB2 19 + +/* All the bus gates are exported */ + +/* The first bunch of module clocks are exported */ + +#define CLK_DRAM 96 + +/* All the DRAM gates are exported */ + +/* Some more module clocks are exported */ + +#define CLK_MBUS 113 + +/* And the GPU module clock is exported */ + +#define CLK_NUMBER (CLK_GPU + 1) + +#endif /* _CCU_SUN8I_H3_H_ */ -- cgit v1.2.3