@@ -8,6 +8,7 @@ source "drivers/soc/aspeed/Kconfig"
source "drivers/soc/atmel/Kconfig"
source "drivers/soc/bcm/Kconfig"
source "drivers/soc/canaan/Kconfig"
+source "drivers/soc/cirrus/Kconfig"
source "drivers/soc/fsl/Kconfig"
source "drivers/soc/fujitsu/Kconfig"
source "drivers/soc/imx/Kconfig"
@@ -9,6 +9,7 @@ obj-y += aspeed/
obj-$(CONFIG_ARCH_AT91) += atmel/
obj-y += bcm/
obj-$(CONFIG_SOC_CANAAN) += canaan/
+obj-$(CONFIG_EP93XX_SOC) += cirrus/
obj-$(CONFIG_ARCH_DOVE) += dove/
obj-$(CONFIG_MACH_DOVE) += dove/
obj-y += fsl/
new file mode 100644
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+
+if ARCH_EP93XX
+
+config EP93XX_SOC
+ bool "Cirrus EP93xx chips SoC"
+ select SOC_BUS
+ default y if !EP93XX_SOC_COMMON
+ help
+ Support SoC for Cirrus EP93xx chips.
+
+endif
new file mode 100644
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-y += soc-ep93xx.o
new file mode 100644
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SoC driver for Cirrus EP93xx chips.
+ * Copyright (C) 2022 Nikita Shubin <nikita.shubin@maquefel.me>
+ *
+ * Based on a rewrite of arch/arm/mach-ep93xx/core.c
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2007 Herbert Valerio Riedel <hvr@gnu.org>
+ *
+ * Thanks go to Michael Burian and Ray Lehtiniemi for their key
+ * role in the ep93xx Linux community
+ */
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/sys_soc.h>
+#include <linux/soc/cirrus/ep93xx.h>
+
+#define EP93XX_EXT_CLK_RATE 14745600
+
+#define EP93XX_SYSCON_DEVCFG 0x80
+
+#define EP93XX_SWLOCK_MAGICK 0xaa
+#define EP93XX_SYSCON_SWLOCK 0xc0
+#define EP93XX_SYSCON_SYSCFG 0x9c
+#define EP93XX_SYSCON_SYSCFG_REV_MASK GENMASK(31, 28)
+#define EP93XX_SYSCON_SYSCFG_REV_SHIFT 28
+
+#define EP93XX_SYSCON_CLKSET1 0x20
+#define EP93XX_SYSCON_CLKSET1_NBYP1 BIT(23)
+#define EP93XX_SYSCON_CLKSET2 0x24
+#define EP93XX_SYSCON_CLKSET2_NBYP2 BIT(19)
+#define EP93XX_SYSCON_CLKSET2_PLL2_EN BIT(18)
+
+static DEFINE_SPINLOCK(ep93xx_swlock);
+
+/*
+ * EP93xx System Controller software locked register write
+ *
+ * Logic safeguards are included to condition the control signals for
+ * power connection to the matrix to prevent part damage. In addition, a
+ * software lock register is included that must be written with 0xAA
+ * before each register write to change the values of the four switch
+ * matrix control registers.
+ */
+void ep93xx_regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ep93xx_swlock, flags);
+
+ regmap_write(map, EP93XX_SYSCON_SWLOCK, EP93XX_SWLOCK_MAGICK);
+ regmap_write(map, reg, val);
+
+ spin_unlock_irqrestore(&ep93xx_swlock, flags);
+}
+
+void ep93xx_regmap_update_bits(struct regmap *map, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ep93xx_swlock, flags);
+
+ regmap_write(map, EP93XX_SYSCON_SWLOCK, EP93XX_SWLOCK_MAGICK);
+ /* force write is required to clear swlock if is no changes are made */
+ regmap_update_bits_base(map, reg, mask, val, NULL, false, true);
+
+ spin_unlock_irqrestore(&ep93xx_swlock, flags);
+}
+
+static unsigned int __init ep93xx_soc_revision(struct regmap *map)
+{
+ unsigned int val;
+
+ regmap_read(map, EP93XX_SYSCON_SYSCFG, &val);
+ val &= EP93XX_SYSCON_SYSCFG_REV_MASK;
+ val >>= EP93XX_SYSCON_SYSCFG_REV_SHIFT;
+ return val;
+}
+
+static const char __init *ep93xx_get_soc_rev(struct regmap *map)
+{
+ switch (ep93xx_soc_revision(map)) {
+ case EP93XX_CHIP_REV_D0:
+ return "D0";
+ case EP93XX_CHIP_REV_D1:
+ return "D1";
+ case EP93XX_CHIP_REV_E0:
+ return "E0";
+ case EP93XX_CHIP_REV_E1:
+ return "E1";
+ case EP93XX_CHIP_REV_E2:
+ return "E2";
+ default:
+ return "unknown";
+ }
+}
+
+/*
+ * PLL rate = 14.7456 MHz * (X1FBD + 1) * (X2FBD + 1) / (X2IPD + 1) / 2^PS
+ */
+static unsigned long __init calc_pll_rate(u64 rate, u32 config_word)
+{
+ rate *= ((config_word >> 11) & GENMASK(4, 0)) + 1; /* X1FBD */
+ rate *= ((config_word >> 5) & GENMASK(5, 0)) + 1; /* X2FBD */
+ do_div(rate, (config_word & GENMASK(4, 0)) + 1); /* X2IPD */
+ rate >>= ((config_word >> 16) & GENMASK(1, 0)); /* PS */
+
+ return rate;
+}
+
+static int __init ep93xx_syscon_probe(struct platform_device *pdev)
+{
+ unsigned int clk_f_div, clk_h_div, clk_p_div, clk_usb_div;
+ const char fclk_divisors[] = { 1, 2, 4, 8, 16, 1, 1, 1 };
+ const char hclk_divisors[] = { 1, 2, 4, 5, 6, 8, 16, 32 };
+ const char pclk_divisors[] = { 1, 2, 4, 8 };
+ unsigned long clk_pll1_rate, clk_pll2_rate;
+ struct soc_device_attribute *attrs;
+ struct soc_device *soc_dev;
+ struct device *dev = &pdev->dev;
+ struct regmap *map;
+ struct clk_hw *hw;
+ u32 value;
+
+ map = device_node_to_regmap(dev->of_node);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ /* Determine the bootloader configured pll1 rate */
+ regmap_read(map, EP93XX_SYSCON_CLKSET1, &value);
+
+ if (value & EP93XX_SYSCON_CLKSET1_NBYP1)
+ clk_pll1_rate = calc_pll_rate(EP93XX_EXT_CLK_RATE, value);
+ else
+ clk_pll1_rate = EP93XX_EXT_CLK_RATE;
+
+ hw = clk_hw_register_fixed_rate(dev, "pll1", "xtali", 0, clk_pll1_rate);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ /* Initialize the pll1 derived clocks */
+ clk_f_div = fclk_divisors[(value >> 25) & GENMASK(2, 0)];
+ clk_h_div = hclk_divisors[(value >> 20) & GENMASK(2, 0)];
+ clk_p_div = pclk_divisors[(value >> 18) & GENMASK(1, 0)];
+
+ hw = clk_hw_register_fixed_factor(dev, "fclk", "pll1", 0, 1, clk_f_div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ hw = clk_hw_register_fixed_factor(dev, "hclk", "pll1", 0, 1, clk_h_div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ hw = clk_hw_register_fixed_factor(dev, "pclk", "hclk", 0, 1, clk_p_div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ /* Determine the bootloader configured pll2 rate */
+ regmap_read(map, EP93XX_SYSCON_CLKSET2, &value);
+ if (!(value & EP93XX_SYSCON_CLKSET2_NBYP2))
+ clk_pll2_rate = EP93XX_EXT_CLK_RATE;
+ else if (value & EP93XX_SYSCON_CLKSET2_PLL2_EN)
+ clk_pll2_rate = calc_pll_rate(EP93XX_EXT_CLK_RATE, value);
+ else
+ clk_pll2_rate = 0;
+
+ hw = clk_hw_register_fixed_rate(dev, "pll2", "xtali", 0, clk_pll2_rate);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ regmap_read(map, EP93XX_SYSCON_CLKSET2, &value);
+ clk_usb_div = (((value >> 28) & GENMASK(3, 0)) + 1);
+ hw = clk_hw_register_fixed_factor(dev, "usb_clk", "pll2", 0, 1, clk_usb_div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ attrs = devm_kzalloc(dev, sizeof(*attrs), GFP_KERNEL);
+ if (!attrs)
+ return -ENOMEM;
+
+ attrs->machine = of_flat_dt_get_machine_name();
+ attrs->family = "Cirrus Logic EP93xx";
+ attrs->revision = ep93xx_get_soc_rev(map);
+
+ soc_dev = soc_device_register(attrs);
+ if (IS_ERR(soc_dev))
+ return PTR_ERR(soc_dev);
+
+ dev_info(dev, "EP93xx SoC revision %s\n", attrs->revision);
+
+ return 0;
+}
+
+static const struct of_device_id ep9301_syscon_of_device_ids[] = {
+ { .compatible = "cirrus,ep9301-syscon" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver ep9301_syscon_driver = {
+ .driver = {
+ .name = "ep9301-syscon",
+ .of_match_table = ep9301_syscon_of_device_ids,
+ },
+};
+builtin_platform_driver_probe(ep9301_syscon_driver, ep93xx_syscon_probe);
@@ -11,7 +11,7 @@ struct regmap;
#define EP93XX_CHIP_REV_E1 6
#define EP93XX_CHIP_REV_E2 7
-#ifdef CONFIG_ARCH_EP93XX
+#if defined(CONFIG_EP93XX_SOC_COMMON)
int ep93xx_pwm_acquire_gpio(struct platform_device *pdev);
void ep93xx_pwm_release_gpio(struct platform_device *pdev);
int ep93xx_ide_acquire_gpio(struct platform_device *pdev);
@@ -33,11 +33,18 @@ static inline int ep93xx_i2s_acquire(void) { return 0; }
static inline void ep93xx_i2s_release(void) {}
static inline unsigned int ep93xx_chip_revision(void) { return 0; }
-#endif
+#if defined(CONFIG_EP93XX_SOC)
+void ep93xx_regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
+void ep93xx_regmap_update_bits(struct regmap *map, unsigned int reg,
+ unsigned int mask, unsigned int val);
+#else
static inline void ep93xx_regmap_write(struct regmap *map, unsigned int reg, unsigned int val) {}
static inline void ep93xx_regmap_update_bits(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val)
{ }
#endif
+#endif
+
+#endif