mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 17:08:10 +00:00
25c2a075eb
The register address used for the clock gate register is the base
register address coming from first reg map (ie. the generic
clock registers) instead of the second reg map defining the clock
gate register.
Use the correct clock gate register address.
Fixes: 5ad5915dea
("clk: lan966x: Extend lan966x clock driver for clock gating support")
Signed-off-by: Herve Codina <herve.codina@bootlin.com>
Link: https://lore.kernel.org/r/20220704102845.168438-2-herve.codina@bootlin.com
Reviewed-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Tested-by: Michael Walle <michael@walle.cc>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
293 lines
6.5 KiB
C
293 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Microchip LAN966x SoC Clock driver.
|
|
*
|
|
* Copyright (C) 2021 Microchip Technology, Inc. and its subsidiaries
|
|
*
|
|
* Author: Kavyasree Kotagiri <kavyasree.kotagiri@microchip.com>
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <dt-bindings/clock/microchip,lan966x.h>
|
|
|
|
#define GCK_ENA BIT(0)
|
|
#define GCK_SRC_SEL GENMASK(9, 8)
|
|
#define GCK_PRESCALER GENMASK(23, 16)
|
|
|
|
#define DIV_MAX 255
|
|
|
|
static const char *clk_names[N_CLOCKS] = {
|
|
"qspi0", "qspi1", "qspi2", "sdmmc0",
|
|
"pi", "mcan0", "mcan1", "flexcom0",
|
|
"flexcom1", "flexcom2", "flexcom3",
|
|
"flexcom4", "timer1", "usb_refclk",
|
|
};
|
|
|
|
struct lan966x_gck {
|
|
struct clk_hw hw;
|
|
void __iomem *reg;
|
|
};
|
|
#define to_lan966x_gck(hw) container_of(hw, struct lan966x_gck, hw)
|
|
|
|
static const struct clk_parent_data lan966x_gck_pdata[] = {
|
|
{ .fw_name = "cpu", },
|
|
{ .fw_name = "ddr", },
|
|
{ .fw_name = "sys", },
|
|
};
|
|
|
|
static struct clk_init_data init = {
|
|
.parent_data = lan966x_gck_pdata,
|
|
.num_parents = ARRAY_SIZE(lan966x_gck_pdata),
|
|
};
|
|
|
|
struct clk_gate_soc_desc {
|
|
const char *name;
|
|
int bit_idx;
|
|
};
|
|
|
|
static const struct clk_gate_soc_desc clk_gate_desc[] = {
|
|
{ "uhphs", 11 },
|
|
{ "udphs", 10 },
|
|
{ "mcramc", 9 },
|
|
{ "hmatrix", 8 },
|
|
{ }
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(clk_gate_lock);
|
|
static void __iomem *base;
|
|
|
|
static int lan966x_gck_enable(struct clk_hw *hw)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 val = readl(gck->reg);
|
|
|
|
val |= GCK_ENA;
|
|
writel(val, gck->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lan966x_gck_disable(struct clk_hw *hw)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 val = readl(gck->reg);
|
|
|
|
val &= ~GCK_ENA;
|
|
writel(val, gck->reg);
|
|
}
|
|
|
|
static int lan966x_gck_set_rate(struct clk_hw *hw,
|
|
unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 div, val = readl(gck->reg);
|
|
|
|
if (rate == 0 || parent_rate == 0)
|
|
return -EINVAL;
|
|
|
|
/* Set Prescalar */
|
|
div = parent_rate / rate;
|
|
val &= ~GCK_PRESCALER;
|
|
val |= FIELD_PREP(GCK_PRESCALER, (div - 1));
|
|
writel(val, gck->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long lan966x_gck_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
unsigned int div;
|
|
|
|
if (rate == 0 || *parent_rate == 0)
|
|
return -EINVAL;
|
|
|
|
if (rate >= *parent_rate)
|
|
return *parent_rate;
|
|
|
|
div = DIV_ROUND_CLOSEST(*parent_rate, rate);
|
|
|
|
return *parent_rate / div;
|
|
}
|
|
|
|
static unsigned long lan966x_gck_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 div, val = readl(gck->reg);
|
|
|
|
div = FIELD_GET(GCK_PRESCALER, val);
|
|
|
|
return parent_rate / (div + 1);
|
|
}
|
|
|
|
static int lan966x_gck_determine_rate(struct clk_hw *hw,
|
|
struct clk_rate_request *req)
|
|
{
|
|
struct clk_hw *parent;
|
|
int i;
|
|
|
|
for (i = 0; i < clk_hw_get_num_parents(hw); ++i) {
|
|
parent = clk_hw_get_parent_by_index(hw, i);
|
|
if (!parent)
|
|
continue;
|
|
|
|
/* Allowed prescaler divider range is 0-255 */
|
|
if (clk_hw_get_rate(parent) / req->rate <= DIV_MAX) {
|
|
req->best_parent_hw = parent;
|
|
req->best_parent_rate = clk_hw_get_rate(parent);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static u8 lan966x_gck_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 val = readl(gck->reg);
|
|
|
|
return FIELD_GET(GCK_SRC_SEL, val);
|
|
}
|
|
|
|
static int lan966x_gck_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct lan966x_gck *gck = to_lan966x_gck(hw);
|
|
u32 val = readl(gck->reg);
|
|
|
|
val &= ~GCK_SRC_SEL;
|
|
val |= FIELD_PREP(GCK_SRC_SEL, index);
|
|
writel(val, gck->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops lan966x_gck_ops = {
|
|
.enable = lan966x_gck_enable,
|
|
.disable = lan966x_gck_disable,
|
|
.set_rate = lan966x_gck_set_rate,
|
|
.round_rate = lan966x_gck_round_rate,
|
|
.recalc_rate = lan966x_gck_recalc_rate,
|
|
.determine_rate = lan966x_gck_determine_rate,
|
|
.set_parent = lan966x_gck_set_parent,
|
|
.get_parent = lan966x_gck_get_parent,
|
|
};
|
|
|
|
static struct clk_hw *lan966x_gck_clk_register(struct device *dev, int i)
|
|
{
|
|
struct lan966x_gck *priv;
|
|
int ret;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
priv->reg = base + (i * 4);
|
|
priv->hw.init = &init;
|
|
ret = devm_clk_hw_register(dev, &priv->hw);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
return &priv->hw;
|
|
};
|
|
|
|
static int lan966x_gate_clk_register(struct device *dev,
|
|
struct clk_hw_onecell_data *hw_data,
|
|
void __iomem *gate_base)
|
|
{
|
|
int i;
|
|
|
|
for (i = GCK_GATE_UHPHS; i < N_CLOCKS; ++i) {
|
|
int idx = i - GCK_GATE_UHPHS;
|
|
|
|
hw_data->hws[i] =
|
|
devm_clk_hw_register_gate(dev, clk_gate_desc[idx].name,
|
|
"lan966x", 0, gate_base,
|
|
clk_gate_desc[idx].bit_idx,
|
|
0, &clk_gate_lock);
|
|
|
|
if (IS_ERR(hw_data->hws[i]))
|
|
return dev_err_probe(dev, PTR_ERR(hw_data->hws[i]),
|
|
"failed to register %s clock\n",
|
|
clk_gate_desc[idx].name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lan966x_clk_probe(struct platform_device *pdev)
|
|
{
|
|
struct clk_hw_onecell_data *hw_data;
|
|
struct device *dev = &pdev->dev;
|
|
void __iomem *gate_base;
|
|
struct resource *res;
|
|
int i, ret;
|
|
|
|
hw_data = devm_kzalloc(dev, struct_size(hw_data, hws, N_CLOCKS),
|
|
GFP_KERNEL);
|
|
if (!hw_data)
|
|
return -ENOMEM;
|
|
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
init.ops = &lan966x_gck_ops;
|
|
|
|
hw_data->num = GCK_GATE_UHPHS;
|
|
|
|
for (i = 0; i < GCK_GATE_UHPHS; i++) {
|
|
init.name = clk_names[i];
|
|
hw_data->hws[i] = lan966x_gck_clk_register(dev, i);
|
|
if (IS_ERR(hw_data->hws[i])) {
|
|
dev_err(dev, "failed to register %s clock\n",
|
|
init.name);
|
|
return PTR_ERR(hw_data->hws[i]);
|
|
}
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
if (res) {
|
|
gate_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(gate_base))
|
|
return PTR_ERR(gate_base);
|
|
|
|
hw_data->num = N_CLOCKS;
|
|
|
|
ret = lan966x_gate_clk_register(dev, hw_data, gate_base);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, hw_data);
|
|
}
|
|
|
|
static const struct of_device_id lan966x_clk_dt_ids[] = {
|
|
{ .compatible = "microchip,lan966x-gck", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lan966x_clk_dt_ids);
|
|
|
|
static struct platform_driver lan966x_clk_driver = {
|
|
.probe = lan966x_clk_probe,
|
|
.driver = {
|
|
.name = "lan966x-clk",
|
|
.of_match_table = lan966x_clk_dt_ids,
|
|
},
|
|
};
|
|
builtin_platform_driver(lan966x_clk_driver);
|
|
|
|
MODULE_AUTHOR("Kavyasree Kotagiri <kavyasree.kotagiri@microchip.com>");
|
|
MODULE_DESCRIPTION("LAN966X clock driver");
|
|
MODULE_LICENSE("GPL v2");
|