diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/ux500/Makefile | 3 | ||||
-rw-r--r-- | drivers/clk/ux500/prcc.h | 19 | ||||
-rw-r--r-- | drivers/clk/ux500/reset-prcc.c | 181 | ||||
-rw-r--r-- | drivers/clk/ux500/reset-prcc.h | 23 | ||||
-rw-r--r-- | drivers/clk/ux500/u8500_of_clk.c | 30 |
5 files changed, 243 insertions, 13 deletions
diff --git a/drivers/clk/ux500/Makefile b/drivers/clk/ux500/Makefile index 53fd29002401..c29b83df403e 100644 --- a/drivers/clk/ux500/Makefile +++ b/drivers/clk/ux500/Makefile @@ -8,6 +8,9 @@ obj-y += clk-prcc.o obj-y += clk-prcmu.o obj-y += clk-sysctrl.o +# Reset control +obj-y += reset-prcc.o + # Clock definitions obj-y += u8500_of_clk.o diff --git a/drivers/clk/ux500/prcc.h b/drivers/clk/ux500/prcc.h new file mode 100644 index 000000000000..5b6774d79506 --- /dev/null +++ b/drivers/clk/ux500/prcc.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __PRCC_H +#define __PRCC_H + +#define PRCC_NUM_PERIPH_CLUSTERS 6 +#define PRCC_PERIPHS_PER_CLUSTER 32 + +/* CLKRST4 is missing making it hard to index things */ +enum clkrst_index { + CLKRST1_INDEX = 0, + CLKRST2_INDEX, + CLKRST3_INDEX, + CLKRST5_INDEX, + CLKRST6_INDEX, + CLKRST_MAX, +}; + +#endif diff --git a/drivers/clk/ux500/reset-prcc.c b/drivers/clk/ux500/reset-prcc.c new file mode 100644 index 000000000000..fcd5d042806a --- /dev/null +++ b/drivers/clk/ux500/reset-prcc.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Reset controller portions for the U8500 PRCC + * Copyright (C) 2021 Linus Walleij <linus.walleij@linaro.org> + */ +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/reset-controller.h> +#include <linux/bits.h> +#include <linux/delay.h> + +#include "prcc.h" +#include "reset-prcc.h" + +#define to_u8500_prcc_reset(p) container_of((p), struct u8500_prcc_reset, rcdev) + +/* This macro flattens the 2-dimensional PRCC numberspace */ +#define PRCC_RESET_LINE(prcc_num, bit) \ + (((prcc_num) * PRCC_PERIPHS_PER_CLUSTER) + (bit)) + +/* + * Reset registers in each PRCC - the reset lines are active low + * so what you need to do is write a bit for the peripheral you + * want to put into reset into the CLEAR register, this will assert + * the reset by pulling the line low. SET take the device out of + * reset. The status reflects the actual state of the line. + */ +#define PRCC_K_SOFTRST_SET 0x018 +#define PRCC_K_SOFTRST_CLEAR 0x01c +#define PRCC_K_RST_STATUS 0x020 + +static int prcc_num_to_index(unsigned int num) +{ + switch (num) { + case 1: + return CLKRST1_INDEX; + case 2: + return CLKRST2_INDEX; + case 3: + return CLKRST3_INDEX; + case 5: + return CLKRST5_INDEX; + case 6: + return CLKRST6_INDEX; + } + return -EINVAL; +} + +static void __iomem *u8500_prcc_reset_base(struct u8500_prcc_reset *ur, + unsigned long id) +{ + unsigned int prcc_num, index; + + prcc_num = id / PRCC_PERIPHS_PER_CLUSTER; + index = prcc_num_to_index(prcc_num); + + if (index > ARRAY_SIZE(ur->base)) + return NULL; + + return ur->base[index]; +} + +static int u8500_prcc_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct u8500_prcc_reset *ur = to_u8500_prcc_reset(rcdev); + void __iomem *base = u8500_prcc_reset_base(ur, id); + unsigned int bit = id % PRCC_PERIPHS_PER_CLUSTER; + + pr_debug("PRCC cycle reset id %lu, bit %u\n", id, bit); + + /* + * Assert reset and then release it. The one microsecond + * delay is found in the vendor reference code. + */ + writel(BIT(bit), base + PRCC_K_SOFTRST_CLEAR); + udelay(1); + writel(BIT(bit), base + PRCC_K_SOFTRST_SET); + udelay(1); + + return 0; +} + +static int u8500_prcc_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct u8500_prcc_reset *ur = to_u8500_prcc_reset(rcdev); + void __iomem *base = u8500_prcc_reset_base(ur, id); + unsigned int bit = id % PRCC_PERIPHS_PER_CLUSTER; + + pr_debug("PRCC assert reset id %lu, bit %u\n", id, bit); + writel(BIT(bit), base + PRCC_K_SOFTRST_CLEAR); + + return 0; +} + +static int u8500_prcc_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct u8500_prcc_reset *ur = to_u8500_prcc_reset(rcdev); + void __iomem *base = u8500_prcc_reset_base(ur, id); + unsigned int bit = id % PRCC_PERIPHS_PER_CLUSTER; + + pr_debug("PRCC deassert reset id %lu, bit %u\n", id, bit); + writel(BIT(bit), base + PRCC_K_SOFTRST_SET); + + return 0; +} + +static int u8500_prcc_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct u8500_prcc_reset *ur = to_u8500_prcc_reset(rcdev); + void __iomem *base = u8500_prcc_reset_base(ur, id); + unsigned int bit = id % PRCC_PERIPHS_PER_CLUSTER; + u32 val; + + pr_debug("PRCC check status on reset line id %lu, bit %u\n", id, bit); + val = readl(base + PRCC_K_RST_STATUS); + + /* Active low so return the inverse value of the bit */ + return !(val & BIT(bit)); +} + +static const struct reset_control_ops u8500_prcc_reset_ops = { + .reset = u8500_prcc_reset, + .assert = u8500_prcc_reset_assert, + .deassert = u8500_prcc_reset_deassert, + .status = u8500_prcc_reset_status, +}; + +static int u8500_prcc_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned int prcc_num, bit; + + if (reset_spec->args_count != 2) + return -EINVAL; + + prcc_num = reset_spec->args[0]; + bit = reset_spec->args[1]; + + if (prcc_num != 1 && prcc_num != 2 && prcc_num != 3 && + prcc_num != 5 && prcc_num != 6) { + pr_err("%s: invalid PRCC %d\n", __func__, prcc_num); + return -EINVAL; + } + + pr_debug("located reset line %d at PRCC %d bit %d\n", + PRCC_RESET_LINE(prcc_num, bit), prcc_num, bit); + + return PRCC_RESET_LINE(prcc_num, bit); +} + +void u8500_prcc_reset_init(struct device_node *np, struct u8500_prcc_reset *ur) +{ + struct reset_controller_dev *rcdev = &ur->rcdev; + int ret; + int i; + + for (i = 0; i < CLKRST_MAX; i++) { + ur->base[i] = ioremap(ur->phy_base[i], SZ_4K); + if (!ur->base[i]) + pr_err("PRCC failed to remap for reset base %d (%08x)\n", + i, ur->phy_base[i]); + } + + rcdev->owner = THIS_MODULE; + rcdev->ops = &u8500_prcc_reset_ops; + rcdev->of_node = np; + rcdev->of_reset_n_cells = 2; + rcdev->of_xlate = u8500_prcc_reset_xlate; + + ret = reset_controller_register(rcdev); + if (ret) + pr_err("PRCC failed to register reset controller\n"); +} diff --git a/drivers/clk/ux500/reset-prcc.h b/drivers/clk/ux500/reset-prcc.h new file mode 100644 index 000000000000..353c9719f2e6 --- /dev/null +++ b/drivers/clk/ux500/reset-prcc.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __RESET_PRCC_H +#define __RESET_PRCC_H + +#include <linux/reset-controller.h> +#include <linux/io.h> + +/** + * struct u8500_prcc_reset - U8500 PRCC reset controller state + * @rcdev: reset controller device + * @phy_base: the physical base address for each PRCC block + * @base: the remapped PRCC bases + */ +struct u8500_prcc_reset { + struct reset_controller_dev rcdev; + u32 phy_base[CLKRST_MAX]; + void __iomem *base[CLKRST_MAX]; +}; + +void u8500_prcc_reset_init(struct device_node *np, struct u8500_prcc_reset *ur); + +#endif diff --git a/drivers/clk/ux500/u8500_of_clk.c b/drivers/clk/ux500/u8500_of_clk.c index 528c5bb397cc..e86ed2eec3fd 100644 --- a/drivers/clk/ux500/u8500_of_clk.c +++ b/drivers/clk/ux500/u8500_of_clk.c @@ -10,10 +10,10 @@ #include <linux/of_address.h> #include <linux/clk-provider.h> #include <linux/mfd/dbx500-prcmu.h> -#include "clk.h" -#define PRCC_NUM_PERIPH_CLUSTERS 6 -#define PRCC_PERIPHS_PER_CLUSTER 32 +#include "clk.h" +#include "prcc.h" +#include "reset-prcc.h" static struct clk *prcmu_clk[PRCMU_NUM_CLKS]; static struct clk *prcc_pclk[(PRCC_NUM_PERIPH_CLUSTERS + 1) * PRCC_PERIPHS_PER_CLUSTER]; @@ -46,16 +46,6 @@ static struct clk *ux500_twocell_get(struct of_phandle_args *clkspec, return PRCC_SHOW(clk_data, base, bit); } -/* CLKRST4 is missing making it hard to index things */ -enum clkrst_index { - CLKRST1_INDEX = 0, - CLKRST2_INDEX, - CLKRST3_INDEX, - CLKRST5_INDEX, - CLKRST6_INDEX, - CLKRST_MAX, -}; - static void u8500_clk_init(struct device_node *np) { struct prcmu_fw_version *fw_version; @@ -63,8 +53,18 @@ static void u8500_clk_init(struct device_node *np) const char *sgaclk_parent = NULL; struct clk *clk, *rtc_clk, *twd_clk; u32 bases[CLKRST_MAX]; + struct u8500_prcc_reset *rstc; int i; + /* + * We allocate the reset controller here so that we can fill in the + * base addresses properly and pass to the reset controller init + * function later on. + */ + rstc = kzalloc(sizeof(*rstc), GFP_KERNEL); + if (!rstc) + return; + for (i = 0; i < ARRAY_SIZE(bases); i++) { struct resource r; @@ -73,6 +73,7 @@ static void u8500_clk_init(struct device_node *np) pr_err("failed to get CLKRST %d base address\n", i + 1); bases[i] = r.start; + rstc->phy_base[i] = r.start; } /* Clock sources */ @@ -563,6 +564,9 @@ static void u8500_clk_init(struct device_node *np) if (of_node_name_eq(child, "smp-twd-clock")) of_clk_add_provider(child, of_clk_src_simple_get, twd_clk); + + if (of_node_name_eq(child, "prcc-reset-controller")) + u8500_prcc_reset_init(child, rstc); } } CLK_OF_DECLARE(u8500_clks, "stericsson,u8500-clks", u8500_clk_init); |