Previously the s3c24xx socs using the swrst machnism simply wrote
the needed value to a statically mapped register.
To generalize and make it usable in the dt case create a reset handler
similar to the already existing watchdog-reset used by different samsung
architectures.
Signed-off-by: Heiko Stuebner <heiko@...>
arch/arm/mach-s3c24xx/Kconfig | 5 ++
arch/arm/mach-s3c24xx/Makefile | 1 +
arch/arm/mach-s3c24xx/common.h | 16 ++++
arch/arm/mach-s3c24xx/swrst-reset.c | 160 +++++++++++++++++++++++++++++++++++
4 files changed, 182 insertions(+)
create mode 100644 arch/arm/mach-s3c24xx/swrst-reset.c
@@ -30,6 +30,11 @@ config S3C2410_COMMON_DCLK
Temporary symbol to build the dclk driver based on the common clock
framework.
+config S3C24XX_SWRST
+ bool
+ help
+ Handle resets using the swrst register available on some s3c24xx SoCs.
+
menu "SAMSUNG S3C24XX SoCs Support"
comment "S3C24XX SoCs"
@@ -41,6 +41,7 @@ obj-$(CONFIG_CPU_S3C2443) += s3c2443.o
# PM
obj-$(CONFIG_PM) += pm.o irq-pm.o sleep.o
+obj-$(CONFIG_S3C24XX_SWRST) += swrst-reset.o
# common code
@@ -131,4 +131,20 @@ void __init s3c2443_common_clk_init(struct device_node *np, unsigned long xti_f,
void __iomem *reg_base);
#endif
+#ifdef CONFIG_S3C24XX_SWRST
+void s3c24xx_swrst_reset(void);
+bool s3c24xx_swrst_reset_available(void);
+void s3c24xx_swrst_reset_of_init(void);
+void s3c24xx_swrst_reset_init(void __iomem *base, bool is_s3c2412);
+#else
+static inline void s3c24xx_swrst_reset(void) {}
+static inline bool s3c24xx_swrst_reset_available(void)
+{
+ return false;
+}
+static inline void s3c24xx_swrst_reset_of_init(void) {}
+static inline void s3c24xx_swrst_reset_init(void __iomem *base,
+ bool is_s3c2412) {}
+#endif
+
#endif /* __ARCH_ARM_MACH_S3C24XX_COMMON_H */
@@ -0,0 +1,160 @@
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+/*
+ * Although the manuals state that the string to write to the register
+ * is soc-specific, at least the s3c2416 and s3c2450 do not seem to care
+ * what gets written to the register and have been using the s3c2443
+ * string all along.
+ */
+#define S3C2412_SWRST_RESET (0x533C2412)
+#define S3C2443_SWRST_RESET (0x533C2443)
+
+enum s3c_cpu_type {
+ TYPE_S3C2412,
+ TYPE_S3C2443,
+};
+
+struct s3c24xx_swrst_drv_data {
+ int cpu_type;
+};
+
+struct s3c2412_rst_clocks {
+ const char *clk;
+ const char *new_parent;
+};
+
+/* S3C2412 errata "Watch-dog/Software Reset Problem" specifies
+ * that this reset must be done with the SYSCLK sourced from
+ * EXTCLK instead of FOUT to avoid a glitch in the reset
+ * mechanism.
+ *
+ * See the watchdog section of the S3C2412 manual for more
+ * information on this fix.
+ *
+ * The proposed fix is to write "0" to the clksrc register,
+ * to reset the sysclk "which generates the ARMCLK, HCLK, PCLK".
+ * Translated to the clock implementation, this looks like:
+ */
+struct s3c2412_rst_clocks s3c2412_clocks[] = {
+ { "mdivclk", "xti" },
+ { "msysclk", "mdivclk" },
+};
+
+static void __iomem *swrst_base;
+static int swrst_type;
+
+void s3c24xx_swrst_reset(void)
+{
+ int i;
+ bool has_faults = true;
+
+ if (!swrst_base) {
+ pr_err("%s: swrst reset not initialized\n", __func__);
+ /* delay to allow the serial port to show the message */
+ mdelay(50);
+ return;
+ }
+
+ switch (swrst_type) {
+ case TYPE_S3C2412:
+ /* handle the needed clock changes */
+ for (i = 0; i < ARRAY_SIZE(s3c2412_clocks); i++) {
+ struct s3c2412_rst_clocks *entry = &s3c2412_clocks[i];
+ struct clk *clk, *new_parent;
+ int ret;
+
+ clk = clk_get(NULL, entry->clk);
+ if (IS_ERR(clk)) {
+ pr_err("s3c24xx-swrst: could not get clock %s, err %ld\n",
+ entry->clk, PTR_ERR(clk));
+ has_faults = true;
+ continue;
+ }
+
+ new_parent = clk_get(NULL, entry->new_parent);
+ if (IS_ERR(new_parent)) {
+ pr_err("s3c24xx-swrst: could not get clock %s, err %ld\n",
+ entry->new_parent, PTR_ERR(new_parent));
+ has_faults = true;
+ clk_put(clk);
+ continue;
+ }
+
+ ret = clk_set_parent(clk, new_parent);
+ if (ret) {
+ pr_err("s3c24xx-swrst: could not set the parent clock of %s to %s, err %d\n",
+ entry->clk, entry->new_parent, ret);
+ has_faults = true;
+ }
+
+ clk_put(new_parent);
+ clk_put(clk);
+ }
+
+ if (has_faults) {
+ pr_warn("s3c24xx-swrst: some clocks could not be set to the needed parent.\n");
+ pr_warn("s3c24xx-swrst: this can trigger a glitch in the s3c2412 soc\n");
+ }
+
+ writel(S3C2412_SWRST_RESET, swrst_base);
+ break;
+ case TYPE_S3C2443:
+ writel(S3C2443_SWRST_RESET, swrst_base);
+ break;
+ }
+}
+
+bool s3c24xx_swrst_reset_available(void)
+{
+ return !!swrst_base;
+}
+
+#ifdef CONFIG_OF
+static struct s3c24xx_swrst_drv_data s3c24xx_swrst_drv_data_array[] = {
+ [TYPE_S3C2412] = { TYPE_S3C2412 },
+ [TYPE_S3C2443] = { TYPE_S3C2443 },
+};
+
+static const struct of_device_id s3c24xx_swrst_match[] = {
+ {
+ .compatible = "samsung,s3c2412-swrst",
+ .data = &s3c24xx_swrst_drv_data_array[TYPE_S3C2412],
+ }, {
+ .compatible = "samsung,s3c2443-swrst",
+ .data = &s3c24xx_swrst_drv_data_array[TYPE_S3C2443],
+ },
+ {},
+};
+
+void __init s3c24xx_swrst_reset_of_init(void)
+{
+ struct device_node *np;
+ const struct of_device_id *match;
+ struct s3c24xx_swrst_drv_data *data;
+
+ np = of_find_matching_node_and_match(NULL, s3c24xx_swrst_match, &match);
+ if (!np)
+ return;
+
+ data = (struct s3c24xx_swrst_drv_data *)match->data;
+ swrst_type = data->cpu_type;
+
+ swrst_base = of_iomap(np, 0);
+ if (!swrst_base) {
+ pr_err("%s: failed to map swrst register\n", __func__);
+ return;
+ }
+
+}
+#endif
+
+void __init s3c24xx_swrst_reset_init(void __iomem *base, bool is_s3c2412)
+{
+ swrst_base = base;
+ swrst_type = (is_s3c2412) ? TYPE_S3C2412 : TYPE_S3C2443;
+}