clk: airoha: Add support for Airoha AN7581 SoC clock
authorChristian Marangi <ansuelsmth@gmail.com>
Fri, 14 Mar 2025 18:59:22 +0000 (19:59 +0100)
committerTom Rini <trini@konsulko.com>
Tue, 1 Apr 2025 14:44:51 +0000 (08:44 -0600)
Add support for Airoha AN7581 SoC clock driver. This mainly needed for
eMMC support to correctly get the current clock applied.

Based on the Linux clk-en7523.c but majorly reworked for U-Boot that
doesn't require CCF subsystem.

Major modification, support for set_rate, realtime get_rate and split
for reset part to a different driver.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
drivers/clk/Makefile
drivers/clk/airoha/Makefile [new file with mode: 0644]
drivers/clk/airoha/clk-airoha.c [new file with mode: 0644]

index fe0e49f..8411205 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_$(PHASE_)CLK_GPIO) += clk-gpio.o
 obj-$(CONFIG_$(PHASE_)CLK_STUB) += clk-stub.o
 
 obj-y += adi/
+obj-y += airoha/
 obj-y += analogbits/
 obj-y += imx/
 obj-$(CONFIG_CLK_JH7110) += starfive/
diff --git a/drivers/clk/airoha/Makefile b/drivers/clk/airoha/Makefile
new file mode 100644 (file)
index 0000000..1744c5f
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+# Core
+obj-$(CONFIG_ARCH_AIROHA) += clk-airoha.o
diff --git a/drivers/clk/airoha/clk-airoha.c b/drivers/clk/airoha/clk-airoha.c
new file mode 100644 (file)
index 0000000..96d120f
--- /dev/null
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Based on the Linux clk-en7523.c but majorly reworked
+ * for U-Boot that doesn't require CCF subsystem.
+ *
+ * Major modification, support for set_rate, realtime
+ * get_rate and split for reset part to a different driver.
+ *
+ * Author: Lorenzo Bianconi <lorenzo@kernel.org> (original driver)
+ *        Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/devres.h>
+#include <dm/device_compat.h>
+#include <dm/lists.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#include <dt-bindings/clock/en7523-clk.h>
+
+#define REG_GSW_CLK_DIV_SEL            0x1b4
+#define REG_EMI_CLK_DIV_SEL            0x1b8
+#define REG_BUS_CLK_DIV_SEL            0x1bc
+#define REG_SPI_CLK_DIV_SEL            0x1c4
+#define REG_SPI_CLK_FREQ_SEL           0x1c8
+#define REG_NPU_CLK_DIV_SEL            0x1fc
+
+#define REG_NP_SCU_PCIC                        0x88
+#define REG_NP_SCU_SSTR                        0x9c
+#define REG_PCIE_XSI0_SEL_MASK         GENMASK(14, 13)
+#define REG_PCIE_XSI1_SEL_MASK         GENMASK(12, 11)
+#define REG_CRYPTO_CLKSRC2             0x20c
+
+#define EN7581_MAX_CLKS                        9
+
+struct airoha_clk_desc {
+       int id;
+       const char *name;
+       u32 base_reg;
+       u8 base_bits;
+       u8 base_shift;
+       union {
+               const unsigned int *base_values;
+               unsigned int base_value;
+       };
+       size_t n_base_values;
+
+       u16 div_reg;
+       u8 div_bits;
+       u8 div_shift;
+       u16 div_val0;
+       u8 div_step;
+       u8 div_offset;
+};
+
+struct airoha_clk_priv {
+       struct regmap *chip_scu_map;
+       struct airoha_clk_soc_data *data;
+};
+
+struct airoha_clk_soc_data {
+       u32 num_clocks;
+       const struct airoha_clk_desc *descs;
+};
+
+static const u32 gsw_base[] = { 400000000, 500000000 };
+static const u32 slic_base[] = { 100000000, 3125000 };
+
+static const u32 emi7581_base[] = { 540000000, 480000000, 400000000, 300000000 };
+static const u32 bus7581_base[] = { 600000000, 540000000 };
+static const u32 npu7581_base[] = { 800000000, 750000000, 720000000, 600000000 };
+static const u32 crypto_base[] = { 540000000, 480000000 };
+static const u32 emmc7581_base[] = { 200000000, 150000000 };
+
+static const struct airoha_clk_desc en7581_base_clks[EN7581_MAX_CLKS] = {
+       [EN7523_CLK_GSW] = {
+               .id = EN7523_CLK_GSW,
+               .name = "gsw",
+
+               .base_reg = REG_GSW_CLK_DIV_SEL,
+               .base_bits = 1,
+               .base_shift = 8,
+               .base_values = gsw_base,
+               .n_base_values = ARRAY_SIZE(gsw_base),
+
+               .div_bits = 3,
+               .div_shift = 0,
+               .div_step = 1,
+               .div_offset = 1,
+       },
+       [EN7523_CLK_EMI] = {
+               .id = EN7523_CLK_EMI,
+               .name = "emi",
+
+               .base_reg = REG_EMI_CLK_DIV_SEL,
+               .base_bits = 2,
+               .base_shift = 8,
+               .base_values = emi7581_base,
+               .n_base_values = ARRAY_SIZE(emi7581_base),
+
+               .div_bits = 3,
+               .div_shift = 0,
+               .div_step = 1,
+               .div_offset = 1,
+       },
+       [EN7523_CLK_BUS] = {
+               .id = EN7523_CLK_BUS,
+               .name = "bus",
+
+               .base_reg = REG_BUS_CLK_DIV_SEL,
+               .base_bits = 1,
+               .base_shift = 8,
+               .base_values = bus7581_base,
+               .n_base_values = ARRAY_SIZE(bus7581_base),
+
+               .div_bits = 3,
+               .div_shift = 0,
+               .div_step = 1,
+               .div_offset = 1,
+       },
+       [EN7523_CLK_SLIC] = {
+               .id = EN7523_CLK_SLIC,
+               .name = "slic",
+
+               .base_reg = REG_SPI_CLK_FREQ_SEL,
+               .base_bits = 1,
+               .base_shift = 0,
+               .base_values = slic_base,
+               .n_base_values = ARRAY_SIZE(slic_base),
+
+               .div_reg = REG_SPI_CLK_DIV_SEL,
+               .div_bits = 5,
+               .div_shift = 24,
+               .div_val0 = 20,
+               .div_step = 2,
+       },
+       [EN7523_CLK_SPI] = {
+               .id = EN7523_CLK_SPI,
+               .name = "spi",
+
+               .base_reg = REG_SPI_CLK_DIV_SEL,
+
+               .base_value = 400000000,
+
+               .div_bits = 5,
+               .div_shift = 8,
+               .div_val0 = 40,
+               .div_step = 2,
+       },
+       [EN7523_CLK_NPU] = {
+               .id = EN7523_CLK_NPU,
+               .name = "npu",
+
+               .base_reg = REG_NPU_CLK_DIV_SEL,
+               .base_bits = 2,
+               .base_shift = 8,
+               .base_values = npu7581_base,
+               .n_base_values = ARRAY_SIZE(npu7581_base),
+
+               .div_bits = 3,
+               .div_shift = 0,
+               .div_step = 1,
+               .div_offset = 1,
+       },
+       [EN7523_CLK_CRYPTO] = {
+               .id = EN7523_CLK_CRYPTO,
+               .name = "crypto",
+
+               .base_reg = REG_CRYPTO_CLKSRC2,
+               .base_bits = 1,
+               .base_shift = 0,
+               .base_values = crypto_base,
+               .n_base_values = ARRAY_SIZE(crypto_base),
+       },
+       [EN7581_CLK_EMMC] = {
+               .id = EN7581_CLK_EMMC,
+               .name = "emmc",
+
+               .base_reg = REG_CRYPTO_CLKSRC2,
+               .base_bits = 1,
+               .base_shift = 12,
+               .base_values = emmc7581_base,
+               .n_base_values = ARRAY_SIZE(emmc7581_base),
+       }
+};
+
+static u32 airoha_clk_get_base_rate(const struct airoha_clk_desc *desc, u32 val)
+{
+       if (!desc->base_bits)
+               return desc->base_value;
+
+       val >>= desc->base_shift;
+       val &= (1 << desc->base_bits) - 1;
+
+       if (val >= desc->n_base_values)
+               return 0;
+
+       return desc->base_values[val];
+}
+
+static u32 airoha_clk_get_div(const struct airoha_clk_desc *desc, u32 val)
+{
+       if (!desc->div_bits)
+               return 1;
+
+       val >>= desc->div_shift;
+       val &= (1 << desc->div_bits) - 1;
+
+       if (!val && desc->div_val0)
+               return desc->div_val0;
+
+       return (val + desc->div_offset) * desc->div_step;
+}
+
+static int airoha_clk_enable(struct clk *clk)
+{
+       struct airoha_clk_priv *priv = dev_get_priv(clk->dev);
+       struct airoha_clk_soc_data *data = priv->data;
+       int id = clk->id;
+
+       if (id > data->num_clocks)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int airoha_clk_disable(struct clk *clk)
+{
+       return 0;
+}
+
+static ulong airoha_clk_get_rate(struct clk *clk)
+{
+       struct airoha_clk_priv *priv = dev_get_priv(clk->dev);
+       struct airoha_clk_soc_data *data = priv->data;
+       const struct airoha_clk_desc *desc;
+       struct regmap *map = priv->chip_scu_map;
+       int id = clk->id;
+       u32 reg, val;
+       ulong rate;
+       int ret;
+
+       if (id > data->num_clocks) {
+               dev_err(clk->dev, "Invalid clk ID %d\n", id);
+               return 0;
+       }
+
+       desc = &data->descs[id];
+
+       ret = regmap_read(map, desc->base_reg, &val);
+       if (ret) {
+               dev_err(clk->dev, "Failed to read reg for clock %s\n",
+                       desc->name);
+               return 0;
+       }
+
+       rate = airoha_clk_get_base_rate(desc, val);
+
+       reg = desc->div_reg ? desc->div_reg : desc->base_reg;
+       ret = regmap_read(map, reg, &val);
+       if (ret) {
+               dev_err(clk->dev, "Failed to read reg for clock %s\n",
+                       desc->name);
+               return 0;
+       }
+
+       rate /= airoha_clk_get_div(desc, val);
+
+       return rate;
+}
+
+static int airoha_clk_search_rate(const struct airoha_clk_desc *desc, int div,
+                                 ulong rate)
+{
+       int i;
+
+       /* Single base rate */
+       if (!desc->base_bits) {
+               if (rate != desc->base_value / div)
+                       goto err;
+
+               return 0;
+       }
+
+       /* Check every base rate with provided divisor */
+       for (i = 0; i < desc->n_base_values; i++)
+               if (rate == desc->base_values[i] / div)
+                       return i;
+
+err:
+       return -EINVAL;
+}
+
+static ulong airoha_clk_set_rate(struct clk *clk, ulong rate)
+{
+       struct airoha_clk_priv *priv = dev_get_priv(clk->dev);
+       struct airoha_clk_soc_data *data = priv->data;
+       const struct airoha_clk_desc *desc;
+       struct regmap *map = priv->chip_scu_map;
+       int div_val, base_val;
+       u32 reg, val, mask;
+       int id = clk->id;
+       int div;
+       int ret;
+
+       if (id > data->num_clocks) {
+               dev_err(clk->dev, "Invalid clk ID %d\n", id);
+               return 0;
+       }
+
+       desc = &data->descs[id];
+
+       if (!desc->base_bits && !desc->div_bits) {
+               dev_err(clk->dev, "Can't set rate for fixed clock %s\n",
+                       desc->name);
+               return 0;
+       }
+
+       if (!desc->div_bits) {
+               /* Divisor not supported, just search in base rate */
+               div_val = 0;
+               base_val = airoha_clk_search_rate(desc, 1, rate);
+               if (base_val < 0) {
+                       dev_err(clk->dev, "Invalid rate for clock %s\n",
+                               desc->name);
+                       return 0;
+               }
+       } else {
+               div_val = 0;
+
+               /* Check if div0 satisfy the request */
+               if (desc->div_val0) {
+                       base_val = airoha_clk_search_rate(desc, desc->div_val0,
+                                                         rate);
+                       if (base_val >= 0) {
+                               div_val = 0;
+                               goto apply;
+                       }
+
+                       /* Skip checking first divisor val */
+                       div_val = 1;
+               }
+
+               /* Simulate rate with every divisor supported */
+               for (div_val = div_val + desc->div_offset;
+                    div_val < BIT(desc->div_bits) - 1; div_val++) {
+                       div = div_val * desc->div_step;
+
+                       base_val = airoha_clk_search_rate(desc, div, rate);
+                       if (base_val >= 0)
+                               break;
+               }
+
+               if (div_val == BIT(desc->div_bits) - 1) {
+                       dev_err(clk->dev, "Invalid rate for clock %s\n",
+                               desc->name);
+                       return 0;
+               }
+       }
+
+apply:
+       if (desc->div_bits) {
+               reg = desc->div_reg ? desc->div_reg : desc->base_reg;
+
+               mask = (BIT(desc->div_bits) - 1) << desc->div_shift;
+               val = div_val << desc->div_shift;
+
+               ret = regmap_update_bits(map, reg, mask, val);
+               if (ret) {
+                       dev_err(clk->dev, "Failed to update div reg for clock %s\n",
+                               desc->name);
+                       return 0;
+               }
+       }
+
+       if (desc->base_bits) {
+               mask = (BIT(desc->base_bits) - 1) << desc->base_shift;
+               val = base_val << desc->base_shift;
+
+               ret = regmap_update_bits(map, desc->base_reg, mask, val);
+               if (ret) {
+                       dev_err(clk->dev, "Failed to update reg for clock %s\n",
+                               desc->name);
+                       return 0;
+               }
+       }
+
+       return rate;
+}
+
+const struct clk_ops airoha_clk_ops = {
+       .enable = airoha_clk_enable,
+       .disable = airoha_clk_disable,
+       .get_rate = airoha_clk_get_rate,
+       .set_rate = airoha_clk_set_rate,
+};
+
+static int airoha_clk_probe(struct udevice *dev)
+{
+       struct airoha_clk_priv *priv = dev_get_priv(dev);
+       ofnode chip_scu_node;
+
+       chip_scu_node = ofnode_by_compatible(ofnode_null(),
+                                            "airoha,en7581-chip-scu");
+       if (!ofnode_valid(chip_scu_node))
+               return -EINVAL;
+
+       priv->chip_scu_map = syscon_node_to_regmap(chip_scu_node);
+       if (IS_ERR(priv->chip_scu_map))
+               return PTR_ERR(priv->chip_scu_map);
+
+       priv->data = (void *)dev_get_driver_data(dev);
+
+       return 0;
+}
+
+static const struct airoha_clk_soc_data en7581_data = {
+       .num_clocks = ARRAY_SIZE(en7581_base_clks),
+       .descs = en7581_base_clks,
+};
+
+static const struct udevice_id airoha_clk_ids[] = {
+       { .compatible = "airoha,en7581-scu",
+         .data = (ulong)&en7581_data,
+       },
+       { }
+};
+
+U_BOOT_DRIVER(airoha_clk) = {
+       .name = "clk-airoha",
+       .id = UCLASS_CLK,
+       .of_match = airoha_clk_ids,
+       .probe = airoha_clk_probe,
+       .priv_auto = sizeof(struct airoha_clk_priv),
+       .ops = &airoha_clk_ops,
+};