-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
clk: lan966x: Add lan966x SoC clock driver
This adds Generic Clock Controller driver for lan966x SoC. Lan966x clock controller contains 3 PLLs - cpu_clk, ddr_clk and sys_clk. It generates and supplies clock to various peripherals within SoC. Register settings required to provide GCK clocking to a peripheral is as below: GCK_SRC_SEL = Select clock source. GCK_PRESCALER = Set divider value. GCK_ENA = 1 - Enable GCK clock. Signed-off-by: Kavyasree Kotagiri <kavyasree.kotagiri@microchip.com> Co-developed-by: Horatiu Vultur <horatiu.vultur@microchip.com> Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com> Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com> Signed-off-by: Nicolas Ferre <nicolas.ferre@microchip.com> Link: https://lore.kernel.org/r/20211103061935.25677-4-kavyasree.kotagiri@microchip.com
- Loading branch information
Kavyasree Kotagiri
authored and
Nicolas Ferre
committed
Dec 8, 2021
1 parent
07300ef
commit 54104ee
Showing
3 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
// 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), | ||
}; | ||
|
||
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_clk_probe(struct platform_device *pdev) | ||
{ | ||
struct clk_hw_onecell_data *hw_data; | ||
struct device *dev = &pdev->dev; | ||
int i; | ||
|
||
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 = N_CLOCKS; | ||
|
||
for (i = 0; i < N_CLOCKS; 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]); | ||
} | ||
} | ||
|
||
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"); |