[PATCH 2/3] clk: vc5: Enable addition output configurations of the Versaclock

Adam Ford
2020-04-14T19:39:03Z

The existing driver is expecting the Versaclock to be pre-programmed,
and only sets the output frequency. Unfortunately, not all devices
are pre-programmed, and the Versaclock chip has more options beyond
just the frequency.

This patch enables the following additional features:

- Programmable voltage: 1.8V, 2.5V, or 3.3V​
- Slew Percentage of normal: 85%, 90%, or 100%
- Output Type: LVPECL, CMOS, HCSL, or LVDS

Signed-off-by: Adam Ford <aford173@gmail.com>

diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c
index 6e4b09f1f074..a1da88baf28c 100644
--- a/drivers/clk/clk-versaclock5.c
+++ b/drivers/clk/clk-versaclock5.c
@@ -24,6 +24,8 @@
#include <linux/regmap.h>
#include <linux/slab.h>

+#include <dt-bindings/clk/versaclock.h>
+
/* VersaClock5 registers */
#define VC5_OTP_CONTROL 0x00

@@ -89,6 +91,28 @@

/* Clock control register for clock 1,2 */
#define VC5_CLK_OUTPUT_CFG(idx, n) (0x60 + ((idx) * 0x2) + (n))
+#define VC5_CLK_OUTPUT_CFG0_CFG_SHIFT 5
+#define VC5_CLK_OUTPUT_CFG0_CFG_MASK GENMASK(7, VC5_CLK_OUTPUT_CFG0_CFG_SHIFT)
+
+#define VC5_CLK_OUTPUT_CFG0_CFG_LVPECL (VC5_LVPECL)
+#define VC5_CLK_OUTPUT_CFG0_CFG_CMOS (VC5_CMOS)
+#define VC5_CLK_OUTPUT_CFG0_CFG_HCSL33 (VC5_HCSL33)
+#define VC5_CLK_OUTPUT_CFG0_CFG_LVDS (VC5_LVDS)
+#define VC5_CLK_OUTPUT_CFG0_CFG_CMOS2 (VC5_CMOS2)
+#define VC5_CLK_OUTPUT_CFG0_CFG_CMOSD (VC5_CMOSD)
+#define VC5_CLK_OUTPUT_CFG0_CFG_HCSL25 (VC5_HCSL25)
+
+#define VC5_CLK_OUTPUT_CFG0_PWR_SHIFT 3
+#define VC5_CLK_OUTPUT_CFG0_PWR_MASK GENMASK(4, VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_PWR_18 (0<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_PWR_25 (2<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_PWR_33 (3<<VC5_CLK_OUTPUT_CFG0_PWR_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT 0
+#define VC5_CLK_OUTPUT_CFG0_SLEW_MASK GENMASK(1, VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_SLEW_80 (0<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_SLEW_85 (1<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_SLEW_90 (2<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
+#define VC5_CLK_OUTPUT_CFG0_SLEW_100 (3<<VC5_CLK_OUTPUT_CFG0_SLEW_SHIFT)
#define VC5_CLK_OUTPUT_CFG1_EN_CLKBUF BIT(0)

#define VC5_CLK_OE_SHDN 0x68
@@ -143,6 +167,8 @@ struct vc5_hw_data {
u32 div_int;
u32 div_frc;
unsigned int num;
+ unsigned int clk_output_cfg0;
+ unsigned int clk_output_cfg0_mask;
};

struct vc5_driver_data {
@@ -567,6 +593,16 @@ static int vc5_clk_out_prepare(struct clk_hw *hw)
regmap_update_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 1),
VC5_CLK_OUTPUT_CFG1_EN_CLKBUF,
VC5_CLK_OUTPUT_CFG1_EN_CLKBUF);
+ if (hwdata->clk_output_cfg0_mask) {
+ dev_dbg(&vc5->client->dev, "Update output %d mask 0x%0X val 0x%0X\n",
+ hwdata->num, hwdata->clk_output_cfg0_mask,
+ hwdata->clk_output_cfg0);
+
+ regmap_update_bits(vc5->regmap, VC5_CLK_OUTPUT_CFG(hwdata->num, 0),
+ hwdata->clk_output_cfg0_mask,
+ hwdata->clk_output_cfg0);
+ }
+
return 0;
}

@@ -675,6 +711,9 @@ static int vc5_probe(struct i2c_client *client,
struct clk_init_data init;
const char *parent_names[2];
unsigned int n, idx = 0;
+ u32 value;
+ struct device_node *np_output;
+ char *child_name;
int ret;

vc5 = devm_kzalloc(&client->dev, sizeof(*vc5), GFP_KERNEL);
@@ -865,6 +904,77 @@ static int vc5_probe(struct i2c_client *client,
init.name);
goto err_clk;
}
+
+ /* Fetch Clock Output configuration from DT (if specified) */
+ child_name = kasprintf(GFP_KERNEL, "OUT%d", n);
+ np_output = of_get_child_by_name(client->dev.of_node, child_name);
+ kfree(child_name);
+ if (!np_output)
+ continue;
+ if (!(ret || of_property_read_u32(np_output,
+ "idt,mode", &value))) {
+ vc5->clk_out[n].clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_CFG_MASK;
+ switch (value) {
+ case VC5_CLK_OUTPUT_CFG0_CFG_LVPECL:
+ case VC5_CLK_OUTPUT_CFG0_CFG_CMOS:
+ case VC5_CLK_OUTPUT_CFG0_CFG_HCSL33:
+ case VC5_CLK_OUTPUT_CFG0_CFG_LVDS:
+ case VC5_CLK_OUTPUT_CFG0_CFG_CMOS2:
+ case VC5_CLK_OUTPUT_CFG0_CFG_CMOSD:
+ case VC5_CLK_OUTPUT_CFG0_CFG_HCSL25:
+ vc5->clk_out[n].clk_output_cfg0 |= value << VC5_CLK_OUTPUT_CFG0_CFG_SHIFT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ }
+ if (!(ret || of_property_read_u32(np_output,
+ "idt,voltage-microvolts", &value))) {
+ vc5->clk_out[n].clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_PWR_MASK;
+ switch (value) {
+ case 1800000:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_18;
+ break;
+ case 2500000:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_25;
+ break;
+ case 3300000:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_PWR_33;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ }
+ if (!(ret || of_property_read_u32(np_output,
+ "idt,slew-percent", &value))) {
+ vc5->clk_out[n].clk_output_cfg0_mask |= VC5_CLK_OUTPUT_CFG0_SLEW_MASK;
+ switch (value) {
+ case 80:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_80;
+ break;
+ case 85:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_85;
+ break;
+ case 90:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_90;
+ break;
+ case 100:
+ vc5->clk_out[n].clk_output_cfg0 |= VC5_CLK_OUTPUT_CFG0_SLEW_100;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ }
+ of_node_put(np_output);
+ if (ret) {
+ dev_err(&client->dev,
+ "Invalid clock output configuration OUT%d\n",
+ n);
+ goto err_clk;
+ }
}

ret = of_clk_add_hw_provider(client->dev.of_node, vc5_of_clk_get, vc5);
diff --git a/include/dt-bindings/clk/versaclock.h b/include/dt-bindings/clk/versaclock.h
new file mode 100644
index 000000000000..30add3488713
--- /dev/null
+++ b/include/dt-bindings/clk/versaclock.h
@@ -0,0 +1,13 @@
+/* HEADER */
+
+/* This file defines field values used by the versaclock 6 family
+ * for defining output type
+ */
+
+#define VC5_LVPECL 0
+#define VC5_CMOS 1
+#define VC5_HCSL33 2
+#define VC5_LVDS 3
+#define VC5_CMOS2 4
+#define VC5_CMOSD 5
+#define VC5_HCSL25 6
--
2.25.1