@@ -12,8 +12,9 @@
* Based on Little Kernel driver, simplified
*/
#include <clk-uclass.h>
+#include <linux/clk-provider.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <errno.h>
@@ -214,11 +215,127 @@ static int msm_clk_enable(struct clk *clk)
return 0;
}
+static void dump_gplls(struct udevice *dev, phys_addr_t base)
+{
+ struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev);
+ u32 i;
+ bool locked;
+ u64 l, a, xo_rate = 19200000;
+ struct clk *clk = NULL;
+ struct udevice *xodev;
+ const phys_addr_t *gplls = data->dbg_pll_addrs;
+
+ uclass_foreach_dev_probe(UCLASS_CLK, xodev) {
+ if (!strcmp(xodev->name, "xo-board") || !strcmp(xodev->name, "xo_board")) {
+ clk = dev_get_clk_ptr(xodev);
+ break;
+ }
+ }
+
+ if (clk) {
+ xo_rate = clk_get_rate(clk);
+
+ /* On SDM845 this needs to be divided by 2 for some reason */
+ if (xo_rate && of_machine_is_compatible("qcom,sdm845"))
+ xo_rate /= 2;
+ } else {
+ printf("Can't find XO clock, XO_BOARD rate may be wrong\n");
+ }
+
+ printf("GPLL clocks:\n");
+ printf("| GPLL | LOCKED | XO_BOARD | PLL_L | ALPHA |\n");
+ printf("+--------+--------+-----------+------------+----------------+\n");
+ for (i = 0; i < data->num_plls; i++) {
+ locked = !!(readl(gplls[i]) & BIT(31));
+ l = readl(gplls[i] + 4) & (BIT(16) - 1);
+ a = readq(gplls[i] + 40) & (BIT(16) - 1);
+ printf("| GPLL%-2d | %-6s | %9llu * (%#-9llx + %#-13llx * 2 ** -40 ) / 1000000\n",
+ i, locked ? "X" : "", xo_rate, l, a);
+ }
+}
+
+static void dump_rcgs(struct udevice *dev)
+{
+ struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev);
+ int i;
+ u32 cmd;
+ u32 cfg;
+ u32 not_n_minus_m;
+ u32 src, m, n, div;
+ bool root_on, d_odd;
+
+ printf("\nRCGs:\n");
+
+ /*
+ * Which GPLL SRC corresponds to depends on the parent map, see gcc-<soc>.c in Linux
+ * and find the parent map associated with the clock. Note that often there are multiple
+ * outputs from a single GPLL where one is actually half the rate of the other (_EVEN).
+ * intput_freq = associated GPLL output freq (potentially divided depending on SRC).
+ */
+ printf("| NAME | ON | SRC | OUT_FREQ = input_freq * (m/n) * (1/d) | [CMD REG ] |\n");
+ printf("+----------------------------------+----+-----+---------------------------------------+--------------+\n");
+ for (i = 0; i < data->num_rcgs; i++) {
+ cmd = readl(data->dbg_rcg_addrs[i]);
+ cfg = readl(data->dbg_rcg_addrs[i] + 0x4);
+ m = readl(data->dbg_rcg_addrs[i] + 0x8);
+ n = 0;
+ not_n_minus_m = readl(data->dbg_rcg_addrs[i] + 0xc);
+
+ root_on = !(cmd & BIT(31)); // ROOT_OFF
+ src = (cfg >> 8) & 7;
+
+ if (not_n_minus_m) {
+ n = (~not_n_minus_m & 0xffff);
+
+ /* A clumsy assumption that this is an 8-bit MND RCG */
+ if ((n & 0xff00) == 0xff00)
+ n = n & 0xff;
+
+ n += m;
+ }
+
+ div = ((cfg & 0b11111) + 1) / 2;
+ d_odd = ((cfg & 0b11111) + 1) % 2 == 1;
+ printf("%-34s | %-2s | %3d | input_freq * (%4d/%5d) * (1/%1d%-2s) | [%#010x]\n",
+ data->dbg_rcg_names[i], root_on ? "X" : "", src,
+ m ?: 1, n ?: 1, div, d_odd ? ".5" : "", cmd);
+ }
+
+ printf("\n");
+}
+
+static void msm_dump_clks(struct udevice *dev)
+{
+ struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(dev);
+ struct msm_clk_priv *priv = dev_get_priv(dev);
+ const struct gate_clk *sclk;
+ int val, i;
+
+ if (!data->clks) {
+ printf("No clocks\n");
+ return;
+ }
+
+ printf("Gate Clocks:\n");
+ for (i = 0; i < data->num_clks; i++) {
+ sclk = &data->clks[i];
+ if (!sclk->name)
+ continue;
+ printf("%-32s: ", sclk->name);
+ val = readl(priv->base + sclk->reg) & sclk->en_val;
+ printf("%s\n", val ? "ON" : "");
+ }
+
+ dump_gplls(dev, priv->base);
+ dump_rcgs(dev);
+}
+
static struct clk_ops msm_clk_ops = {
.set_rate = msm_clk_set_rate,
.enable = msm_clk_enable,
+ .dump = msm_dump_clks,
};
U_BOOT_DRIVER(qcom_clk) = {
.name = "qcom_clk",
@@ -75,8 +75,14 @@ struct msm_clk_data {
unsigned long num_resets;
const struct gate_clk *clks;
unsigned long num_clks;
+ const phys_addr_t *dbg_pll_addrs;
+ unsigned long num_plls;
+ const phys_addr_t *dbg_rcg_addrs;
+ unsigned long num_rcgs;
+ const char * const *dbg_rcg_names;
+
int (*enable)(struct clk *clk);
unsigned long (*set_rate)(struct clk *clk, unsigned long rate);
};
Add support for dumping a few of the clocks used on Qualcomm platforms. Naming the Global PLL's, Root Clock Generators, and gate clocks. This helps a lot with platform bringup and feature enablement by making it easy to sanity check that the clocks are programmed correctly. == Usage == Enable CONFIG_CMD_CLK and "#define LOG_DEBUG" at the top of qcom-<soc>.c. The "clk dump" command should print the states of all the gates, GPLLs and RCGs for your SoC. == Glossary == RCG: Root Clock Generator * Takes in some fairly arbitrary high freq clock (configurable clock source and options for taking just even pulses and other things) * Output frequency = input_freq * (m/n) * (1/d) where m/n are arbitrary 8 or 16-bit values (depending on the RCG), and d is a number (with support for .5 offsets). GPLL: Global Phase Locked Loop * Crystal as input * integer multiplier + exponent part (2^-40) Gate: Simple on/off clock * Put between RCGs and the peripherals they power * Required to allow for correct power sequencing If you do the maths manually using the equations from "clk dump", the numbers should roughly line up by they're likely to be out by a handful of MHz. They output is formatted so that it can be pasted directly into the python interpreter. Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org> --- drivers/clk/qcom/clock-qcom.c | 117 ++++++++++++++++++++++++++++++++++++++++++ drivers/clk/qcom/clock-qcom.h | 6 +++ 2 files changed, 123 insertions(+)