blob: 1b2c4c98de5419d9be0b8eed2e62660bee7f67e5 [file] [log] [blame]
// 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 int airoha_clk_bind(struct udevice *dev)
{
struct udevice *rst_dev;
int ret = 0;
if (CONFIG_IS_ENABLED(RESET_AIROHA)) {
ret = device_bind_driver_to_node(dev, "airoha-reset", "reset",
dev_ofnode(dev), &rst_dev);
if (ret)
debug("Warning: failed to bind reset controller\n");
}
return ret;
}
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,
.bind = airoha_clk_bind,
.priv_auto = sizeof(struct airoha_clk_priv),
.ops = &airoha_clk_ops,
};