#include <linux/platform_device.h>
#include <linux/slab.h>
#include <dt-bindings/clock/bcm2835.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
#define CM_PASSWORD 0x5a000000
#define LOCK_TIMEOUT_NS 100000000
#define BCM2835_MAX_FB_RATE 1750000000u
+#define VCMSG_ID_CORE_CLOCK 4
+
struct bcm2835_cprman {
struct device *dev;
void __iomem *regs;
+ struct rpi_firmware *fw;
spinlock_t regs_lock; /* spinlock for all clocks */
const char *osc_name;
u32 load_mask;
u32 hold_mask;
u32 fixed_divider;
+ u32 flags;
};
struct bcm2835_clock_data {
using_prediv = cprman_read(cprman, data->ana_reg_base + 4) &
data->ana->fb_prediv_mask;
- if (using_prediv)
+ if (using_prediv) {
ndiv *= 2;
+ fdiv *= 2;
+ }
return bcm2835_pll_rate_from_divisors(parent_rate, ndiv, fdiv, pdiv);
}
return bcm2835_clock_rate_from_divisor(clock, parent_rate, div);
}
+static unsigned long bcm2835_clock_get_rate_vpu(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
+ struct bcm2835_cprman *cprman = clock->cprman;
+
+ if (cprman->fw) {
+ struct {
+ u32 id;
+ u32 val;
+ } packet;
+
+ packet.id = VCMSG_ID_CORE_CLOCK;
+ packet.val = 0;
+
+ if (!rpi_firmware_property(cprman->fw,
+ RPI_FIRMWARE_GET_MAX_CLOCK_RATE,
+ &packet, sizeof(packet)))
+ return packet.val;
+ }
+
+ return bcm2835_clock_get_rate(hw, parent_rate);
+}
+
static void bcm2835_clock_wait_busy(struct bcm2835_clock *clock)
{
struct bcm2835_cprman *cprman = clock->cprman;
*/
static const struct clk_ops bcm2835_vpu_clock_clk_ops = {
.is_prepared = bcm2835_vpu_clock_is_on,
- .recalc_rate = bcm2835_clock_get_rate,
+ .recalc_rate = bcm2835_clock_get_rate_vpu,
.set_rate = bcm2835_clock_set_rate,
.determine_rate = bcm2835_clock_determine_rate,
.set_parent = bcm2835_clock_set_parent,
.debug_init = bcm2835_clock_debug_init,
};
+static bool bcm2835_clk_is_claimed(const char *name);
+
static struct clk_hw *bcm2835_register_pll(struct bcm2835_cprman *cprman,
const struct bcm2835_pll_data *data)
{
init.ops = &bcm2835_pll_clk_ops;
init.flags = CLK_IGNORE_UNUSED;
+ if (!bcm2835_clk_is_claimed(data->name))
+ init.flags |= CLK_IS_CRITICAL;
+
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return NULL;
init.num_parents = 1;
init.name = divider_name;
init.ops = &bcm2835_pll_divider_clk_ops;
- init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
+ init.flags = data->flags | CLK_IGNORE_UNUSED;
divider = devm_kzalloc(cprman->dev, sizeof(*divider), GFP_KERNEL);
if (!divider)
divider->div.hw.init = &init;
divider->div.table = NULL;
+ if (!(cprman_read(cprman, data->cm_reg) & data->hold_mask)) {
+ if (!bcm2835_clk_is_claimed(data->source_pll))
+ init.flags |= CLK_IS_CRITICAL;
+ if (!bcm2835_clk_is_claimed(data->name))
+ divider->div.flags |= CLK_IS_CRITICAL;
+ }
+
divider->cprman = cprman;
divider->data = data;
init.name = data->name;
init.flags = data->flags | CLK_IGNORE_UNUSED;
+ /*
+ * Some GPIO clocks for ethernet/wifi PLLs are marked as
+ * critical (since some platforms use them), but if the
+ * firmware didn't have them turned on then they clearly
+ * aren't actually critical.
+ */
+ if ((cprman_read(cprman, data->ctl_reg) & CM_ENABLE) == 0)
+ init.flags &= ~CLK_IS_CRITICAL;
+
/*
* Pass the CLK_SET_RATE_PARENT flag if we are allowed to propagate
* rate changes on at least of the parents.
.a2w_reg = A2W_PLLA_CORE,
.load_mask = CM_PLLA_LOADCORE,
.hold_mask = CM_PLLA_HOLDCORE,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLA_PER] = REGISTER_PLL_DIV(
.name = "plla_per",
.source_pll = "plla",
.a2w_reg = A2W_PLLA_PER,
.load_mask = CM_PLLA_LOADPER,
.hold_mask = CM_PLLA_HOLDPER,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLA_DSI0] = REGISTER_PLL_DIV(
.name = "plla_dsi0",
.source_pll = "plla",
.a2w_reg = A2W_PLLA_CCP2,
.load_mask = CM_PLLA_LOADCCP2,
.hold_mask = CM_PLLA_HOLDCCP2,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
/* PLLB is used for the ARM's clock. */
[BCM2835_PLLB] = REGISTER_PLL(
.a2w_reg = A2W_PLLB_ARM,
.load_mask = CM_PLLB_LOADARM,
.hold_mask = CM_PLLB_HOLDARM,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
/*
* PLLC is the core PLL, used to drive the core VPU clock.
.a2w_reg = A2W_PLLC_CORE0,
.load_mask = CM_PLLC_LOADCORE0,
.hold_mask = CM_PLLC_HOLDCORE0,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLC_CORE1] = REGISTER_PLL_DIV(
.name = "pllc_core1",
.source_pll = "pllc",
.a2w_reg = A2W_PLLC_CORE1,
.load_mask = CM_PLLC_LOADCORE1,
.hold_mask = CM_PLLC_HOLDCORE1,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLC_CORE2] = REGISTER_PLL_DIV(
.name = "pllc_core2",
.source_pll = "pllc",
.a2w_reg = A2W_PLLC_CORE2,
.load_mask = CM_PLLC_LOADCORE2,
.hold_mask = CM_PLLC_HOLDCORE2,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLC_PER] = REGISTER_PLL_DIV(
.name = "pllc_per",
.source_pll = "pllc",
.a2w_reg = A2W_PLLC_PER,
.load_mask = CM_PLLC_LOADPER,
.hold_mask = CM_PLLC_HOLDPER,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
/*
* PLLD is the display PLL, used to drive DSI display panels.
.a2w_reg = A2W_PLLD_CORE,
.load_mask = CM_PLLD_LOADCORE,
.hold_mask = CM_PLLD_HOLDCORE,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLD_PER] = REGISTER_PLL_DIV(
.name = "plld_per",
.source_pll = "plld",
.a2w_reg = A2W_PLLD_PER,
.load_mask = CM_PLLD_LOADPER,
.hold_mask = CM_PLLD_HOLDPER,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLD_DSI0] = REGISTER_PLL_DIV(
.name = "plld_dsi0",
.source_pll = "plld",
.a2w_reg = A2W_PLLH_RCAL,
.load_mask = CM_PLLH_LOADRCAL,
.hold_mask = 0,
- .fixed_divider = 10),
+ .fixed_divider = 10,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLH_AUX] = REGISTER_PLL_DIV(
.name = "pllh_aux",
.source_pll = "pllh",
.a2w_reg = A2W_PLLH_AUX,
.load_mask = CM_PLLH_LOADAUX,
.hold_mask = 0,
- .fixed_divider = 1),
+ .fixed_divider = 1,
+ .flags = CLK_SET_RATE_PARENT),
[BCM2835_PLLH_PIX] = REGISTER_PLL_DIV(
.name = "pllh_pix",
.source_pll = "pllh",
.a2w_reg = A2W_PLLH_PIX,
.load_mask = CM_PLLH_LOADPIX,
.hold_mask = 0,
- .fixed_divider = 10),
+ .fixed_divider = 10,
+ .flags = CLK_SET_RATE_PARENT),
/* the clocks */
.ctl_reg = CM_PERIICTL),
};
+static bool bcm2835_clk_claimed[ARRAY_SIZE(clk_desc_array)];
+
/*
* Permanently take a reference on the parent of the SDRAM clock.
*
return clk_prepare_enable(parent);
}
+static bool bcm2835_clk_is_claimed(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(clk_desc_array); i++) {
+ const char *clk_name = *(const char **)(clk_desc_array[i].data);
+ if (!strcmp(name, clk_name))
+ return bcm2835_clk_claimed[i];
+ }
+
+ return false;
+}
+
static int bcm2835_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
const struct bcm2835_clk_desc *desc;
const size_t asize = ARRAY_SIZE(clk_desc_array);
+ struct device_node *fw_node;
size_t i;
+ u32 clk_id;
int ret;
cprman = devm_kzalloc(dev, sizeof(*cprman) +
if (IS_ERR(cprman->regs))
return PTR_ERR(cprman->regs);
+ fw_node = of_parse_phandle(dev->of_node, "firmware", 0);
+ if (fw_node) {
+ struct rpi_firmware *fw = rpi_firmware_get(NULL);
+ if (!fw)
+ return -EPROBE_DEFER;
+ cprman->fw = fw;
+ }
+
+ memset(bcm2835_clk_claimed, 0, sizeof(bcm2835_clk_claimed));
+ for (i = 0;
+ !of_property_read_u32_index(pdev->dev.of_node, "claim-clocks",
+ i, &clk_id);
+ i++)
+ bcm2835_clk_claimed[clk_id]= true;
+
cprman->osc_name = of_clk_get_parent_name(dev->of_node, 0);
if (!cprman->osc_name)
return -ENODEV;
if (ret)
return ret;
- return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
+ ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
&cprman->onecell);
+ if (ret)
+ return ret;
+
+ /* note that we have registered all the clocks */
+ dev_dbg(dev, "registered %d clocks\n", asize);
+
+ return 0;
}
static const struct of_device_id bcm2835_clk_of_match[] = {
.probe = bcm2835_clk_probe,
};
-builtin_platform_driver(bcm2835_clk_driver);
+static int __init __bcm2835_clk_driver_init(void)
+{
+ return platform_driver_register(&bcm2835_clk_driver);
+}
+core_initcall(__bcm2835_clk_driver_init);
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_DESCRIPTION("BCM2835 clock driver");