Linux clock framework development
 help / color / mirror / Atom feed
* [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-30 13:58 ` Herman van Hazendonk
  2026-05-31 15:39   ` Dmitry Baryshkov
  2026-05-30 13:58 ` [PATCH 3/3] clk: qcom: add MSM8x60 MMCC driver Herman van Hazendonk
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:58 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-arm-msm,
	linux-clk, devicetree
  Cc: Herman van Hazendonk

Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Multimedia Clock Controller (MMCC). The
header enumerates the clocks and power-domains consumed by the
multimedia subsystem (MDP4 display, Adreno A220 GPU, CAMSS image
pipeline, VFE, Gemini JPEG, video codec, rotator, VPE and the GFX2D
Z180 cores).

IDs intentionally match the numeric values used by the original
shared mmcc-msm8960.h so the driver's clk array indexing is preserved;
only the clocks actually implemented by mmcc-msm8660.c are defined.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 include/dt-bindings/clock/qcom,mmcc-msm8660.h | 126 ++++++++++++++++++
 1 file changed, 126 insertions(+)
 create mode 100644 include/dt-bindings/clock/qcom,mmcc-msm8660.h

diff --git a/include/dt-bindings/clock/qcom,mmcc-msm8660.h b/include/dt-bindings/clock/qcom,mmcc-msm8660.h
new file mode 100644
index 000000000000..00c3a75e8b71
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,mmcc-msm8660.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Clock and power-domain bindings for the MSM8x60 family (MSM8260/MSM8660/APQ8060)
+ * Multimedia Clock Controller (MMCC).
+ *
+ * MSM8260, MSM8660 and APQ8060 are the same Scorpion-class MSM8x60 SoC
+ * with different bin/feature labels. MSM8960 is a newer generation (Krait)
+ * — its bindings live in
+ * <dt-bindings/clock/qcom,mmcc-msm8960.h> and must not be reused here.
+ *
+ * IDs below intentionally match the numeric values used by the original
+ * shared mmcc-msm8960.h so the driver's clk array indexing is preserved;
+ * only the clocks actually implemented by mmcc-msm8660.c are defined.
+ */
+
+#ifndef _DT_BINDINGS_CLK_MSM_MMCC_8660_H
+#define _DT_BINDINGS_CLK_MSM_MMCC_8660_H
+
+#define TV_ENC_AHB_CLK					3
+#define AMP_AHB_CLK					4
+#define JPEGD_AHB_CLK					6
+#define GFX2D0_AHB_CLK					7
+#define DSI_S_AHB_CLK					8
+#define VPE_AHB_CLK					10
+#define SMMU_AHB_CLK					11
+#define HDMI_M_AHB_CLK					12
+#define VFE_AHB_CLK					13
+#define ROT_AHB_CLK					14
+#define VCODEC_AHB_CLK					15
+#define MDP_AHB_CLK					16
+#define DSI_M_AHB_CLK					17
+#define CSI0_AHB_CLK					18
+#define MMSS_IMEM_AHB_CLK				19
+#define IJPEG_AHB_CLK					20
+#define HDMI_S_AHB_CLK					21
+#define GFX3D_AHB_CLK					22
+#define GFX2D1_AHB_CLK					23
+#define JPEGD_AXI_CLK					28
+#define GMEM_AXI_CLK					29
+#define MDP_AXI_CLK					30
+#define MMSS_IMEM_AXI_CLK				31
+#define IJPEG_AXI_CLK					32
+#define GFX3D_AXI_CLK					33
+#define VCODEC_AXI_CLK					34
+#define VFE_AXI_CLK					35
+#define VPE_AXI_CLK					36
+#define ROT_AXI_CLK					37
+#define VCODEC_AXI_A_CLK				38
+#define VCODEC_AXI_B_CLK				39
+#define CSI0_SRC					47
+#define CSI0_CLK					48
+#define CSI0_PHY_CLK					49
+#define CSI1_SRC					50
+#define CSI1_CLK					51
+#define CSI1_PHY_CLK					52
+#define DSI_SRC						56
+#define DSI_CLK						57
+#define CSI_PIX_CLK					58
+#define CSI_RDI_CLK					59
+#define MDP_VSYNC_CLK					60
+#define HDMI_APP_CLK					62
+#define GFX2D0_SRC					66
+#define GFX2D0_CLK					67
+#define GFX2D1_SRC					68
+#define GFX2D1_CLK					69
+#define GFX3D_SRC					70
+#define GFX3D_CLK					71
+#define IJPEG_SRC					72
+#define IJPEG_CLK					73
+#define JPEGD_SRC					74
+#define JPEGD_CLK					75
+#define MDP_SRC						76
+#define MDP_CLK						77
+#define MDP_LUT_CLK					78
+#define DSI1_BYTE_SRC					83
+#define DSI1_BYTE_CLK					84
+#define DSI1_ESC_SRC					87
+#define DSI1_ESC_CLK					88
+#define ROT_SRC						91
+#define ROT_CLK						92
+#define TV_ENC_CLK					93
+#define TV_DAC_CLK					94
+#define HDMI_TV_CLK					95
+#define MDP_TV_CLK					96
+#define TV_SRC						97
+#define VCODEC_SRC					98
+#define VCODEC_CLK					99
+#define VFE_SRC						100
+#define VFE_CLK						101
+#define VFE_CSI0_CLK					102
+#define VPE_SRC						103
+#define VPE_CLK						104
+#define DSI_PIXEL_SRC					105
+#define DSI_PIXEL_CLK					106
+#define CAMCLK0_SRC					107
+#define CAMCLK0_CLK					108
+#define CAMCLK1_SRC					109
+#define CAMCLK1_CLK					110
+#define CSIPHYTIMER_SRC					113
+#define CSIPHY1_TIMER_CLK				115
+#define CSIPHY0_TIMER_CLK				116
+#define PLL2						118
+#define MDP_PIXEL_SRC					129
+#define MDP_PIXEL_CLK					130
+#define MDP_LCDC_CLK					131
+#define VFE_CSI1_CLK					132
+#define CSI1_AHB_CLK					133
+
+/*
+ * MSM8x60 legacy footswitch power domains.
+ * Used with the MMCC power-domain provider (#power-domain-cells = <1>).
+ * Numbering is independent of the clock ID space above.
+ */
+#define GFX2D0_GDSC					0
+#define GFX2D1_GDSC					1
+#define GFX3D_GDSC					2
+#define IJPEG_GDSC					3
+#define MDP_GDSC					4
+#define ROT_GDSC					5
+#define VED_GDSC					6
+#define VFE_GDSC					7
+#define VPE_GDSC					8
+
+#endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 3/3] clk: qcom: add MSM8x60 MMCC driver
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
  2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
@ 2026-05-30 13:58 ` Herman van Hazendonk
  2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:58 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, linux-kernel,
	linux-arm-msm, linux-clk
  Cc: Herman van Hazendonk

Add a clock driver for the Multimedia Clock Controller (MMCC) on the
MSM8x60 family (MSM8260/MSM8660/APQ8060) - the Scorpion-class
generation that preceded MSM8960's Krait CPUs.

The MMCC layout on MSM8x60 differs from MSM8960 in several ways that
make a separate driver cleaner than parameterising mmcc-msm8960.c:

  - the pix_rdi mux requires a custom set_parent op that temporarily
    enables both parents during the glitch-free transition;
  - the IJPEG GDSC requires releasing AXI, AHB and CORE resets;
  - several rate-source pairs (MDP pixel, GFX2D/3D) only exist on
    8x60 (e.g. PLL2-derived 228571000/266667000 for graphics);
  - the camera CSI / VFE / JPEG / VPE / ROT clock topology lacks the
    later 8960 reorganisation.

Used on the HP TouchPad (Tenderloin) for graphics (Adreno A220),
display (MDP4), camera (CSI/VFE), JPEG (Gemini), VIDC, VPE and
rotator. Reset IDs are exposed via a separate header so consumers
can reset the GDSCs and individual blocks.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/clk/qcom/Kconfig        |   11 +
 drivers/clk/qcom/Makefile       |    1 +
 drivers/clk/qcom/mmcc-msm8660.c | 2998 +++++++++++++++++++++++++++++++
 3 files changed, 3010 insertions(+)
 create mode 100644 drivers/clk/qcom/mmcc-msm8660.c

diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d9cff5b0281d..f64b0002c85a 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -572,6 +572,17 @@ config MSM_MMCC_8960
 	  Say Y if you want to support multimedia devices such as display,
 	  graphics, video encode/decode, camera, etc.
 
+config MSM_MMCC_8660
+	tristate "MSM8x60 Multimedia Clock Controller"
+	depends on ARM || COMPILE_TEST
+	select MSM_GCC_8660
+	select QCOM_GDSC
+	help
+	  Support for the multimedia clock controller on the MSM8x60 family
+	  (MSM8260/MSM8660/APQ8060). Say Y if you want to support multimedia
+	  devices such as display, graphics, video encode/decode, camera, etc.
+	  on these Scorpion-class SoCs (e.g. HP TouchPad).
+
 config MSM_GCC_8953
 	tristate "MSM8953 Global Clock Controller"
 	select QCOM_GDSC
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index e100cfd6a52d..ff317fce086d 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
 obj-$(CONFIG_MSM_GCC_8998) += gcc-msm8998.o
 obj-$(CONFIG_MSM_GPUCC_8998) += gpucc-msm8998.o
 obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
+obj-$(CONFIG_MSM_MMCC_8660) += mmcc-msm8660.o
 obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
 obj-$(CONFIG_MSM_MMCC_8994) += mmcc-msm8994.o
 obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
diff --git a/drivers/clk/qcom/mmcc-msm8660.c b/drivers/clk/qcom/mmcc-msm8660.c
new file mode 100644
index 000000000000..5e6943430d29
--- /dev/null
+++ b/drivers/clk/qcom/mmcc-msm8660.c
@@ -0,0 +1,2998 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ Herman van Hazendonk <github.com@herrie.org>
+ *
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) Multimedia Clock Controller driver
+ *
+ * Split from mmcc-msm8960.c to properly handle MSM8x60-specific clock
+ * configurations, particularly the GFX3D reset bits which differ from MSM8960.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,mmcc-msm8660.h>
+#include <dt-bindings/mfd/qcom-rpm.h>
+#include <dt-bindings/reset/qcom,mmcc-msm8660.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-branch.h"
+#include "reset.h"
+#include "gdsc.h"
+
+enum {
+	P_PXO,
+	P_PLL8,
+	P_PLL2,
+	P_PLL3,
+	P_HDMI_PLL,
+	P_DSI1_PLL_DSICLK,
+	P_DSI2_PLL_DSICLK,
+	P_DSI1_PLL_BYTECLK,
+	P_DSI2_PLL_BYTECLK,
+};
+
+#define F_MN(f, s, _m, _n) { .freq = f, .src = s, .pre_div = 1, .m = _m, .n = _n }
+/* Pure divider+source RCG (no MND), e.g. VPE on MSM8660: NS_DIVSRC(15,12,d,2,0,s) */
+#define F_DIV(f, s, d) { .freq = f, .src = s, .pre_div = d }
+
+static struct clk_pll pll2 = {
+	.l_reg = 0x320,
+	.m_reg = 0x324,
+	.n_reg = 0x328,
+	.config_reg = 0x32c,
+	.mode_reg = 0x31c,
+	.status_reg = 0x334,
+	.status_bit = 16,
+	.clkr.hw.init = &(struct clk_init_data){
+		.name = "pll2",
+		.parent_data = (const struct clk_parent_data[]){
+			{ .fw_name = "pxo", .name = "pxo_board" },
+		},
+		.num_parents = 1,
+		.ops = &clk_pll_ops,
+	},
+};
+
+static const struct parent_map mmcc_pxo_pll8_pll2_map[] = {
+	{ P_PXO, 0 },
+	{ P_PLL8, 2 },
+	{ P_PLL2, 1 }
+};
+
+static const struct clk_parent_data mmcc_pxo_pll8_pll2[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "pll8_vote", .name = "pll8_vote" },
+	{ .hw = &pll2.clkr.hw },
+};
+
+static const struct parent_map mmcc_pxo_pll8_pll2_pll3_map[] = {
+	{ P_PXO, 0 },
+	{ P_PLL8, 2 },
+	{ P_PLL2, 1 },
+	{ P_PLL3, 3 }
+};
+
+static const struct clk_parent_data mmcc_pxo_pll8_pll2_pll3[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "pll8_vote", .name = "pll8_vote" },
+	{ .hw = &pll2.clkr.hw },
+	{ .fw_name = "pll3", .name = "pll3" },
+};
+
+static const struct parent_map mmcc_pxo_dsi2_dsi1_map[] = {
+	{ P_PXO, 0 },
+	{ P_DSI2_PLL_DSICLK, 1 },
+	{ P_DSI1_PLL_DSICLK, 3 },
+};
+
+static const struct clk_parent_data mmcc_pxo_dsi2_dsi1[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "dsi2pll", .name = "dsi2pll" },
+	{ .fw_name = "dsi1pll", .name = "dsi1pll" },
+};
+
+static const struct parent_map mmcc_pxo_dsi1_dsi2_byte_map[] = {
+	{ P_PXO, 0 },
+	{ P_DSI1_PLL_BYTECLK, 1 },
+	{ P_DSI2_PLL_BYTECLK, 2 },
+};
+
+static const struct clk_parent_data mmcc_pxo_dsi1_dsi2_byte[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "dsi1pllbyte", .name = "dsi1pllbyte" },
+	{ .fw_name = "dsi2pllbyte", .name = "dsi2pllbyte" },
+};
+
+static const struct freq_tbl clk_tbl_cam[] = {
+	{   6000000, P_PLL8, 4, 1, 16 },
+	{   8000000, P_PLL8, 4, 1, 12 },
+	{  12000000, P_PLL8, 4, 1,  8 },
+	{  16000000, P_PLL8, 4, 1,  6 },
+	{  19200000, P_PLL8, 4, 1,  5 },
+	{  24000000, P_PLL8, 4, 1,  4 },
+	{  32000000, P_PLL8, 4, 1,  3 },
+	{  48000000, P_PLL8, 4, 1,  2 },
+	{  64000000, P_PLL8, 3, 1,  2 },
+	{  96000000, P_PLL8, 4, 0,  0 },
+	{ 128000000, P_PLL8, 3, 0,  0 },
+	{ }
+};
+
+static struct clk_rcg camclk0_src = {
+	.ns_reg = 0x0148,
+	.md_reg = 0x0144,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 8,
+		.reset_in_cc = true,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_cam,
+	.clkr = {
+		.enable_reg = 0x0140,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "camclk0_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch camclk0_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 15,
+	/*
+	 * The legacy webOS kernel used halt_reg = NULL for this clock,
+	 * meaning it never checked the halt status. The hardware doesn't
+	 * properly report the clock state via the halt register. Use
+	 * BRANCH_HALT_SKIP to avoid the "status stuck at 'off'" warning.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0140,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "camclk0_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&camclk0_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_rcg camclk1_src = {
+	.ns_reg = 0x015c,
+	.md_reg = 0x0158,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 8,
+		.reset_in_cc = true,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_cam,
+	.clkr = {
+		.enable_reg = 0x0154,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "camclk1_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch camclk1_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 16,
+	/* Same issue as camclk0_clk - hardware doesn't report halt status */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0154,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "camclk1_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&camclk1_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+/*
+ * CSI clock frequency table for MSM8660.
+ * Uses simple pre-divider from PLL8 (384 MHz), NOT MND divider.
+ * Reference: webOS clock-8x60.c clk_tbl_csi[]
+ */
+static const struct freq_tbl clk_tbl_csi[] = {
+	{ 192000000, P_PLL8, 2, 0, 0 },
+	{ 384000000, P_PLL8, 1, 0, 0 },
+	{ }
+};
+
+/*
+ * CSI clock for MSM8660 uses simple pre-divider, NOT MND divider.
+ * CC_REG = 0x0040, NS_REG = 0x0048, no MD register.
+ * Pre-divider is in NS_REG bits [15:12], source select in bits [2:0].
+ * Reference: webOS clock-8x60.c CLK_CSI macro
+ */
+static struct clk_rcg csi0_src = {
+	.ns_reg = 0x0048,
+	/* No md_reg - CSI uses pre-divider only, not MND */
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_csi,
+	.clkr = {
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi0_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch csi0_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 13,
+	/*
+	 * The CSI clock halt status is unreliable when the CSI block is not
+	 * actively receiving data. Use BRANCH_HALT_SKIP to avoid timeouts.
+	 * This matches the behavior of camclk and vfe_ahb_clk on MSM8660.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csi0_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csi0_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch csi0_phy_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 9,
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(8),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csi0_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csi0_phy_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+/*
+ * CSI1 on MSM8660 shares the same source clock as CSI0.
+ * In webOS, both CSI0 and CSI1 are branches from a single CSI_SRC.
+ * Use same registers as csi0_src (CC=0x0040, NS=0x0048).
+ * The enable bit for csi1_src root is also BIT(2) in CC_REG.
+ */
+static struct clk_rcg csi1_src = {
+	.ns_reg = 0x0048,
+	/* No md_reg - CSI uses pre-divider only, not MND */
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_csi,
+	.clkr = {
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi1_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch csi1_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 14,
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		/* CSI1 enable is in CSI_CC_REG (0x0040) BIT(7) per webOS */
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(7),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csi1_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csi1_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch csi1_phy_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 10,
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		/* Use CSI_CC_REG (0x0040) like csi0_phy_clk, with BIT(9) for CSI1 */
+		.enable_reg = 0x0040,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csi1_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csi1_phy_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+/*
+ * CSI PIX/RDI clock mux support
+ *
+ * On MSM8660, the csi_pix_clk and csi_rdi_clk can source from either
+ * CSI0 or CSI1. The selection is controlled by bits in CSC_CC_REG (0x0058):
+ *   - BIT(25): csi_pix_clk source (0=CSI0, 1=CSI1)
+ *   - BIT(12): csi_rdi_clk source (0=CSI0, 1=CSI1)
+ *
+ * This mux is critical for HP TouchPad which uses CSI1 for the front camera.
+ */
+struct clk_pix_rdi {
+	u32 s_reg;
+	u32 s_mask;
+	struct clk_regmap clkr;
+};
+
+#define to_clk_pix_rdi(_hw) \
+	container_of(to_clk_regmap(_hw), struct clk_pix_rdi, clkr)
+
+static int pix_rdi_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_pix_rdi *rdi = to_clk_pix_rdi(hw);
+	u32 val;
+	int i, ret = 0;
+	int num_parents = clk_hw_get_num_parents(hw);
+
+	/*
+	 * Turn on all parent sources during mux switch to ensure
+	 * glitch-free transition.
+	 */
+	for (i = 0; i < num_parents; i++) {
+		struct clk_hw *p = clk_hw_get_parent_by_index(hw, i);
+
+		ret = clk_prepare_enable(p->clk);
+		if (ret)
+			goto err;
+	}
+
+	/* Select parent: 0=CSI0, 1=CSI1 */
+	val = (index == 1) ? rdi->s_mask : 0;
+	regmap_update_bits(rdi->clkr.regmap, rdi->s_reg, rdi->s_mask, val);
+
+	/*
+	 * Wait at least 6 cycles of slowest clock for the
+	 * glitch-free MUX to fully switch sources.
+	 */
+	udelay(1);
+
+	/*
+	 * Now disable all parents that were temporarily enabled.
+	 * The clock framework will keep the selected parent enabled
+	 * as long as the child (csi_pix_clk/csi_rdi_clk) is enabled.
+	 */
+	for (i = num_parents - 1; i >= 0; i--) {
+		struct clk_hw *p = clk_hw_get_parent_by_index(hw, i);
+
+		clk_disable_unprepare(p->clk);
+	}
+
+	return 0;
+
+err:
+	/* On error, disable only the parents we successfully enabled */
+	for (i--; i >= 0; i--) {
+		struct clk_hw *p = clk_hw_get_parent_by_index(hw, i);
+
+		clk_disable_unprepare(p->clk);
+	}
+
+	return ret;
+}
+
+static u8 pix_rdi_get_parent(struct clk_hw *hw)
+{
+	struct clk_pix_rdi *rdi = to_clk_pix_rdi(hw);
+	u32 val;
+
+	regmap_read(rdi->clkr.regmap, rdi->s_reg, &val);
+	return (val & rdi->s_mask) ? 1 : 0;
+}
+
+static const struct clk_ops clk_ops_pix_rdi = {
+	.enable = clk_enable_regmap,
+	.disable = clk_disable_regmap,
+	.is_enabled = clk_is_enabled_regmap,
+	.set_parent = pix_rdi_set_parent,
+	.get_parent = pix_rdi_get_parent,
+	.determine_rate = __clk_mux_determine_rate,
+};
+
+static const struct clk_hw *pix_rdi_parents[] = {
+	&csi0_clk.clkr.hw,
+	&csi1_clk.clkr.hw,
+};
+
+static struct clk_pix_rdi csi_pix_clk = {
+	.s_reg = 0x0058,
+	.s_mask = BIT(25),
+	.clkr = {
+		.enable_reg = 0x0058,
+		.enable_mask = BIT(26),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi_pix_clk",
+			.parent_hws = pix_rdi_parents,
+			.num_parents = ARRAY_SIZE(pix_rdi_parents),
+			.ops = &clk_ops_pix_rdi,
+		},
+	},
+};
+
+static struct clk_pix_rdi csi_rdi_clk = {
+	.s_reg = 0x0058,
+	.s_mask = BIT(12),
+	.clkr = {
+		.enable_reg = 0x0058,
+		.enable_mask = BIT(13),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi_rdi_clk",
+			.parent_hws = pix_rdi_parents,
+			.num_parents = ARRAY_SIZE(pix_rdi_parents),
+			.ops = &clk_ops_pix_rdi,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_csiphytimer[] = {
+	{  85330000, P_PLL8, 1, 2, 9 },
+	{ 177780000, P_PLL2, 1, 2, 9 },
+	{ }
+};
+
+static struct clk_rcg csiphytimer_src = {
+	.ns_reg = 0x0168,
+	.md_reg = 0x0164,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 8,
+		.reset_in_cc = true,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_csiphytimer,
+	.clkr = {
+		.enable_reg = 0x0160,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "csiphytimer_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch csiphy0_timer_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 17,
+	.clkr = {
+		.enable_reg = 0x0160,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csiphytimer_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csiphy0_timer_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch csiphy1_timer_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 18,
+	.clkr = {
+		.enable_reg = 0x0160,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&csiphytimer_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "csiphy1_timer_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_dsi[] = {
+	{ }
+};
+
+static struct clk_rcg dsi1_src = {
+	.ns_reg = 0x0054,
+	.md_reg = 0x0050,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_dsi2_dsi1_map,
+	},
+	.freq_tbl = clk_tbl_dsi,
+	.clkr = {
+		.enable_reg = 0x004c,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_src",
+			.parent_data = mmcc_pxo_dsi2_dsi1,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_dsi2_dsi1),
+			.ops = &clk_rcg_bypass_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch dsi1_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 2,
+	.clkr = {
+		.enable_reg = 0x004c,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&dsi1_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_rcg dsi1_byte_src = {
+	.ns_reg = 0x00b0,
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_dsi1_dsi2_byte_map,
+	},
+	.clkr = {
+		.enable_reg = 0x0090,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_byte_src",
+			.parent_data = mmcc_pxo_dsi1_dsi2_byte,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_dsi1_dsi2_byte),
+			.ops = &clk_rcg_bypass_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch dsi1_byte_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 21,
+	.clkr = {
+		.enable_reg = 0x0090,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_byte_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&dsi1_byte_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_rcg dsi1_esc_src = {
+	.ns_reg = 0x00b8,
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_dsi1_dsi2_byte_map,
+	},
+	.clkr = {
+		.enable_reg = 0x00b4,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_esc_src",
+			.parent_data = mmcc_pxo_dsi1_dsi2_byte,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_dsi1_dsi2_byte),
+			.ops = &clk_rcg_bypass_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch dsi1_esc_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 1,
+	.clkr = {
+		.enable_reg = 0x00b4,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_esc_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&dsi1_esc_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_rcg dsi1_pixel_src = {
+	.ns_reg = 0x0138,
+	.md_reg = 0x0134,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_dsi2_dsi1_map,
+	},
+	.clkr = {
+		.enable_reg = 0x0130,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_pixel_src",
+			.parent_data = mmcc_pxo_dsi2_dsi1,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_dsi2_dsi1),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch dsi1_pixel_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 6,
+	.clkr = {
+		.enable_reg = 0x0130,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi1_pixel_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&dsi1_pixel_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_gfx2d[] = {
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 48000000, P_PLL8, 1,  8),
+	F_MN( 54857000, P_PLL8, 1,  7),
+	F_MN( 64000000, P_PLL8, 1,  6),
+	F_MN( 76800000, P_PLL8, 1,  5),
+	F_MN( 96000000, P_PLL8, 1,  4),
+	F_MN(128000000, P_PLL8, 1,  3),
+	F_MN(145455000, P_PLL2, 2, 11),
+	F_MN(160000000, P_PLL2, 1,  5),
+	F_MN(177778000, P_PLL2, 2,  9),
+	F_MN(200000000, P_PLL2, 1,  4),
+	F_MN(228571000, P_PLL2, 2,  7),
+	{ }
+};
+
+static struct clk_dyn_rcg gfx2d0_src = {
+	.ns_reg[0] = 0x0070,
+	.ns_reg[1] = 0x0070,
+	.md_reg[0] = 0x0064,
+	.md_reg[1] = 0x0068,
+	.bank_reg = 0x0060,
+	.mn[0] = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 25,
+		.mnctr_mode_shift = 9,
+		.n_val_shift = 20,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 24,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.s[0] = {
+		.src_sel_shift = 3,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 11,
+	.freq_tbl = clk_tbl_gfx2d,
+	.clkr = {
+		.enable_reg = 0x0060,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d0_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx2d0_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 9,
+	.clkr = {
+		.enable_reg = 0x0060,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d0_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&gfx2d0_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_dyn_rcg gfx2d1_src = {
+	.ns_reg[0] = 0x007c,
+	.ns_reg[1] = 0x007c,
+	.md_reg[0] = 0x0078,
+	.md_reg[1] = 0x006c,
+	.bank_reg = 0x0074,
+	.mn[0] = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 25,
+		.mnctr_mode_shift = 9,
+		.n_val_shift = 20,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 24,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.s[0] = {
+		.src_sel_shift = 3,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 11,
+	.freq_tbl = clk_tbl_gfx2d,
+	.clkr = {
+		.enable_reg = 0x0074,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d1_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx2d1_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 14,
+	.clkr = {
+		.enable_reg = 0x0074,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d1_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&gfx2d1_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_gfx3d[] = {
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 48000000, P_PLL8, 1,  8),
+	F_MN( 54857000, P_PLL8, 1,  7),
+	F_MN( 64000000, P_PLL8, 1,  6),
+	F_MN( 76800000, P_PLL8, 1,  5),
+	F_MN( 96000000, P_PLL8, 1,  4),
+	F_MN(128000000, P_PLL8, 1,  3),
+	F_MN(145455000, P_PLL2, 2, 11),
+	F_MN(160000000, P_PLL2, 1,  5),
+	F_MN(177778000, P_PLL2, 2,  9),
+	F_MN(200000000, P_PLL2, 1,  4),
+	F_MN(228571000, P_PLL2, 2,  7),
+	F_MN(266667000, P_PLL2, 1,  3),
+	F_MN(320000000, P_PLL2, 2,  5),
+	{ }
+};
+
+/*
+ * MSM8x60-specific GFX3D clocks
+ *
+ * MSM8660 uses different reset bits for the GFX3D banked MND divider:
+ *   - Bank 0: mnctr_reset_bit = 23 (MSM8960 uses 25)
+ *   - Bank 1: mnctr_reset_bit = 22 (MSM8960 uses 24)
+ *
+ * This was verified against the webOS 2.6 kernel source (clock-8x60.c).
+ */
+static struct clk_dyn_rcg gfx3d_src = {
+	.ns_reg[0] = 0x008c,
+	.ns_reg[1] = 0x008c,
+	.md_reg[0] = 0x0084,
+	.md_reg[1] = 0x0088,
+	.bank_reg = 0x0080,
+	.mn[0] = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 23,
+		.mnctr_mode_shift = 9,
+		.n_val_shift = 18,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 22,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 14,
+		.m_val_shift = 4,
+		.width = 4,
+	},
+	.s[0] = {
+		.src_sel_shift = 3,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 11,
+	.freq_tbl = clk_tbl_gfx3d,
+	.clkr = {
+		.enable_reg = 0x0080,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx3d_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx3d_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 4,
+	.clkr = {
+		.enable_reg = 0x0080,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx3d_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&gfx3d_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_ijpeg[] = {
+	F_MN( 27000000, P_PXO,  1, 0),
+	F_MN( 36570000, P_PLL8, 2, 21),
+	F_MN( 54860000, P_PLL8, 1, 7),
+	F_MN( 96000000, P_PLL8, 1, 4),
+	F_MN(109710000, P_PLL8, 2, 7),
+	F_MN(128000000, P_PLL8, 1, 3),
+	F_MN(153600000, P_PLL8, 2, 5),
+	F_MN(200000000, P_PLL2, 1, 4),
+	F_MN(228571000, P_PLL2, 2, 7),
+	{ }
+};
+
+static struct clk_rcg ijpeg_src = {
+	.ns_reg = 0x00a0,
+	.md_reg = 0x009c,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_ijpeg,
+	.clkr = {
+		.enable_reg = 0x0098,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "ijpeg_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch ijpeg_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 24,
+	.clkr = {
+		.enable_reg = 0x0098,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "ijpeg_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&ijpeg_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_jpegd[] = {
+	F_MN( 64000000, P_PLL8, 1, 6),
+	F_MN( 76800000, P_PLL8, 1, 5),
+	F_MN( 96000000, P_PLL8, 1, 4),
+	F_MN(160000000, P_PLL2, 1, 5),
+	F_MN(200000000, P_PLL2, 1, 4),
+	{ }
+};
+
+static struct clk_rcg jpegd_src = {
+	.ns_reg = 0x00ac,
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_jpegd,
+	.clkr = {
+		.enable_reg = 0x00a4,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "jpegd_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch jpegd_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 19,
+	.clkr = {
+		.enable_reg = 0x00a4,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "jpegd_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&jpegd_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_mdp[] = {
+	F_MN(  9600000, P_PLL8, 1, 40),
+	F_MN( 13710000, P_PLL8, 1, 28),
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 29540000, P_PLL8, 1, 13),
+	F_MN( 34910000, P_PLL8, 1, 11),
+	F_MN( 38400000, P_PLL8, 1, 10),
+	F_MN( 59080000, P_PLL8, 2, 13),
+	F_MN( 76800000, P_PLL8, 1,  5),
+	F_MN( 85330000, P_PLL8, 2,  9),
+	F_MN( 96000000, P_PLL8, 1,  4),
+	F_MN(128000000, P_PLL8, 1,  3),
+	F_MN(160000000, P_PLL2, 1,  5),
+	F_MN(177780000, P_PLL2, 2,  9),
+	F_MN(200000000, P_PLL2, 1,  4),
+	{ }
+};
+
+static struct clk_dyn_rcg mdp_src = {
+	.ns_reg[0] = 0x00d0,
+	.ns_reg[1] = 0x00d0,
+	.md_reg[0] = 0x00c4,
+	.md_reg[1] = 0x00c8,
+	.bank_reg = 0x00c0,
+	.mn[0] = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 31,
+		.mnctr_mode_shift = 9,
+		.n_val_shift = 22,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 30,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 14,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.s[0] = {
+		.src_sel_shift = 3,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 11,
+	.freq_tbl = clk_tbl_mdp,
+	.clkr = {
+		.enable_reg = 0x00c0,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch mdp_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 10,
+	.clkr = {
+		.enable_reg = 0x00c0,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&mdp_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch mdp_lut_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 13,
+	.clkr = {
+		.enable_reg = 0x016c,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&mdp_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "mdp_lut_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch mdp_vsync_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 22,
+	.clkr = {
+		.enable_reg = 0x0058,
+		.enable_mask = BIT(6),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_vsync_clk",
+			.parent_data = (const struct clk_parent_data[]){
+				{ .fw_name = "pxo", .name = "pxo_board" },
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+/*
+ * MSM8x60-specific MDP pixel clocks
+ *
+ * MSM8660 uses MD16 register format where M value is in bits [31:16] and
+ * D value (2*N-M) is in bits [15:0]. This differs from MSM8960 which uses
+ * MD8 format with M in bits [23:16] and D in bits [7:0].
+ *
+ * NS register offset is 0x00DC (not 0x00E0 as in MSM8960).
+ */
+static const struct freq_tbl clk_tbl_mdp_pixel[] = {
+	/* Format: { freq, src, pre_div, m, n } - use pre_div like clk_tbl_cam */
+	{  25600000, P_PLL8, 3,   1,    5 },	/* 384 / 3 * 1/5  = 25.6 MHz */
+	{  27000000, P_PXO,  1,   0,    0 },	/* PXO direct      = 27 MHz */
+	{  42667000, P_PLL8, 1,   1,    9 },	/* 384 * 1/9       = 42.667 MHz */
+	{  43192000, P_PLL8, 1,  64,  569 },	/* 384 * 64/569    = 43.192 MHz */
+	{  48000000, P_PLL8, 4,   1,    2 },	/* 384 / 4 * 1/2   = 48 MHz */
+	{  53990000, P_PLL8, 2, 169,  601 },	/* 384 / 2 * 169/601 = 53.99 MHz */
+	{  64000000, P_PLL8, 3,   1,    2 },	/* 384 / 3 * 1/2   = 64 MHz */
+	{  69300000, P_PLL8, 1, 231, 1280 },	/* 384 * 231/1280  = 69.3 MHz - HP TouchPad panel */
+	{  76800000, P_PLL8, 1,   1,    5 },	/* 384 * 1/5       = 76.8 MHz */
+	{  85333000, P_PLL8, 1,   2,    9 },	/* 384 * 2/9       = 85.333 MHz */
+	{  96000000, P_PLL8, 4,   0,    0 },	/* 384 / 4         = 96 MHz */
+	{ 100030000, P_PLL8, 2, 211,  405 },	/* 384 / 2 * 211/405 = 100.03 MHz */
+	{ 106500000, P_PLL8, 1,  71,  256 },	/* 384 * 71/256    = 106.5 MHz */
+	{ 109714000, P_PLL8, 1,   2,    7 },	/* 384 * 2/7       = 109.714 MHz */
+	{ 128000000, P_PLL8, 3,   0,    0 },	/* 384 / 3         = 128 MHz */
+	{ }
+};
+
+static struct clk_rcg mdp_pixel_src = {
+	.ns_reg = 0x00dc,
+	.md_reg = 0x00d8,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 8,		/* Try standard 8-bit format */
+		.width = 8,			/* Try standard 8-bit width */
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_mdp_pixel,
+	.clkr = {
+		.enable_reg = 0x00d4,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_pixel_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,  /* Testing with clk_tbl_cam-style freq_tbl */
+		},
+	},
+};
+
+static struct clk_branch mdp_pixel_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 23,
+	.clkr = {
+		.enable_reg = 0x00d4,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_pixel_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&mdp_pixel_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch mdp_lcdc_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 21,
+	.clkr = {
+		.enable_reg = 0x00d4,
+		.enable_mask = BIT(8),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_lcdc_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&mdp_pixel_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_rot[] = {
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 29540000, P_PLL8, 1, 13),
+	F_MN( 32000000, P_PLL8, 1, 12),
+	F_MN( 38400000, P_PLL8, 1, 10),
+	F_MN( 48000000, P_PLL8, 1,  8),
+	F_MN( 54860000, P_PLL8, 1,  7),
+	F_MN( 64000000, P_PLL8, 1,  6),
+	F_MN( 76800000, P_PLL8, 1,  5),
+	F_MN( 96000000, P_PLL8, 1,  4),
+	F_MN(100000000, P_PLL2, 1,  8),
+	F_MN(114290000, P_PLL2, 2, 14),
+	F_MN(128000000, P_PLL8, 1,  3),
+	F_MN(133330000, P_PLL2, 1,  6),
+	F_MN(160000000, P_PLL2, 1,  5),
+	{ }
+};
+
+static struct clk_dyn_rcg rot_src = {
+	.ns_reg[0] = 0x00e8,
+	.ns_reg[1] = 0x00e8,
+	.md_reg[0] = 0x00e0,
+	.md_reg[1] = 0x00e4,
+	.bank_reg = 0x00e8,
+	.mn[0] = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 25,
+		.mnctr_mode_shift = 9,
+		.n_val_shift = 22,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 24,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 14,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.s[0] = {
+		.src_sel_shift = 3,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 11,
+	.freq_tbl = clk_tbl_rot,
+	.clkr = {
+		.enable_reg = 0x00e0,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "rot_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch rot_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 15,
+	/*
+	 * The rotator core branch is fed through the 'rot' GDSC, which uses
+	 * the legacy footswitch sequence. During rotator_runtime_suspend the
+	 * clock is disabled before genpd collapses the footswitch, and the
+	 * branch halt status does not assert while the legacy footswitch is
+	 * still powered — so clk_branch_toggle() times out with "rot_clk
+	 * status stuck at 'on'". The enable bit is still cleared (the clock
+	 * is gated); only the readback is unreliable, exactly like the
+	 * rot_axi / gfx3d_axi / vcodec_axi branches below. Skip the poll.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x00e0,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "rot_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&rot_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+#define F_TV(f, s, p_r, _m, _n) \
+	{ \
+		.freq = f, \
+		.src = s, \
+		.pre_div = p_r, \
+		.m = _m, \
+		.n = _n, \
+	}
+
+static const struct freq_tbl clk_tbl_tv[] = {
+	F_TV( 25200000, P_HDMI_PLL, 1, 0, 0),
+	F_TV( 27000000, P_HDMI_PLL, 1, 0, 0),
+	F_TV( 27030000, P_HDMI_PLL, 1, 0, 0),
+	F_TV( 74250000, P_HDMI_PLL, 1, 0, 0),
+	F_TV(148500000, P_HDMI_PLL, 1, 0, 0),
+	{ }
+};
+
+static const struct parent_map mmcc_pxo_hdmi_map[] = {
+	{ P_PXO, 0 },
+	{ P_HDMI_PLL, 3 }
+};
+
+static const struct clk_parent_data mmcc_pxo_hdmi[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "hdmipll", .name = "hdmi_pll" },
+};
+
+static struct clk_rcg tv_src = {
+	.ns_reg = 0x00f4,
+	.md_reg = 0x00f0,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 14,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_hdmi_map,
+	},
+	.freq_tbl = clk_tbl_tv,
+	.clkr = {
+		.enable_reg = 0x00ec,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "tv_src",
+			.parent_data = mmcc_pxo_hdmi,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_hdmi),
+			.ops = &clk_rcg_bypass_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch tv_enc_clk = {
+	.halt_reg = 0x01d4,
+	.halt_bit = 9,
+	.clkr = {
+		.enable_reg = 0x00ec,
+		.enable_mask = BIT(8),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&tv_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "tv_enc_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch tv_dac_clk = {
+	.halt_reg = 0x01d4,
+	.halt_bit = 10,
+	.clkr = {
+		.enable_reg = 0x00ec,
+		.enable_mask = BIT(10),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&tv_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "tv_dac_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch mdp_tv_clk = {
+	.halt_reg = 0x01d4,
+	.halt_bit = 12,
+	.clkr = {
+		.enable_reg = 0x00ec,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&tv_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "mdp_tv_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch hdmi_tv_clk = {
+	.halt_reg = 0x01d4,
+	.halt_bit = 11,
+	.clkr = {
+		.enable_reg = 0x00ec,
+		.enable_mask = BIT(12),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&tv_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "hdmi_tv_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch hdmi_app_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 25,
+	.clkr = {
+		.enable_reg = 0x005c,
+		.enable_mask = BIT(11),
+		.hw.init = &(struct clk_init_data){
+			.parent_data = (const struct clk_parent_data[]){
+				{ .fw_name = "pxo", .name = "pxo_board" },
+			},
+			.num_parents = 1,
+			.name = "hdmi_app_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_vcodec[] = {
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 32000000, P_PLL8, 1, 12),
+	F_MN( 48000000, P_PLL8, 1,  8),
+	F_MN( 54860000, P_PLL8, 1,  7),
+	F_MN( 96000000, P_PLL8, 1,  4),
+	F_MN(133330000, P_PLL2, 1,  6),
+	F_MN(200000000, P_PLL2, 1,  4),
+	F_MN(228570000, P_PLL2, 2,  7),
+	{ }
+};
+
+static struct clk_dyn_rcg vcodec_src = {
+	.ns_reg[0] = 0x0100,
+	.ns_reg[1] = 0x0100,
+	.md_reg[0] = 0x00fc,
+	.md_reg[1] = 0x0128,
+	.bank_reg = 0x00f8,
+	.mn[0] = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 31,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 11,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.mn[1] = {
+		.mnctr_en_bit = 10,
+		.mnctr_reset_bit = 30,
+		.mnctr_mode_shift = 11,
+		.n_val_shift = 19,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.s[0] = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.s[1] = {
+		.src_sel_shift = 14,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.mux_sel_bit = 13,
+	.freq_tbl = clk_tbl_vcodec,
+	.clkr = {
+		.enable_reg = 0x00f8,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_dyn_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch vcodec_clk = {
+	.halt_reg = 0x01d0,
+	.halt_bit = 29,
+	.clkr = {
+		.enable_reg = 0x00f8,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&vcodec_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+/*
+ * VPE source is a simple divider+source RCG (no MND), per legacy clock-8x60.c
+ * (NS_DIVSRC(15,12,d,2,0,s), set_rate_nop). The divisor lives in NS[15:12]
+ * (4 bits); 160 MHz = PLL2 / 5. Using F_MN here silently forces pre_div=1 and
+ * drops the divisor into the nonexistent MND, leaving vpe_src grossly
+ * misclocked -> the first VPE register access hangs the AXI bus.
+ */
+static const struct freq_tbl clk_tbl_vpe[] = {
+	F_DIV( 27000000, P_PXO,   1),
+	F_DIV( 34909000, P_PLL8, 11),
+	F_DIV( 38400000, P_PLL8, 10),
+	F_DIV( 64000000, P_PLL8,  6),
+	F_DIV( 76800000, P_PLL8,  5),
+	F_DIV( 96000000, P_PLL8,  4),
+	F_DIV(100000000, P_PLL2,  8),
+	F_DIV(160000000, P_PLL2,  5),
+	{ }
+};
+
+static struct clk_rcg vpe_src = {
+	.ns_reg = 0x0118,
+	.p = {
+		.pre_div_shift = 12,
+		.pre_div_width = 4,	/* NS[15:12], legacy BM(15,12); holds /11 */
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_vpe,
+	.clkr = {
+		.enable_reg = 0x0110,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "vpe_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch vpe_clk = {
+	.halt_reg = 0x01c8,
+	.halt_bit = 28,
+	/*
+	 * Same as rot_clk: the VPE core branch runs through the legacy-
+	 * footswitch 'vpe' GDSC, so its halt status is unreliable at
+	 * runtime-suspend disable time ("vpe_clk status stuck at 'on'").
+	 * The enable bit still gates the clock; skip the readback poll.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0110,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "vpe_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&vpe_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static const struct freq_tbl clk_tbl_vfe[] = {
+	F_MN( 13960000, P_PLL8, 2, 55),
+	F_MN( 27000000, P_PXO,  1,  0),
+	F_MN( 36570000, P_PLL8, 2, 21),
+	F_MN( 38400000, P_PLL8, 2, 20),
+	F_MN( 45180000, P_PLL8, 2, 17),
+	F_MN( 48000000, P_PLL8, 2, 16),
+	F_MN( 54860000, P_PLL8, 1,  7),
+	F_MN( 64000000, P_PLL8, 2, 12),
+	F_MN( 76800000, P_PLL8, 1,  5),
+	F_MN( 96000000, P_PLL8, 2,  8),
+	F_MN(109710000, P_PLL8, 2,  7),
+	F_MN(128000000, P_PLL8, 1,  3),
+	F_MN(153600000, P_PLL8, 2,  5),
+	F_MN(200000000, P_PLL2, 2,  8),
+	F_MN(228570000, P_PLL2, 2,  7),
+	F_MN(266667000, P_PLL2, 1,  3),
+	{ }
+};
+
+static struct clk_rcg vfe_src = {
+	.ns_reg = 0x010c,
+	.md_reg = 0x0108,
+	.mn = {
+		.mnctr_en_bit = 5,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 6,
+		.n_val_shift = 16,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 10,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = mmcc_pxo_pll8_pll2_map,
+	},
+	.freq_tbl = clk_tbl_vfe,
+	.clkr = {
+		.enable_reg = 0x0104,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "vfe_src",
+			.parent_data = mmcc_pxo_pll8_pll2,
+			.num_parents = ARRAY_SIZE(mmcc_pxo_pll8_pll2),
+			.ops = &clk_rcg_ops,
+		},
+	},
+};
+
+static struct clk_branch vfe_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 6,
+	/*
+	 * VFE clock halt status is unreliable when VFE is not actively
+	 * processing data. Use BRANCH_HALT_SKIP to avoid timeouts during
+	 * clock enable before camera streaming starts.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0104,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "vfe_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&vfe_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch vfe_csi0_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 7,
+	/*
+	 * The VFE CSI clock halt status is unreliable when the CSI block
+	 * is not actively receiving data. Use BRANCH_HALT_SKIP to avoid
+	 * timeouts during clock enable.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0104,
+		.enable_mask = BIT(12),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&vfe_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "vfe_csi0_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+/*
+ * VFE CSI1 clock - enables CSI1 to VFE data path.
+ * Legacy kernel had separate CSI0_VFE (BIT 12) and CSI1_VFE (BIT 10).
+ */
+static struct clk_branch vfe_csi1_clk = {
+	.halt_reg = 0x01cc,
+	.halt_bit = 8,
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0104,
+		.enable_mask = BIT(10),
+		.hw.init = &(struct clk_init_data){
+			.parent_hws = (const struct clk_hw*[]){
+				&vfe_src.clkr.hw
+			},
+			.num_parents = 1,
+			.name = "vfe_csi1_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch gmem_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 6,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(24),
+		.hw.init = &(struct clk_init_data){
+			.name = "gmem_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch ijpeg_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 4,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(21),
+		.hw.init = &(struct clk_init_data){
+			.name = "ijpeg_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch mmss_imem_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 7,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(22),
+		.hw.init = &(struct clk_init_data){
+			.name = "mmss_imem_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch jpegd_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 5,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(25),
+		.hw.init = &(struct clk_init_data){
+			.name = "jpegd_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vcodec_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 3,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(19),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vcodec_axi_a_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 26,
+	.clkr = {
+		.enable_reg = 0x0020,
+		.enable_mask = BIT(25),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_axi_a_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vcodec_axi_b_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 25,
+	.clkr = {
+		.enable_reg = 0x0020,
+		.enable_mask = BIT(26),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_axi_b_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vfe_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 0,
+	/*
+	 * VFE clocks don't properly report halt status when the VFE power
+	 * domain is off. Skip halt checking to avoid enable failures.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(18),
+		.hw.init = &(struct clk_init_data){
+			.name = "vfe_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vpe_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 1,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(26),
+		.hw.init = &(struct clk_init_data){
+			.name = "vpe_axi_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_IS_CRITICAL,
+		},
+	},
+};
+
+static struct clk_branch mdp_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 8,
+	.halt_check = BRANCH_HALT_DELAY,
+	.clkr = {
+		.enable_reg = 0x0018,
+		.enable_mask = BIT(23),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch rot_axi_clk = {
+	.halt_reg = 0x01d8,
+	.halt_bit = 2,
+	/*
+	 * The rotator AXI clock shares the MMSS fabric with MDP. While MDP is
+	 * scanning out to the display the fabric never idles, so this branch
+	 * cannot halt and its status stays stuck at 'on'. Without skipping the
+	 * halt check, every rotator runtime-PM suspend triggers a
+	 * "rot_axi_clk status stuck at 'on'" WARN storm (same class as
+	 * gfx3d_axi_clk / vcodec_axi_b_clk). Skip the halt poll.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0020,
+		.enable_mask = BIT(22),
+		.hw.init = &(struct clk_init_data){
+			.name = "rot_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx3d_axi_clk = {
+	.halt_reg = 0x01e8,
+	.halt_bit = 21,
+	/*
+	 * This branch clock shares the MMSS fabric with MDP. When MDP is
+	 * actively scanning out to the display, the fabric never idles,
+	 * preventing this clock from halting. Use BRANCH_HALT_SKIP to avoid
+	 * the "status stuck at 'on'" warning during GPU runtime PM suspend.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0244,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx3d_axi_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch amp_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 18,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(24),
+		.hw.init = &(struct clk_init_data){
+			.name = "amp_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch csi0_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 16,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(7),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi0_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+/*
+ * CSI1 AHB clock - separate from CSI0.
+ * Legacy kernel had CSI0_PCLK (BIT 7) and CSI1_PCLK (BIT 20).
+ */
+static struct clk_branch csi1_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 17,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(20),
+		.hw.init = &(struct clk_init_data){
+			.name = "csi1_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch dsi_m_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 19,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi_m_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch dsi_s_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 21,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(18),
+		.hw.init = &(struct clk_init_data){
+			.name = "dsi_s_ahb_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_IS_CRITICAL,
+		},
+	},
+};
+
+static struct clk_branch gfx2d0_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 2,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(19),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d0_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx2d1_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 3,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(2),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx2d1_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch gfx3d_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 4,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(3),
+		.hw.init = &(struct clk_init_data){
+			.name = "gfx3d_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch hdmi_m_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 5,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(14),
+		.hw.init = &(struct clk_init_data){
+			.name = "hdmi_m_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch hdmi_s_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 6,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(4),
+		.hw.init = &(struct clk_init_data){
+			.name = "hdmi_s_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch ijpeg_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 9,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(5),
+		.hw.init = &(struct clk_init_data){
+			.name = "ijpeg_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch jpegd_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 7,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(21),
+		.hw.init = &(struct clk_init_data){
+			.name = "jpegd_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch mdp_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 11,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(10),
+		.hw.init = &(struct clk_init_data){
+			.name = "mdp_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch mmss_imem_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 12,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(6),
+		.hw.init = &(struct clk_init_data){
+			.name = "mmss_imem_ahb_clk",
+			.ops = &clk_branch_ops,
+			.flags = CLK_IS_CRITICAL,
+		},
+	},
+};
+
+static struct clk_branch rot_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 13,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(12),
+		.hw.init = &(struct clk_init_data){
+			.name = "rot_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch smmu_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 22,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(15),
+		.hw.init = &(struct clk_init_data){
+			.name = "smmu_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch tv_enc_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 23,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(25),
+		.hw.init = &(struct clk_init_data){
+			.name = "tv_enc_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vcodec_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 10,
+	/*
+	 * The halt bit at 0x01dc[10] does not settle within the standard
+	 * 200 µs poll window when this branch is disabled (e.g. on VIDC
+	 * runtime suspend), producing "vcodec_ahb_clk status stuck at 'on'"
+	 * WARN traces with an EBUSY return.  Skip the halt check like
+	 * vfe_ahb_clk below — the AHB bus stays clocked while other MMSS
+	 * peripherals share it, so the halt bit can't be relied on.
+	 */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(11),
+		.hw.init = &(struct clk_init_data){
+			.name = "vcodec_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vfe_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 14,
+	/* Same as vfe_axi_clk - skip halt check */
+	.halt_check = BRANCH_HALT_SKIP,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(13),
+		.hw.init = &(struct clk_init_data){
+			.name = "vfe_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_branch vpe_ahb_clk = {
+	.halt_reg = 0x01dc,
+	.halt_bit = 15,
+	.clkr = {
+		.enable_reg = 0x0008,
+		.enable_mask = BIT(16),
+		.hw.init = &(struct clk_init_data){
+			.name = "vpe_ahb_clk",
+			.ops = &clk_branch_ops,
+		},
+	},
+};
+
+static struct clk_regmap *mmcc_msm8660_clks[] = {
+	[TV_ENC_AHB_CLK] = &tv_enc_ahb_clk.clkr,
+	[AMP_AHB_CLK] = &amp_ahb_clk.clkr,
+	[JPEGD_AHB_CLK] = &jpegd_ahb_clk.clkr,
+	[GFX2D0_AHB_CLK] = &gfx2d0_ahb_clk.clkr,
+	[DSI_S_AHB_CLK] = &dsi_s_ahb_clk.clkr,
+	[VPE_AHB_CLK] = &vpe_ahb_clk.clkr,
+	[SMMU_AHB_CLK] = &smmu_ahb_clk.clkr,
+	[HDMI_M_AHB_CLK] = &hdmi_m_ahb_clk.clkr,
+	[VFE_AHB_CLK] = &vfe_ahb_clk.clkr,
+	[ROT_AHB_CLK] = &rot_ahb_clk.clkr,
+	[VCODEC_AHB_CLK] = &vcodec_ahb_clk.clkr,
+	[MDP_AHB_CLK] = &mdp_ahb_clk.clkr,
+	[DSI_M_AHB_CLK] = &dsi_m_ahb_clk.clkr,
+	[CSI0_AHB_CLK] = &csi0_ahb_clk.clkr,
+	[CSI1_AHB_CLK] = &csi1_ahb_clk.clkr,
+	[MMSS_IMEM_AHB_CLK] = &mmss_imem_ahb_clk.clkr,
+	[IJPEG_AHB_CLK] = &ijpeg_ahb_clk.clkr,
+	[HDMI_S_AHB_CLK] = &hdmi_s_ahb_clk.clkr,
+	[GFX3D_AHB_CLK] = &gfx3d_ahb_clk.clkr,
+	[GFX2D1_AHB_CLK] = &gfx2d1_ahb_clk.clkr,
+	[JPEGD_AXI_CLK] = &jpegd_axi_clk.clkr,
+	[GMEM_AXI_CLK] = &gmem_axi_clk.clkr,
+	[MDP_AXI_CLK] = &mdp_axi_clk.clkr,
+	[MMSS_IMEM_AXI_CLK] = &mmss_imem_axi_clk.clkr,
+	[IJPEG_AXI_CLK] = &ijpeg_axi_clk.clkr,
+	[GFX3D_AXI_CLK] = &gfx3d_axi_clk.clkr,
+	[VCODEC_AXI_CLK] = &vcodec_axi_clk.clkr,
+	[VFE_AXI_CLK] = &vfe_axi_clk.clkr,
+	[VPE_AXI_CLK] = &vpe_axi_clk.clkr,
+	[ROT_AXI_CLK] = &rot_axi_clk.clkr,
+	[VCODEC_AXI_A_CLK] = &vcodec_axi_a_clk.clkr,
+	[VCODEC_AXI_B_CLK] = &vcodec_axi_b_clk.clkr,
+	[CSI0_SRC] = &csi0_src.clkr,
+	[CSI0_CLK] = &csi0_clk.clkr,
+	[CSI0_PHY_CLK] = &csi0_phy_clk.clkr,
+	[CSI1_SRC] = &csi1_src.clkr,
+	[CSI1_CLK] = &csi1_clk.clkr,
+	[CSI1_PHY_CLK] = &csi1_phy_clk.clkr,
+	[DSI_SRC] = &dsi1_src.clkr,
+	[DSI_CLK] = &dsi1_clk.clkr,
+	[CSI_PIX_CLK] = &csi_pix_clk.clkr,
+	[CSI_RDI_CLK] = &csi_rdi_clk.clkr,
+	[MDP_VSYNC_CLK] = &mdp_vsync_clk.clkr,
+	[HDMI_APP_CLK] = &hdmi_app_clk.clkr,
+	[GFX2D0_SRC] = &gfx2d0_src.clkr,
+	[GFX2D0_CLK] = &gfx2d0_clk.clkr,
+	[GFX2D1_SRC] = &gfx2d1_src.clkr,
+	[GFX2D1_CLK] = &gfx2d1_clk.clkr,
+	[GFX3D_SRC] = &gfx3d_src.clkr,
+	[GFX3D_CLK] = &gfx3d_clk.clkr,
+	[IJPEG_SRC] = &ijpeg_src.clkr,
+	[IJPEG_CLK] = &ijpeg_clk.clkr,
+	[JPEGD_SRC] = &jpegd_src.clkr,
+	[JPEGD_CLK] = &jpegd_clk.clkr,
+	[MDP_SRC] = &mdp_src.clkr,
+	[MDP_CLK] = &mdp_clk.clkr,
+	[MDP_LUT_CLK] = &mdp_lut_clk.clkr,
+	[MDP_PIXEL_SRC] = &mdp_pixel_src.clkr,
+	[MDP_PIXEL_CLK] = &mdp_pixel_clk.clkr,
+	[MDP_LCDC_CLK] = &mdp_lcdc_clk.clkr,
+	[DSI1_BYTE_SRC] = &dsi1_byte_src.clkr,
+	[DSI1_BYTE_CLK] = &dsi1_byte_clk.clkr,
+	[DSI1_ESC_SRC] = &dsi1_esc_src.clkr,
+	[DSI1_ESC_CLK] = &dsi1_esc_clk.clkr,
+	[ROT_SRC] = &rot_src.clkr,
+	[ROT_CLK] = &rot_clk.clkr,
+	[TV_ENC_CLK] = &tv_enc_clk.clkr,
+	[TV_DAC_CLK] = &tv_dac_clk.clkr,
+	[HDMI_TV_CLK] = &hdmi_tv_clk.clkr,
+	[MDP_TV_CLK] = &mdp_tv_clk.clkr,
+	[TV_SRC] = &tv_src.clkr,
+	[VCODEC_SRC] = &vcodec_src.clkr,
+	[VCODEC_CLK] = &vcodec_clk.clkr,
+	[VFE_SRC] = &vfe_src.clkr,
+	[VFE_CLK] = &vfe_clk.clkr,
+	[VFE_CSI0_CLK] = &vfe_csi0_clk.clkr,
+	[VFE_CSI1_CLK] = &vfe_csi1_clk.clkr,
+	[VPE_SRC] = &vpe_src.clkr,
+	[VPE_CLK] = &vpe_clk.clkr,
+	[DSI_PIXEL_SRC] = &dsi1_pixel_src.clkr,
+	[DSI_PIXEL_CLK] = &dsi1_pixel_clk.clkr,
+	[CAMCLK0_SRC] = &camclk0_src.clkr,
+	[CAMCLK0_CLK] = &camclk0_clk.clkr,
+	[CAMCLK1_SRC] = &camclk1_src.clkr,
+	[CAMCLK1_CLK] = &camclk1_clk.clkr,
+	[CSIPHYTIMER_SRC] = &csiphytimer_src.clkr,
+	[CSIPHY1_TIMER_CLK] = &csiphy1_timer_clk.clkr,
+	[CSIPHY0_TIMER_CLK] = &csiphy0_timer_clk.clkr,
+	[PLL2] = &pll2.clkr,
+};
+
+static const struct qcom_reset_map mmcc_msm8660_resets[] = {
+	[GFX3D_AXI_RESET] = { 0x0208, 17 },
+	[VPE_AXI_RESET] = { 0x0208, 15 },
+	[IJPEG_AXI_RESET] = { 0x0208, 14 },
+	[MPD_AXI_RESET] = { 0x0208, 13 },
+	[VFE_AXI_RESET] = { 0x0208, 9 },
+	[SP_AXI_RESET] = { 0x0208, 8 },
+	[VCODEC_AXI_RESET] = { 0x0208, 7 },
+	[ROT_AXI_RESET] = { 0x0208, 6 },
+	[VCODEC_AXI_A_RESET] = { 0x0208, 5 },
+	[VCODEC_AXI_B_RESET] = { 0x0208, 4 },
+	[FAB_S3_AXI_RESET] = { 0x0208, 3 },
+	[FAB_S2_AXI_RESET] = { 0x0208, 2 },
+	[FAB_S1_AXI_RESET] = { 0x0208, 1 },
+	[FAB_S0_AXI_RESET] = { 0x0208 },
+	[SMMU_GFX3D_ABH_RESET] = { 0x020c, 31 },
+	[SMMU_VPE_AHB_RESET] = { 0x020c, 30 },
+	[SMMU_VFE_AHB_RESET] = { 0x020c, 29 },
+	[SMMU_ROT_AHB_RESET] = { 0x020c, 28 },
+	[SMMU_VCODEC_B_AHB_RESET] = { 0x020c, 27 },
+	[SMMU_VCODEC_A_AHB_RESET] = { 0x020c, 26 },
+	[SMMU_MDP1_AHB_RESET] = { 0x020c, 25 },
+	[SMMU_MDP0_AHB_RESET] = { 0x020c, 24 },
+	[SMMU_JPEGD_AHB_RESET] = { 0x020c, 23 },
+	[SMMU_IJPEG_AHB_RESET] = { 0x020c, 22 },
+	[APU_AHB_RESET] = { 0x020c, 18 },
+	[CSI_AHB_RESET] = { 0x020c, 17 },
+	[TV_ENC_AHB_RESET] = { 0x020c, 15 },
+	[VPE_AHB_RESET] = { 0x020c, 14 },
+	[FABRIC_AHB_RESET] = { 0x020c, 13 },
+	[GFX3D_AHB_RESET] = { 0x020c, 10 },
+	[HDMI_AHB_RESET] = { 0x020c, 9 },
+	[MSSS_IMEM_AHB_RESET] = { 0x020c, 8 },
+	[IJPEG_AHB_RESET] = { 0x020c, 7 },
+	[DSI_M_AHB_RESET] = { 0x020c, 6 },
+	[DSI_S_AHB_RESET] = { 0x020c, 5 },
+	[JPEGD_AHB_RESET] = { 0x020c, 4 },
+	[MDP_AHB_RESET] = { 0x020c, 3 },
+	[ROT_AHB_RESET] = { 0x020c, 2 },
+	[VCODEC_AHB_RESET] = { 0x020c, 1 },
+	[VFE_AHB_RESET] = { 0x020c, 0 },
+	[CSIPHY0_RESET] = { 0x0210, 29 },
+	[CSIPHY1_RESET] = { 0x0210, 28 },
+	[CSI_RDI_RESET] = { 0x0210, 27 },
+	[CSI_PIX_RESET] = { 0x0210, 26 },
+	[VFE_CSI_RESET] = { 0x0210, 24 },
+	[MDP_RESET] = { 0x0210, 21 },
+	[AMP_RESET] = { 0x0210, 20 },
+	[JPEGD_RESET] = { 0x0210, 19 },
+	[CSI1_RESET] = { 0x0210, 18 },
+	[VPE_RESET] = { 0x0210, 17 },
+	[MMSS_FABRIC_RESET] = { 0x0210, 16 },
+	[VFE_RESET] = { 0x0210, 15 },
+	[GFX3D_RESET] = { 0x0210, 12 },
+	[HDMI_RESET] = { 0x0210, 11 },
+	[MMSS_IMEM_RESET] = { 0x0210, 10 },
+	[IJPEG_RESET] = { 0x0210, 9 },
+	[CSI0_RESET] = { 0x0210, 8 },
+	[DSI_RESET] = { 0x0210, 7 },
+	[VCODEC_RESET] = { 0x0210, 6 },
+	[MDP_TV_RESET] = { 0x0210, 4 },
+	[MDP_VSYNC_RESET] = { 0x0210, 3 },
+	[ROT_RESET] = { 0x0210, 2 },
+	[TV_HDMI_RESET] = { 0x0210, 1 },
+	[TV_ENC_RESET] = { 0x0210, 0 },
+};
+
+/*
+ * MSM8x60 legacy footswitches.
+ * These use a different register layout than modern GDSCs:
+ * - Bit 8: ENABLE (set to enable power)
+ * - Bit 5: CLAMP (set to clamp I/O)
+ * - No status bit, requires timed delays
+ */
+static struct gdsc gfx2d0_gdsc = {
+	.gdscr = 0x0180,
+	.resets = (unsigned int []){ GFX2D0_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "gfx2d0",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	.flags = LEGACY_FOOTSWITCH | SW_RESET,
+};
+
+static struct gdsc gfx2d1_gdsc = {
+	.gdscr = 0x0184,
+	.resets = (unsigned int []){ GFX2D1_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "gfx2d1",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	.flags = LEGACY_FOOTSWITCH | SW_RESET,
+};
+
+static struct gdsc gfx3d_gdsc = {
+	.gdscr = 0x0188,
+	/*
+	 * GFX3D (Adreno 220) requires the core reset to be toggled on every
+	 * power-on, in addition to the AHB reset. The legacy MSM8x60
+	 * footswitch driver (mach-msm/footswitch-8x60.c) does exactly this:
+	 * after powering the GFX3D rail it issues an extra
+	 * clk_reset(core_clk, ASSERT/DEASSERT). Without it the Adreno 220
+	 * core (parameter cache) comes up free-running, producing the
+	 * deterministic period-8 render cycle on the HP TouchPad. Listing
+	 * GFX3D_RESET here lets the GDSC framework assert/deassert it around
+	 * the rail charge (see gdsc_enable LEGACY_FOOTSWITCH path), which is
+	 * the framework-correct equivalent of the legacy toggle.
+	 */
+	.resets = (unsigned int []){ GFX3D_AHB_RESET, GFX3D_RESET },
+	.reset_count = 2,
+	.pd = {
+		.name = "gfx3d",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	/*
+	 * RPM_ALWAYS_ON: keep the GFX3D footswitch powered across runtime PM
+	 * idle (clocks still gate), collapsing it only on system suspend. The
+	 * Adreno 220 shares the MMSS AXI fabric with the MDP4 display; cold-
+	 * cycling this footswitch on every GPU idle forces an a2xx_hw_init
+	 * microcode reload whose MMIO burst can stall the shared bus when it
+	 * lands during an MDP client-switch underrun, hard-hanging the SoC.
+	 * Mirrors legacy KGSL, which parked the GPU in SLEEP (rail up, clocks
+	 * gated) during use and only power-collapsed (SLUMBER) on suspend.
+	 */
+	.flags = LEGACY_FOOTSWITCH | SW_RESET | RPM_ALWAYS_ON,
+};
+
+static struct gdsc ijpeg_gdsc = {
+	.gdscr = 0x01a0,
+	/*
+	 * IJPEG (Gemini) requires the AXI and CORE resets to be toggled on
+	 * every power-on, in addition to the AHB reset. The legacy
+	 * mach-msm/footswitch-8x60.c does exactly this: setup_clocks ->
+	 * clk_reset(axi, ASSERT) + clk_reset(ahb, ASSERT) + clk_reset(core,
+	 * ASSERT) -> udelay -> rail charge -> deassert in reverse -> extra
+	 * core ASSERT/DEASSERT toggle. HTC and Samsung MSM8660 trees use the
+	 * same sequence. With only the AHB reset toggled the JPEG register
+	 * file comes up healthy (CPU reads/writes look fine) but the FE's
+	 * AXI-side address generator and burst sequencer stay in whatever
+	 * sub-state QSBL left them -- typically wedged waiting for an
+	 * AR-channel ready that never comes. The WE survives because writes
+	 * are post-and-forget and don't drive AR. Symptom: FE reads return
+	 * idle 0x80 regardless of buffer contents while WE writes succeed.
+	 * Listing AXI + CORE here lets the GDSC framework assert/deassert
+	 * them around rail charge (gdsc_enable LEGACY_FOOTSWITCH | SW_RESET
+	 * path), matching the legacy/HTC/Samsung convergent recipe and
+	 * mirroring the gfx3d_gdsc precedent above.
+	 */
+	.resets = (unsigned int []){
+		IJPEG_AXI_RESET,
+		IJPEG_AHB_RESET,
+		IJPEG_RESET,
+	},
+	.reset_count = 3,
+	.pd = {
+		.name = "ijpeg",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	.flags = LEGACY_FOOTSWITCH | SW_RESET,
+};
+
+static struct gdsc mdp_gdsc = {
+	.gdscr = 0x0190,
+	.resets = (unsigned int []){ MDP_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "mdp",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	.flags = LEGACY_FOOTSWITCH | SW_RESET,
+};
+
+static struct gdsc rot_gdsc = {
+	.gdscr = 0x018c,
+	.resets = (unsigned int []){ ROT_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "rot",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	/*
+	 * Skip SW_RESET during power domain enable, similar to VFE_GDSC.
+	 * Asserting ROT_AHB_RESET during GDSC enable may cause a glitch
+	 * affecting other MMSS peripherals during early boot.
+	 *
+	 * RPM_ALWAYS_ON: the rotator is an m2m block that pm_runtime-cycles per
+	 * job; without this its footswitch re-enables on the shared MMSS fabric
+	 * on every rotation burst, glitching MDP scanout. Keep it powered across
+	 * runtime idle (clocks still gate); collapse only on system suspend.
+	 */
+	.flags = LEGACY_FOOTSWITCH | RPM_ALWAYS_ON,
+};
+
+static struct gdsc ved_gdsc = {
+	.gdscr = 0x0194,
+	.resets = (unsigned int []){ VCODEC_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "ved",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	.flags = LEGACY_FOOTSWITCH | SW_RESET,
+};
+
+static struct gdsc vfe_gdsc = {
+	.gdscr = 0x0198,
+	.resets = (unsigned int []){ VFE_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "vfe",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	/*
+	 * Skip SW_RESET during power domain enable. Asserting VFE_AHB_RESET
+	 * during GDSC enable appears to cause a glitch that affects other
+	 * MMSS peripherals (specifically MDP display). The VFE driver performs
+	 * its own software reset via vfe_reset() after enabling clocks, so
+	 * the GDSC-level reset is not strictly required.
+	 *
+	 * Note: VFE is session-scoped (powered at streamon, collapsed at
+	 * streamoff) so it only re-enables once per capture session -- not
+	 * worth RPM_ALWAYS_ON (cf. rot_gdsc, which cycles per m2m job).
+	 */
+	.flags = LEGACY_FOOTSWITCH,
+};
+
+static struct gdsc vpe_gdsc = {
+	.gdscr = 0x019c,
+	.resets = (unsigned int []){ VPE_AHB_RESET },
+	.reset_count = 1,
+	.pd = {
+		.name = "vpe",
+	},
+	.pwrsts = PWRSTS_OFF_ON,
+	/*
+	 * Skip SW_RESET during power domain enable, similar to VFE_GDSC.
+	 * Asserting VPE_AHB_RESET during GDSC enable may cause a glitch
+	 * affecting other MMSS peripherals during early boot.
+	 *
+	 * Note: VPE is session-scoped (powered at streamon, collapsed at
+	 * streamoff) so it only re-enables once per session -- not worth
+	 * RPM_ALWAYS_ON (cf. rot_gdsc, which cycles per m2m job).
+	 */
+	.flags = LEGACY_FOOTSWITCH,
+};
+
+static struct gdsc *mmcc_msm8660_gdscs[] = {
+	[GFX2D0_GDSC] = &gfx2d0_gdsc,
+	[GFX2D1_GDSC] = &gfx2d1_gdsc,
+	[GFX3D_GDSC] = &gfx3d_gdsc,
+	[IJPEG_GDSC] = &ijpeg_gdsc,
+	[MDP_GDSC] = &mdp_gdsc,
+	[ROT_GDSC] = &rot_gdsc,
+	[VED_GDSC] = &ved_gdsc,
+	[VFE_GDSC] = &vfe_gdsc,
+	[VPE_GDSC] = &vpe_gdsc,
+};
+
+static const struct regmap_config mmcc_msm8660_regmap_config = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.max_register	= 0x334,
+	.fast_io	= true,
+};
+
+static const struct qcom_cc_desc mmcc_msm8660_desc = {
+	.config = &mmcc_msm8660_regmap_config,
+	.clks = mmcc_msm8660_clks,
+	.num_clks = ARRAY_SIZE(mmcc_msm8660_clks),
+	.resets = mmcc_msm8660_resets,
+	.num_resets = ARRAY_SIZE(mmcc_msm8660_resets),
+	.gdscs = mmcc_msm8660_gdscs,
+	.num_gdscs = ARRAY_SIZE(mmcc_msm8660_gdscs),
+};
+
+static const struct of_device_id mmcc_msm8660_match_table[] = {
+	{ .compatible = "qcom,mmcc-msm8660", .data = &mmcc_msm8660_desc },
+	{ .compatible = "qcom,mmcc-apq8060", .data = &mmcc_msm8660_desc },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mmcc_msm8660_match_table);
+
+/*
+ * MSM8660 MMCC register offsets for initialization.
+ * Based on webOS kernel arch/arm/mach-msm/clock-8x60.c
+ */
+
+/* Reset registers - safe to deassert */
+#define SW_RESET_ALL_REG	0x0204
+#define SW_RESET_AHB_REG	0x020c
+#define SW_RESET_AXI_REG	0x0208
+#define SW_RESET_CORE_REG	0x0210
+
+/* AHB enable registers - contain both control bits and clock enables */
+#define AHB_EN_REG		0x0008
+#define AHB_EN2_REG		0x0038
+
+/* AXI enable registers - control AXI bus paths for memory access */
+#define MAXI_EN_REG		0x0018
+#define MAXI_EN3_REG		0x002c
+#define SAXI_EN_REG		0x01d8
+
+/* Misc CC registers */
+#define CSI_CC_REG		0x0040
+#define MISC_CC_REG		0x0058
+#define MISC_CC2_REG		0x005c
+
+/* CC registers - FORCE_CORE_ON in upper bits, enable in lower bits */
+#define GFX2D0_CC_REG		0x0060
+#define GFX2D1_CC_REG		0x0074
+#define GFX3D_CC_REG		0x0080
+#define IJPEG_CC_REG		0x0098
+#define JPEGD_CC_REG		0x00a4
+#define MDP_CC_REG		0x00c0
+#define PIXEL_CC_REG		0x00d4
+#define PIXEL_CC2_REG		0x0120  /* Not used as enable_reg */
+#define ROT_CC_REG		0x00e0
+#define TV_CC_REG		0x00ec
+#define TV_CC2_REG		0x0124  /* Not used as enable_reg */
+#define VCODEC_CC_REG		0x00f8
+#define VFE_CC_REG		0x0104
+#define VPE_CC_REG		0x0110
+
+/*
+ * Mask for FORCE_CORE_ON and sleep/wakeup delay bits in CC registers.
+ * Only touches upper bits to avoid conflicting with clock enable bits
+ * in the lower portion of the register.
+ */
+#define CC_FORCE_CORE_ON_MASK	0xe0ff0000
+#define CC_FORCE_CORE_ON_VAL	0x80ff0000  /* FORCE_CORE_ON + 0xFF delays */
+#define VCODEC_FORCE_CORE_ON	0xc0ff0000  /* VCODEC uses different bits */
+
+static void mmcc_msm8660_init_hw(struct regmap *regmap)
+{
+	u32 val;
+
+	/*
+	 * Configure PLL2 (MM_PLL1) to 800 MHz for VFE and other MM clocks.
+	 *
+	 * PLL rate = PXO * (L + M/N) = 27 MHz * (29 + 17/27) = 800 MHz
+	 *
+	 * The bootloader (moboot) enables PLL2 but does not configure L/M/N,
+	 * leaving it at whatever values are in the hardware. We must set it
+	 * properly for VFE to reach 228 MHz. Without this, VFE gets ~66 MHz.
+	 */
+	regmap_read(regmap, 0x320, &val);  /* PLL2 L value */
+	if (val != 29) {
+		u32 m_val, n_val;
+		/* PLL2 is not configured for 800 MHz, configure it */
+		regmap_read(regmap, 0x324, &m_val);
+		regmap_read(regmap, 0x328, &n_val);
+		pr_info("mmcc-msm8660: Configuring PLL2 for 800 MHz (was L=%u M=%u N=%u)\n",
+			val, m_val, n_val);
+
+		/* Disable PLL output first */
+		regmap_update_bits(regmap, 0x31c, BIT(0), 0);
+		udelay(10);
+
+		/* Set L=29, M=17, N=27 for 800 MHz from 27 MHz PXO */
+		regmap_write(regmap, 0x320, 29);   /* L value */
+		regmap_write(regmap, 0x324, 17);   /* M value */
+		regmap_write(regmap, 0x328, 27);   /* N value */
+
+		/* Configure PLL: enable main output, set VCO */
+		regmap_write(regmap, 0x32c, 0x00800000);
+
+		/* Enable PLL: bypass off, reset deassert, output enable */
+		regmap_update_bits(regmap, 0x31c, BIT(1), BIT(1));  /* Bypass off */
+		udelay(10);
+		regmap_update_bits(regmap, 0x31c, BIT(2), BIT(2));  /* Reset deassert */
+		udelay(50);
+		regmap_update_bits(regmap, 0x31c, BIT(0), BIT(0));  /* Output enable */
+		udelay(50);
+
+		/* Verify PLL locked (status register at 0x334, bit 16) */
+		regmap_read(regmap, 0x334, &val);
+		if (val & BIT(16))
+			pr_info("mmcc-msm8660: PLL2 locked at 800 MHz\n");
+		else
+			pr_warn("mmcc-msm8660: PLL2 may not be locked (status=0x%x)\n", val);
+	}
+
+	/*
+	 * MSM8660 MMCC hardware initialization based on webOS kernel.
+	 *
+	 * WebOS sets specific control bits in AHB_EN_REG:
+	 *   rmwreg(0x00000003, AHB_EN_REG, 0x0F7FFFFF);
+	 * BIT(0) and BIT(1) are control bits (FPB enable, HW gating disable),
+	 * NOT clock enables. Clock enables start at BIT(2) and above.
+	 *
+	 * We initialize these control bits but leave clock enable bits
+	 * for the clock framework to manage.
+	 */
+
+	/*
+	 * Set FPB enable and disable HW gating in AHB_EN_REG.
+	 * BIT(0) = FPB clock enable
+	 * BIT(1) = Disable HW gating for all AHB clocks
+	 * These are required for CSI register writes to work.
+	 */
+	regmap_update_bits(regmap, AHB_EN_REG, 0x3, 0x3);
+
+	/*
+	 * AHB_EN2_REG contains additional control bits including
+	 * VFE_AHB FORCE_CORE_ON to prevent memory collapse.
+	 * WebOS: rmwreg(0x000007F9, AHB_EN2_REG, 0x7FFFBFFF);
+	 */
+	regmap_update_bits(regmap, AHB_EN2_REG, 0x7fffbfff, 0x000007f9);
+
+	/*
+	 * Initialize AXI bus registers for memory access paths.
+	 * These enable HW gating and set FORCE_CORE_ON bits for AXI clocks.
+	 * WebOS: rmwreg(0x000307F9, MAXI_EN_REG, 0x0FFFFFFF);
+	 *        writel(0x3FE7FCFF, MAXI_EN3_REG);
+	 *        writel(0x000001D8, SAXI_EN_REG);
+	 * Note: MAXI_EN2_REG is owned by RPM, don't touch it.
+	 */
+	regmap_update_bits(regmap, MAXI_EN_REG, 0x0fffffff, 0x000307f9);
+	regmap_write(regmap, MAXI_EN3_REG, 0x3fe7fcff);
+	regmap_write(regmap, SAXI_EN_REG, 0x000001d8);
+
+	/* Deassert all MM resets */
+	regmap_write(regmap, SW_RESET_ALL_REG, 0);
+	regmap_write(regmap, SW_RESET_AHB_REG, 0);
+	regmap_write(regmap, SW_RESET_AXI_REG, 0);
+	regmap_write(regmap, SW_RESET_CORE_REG, 0);
+
+	/*
+	 * Initialize CSI and MISC CC registers.
+	 *
+	 * CSI_CC_REG (0x040): webOS camera dump shows 0x85
+	 *   - Bit 0: CSI digital wrapper 0 enable
+	 *   - Bit 2: CSI digital wrapper 1 enable
+	 *   - Bit 7: Unknown (possibly global CSI enable)
+	 * Without these bits set, CSIPHY data never reaches VFE.
+	 *
+	 * MISC_CC_REG (0x058): webOS camera dump shows 0x0400
+	 *   - Bit 10: CSI1-to-VFE async bridge enable
+	 * MSM8660 has direct CSIPHY->VFE path, NOT the csi_pix/csi_rdi mux
+	 * architecture found in VFE3.2+. The csi_pix_clk/csi_rdi_clk (bits
+	 * 25-26 and 12-13) don't exist on this SoC - only bit 10 matters.
+	 *
+	 * MISC_CC2_REG: webOS shows 0x004007fd - additional enables.
+	 */
+	regmap_write(regmap, CSI_CC_REG, 0x00000085);
+	regmap_update_bits(regmap, MISC_CC_REG, 0xfefff7ff, 0x00000400);
+	regmap_update_bits(regmap, MISC_CC2_REG, 0xffff7fff, 0x000007fd);
+	/* Set dsi_byte_clk src to DSI PHY PLL, hdmi_app_clk src to PXO */
+	regmap_update_bits(regmap, MISC_CC2_REG, 0x00424003, 0x00400001);
+
+	/*
+	 * Set FORCE_CORE_ON bits in all multimedia CC registers to prevent
+	 * core memories from being collapsed when clocks are halted.
+	 *
+	 * Value 0x80ff0000 sets:
+	 *   - Bit 31: FORCE_CORE_ON
+	 *   - Bits 16-23: Safe sleep/wakeup delay values (0xFF)
+	 *
+	 * We use regmap_update_bits to only touch upper bits, avoiding
+	 * conflict with clock enable bits in the lower portion.
+	 */
+
+	/* Graphics */
+	regmap_update_bits(regmap, GFX2D0_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_update_bits(regmap, GFX2D1_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_update_bits(regmap, GFX3D_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+
+	/* Display */
+	regmap_update_bits(regmap, MDP_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_update_bits(regmap, PIXEL_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_write(regmap, PIXEL_CC2_REG, 0x000004ff);
+	regmap_update_bits(regmap, TV_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_write(regmap, TV_CC2_REG, 0x000004ff);
+	regmap_update_bits(regmap, ROT_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+
+	/* Video */
+	regmap_update_bits(regmap, VCODEC_CC_REG, CC_FORCE_CORE_ON_MASK, VCODEC_FORCE_CORE_ON);
+	regmap_update_bits(regmap, VPE_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+
+	/* Camera */
+	regmap_update_bits(regmap, VFE_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+
+	/* JPEG */
+	regmap_update_bits(regmap, IJPEG_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+	regmap_update_bits(regmap, JPEGD_CC_REG, CC_FORCE_CORE_ON_MASK, CC_FORCE_CORE_ON_VAL);
+}
+
+/*
+ * Unhalt all RPM fabric AXI master ports.
+ *
+ * webOS downstream kernels (msm_bus_board_8660.c) program halt registers
+ * on three fabrics: APPS (4 masters), SYSTEM (17 masters), MMSS
+ * (14 masters). The downstream GDSC driver calls msm_bus_axi_portunhalt()
+ * in footswitch_enable() when each power domain comes up; mainline GDSC
+ * does not, leaving fabric master ports in their default (potentially
+ * halted) state. This can cause DMA stalls and arbitration hangs.
+ *
+ * MMSS fabric master ports (port:name from webOS enum):
+ *   0:MDP_PORT0   1:MDP_PORT1   2:ADM1_PORT0  3:ROTATOR
+ *   4:GFX3D       5:JPEG_DEC    6:GFX2D0      7:VFE
+ *   8:VPE         9:JPEG_ENC   10:GFX2D1     11:APPS_FAB
+ *  12:HDCODEC0   13:HDCODEC1
+ *
+ * SYSTEM fabric master ports:
+ *   0:APPSS_FAB    1:SPS         2:ADM0_PORT0  3:ADM0_PORT1
+ *   4:ADM1_PORT0   5:ADM1_PORT1  6:LPASS_PROC  7:MSS_PROCI
+ *   8:MSS_PROCD    9:MDM_PORT0  10:LPASS      11:CPSS_FPB
+ *  12:SYSTEM_FPB  13:MMSS_FPB   14:ADM1_AHB   15:ADM0_AHB
+ *  16:MSS_MDM_PORT1
+ *
+ * APPS fabric has 4 masters; unhalting all is safe.
+ *
+ * Driven from mmcc probe because it runs after the qcom_rpm platform
+ * driver registers (mmcc is module_platform_driver, RPM is platform_init).
+ * Doing the same in gcc probe (core_initcall) is too early -- RPM is
+ * not yet bound and qcom_rpm_write() has no target.
+ */
+static void mmcc_msm8660_unhalt_fabric_ports(struct device *dev)
+{
+	struct device_node *rpm_node;
+	struct platform_device *rpm_pdev;
+	struct qcom_rpm *rpm;
+	/* halt_data[0]=0 = CLK_UNHALT for all bits; halt_data[1] = port mask */
+	u32 mmss_halt[2] = {0, GENMASK(13, 0)};
+	u32 sys_halt[2]  = {0, GENMASK(16, 0)};
+	u32 apps_halt[2] = {0, GENMASK(3, 0)};
+	int rc;
+
+	rpm_node = of_find_compatible_node(NULL, NULL, "qcom,rpm-msm8660");
+	if (!rpm_node)
+		return;
+
+	rpm_pdev = of_find_device_by_node(rpm_node);
+	of_node_put(rpm_node);
+	if (!rpm_pdev)
+		return;
+
+	rpm = dev_get_drvdata(&rpm_pdev->dev);
+	if (!rpm) {
+		put_device(&rpm_pdev->dev);
+		return;
+	}
+
+	rc = qcom_rpm_write(rpm, QCOM_RPM_ACTIVE_STATE,
+			    QCOM_RPM_MM_FABRIC_HALT, mmss_halt, 2);
+	if (rc)
+		dev_warn(dev, "MMSS fabric unhalt failed: %d\n", rc);
+	else
+		dev_info(dev, "MMSS fabric: unhalted all master ports (0-13)\n");
+
+	rc = qcom_rpm_write(rpm, QCOM_RPM_ACTIVE_STATE,
+			    QCOM_RPM_SYS_FABRIC_HALT, sys_halt, 2);
+	if (rc)
+		dev_warn(dev, "system fabric unhalt failed: %d\n", rc);
+	else
+		dev_info(dev, "system fabric: unhalted all master ports (0-16)\n");
+
+	rc = qcom_rpm_write(rpm, QCOM_RPM_ACTIVE_STATE,
+			    QCOM_RPM_APPS_FABRIC_HALT, apps_halt, 2);
+	if (rc)
+		dev_warn(dev, "apps fabric unhalt failed: %d\n", rc);
+	else
+		dev_info(dev, "apps fabric: unhalted all master ports (0-3)\n");
+
+	put_device(&rpm_pdev->dev);
+}
+
+static int mmcc_msm8660_probe(struct platform_device *pdev)
+{
+	struct regmap *regmap;
+
+	regmap = qcom_cc_map(pdev, &mmcc_msm8660_desc);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	/* Initialize MMCC hardware before registering clocks */
+	mmcc_msm8660_init_hw(regmap);
+
+	/*
+	 * Unhalt all MMSS / SYSTEM / APPS fabric AXI master ports.  Must
+	 * happen before any peripheral (MMSS block for MMSS fabric;
+	 * CE2/ADM/SDC/USB for SYSTEM fabric) performs DMA.  Driven from
+	 * mmcc probe because it runs after the qcom_rpm platform driver
+	 * has bound (gcc core_initcall is too early).
+	 */
+	mmcc_msm8660_unhalt_fabric_ports(&pdev->dev);
+
+	return qcom_cc_really_probe(&pdev->dev, &mmcc_msm8660_desc, regmap);
+}
+
+static struct platform_driver mmcc_msm8660_driver = {
+	.probe		= mmcc_msm8660_probe,
+	.driver		= {
+		.name	= "mmcc-msm8660",
+		.of_match_table = mmcc_msm8660_match_table,
+	},
+};
+
+module_platform_driver(mmcc_msm8660_driver);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 Multimedia Clock Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mmcc-msm8660");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
  2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
  2026-05-30 13:58 ` [PATCH 3/3] clk: qcom: add MSM8x60 MMCC driver Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
  2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Philipp Zabel, Rob Herring, Stephen Boyd

Hi all,

This series adds the Multimedia Clock Controller (MMCC) driver for the
MSM8x60 family of SoCs (MSM8260/MSM8660/APQ8060) - the Scorpion-class
generation that preceded MSM8960's Krait CPUs. It also introduces the
clock-ID and reset-ID device-tree binding headers that the MMCC consumer
nodes will reference.

The MMCC layout on MSM8x60 differs from MSM8960 in several ways that
make a separate driver cleaner than parameterising mmcc-msm8960.c, most
notably:

  - the pix_rdi mux requires a custom set_parent op that temporarily
    enables both parents during the glitch-free transition;
  - the IJPEG GDSC requires releasing AXI, AHB and CORE resets;
  - several rate-source pairs (MDP pixel, GFX2D/3D) only exist on 8x60
    (e.g. PLL2-derived 228571000/266667000 for graphics);
  - the camera CSI / VFE / JPEG / VPE / ROT clock topology lacks the
    later 8960 reorganisation.

Used on the HP TouchPad (Tenderloin) for graphics (Adreno A220),
display (MDP4), camera (CSI/VFE), JPEG (Gemini), VIDC, VPE and rotator.

The driver compiles cleanly against current arm-msm/for-next. The two
new binding headers are dual-licensed (GPL-2.0-only OR BSD-2-Clause)
per current qcom-binding convention.

A companion series adds the LPASS Clock Controller (LCC) for the same
SoC family.

Tested on HP TouchPad. Full board DTS will be sent once this and the
other foundation series (interconnect, irqchip MPM, gcc cleanup) are
in -next.

Thanks,
Herman

Herman van Hazendonk (3):
  dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs
  dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs
  clk: qcom: add MSM8x60 MMCC driver

 drivers/clk/qcom/Kconfig                      |   11 +
 drivers/clk/qcom/Makefile                     |    1 +
 drivers/clk/qcom/mmcc-msm8660.c               | 2998 +++++++++++++++++
 include/dt-bindings/clock/qcom,mmcc-msm8660.h |  126 +
 include/dt-bindings/reset/qcom,mmcc-msm8660.h |   88 +
 5 files changed, 3224 insertions(+)
 create mode 100644 drivers/clk/qcom/mmcc-msm8660.c
 create mode 100644 include/dt-bindings/clock/qcom,mmcc-msm8660.h
 create mode 100644 include/dt-bindings/reset/qcom,mmcc-msm8660.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller
@ 2026-05-30 13:59 Herman van Hazendonk
  2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
                   ` (6 more replies)
  0 siblings, 7 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Hi all,

This series adds the LPASS (Low Power Audio SubSystem) Clock Controller
driver for the MSM8x60 family of SoCs (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.

The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising lcc-msm8960.c. Both drivers can
coexist (different Kconfig, match table and compatible).

Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP needs
functional MI2S / SLIMBus / PCM clocks before audio playback or capture
works.

The new binding header is dual-licensed (GPL-2.0-only OR BSD-2-Clause)
per current qcom-binding convention.

Companion to the MSM8x60 MMCC series.

Thanks,
Herman

Herman van Hazendonk (2):
  dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
  clk: qcom: add MSM8x60 LCC (LPASS) driver

 drivers/clk/qcom/Kconfig                     |   9 +
 drivers/clk/qcom/Makefile                    |   1 +
 drivers/clk/qcom/lcc-msm8660.c               | 517 +++++++++++++++++++
 include/dt-bindings/clock/qcom,lcc-msm8660.h |  48 ++
 4 files changed, 575 insertions(+)
 create mode 100644 drivers/clk/qcom/lcc-msm8660.c
 create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
                   ` (2 preceding siblings ...)
  2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
  2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Low Power Audio SubSystem Clock Controller
(LCC). The header enumerates the LPASS clocks consumed by the
qcom,apq8060-lpaif sound card and the codec/AIF nodes downstream of
it. It mirrors the format and ID range of the existing LCC headers
for newer Qualcomm SoCs (lcc-msm8960, lcc-msm8974) so the
drivers/clk/qcom/lcc-msm8660.c driver can be hooked up the same way
once it lands.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h

diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
new file mode 100644
index 000000000000..d5d9b0d71a78
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
+#define _DT_BINDINGS_CLK_LCC_MSM8660_H
+
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller (LCC)
+ * clock IDs. These are compatible with MSM8960 LCC as MSM8x60 and
+ * MSM8960 share the same audio subsystem clock architecture.
+ */
+
+#define PLL4				0
+#define MI2S_OSR_SRC			1
+#define MI2S_OSR_CLK			2
+#define MI2S_DIV_CLK			3
+#define MI2S_BIT_DIV_CLK		4
+#define MI2S_BIT_CLK			5
+#define PCM_SRC				6
+#define PCM_CLK_OUT			7
+#define PCM_CLK				8
+#define SLIMBUS_SRC			9
+#define AUDIO_SLIMBUS_CLK		10
+#define SPS_SLIMBUS_CLK			11
+#define CODEC_I2S_MIC_OSR_SRC		12
+#define CODEC_I2S_MIC_OSR_CLK		13
+#define CODEC_I2S_MIC_DIV_CLK		14
+#define CODEC_I2S_MIC_BIT_DIV_CLK	15
+#define CODEC_I2S_MIC_BIT_CLK		16
+#define SPARE_I2S_MIC_OSR_SRC		17
+#define SPARE_I2S_MIC_OSR_CLK		18
+#define SPARE_I2S_MIC_DIV_CLK		19
+#define SPARE_I2S_MIC_BIT_DIV_CLK	20
+#define SPARE_I2S_MIC_BIT_CLK		21
+#define CODEC_I2S_SPKR_OSR_SRC		22
+#define CODEC_I2S_SPKR_OSR_CLK		23
+#define CODEC_I2S_SPKR_DIV_CLK		24
+#define CODEC_I2S_SPKR_BIT_DIV_CLK	25
+#define CODEC_I2S_SPKR_BIT_CLK		26
+#define SPARE_I2S_SPKR_OSR_SRC		27
+#define SPARE_I2S_SPKR_OSR_CLK		28
+#define SPARE_I2S_SPKR_DIV_CLK		29
+#define SPARE_I2S_SPKR_BIT_DIV_CLK	30
+#define SPARE_I2S_SPKR_BIT_CLK		31
+
+#endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
                   ` (3 preceding siblings ...)
  2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
  2026-05-31 15:46   ` Dmitry Baryshkov
  2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
  2026-05-31 15:36 ` [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Dmitry Baryshkov
  6 siblings, 1 reply; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Add a clock driver for the LPASS (Low Power Audio SubSystem) Clock
Controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.

The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising mmcc-msm8960.c. Both drivers
can coexist in tree (different KConfig options, different match
table, different device-tree compatible).

Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP
needs functional MI2S / SLIMBus / PCM clocks before audio playback
or capture works.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/clk/qcom/Kconfig       |   9 +
 drivers/clk/qcom/Makefile      |   1 +
 drivers/clk/qcom/lcc-msm8660.c | 517 +++++++++++++++++++++++++++++++++
 3 files changed, 527 insertions(+)
 create mode 100644 drivers/clk/qcom/lcc-msm8660.c

diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d9cff5b0281d..2ea95f69355e 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -547,6 +547,15 @@ config MSM_LCC_8960
 	  Say Y if you want to use audio devices such as i2s, pcm,
 	  SLIMBus, etc.
 
+config MSM_LCC_8660
+	tristate "MSM8x60 LPASS Clock Controller"
+	depends on ARM || COMPILE_TEST
+	help
+	  Support for the LPASS clock controller on the MSM8x60 family
+	  (MSM8260/MSM8660/APQ8060). MSM8960 is the newer
+	  Krait-based generation handled separately by MSM_LCC_8960.
+	  Say Y if you want to use audio devices such as i2s, pcm, SLIMBus.
+
 config MDM_GCC_9607
 	tristate "MDM9607 Global Clock Controller"
 	depends on ARM || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index e100cfd6a52d..41c973d7db59 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
 obj-$(CONFIG_MSM_GCC_8976) += gcc-msm8976.o
 obj-$(CONFIG_MSM_GCC_8994) += gcc-msm8994.o
 obj-$(CONFIG_MSM_GCC_8996) += gcc-msm8996.o
+obj-$(CONFIG_MSM_LCC_8660) += lcc-msm8660.o
 obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
 obj-$(CONFIG_MSM_GCC_8998) += gcc-msm8998.o
 obj-$(CONFIG_MSM_GPUCC_8998) += gpucc-msm8998.o
diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
new file mode 100644
index 000000000000..7f13279467f9
--- /dev/null
+++ b/drivers/clk/qcom/lcc-msm8660.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller driver.
+ *
+ * Split from lcc-msm8960.c because the MSM8x60 family is a separate
+ * SoC generation (Scorpion) from MSM8960 (Krait). The clock topology is
+ * compatible but PLL4 runs at a different rate (540.672 MHz, L=22) and the
+ * driver has no need for the MDM9615 CXO patch or the 492 MHz frequency plan.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,lcc-msm8660.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-branch.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+
+static struct clk_pll pll4 = {
+	.l_reg = 0x4,
+	.m_reg = 0x8,
+	.n_reg = 0xc,
+	.config_reg = 0x14,
+	.mode_reg = 0x0,
+	.status_reg = 0x18,
+	.status_bit = 16,
+	.clkr.hw.init = &(struct clk_init_data){
+		.name = "pll4",
+		.parent_data = &(const struct clk_parent_data){
+			.fw_name = "pxo", .name = "pxo_board",
+		},
+		.num_parents = 1,
+		.ops = &clk_pll_ops,
+	},
+};
+
+enum {
+	P_PXO,
+	P_PLL4,
+};
+
+static const struct parent_map lcc_pxo_pll4_map[] = {
+	{ P_PXO, 0 },
+	{ P_PLL4, 2 }
+};
+
+static const struct clk_parent_data lcc_pxo_pll4[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "pll4_vote", .name = "pll4_vote" },
+};
+
+/*
+ * MSM8x60 PLL4 runs at 540.672 MHz (24.576 MHz * 22, L=0x16).
+ * Divisors taken from the legacy webOS clock-8x60.c driver.
+ * AIF_OSR has an 8-bit M/N counter, so 512000 Hz is not achievable with
+ * this PLL frequency and is intentionally omitted from the 540 MHz tables.
+ */
+static const struct freq_tbl clk_tbl_aif_osr_540[] = {
+	{   768000, P_PLL4, 4, 1, 176 },
+	{  1024000, P_PLL4, 4, 1, 132 },
+	{  1536000, P_PLL4, 4, 1,  88 },
+	{  2048000, P_PLL4, 4, 1,  66 },
+	{  3072000, P_PLL4, 4, 1,  44 },
+	{  4096000, P_PLL4, 4, 1,  33 },
+	{  6144000, P_PLL4, 4, 1,  22 },
+	{  8192000, P_PLL4, 2, 1,  33 },
+	{ 12288000, P_PLL4, 4, 1,  11 },
+	{ 24576000, P_PLL4, 2, 1,  11 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+static const struct freq_tbl clk_tbl_aif_osr_393[] = {
+	{   512000, P_PLL4, 4, 1, 192 },
+	{   768000, P_PLL4, 4, 1, 128 },
+	{  1024000, P_PLL4, 4, 1,  96 },
+	{  1536000, P_PLL4, 4, 1,  64 },
+	{  2048000, P_PLL4, 4, 1,  48 },
+	{  3072000, P_PLL4, 4, 1,  32 },
+	{  4096000, P_PLL4, 4, 1,  24 },
+	{  6144000, P_PLL4, 4, 1,  16 },
+	{  8192000, P_PLL4, 4, 1,  12 },
+	{ 12288000, P_PLL4, 4, 1,   8 },
+	{ 24576000, P_PLL4, 4, 1,   4 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+#define CLK_AIF_OSR_SRC(prefix, _ns, _md)			\
+static struct clk_rcg prefix##_osr_src = {			\
+	.ns_reg = _ns,						\
+	.md_reg = _md,						\
+	.mn = {							\
+		.mnctr_en_bit = 8,				\
+		.mnctr_reset_bit = 7,				\
+		.mnctr_mode_shift = 5,				\
+		.n_val_shift = 24,				\
+		.m_val_shift = 8,				\
+		.width = 8,					\
+	},							\
+	.p = {							\
+		.pre_div_shift = 3,				\
+		.pre_div_width = 2,				\
+	},							\
+	.s = {							\
+		.src_sel_shift = 0,				\
+		.parent_map = lcc_pxo_pll4_map,			\
+	},							\
+	.freq_tbl = clk_tbl_aif_osr_393,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(9),				\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_osr_src",		\
+			.parent_data = lcc_pxo_pll4,		\
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4), \
+			.ops = &clk_rcg_ops,			\
+			.flags = CLK_SET_RATE_GATE,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_CLK(prefix, _ns, hr, en_bit)		\
+static struct clk_branch prefix##_osr_clk = {			\
+	.halt_reg = hr,						\
+	.halt_bit = 1,						\
+	.halt_check = BRANCH_HALT_ENABLE,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(en_bit),			\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_osr_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_osr_src.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_branch_ops,			\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_DIV_CLK(prefix, _ns, _width)		\
+static struct clk_regmap_div prefix##_div_clk = {		\
+	.reg = _ns,						\
+	.shift = 10,						\
+	.width = _width,					\
+	.clkr = {						\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_div_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_osr_src.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_regmap_div_ops,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, en_bit)	\
+static struct clk_branch prefix##_bit_div_clk = {		\
+	.halt_reg = hr,						\
+	.halt_bit = 0,						\
+	.halt_check = BRANCH_HALT_ENABLE,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(en_bit),			\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_bit_div_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_div_clk.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_branch_ops,			\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_BIT_CLK(prefix, _ns, _shift)		\
+static struct clk_regmap_mux prefix##_bit_clk = {		\
+	.reg = _ns,						\
+	.shift = _shift,					\
+	.width = 1,						\
+	.clkr = {						\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_bit_clk",		\
+			.parent_data = (const struct clk_parent_data[]){ \
+				{ .hw = &prefix##_bit_div_clk.clkr.hw, }, \
+				{ .fw_name = #prefix "_codec_clk", \
+				  .name = #prefix "_codec_clk", }, \
+			},					\
+			.num_parents = 2,			\
+			.ops = &clk_regmap_mux_closest_ops,	\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};
+
+CLK_AIF_OSR_SRC(mi2s, 0x48, 0x4c)
+CLK_AIF_OSR_CLK(mi2s, 0x48, 0x50, 17)
+CLK_AIF_OSR_DIV_CLK(mi2s, 0x48, 4)
+CLK_AIF_OSR_BIT_DIV_CLK(mi2s, 0x48, 0x50, 15)
+CLK_AIF_OSR_BIT_CLK(mi2s, 0x48, 14)
+
+/*
+ * CLK_AIF_OSR_DIV - Audio Interface with divider clocks.
+ * Enable bits per legacy MSM8660 kernel:
+ *   - OSR branch enable: BIT(17)
+ *   - BIT_DIV branch enable: BIT(15)
+ */
+#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr)			\
+	CLK_AIF_OSR_SRC(prefix, _ns, _md)			\
+	CLK_AIF_OSR_CLK(prefix, _ns, hr, 17)			\
+	CLK_AIF_OSR_DIV_CLK(prefix, _ns, 8)			\
+	CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, 15)		\
+	CLK_AIF_OSR_BIT_CLK(prefix, _ns, 18)
+
+CLK_AIF_OSR_DIV(codec_i2s_mic, 0x60, 0x64, 0x68);
+CLK_AIF_OSR_DIV(spare_i2s_mic, 0x78, 0x7c, 0x80);
+CLK_AIF_OSR_DIV(codec_i2s_spkr, 0x6c, 0x70, 0x74);
+CLK_AIF_OSR_DIV(spare_i2s_spkr, 0x84, 0x88, 0x8c);
+
+/* PCM frequency table for MSM8x60 with PLL4 at 540.672 MHz */
+static const struct freq_tbl clk_tbl_pcm_540[] = {
+	{   256000, P_PLL4, 4, 1, 528 },
+	{   512000, P_PLL4, 4, 1, 264 },
+	{   768000, P_PLL4, 4, 1, 176 },
+	{  1024000, P_PLL4, 4, 1, 132 },
+	{  1536000, P_PLL4, 4, 1,  88 },
+	{  2048000, P_PLL4, 4, 1,  66 },
+	{  3072000, P_PLL4, 4, 1,  44 },
+	{  4096000, P_PLL4, 4, 1,  33 },
+	{  6144000, P_PLL4, 4, 1,  22 },
+	{  8192000, P_PLL4, 2, 1,  33 },
+	{ 12288000, P_PLL4, 4, 1,  11 },
+	{ 24576000, P_PLL4, 2, 1,  11 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+static const struct freq_tbl clk_tbl_pcm_393[] = {
+	{   256000, P_PLL4, 4, 1, 384 },
+	{   512000, P_PLL4, 4, 1, 192 },
+	{   768000, P_PLL4, 4, 1, 128 },
+	{  1024000, P_PLL4, 4, 1,  96 },
+	{  1536000, P_PLL4, 4, 1,  64 },
+	{  2048000, P_PLL4, 4, 1,  48 },
+	{  3072000, P_PLL4, 4, 1,  32 },
+	{  4096000, P_PLL4, 4, 1,  24 },
+	{  6144000, P_PLL4, 4, 1,  16 },
+	{  8192000, P_PLL4, 4, 1,  12 },
+	{ 12288000, P_PLL4, 4, 1,   8 },
+	{ 24576000, P_PLL4, 4, 1,   4 },
+	{ }
+};
+
+static struct clk_rcg pcm_src = {
+	.ns_reg = 0x54,
+	.md_reg = 0x58,
+	.mn = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 5,
+		.n_val_shift = 16,
+		.m_val_shift = 16,
+		.width = 16,
+	},
+	.p = {
+		.pre_div_shift = 3,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = lcc_pxo_pll4_map,
+	},
+	.freq_tbl = clk_tbl_pcm_393,
+	.clkr = {
+		.enable_reg = 0x54,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_src",
+			.parent_data = lcc_pxo_pll4,
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+			.ops = &clk_rcg_ops,
+			.flags = CLK_SET_RATE_GATE,
+		},
+	},
+};
+
+static struct clk_branch pcm_clk_out = {
+	.halt_reg = 0x5c,
+	.halt_bit = 0,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0x54,
+		.enable_mask = BIT(11),
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_clk_out",
+			.parent_hws = (const struct clk_hw*[]){
+				&pcm_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_regmap_mux pcm_clk = {
+	.reg = 0x54,
+	.shift = 10,
+	.width = 1,
+	.clkr = {
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_clk",
+			.parent_data = (const struct clk_parent_data[]){
+				{ .hw = &pcm_clk_out.clkr.hw },
+				{ .fw_name = "pcm_codec_clk", .name = "pcm_codec_clk" },
+			},
+			.num_parents = 2,
+			.ops = &clk_regmap_mux_closest_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_rcg slimbus_src = {
+	.ns_reg = 0xcc,
+	.md_reg = 0xd0,
+	.mn = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 5,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 3,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = lcc_pxo_pll4_map,
+	},
+	.freq_tbl = clk_tbl_aif_osr_393,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.name = "slimbus_src",
+			.parent_data = lcc_pxo_pll4,
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+			.ops = &clk_rcg_ops,
+			.flags = CLK_SET_RATE_GATE,
+		},
+	},
+};
+
+static struct clk_branch audio_slimbus_clk = {
+	.halt_reg = 0xd4,
+	.halt_bit = 0,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(10),
+		.hw.init = &(struct clk_init_data){
+			.name = "audio_slimbus_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&slimbus_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch sps_slimbus_clk = {
+	.halt_reg = 0xd4,
+	.halt_bit = 1,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(12),
+		.hw.init = &(struct clk_init_data){
+			.name = "sps_slimbus_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&slimbus_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_regmap *lcc_msm8660_clks[] = {
+	[PLL4] = &pll4.clkr,
+	[MI2S_OSR_SRC] = &mi2s_osr_src.clkr,
+	[MI2S_OSR_CLK] = &mi2s_osr_clk.clkr,
+	[MI2S_DIV_CLK] = &mi2s_div_clk.clkr,
+	[MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr,
+	[MI2S_BIT_CLK] = &mi2s_bit_clk.clkr,
+	[PCM_SRC] = &pcm_src.clkr,
+	[PCM_CLK_OUT] = &pcm_clk_out.clkr,
+	[PCM_CLK] = &pcm_clk.clkr,
+	[SLIMBUS_SRC] = &slimbus_src.clkr,
+	[AUDIO_SLIMBUS_CLK] = &audio_slimbus_clk.clkr,
+	[SPS_SLIMBUS_CLK] = &sps_slimbus_clk.clkr,
+	[CODEC_I2S_MIC_OSR_SRC] = &codec_i2s_mic_osr_src.clkr,
+	[CODEC_I2S_MIC_OSR_CLK] = &codec_i2s_mic_osr_clk.clkr,
+	[CODEC_I2S_MIC_DIV_CLK] = &codec_i2s_mic_div_clk.clkr,
+	[CODEC_I2S_MIC_BIT_DIV_CLK] = &codec_i2s_mic_bit_div_clk.clkr,
+	[CODEC_I2S_MIC_BIT_CLK] = &codec_i2s_mic_bit_clk.clkr,
+	[SPARE_I2S_MIC_OSR_SRC] = &spare_i2s_mic_osr_src.clkr,
+	[SPARE_I2S_MIC_OSR_CLK] = &spare_i2s_mic_osr_clk.clkr,
+	[SPARE_I2S_MIC_DIV_CLK] = &spare_i2s_mic_div_clk.clkr,
+	[SPARE_I2S_MIC_BIT_DIV_CLK] = &spare_i2s_mic_bit_div_clk.clkr,
+	[SPARE_I2S_MIC_BIT_CLK] = &spare_i2s_mic_bit_clk.clkr,
+	[CODEC_I2S_SPKR_OSR_SRC] = &codec_i2s_spkr_osr_src.clkr,
+	[CODEC_I2S_SPKR_OSR_CLK] = &codec_i2s_spkr_osr_clk.clkr,
+	[CODEC_I2S_SPKR_DIV_CLK] = &codec_i2s_spkr_div_clk.clkr,
+	[CODEC_I2S_SPKR_BIT_DIV_CLK] = &codec_i2s_spkr_bit_div_clk.clkr,
+	[CODEC_I2S_SPKR_BIT_CLK] = &codec_i2s_spkr_bit_clk.clkr,
+	[SPARE_I2S_SPKR_OSR_SRC] = &spare_i2s_spkr_osr_src.clkr,
+	[SPARE_I2S_SPKR_OSR_CLK] = &spare_i2s_spkr_osr_clk.clkr,
+	[SPARE_I2S_SPKR_DIV_CLK] = &spare_i2s_spkr_div_clk.clkr,
+	[SPARE_I2S_SPKR_BIT_DIV_CLK] = &spare_i2s_spkr_bit_div_clk.clkr,
+	[SPARE_I2S_SPKR_BIT_CLK] = &spare_i2s_spkr_bit_clk.clkr,
+};
+
+static const struct regmap_config lcc_msm8660_regmap_config = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.max_register	= 0xfc,
+	.fast_io	= true,
+};
+
+static const struct qcom_cc_desc lcc_msm8660_desc = {
+	.config = &lcc_msm8660_regmap_config,
+	.clks = lcc_msm8660_clks,
+	.num_clks = ARRAY_SIZE(lcc_msm8660_clks),
+};
+
+static const struct of_device_id lcc_msm8660_match_table[] = {
+	{ .compatible = "qcom,lcc-msm8660" },
+	{ .compatible = "qcom,lcc-apq8060" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, lcc_msm8660_match_table);
+
+static int lcc_msm8660_probe(struct platform_device *pdev)
+{
+	struct regmap *regmap;
+	u32 val;
+
+	regmap = qcom_cc_map(pdev, &lcc_msm8660_desc);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	/*
+	 * MSM8x60 should always boot with PLL4 L=22 (540.672 MHz).
+	 * Detect anyway so a board with a non-standard L value still gets a
+	 * coherent frequency plan instead of silently producing wrong rates.
+	 */
+	regmap_read(regmap, 0x4, &val);
+	if (val == 0x16) {
+		dev_info(&pdev->dev,
+			 "PLL4 L=0x%x, using 540MHz frequency plan\n", val);
+		slimbus_src.freq_tbl = clk_tbl_aif_osr_540;
+		mi2s_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+		codec_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+		spare_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+		codec_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+		spare_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+		pcm_src.freq_tbl = clk_tbl_pcm_540;
+	} else {
+		dev_info(&pdev->dev,
+			 "PLL4 L=0x%x, using fallback 393MHz frequency plan\n",
+			 val);
+	}
+
+	/* Enable PLL4 source on the LPASS Primary PLL Mux */
+	regmap_write(regmap, 0xc4, 0x1);
+
+	return qcom_cc_really_probe(&pdev->dev, &lcc_msm8660_desc, regmap);
+}
+
+static struct platform_driver lcc_msm8660_driver = {
+	.probe		= lcc_msm8660_probe,
+	.driver		= {
+		.name	= "lcc-msm8660",
+		.of_match_table = lcc_msm8660_match_table,
+	},
+};
+module_platform_driver(lcc_msm8660_driver);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 LPASS Clock Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lcc-msm8660");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
                   ` (4 preceding siblings ...)
  2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
@ 2026-05-31  4:08 ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
                     ` (2 more replies)
  2026-05-31 15:36 ` [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Dmitry Baryshkov
  6 siblings, 3 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:08 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Hi all,

Self-review (with Sashiko AI assist) caught five issues in v1 before
maintainer review reached them. v1:

  https://lore.kernel.org/linux-arm-msm/cover.1780148149.git.github.com@herrie.org/

v2 changes:

  - NEW patch 1/3: extend Documentation/devicetree/bindings/clock/
    qcom,lcc.yaml with the three MSM8x60 family compatibles
    (qcom,lcc-msm8260, qcom,lcc-msm8660, qcom,lcc-apq8060) and add
    them to the existing apq8064/msm8960 conditional block. Without
    this, board DTs using the new compatibles fail dt_binding_check.

  - patch 2/3: dt-bindings clock-ID header for lcc-msm8660,
    unchanged from v1.

  - patch 3/3: LCC driver. Five fixes folded in:

      * CLK_AIF_OSR_DIV macro: divider width was 8, which made the
        bit-divider field span bits 10 through 17 on the _ns
        register. On MSM8x60 BIT(15) (BIT_DIV branch enable) and
        BIT(17) (OSR branch enable) sit inside that range, so any
        clk_regmap_div read-modify-write would clobber both branch
        gates. Width 4 (bits 10 through 13) matches the standalone
        mi2s div clock and the legacy downstream Samsung MSM8660 /
        webOS clock-8x60.c register layout. Added a block comment
        documenting the verified bit assignments and the rationale
        for the width change.

      * Match table: add qcom,lcc-msm8260 alongside the existing
        qcom,lcc-msm8660 and qcom,lcc-apq8060. MSM8x60 covers all
        three variants.

      * clk_tbl_pcm_393: add the 27 MHz PXO fallback entry that
        clk_tbl_pcm_540 already carries, so the 393 plan can also
        fall back to the board PXO source.

      * lcc_msm8660_probe(): check the return value of regmap_read()
        on PLL4 L. v1 ignored it, so a regmap failure would have
        left val as uninitialised stack memory and steered the rest
        of probe down a random branch. Use dev_err_probe() on
        failure.

      * lcc_msm8660_probe(): assign the freq_tbl pointers on the
        static clk_rcg structs unconditionally on both PLL4 plans
        instead of mutating-on-540 / leaving-mutated-on-393. The
        driver is effectively singleton today, but explicit
        assignment also restores the defaults if the driver is ever
        rebound on a system whose PLL4 has been reprogrammed.

Thanks,
Herman

Herman van Hazendonk (3):
  dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
  dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
  clk: qcom: add MSM8x60 LCC (LPASS) driver

 .../devicetree/bindings/clock/qcom,lcc.yaml   |   6 +
 drivers/clk/qcom/Kconfig                      |   9 +
 drivers/clk/qcom/Makefile                     |   1 +
 drivers/clk/qcom/lcc-msm8660.c                | 551 ++++++++++++++++++
 include/dt-bindings/clock/qcom,lcc-msm8660.h  |  48 ++
 5 files changed, 615 insertions(+)
 create mode 100644 drivers/clk/qcom/lcc-msm8660.c
 create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
  2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  7:58     ` Krzysztof Kozlowski
  2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
  2 siblings, 1 reply; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Add "qcom,lcc-msm8260", "qcom,lcc-msm8660" and "qcom,lcc-apq8060" to
the qcom,lcc enum and to the existing parent-clock-name conditional
block that already covers apq8064/msm8960.

The MSM8x60 family (MSM8260/MSM8660/APQ8060) is the Scorpion-class
generation preceding MSM8960; its LPASS Clock Controller is fed by
the same pxo board source and pll4_vote parents as apq8064/msm8960,
and the codec-fed slave-mode I2S/PCM clocks have the same names, so
the existing apq8064/msm8960 conditional block applies unchanged
once the new compatibles are listed.

This allows board DTs using the new compatibles to pass
dt_binding_check; the actual driver support is added by a later
patch in this series.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 Documentation/devicetree/bindings/clock/qcom,lcc.yaml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
index 55985e562a34..bbdad157f03f 100644
--- a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
+++ b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
@@ -12,9 +12,12 @@ maintainers:
 properties:
   compatible:
     enum:
+      - qcom,lcc-apq8060
       - qcom,lcc-apq8064
       - qcom,lcc-ipq8064
       - qcom,lcc-mdm9615
+      - qcom,lcc-msm8260
+      - qcom,lcc-msm8660
       - qcom,lcc-msm8960
 
   clocks:
@@ -46,7 +49,10 @@ allOf:
         compatible:
           contains:
             enum:
+              - qcom,lcc-apq8060
               - qcom,lcc-apq8064
+              - qcom,lcc-msm8260
+              - qcom,lcc-msm8660
               - qcom,lcc-msm8960
     then:
       properties:
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
  2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
  2 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Low Power Audio SubSystem Clock Controller
(LCC). The header enumerates the LPASS clocks consumed by the
qcom,apq8060-lpaif sound card and the codec/AIF nodes downstream of
it. It mirrors the format and ID range of the existing LCC headers
for newer Qualcomm SoCs (lcc-msm8960, lcc-msm8974) so the
drivers/clk/qcom/lcc-msm8660.c driver can be hooked up the same way
once it lands.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h

diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
new file mode 100644
index 000000000000..d5d9b0d71a78
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
+#define _DT_BINDINGS_CLK_LCC_MSM8660_H
+
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller (LCC)
+ * clock IDs. These are compatible with MSM8960 LCC as MSM8x60 and
+ * MSM8960 share the same audio subsystem clock architecture.
+ */
+
+#define PLL4				0
+#define MI2S_OSR_SRC			1
+#define MI2S_OSR_CLK			2
+#define MI2S_DIV_CLK			3
+#define MI2S_BIT_DIV_CLK		4
+#define MI2S_BIT_CLK			5
+#define PCM_SRC				6
+#define PCM_CLK_OUT			7
+#define PCM_CLK				8
+#define SLIMBUS_SRC			9
+#define AUDIO_SLIMBUS_CLK		10
+#define SPS_SLIMBUS_CLK			11
+#define CODEC_I2S_MIC_OSR_SRC		12
+#define CODEC_I2S_MIC_OSR_CLK		13
+#define CODEC_I2S_MIC_DIV_CLK		14
+#define CODEC_I2S_MIC_BIT_DIV_CLK	15
+#define CODEC_I2S_MIC_BIT_CLK		16
+#define SPARE_I2S_MIC_OSR_SRC		17
+#define SPARE_I2S_MIC_OSR_CLK		18
+#define SPARE_I2S_MIC_DIV_CLK		19
+#define SPARE_I2S_MIC_BIT_DIV_CLK	20
+#define SPARE_I2S_MIC_BIT_CLK		21
+#define CODEC_I2S_SPKR_OSR_SRC		22
+#define CODEC_I2S_SPKR_OSR_CLK		23
+#define CODEC_I2S_SPKR_DIV_CLK		24
+#define CODEC_I2S_SPKR_BIT_DIV_CLK	25
+#define CODEC_I2S_SPKR_BIT_CLK		26
+#define SPARE_I2S_SPKR_OSR_SRC		27
+#define SPARE_I2S_SPKR_OSR_CLK		28
+#define SPARE_I2S_SPKR_DIV_CLK		29
+#define SPARE_I2S_SPKR_BIT_DIV_CLK	30
+#define SPARE_I2S_SPKR_BIT_CLK		31
+
+#endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver
  2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

Add a clock driver for the LPASS (Low Power Audio SubSystem) Clock
Controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.

The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising mmcc-msm8960.c. Both drivers
can coexist in tree (different KConfig options, different match
table, different device-tree compatible).

Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP
needs functional MI2S / SLIMBus / PCM clocks before audio playback
or capture works.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/clk/qcom/Kconfig       |   9 +
 drivers/clk/qcom/Makefile      |   1 +
 drivers/clk/qcom/lcc-msm8660.c | 551 +++++++++++++++++++++++++++++++++
 3 files changed, 561 insertions(+)
 create mode 100644 drivers/clk/qcom/lcc-msm8660.c

diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d9cff5b0281d..2ea95f69355e 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -547,6 +547,15 @@ config MSM_LCC_8960
 	  Say Y if you want to use audio devices such as i2s, pcm,
 	  SLIMBus, etc.
 
+config MSM_LCC_8660
+	tristate "MSM8x60 LPASS Clock Controller"
+	depends on ARM || COMPILE_TEST
+	help
+	  Support for the LPASS clock controller on the MSM8x60 family
+	  (MSM8260/MSM8660/APQ8060). MSM8960 is the newer
+	  Krait-based generation handled separately by MSM_LCC_8960.
+	  Say Y if you want to use audio devices such as i2s, pcm, SLIMBus.
+
 config MDM_GCC_9607
 	tristate "MDM9607 Global Clock Controller"
 	depends on ARM || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index e100cfd6a52d..41c973d7db59 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
 obj-$(CONFIG_MSM_GCC_8976) += gcc-msm8976.o
 obj-$(CONFIG_MSM_GCC_8994) += gcc-msm8994.o
 obj-$(CONFIG_MSM_GCC_8996) += gcc-msm8996.o
+obj-$(CONFIG_MSM_LCC_8660) += lcc-msm8660.o
 obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
 obj-$(CONFIG_MSM_GCC_8998) += gcc-msm8998.o
 obj-$(CONFIG_MSM_GPUCC_8998) += gpucc-msm8998.o
diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
new file mode 100644
index 000000000000..f71846d493f8
--- /dev/null
+++ b/drivers/clk/qcom/lcc-msm8660.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller driver.
+ *
+ * Split from lcc-msm8960.c because the MSM8x60 family is a separate
+ * SoC generation (Scorpion) from MSM8960 (Krait). The clock topology is
+ * compatible but PLL4 runs at a different rate (540.672 MHz, L=22) and the
+ * driver has no need for the MDM9615 CXO patch or the 492 MHz frequency plan.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,lcc-msm8660.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-branch.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+
+static struct clk_pll pll4 = {
+	.l_reg = 0x4,
+	.m_reg = 0x8,
+	.n_reg = 0xc,
+	.config_reg = 0x14,
+	.mode_reg = 0x0,
+	.status_reg = 0x18,
+	.status_bit = 16,
+	.clkr.hw.init = &(struct clk_init_data){
+		.name = "pll4",
+		.parent_data = &(const struct clk_parent_data){
+			.fw_name = "pxo", .name = "pxo_board",
+		},
+		.num_parents = 1,
+		.ops = &clk_pll_ops,
+	},
+};
+
+enum {
+	P_PXO,
+	P_PLL4,
+};
+
+static const struct parent_map lcc_pxo_pll4_map[] = {
+	{ P_PXO, 0 },
+	{ P_PLL4, 2 }
+};
+
+static const struct clk_parent_data lcc_pxo_pll4[] = {
+	{ .fw_name = "pxo", .name = "pxo_board" },
+	{ .fw_name = "pll4_vote", .name = "pll4_vote" },
+};
+
+/*
+ * MSM8x60 PLL4 runs at 540.672 MHz (24.576 MHz * 22, L=0x16).
+ * Divisors taken from the legacy webOS clock-8x60.c driver.
+ * AIF_OSR has an 8-bit M/N counter, so 512000 Hz is not achievable with
+ * this PLL frequency and is intentionally omitted from the 540 MHz tables.
+ */
+static const struct freq_tbl clk_tbl_aif_osr_540[] = {
+	{   768000, P_PLL4, 4, 1, 176 },
+	{  1024000, P_PLL4, 4, 1, 132 },
+	{  1536000, P_PLL4, 4, 1,  88 },
+	{  2048000, P_PLL4, 4, 1,  66 },
+	{  3072000, P_PLL4, 4, 1,  44 },
+	{  4096000, P_PLL4, 4, 1,  33 },
+	{  6144000, P_PLL4, 4, 1,  22 },
+	{  8192000, P_PLL4, 2, 1,  33 },
+	{ 12288000, P_PLL4, 4, 1,  11 },
+	{ 24576000, P_PLL4, 2, 1,  11 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+static const struct freq_tbl clk_tbl_aif_osr_393[] = {
+	{   512000, P_PLL4, 4, 1, 192 },
+	{   768000, P_PLL4, 4, 1, 128 },
+	{  1024000, P_PLL4, 4, 1,  96 },
+	{  1536000, P_PLL4, 4, 1,  64 },
+	{  2048000, P_PLL4, 4, 1,  48 },
+	{  3072000, P_PLL4, 4, 1,  32 },
+	{  4096000, P_PLL4, 4, 1,  24 },
+	{  6144000, P_PLL4, 4, 1,  16 },
+	{  8192000, P_PLL4, 4, 1,  12 },
+	{ 12288000, P_PLL4, 4, 1,   8 },
+	{ 24576000, P_PLL4, 4, 1,   4 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+#define CLK_AIF_OSR_SRC(prefix, _ns, _md)			\
+static struct clk_rcg prefix##_osr_src = {			\
+	.ns_reg = _ns,						\
+	.md_reg = _md,						\
+	.mn = {							\
+		.mnctr_en_bit = 8,				\
+		.mnctr_reset_bit = 7,				\
+		.mnctr_mode_shift = 5,				\
+		.n_val_shift = 24,				\
+		.m_val_shift = 8,				\
+		.width = 8,					\
+	},							\
+	.p = {							\
+		.pre_div_shift = 3,				\
+		.pre_div_width = 2,				\
+	},							\
+	.s = {							\
+		.src_sel_shift = 0,				\
+		.parent_map = lcc_pxo_pll4_map,			\
+	},							\
+	.freq_tbl = clk_tbl_aif_osr_393,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(9),				\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_osr_src",		\
+			.parent_data = lcc_pxo_pll4,		\
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4), \
+			.ops = &clk_rcg_ops,			\
+			.flags = CLK_SET_RATE_GATE,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_CLK(prefix, _ns, hr, en_bit)		\
+static struct clk_branch prefix##_osr_clk = {			\
+	.halt_reg = hr,						\
+	.halt_bit = 1,						\
+	.halt_check = BRANCH_HALT_ENABLE,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(en_bit),			\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_osr_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_osr_src.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_branch_ops,			\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_DIV_CLK(prefix, _ns, _width)		\
+static struct clk_regmap_div prefix##_div_clk = {		\
+	.reg = _ns,						\
+	.shift = 10,						\
+	.width = _width,					\
+	.clkr = {						\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_div_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_osr_src.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_regmap_div_ops,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, en_bit)	\
+static struct clk_branch prefix##_bit_div_clk = {		\
+	.halt_reg = hr,						\
+	.halt_bit = 0,						\
+	.halt_check = BRANCH_HALT_ENABLE,			\
+	.clkr = {						\
+		.enable_reg = _ns,				\
+		.enable_mask = BIT(en_bit),			\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_bit_div_clk",		\
+			.parent_hws = (const struct clk_hw*[]){	\
+				&prefix##_div_clk.clkr.hw,	\
+			},					\
+			.num_parents = 1,			\
+			.ops = &clk_branch_ops,			\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};								\
+
+#define CLK_AIF_OSR_BIT_CLK(prefix, _ns, _shift)		\
+static struct clk_regmap_mux prefix##_bit_clk = {		\
+	.reg = _ns,						\
+	.shift = _shift,					\
+	.width = 1,						\
+	.clkr = {						\
+		.hw.init = &(struct clk_init_data){		\
+			.name = #prefix "_bit_clk",		\
+			.parent_data = (const struct clk_parent_data[]){ \
+				{ .hw = &prefix##_bit_div_clk.clkr.hw, }, \
+				{ .fw_name = #prefix "_codec_clk", \
+				  .name = #prefix "_codec_clk", }, \
+			},					\
+			.num_parents = 2,			\
+			.ops = &clk_regmap_mux_closest_ops,	\
+			.flags = CLK_SET_RATE_PARENT,		\
+		},						\
+	},							\
+};
+
+CLK_AIF_OSR_SRC(mi2s, 0x48, 0x4c)
+CLK_AIF_OSR_CLK(mi2s, 0x48, 0x50, 17)
+CLK_AIF_OSR_DIV_CLK(mi2s, 0x48, 4)
+CLK_AIF_OSR_BIT_DIV_CLK(mi2s, 0x48, 0x50, 15)
+CLK_AIF_OSR_BIT_CLK(mi2s, 0x48, 14)
+
+/*
+ * CLK_AIF_OSR_DIV - Audio Interface with divider clocks.
+ *
+ * MSM8x60 LPASS AIF register layout, verified against the legacy
+ * downstream Samsung MSM8660 source and the webOS clock-8x60.c
+ * CLK_AIF_BIT macro. Both legacy sources agree on the bit assignment:
+ * the bit-divider field starts at offset 10 and is four bits wide;
+ * bit 14 doubles as the cdiv external-source select; BIT_DIV branch
+ * enable is BIT(15); OSR branch enable is BIT(17); the BIT clock mux
+ * select is BIT(18) for codec_i2s and spare_i2s (mi2s uses bit 14,
+ * handled explicitly above this macro); reset is BIT(19).
+ *
+ * Earlier revisions of this driver used a width of 8 here, inherited
+ * from lcc-msm8960.c whose enables are at BIT(19) and BIT(21) and so
+ * do not overlap a wide divider field. On MSM8x60 the enables are at
+ * BIT(15) and BIT(17), so a width of 8 made the divider field cover
+ * bits 10 through 17 and a read-modify-write on the divider would
+ * clobber the two branch gates. A width of 4 confines the divider to
+ * bits 10 through 13, matching the standalone mi2s div clock above
+ * and what the legacy stack programs.
+ */
+#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr)			\
+	CLK_AIF_OSR_SRC(prefix, _ns, _md)			\
+	CLK_AIF_OSR_CLK(prefix, _ns, hr, 17)			\
+	CLK_AIF_OSR_DIV_CLK(prefix, _ns, 4)			\
+	CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, 15)		\
+	CLK_AIF_OSR_BIT_CLK(prefix, _ns, 18)
+
+CLK_AIF_OSR_DIV(codec_i2s_mic, 0x60, 0x64, 0x68);
+CLK_AIF_OSR_DIV(spare_i2s_mic, 0x78, 0x7c, 0x80);
+CLK_AIF_OSR_DIV(codec_i2s_spkr, 0x6c, 0x70, 0x74);
+CLK_AIF_OSR_DIV(spare_i2s_spkr, 0x84, 0x88, 0x8c);
+
+/* PCM frequency table for MSM8x60 with PLL4 at 540.672 MHz */
+static const struct freq_tbl clk_tbl_pcm_540[] = {
+	{   256000, P_PLL4, 4, 1, 528 },
+	{   512000, P_PLL4, 4, 1, 264 },
+	{   768000, P_PLL4, 4, 1, 176 },
+	{  1024000, P_PLL4, 4, 1, 132 },
+	{  1536000, P_PLL4, 4, 1,  88 },
+	{  2048000, P_PLL4, 4, 1,  66 },
+	{  3072000, P_PLL4, 4, 1,  44 },
+	{  4096000, P_PLL4, 4, 1,  33 },
+	{  6144000, P_PLL4, 4, 1,  22 },
+	{  8192000, P_PLL4, 2, 1,  33 },
+	{ 12288000, P_PLL4, 4, 1,  11 },
+	{ 24576000, P_PLL4, 2, 1,  11 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+static const struct freq_tbl clk_tbl_pcm_393[] = {
+	{   256000, P_PLL4, 4, 1, 384 },
+	{   512000, P_PLL4, 4, 1, 192 },
+	{   768000, P_PLL4, 4, 1, 128 },
+	{  1024000, P_PLL4, 4, 1,  96 },
+	{  1536000, P_PLL4, 4, 1,  64 },
+	{  2048000, P_PLL4, 4, 1,  48 },
+	{  3072000, P_PLL4, 4, 1,  32 },
+	{  4096000, P_PLL4, 4, 1,  24 },
+	{  6144000, P_PLL4, 4, 1,  16 },
+	{  8192000, P_PLL4, 4, 1,  12 },
+	{ 12288000, P_PLL4, 4, 1,   8 },
+	{ 24576000, P_PLL4, 4, 1,   4 },
+	{ 27000000, P_PXO,  1, 0,   0 },
+	{ }
+};
+
+static struct clk_rcg pcm_src = {
+	.ns_reg = 0x54,
+	.md_reg = 0x58,
+	.mn = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 5,
+		.n_val_shift = 16,
+		.m_val_shift = 16,
+		.width = 16,
+	},
+	.p = {
+		.pre_div_shift = 3,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = lcc_pxo_pll4_map,
+	},
+	.freq_tbl = clk_tbl_pcm_393,
+	.clkr = {
+		.enable_reg = 0x54,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_src",
+			.parent_data = lcc_pxo_pll4,
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+			.ops = &clk_rcg_ops,
+			.flags = CLK_SET_RATE_GATE,
+		},
+	},
+};
+
+static struct clk_branch pcm_clk_out = {
+	.halt_reg = 0x5c,
+	.halt_bit = 0,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0x54,
+		.enable_mask = BIT(11),
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_clk_out",
+			.parent_hws = (const struct clk_hw*[]){
+				&pcm_src.clkr.hw
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_regmap_mux pcm_clk = {
+	.reg = 0x54,
+	.shift = 10,
+	.width = 1,
+	.clkr = {
+		.hw.init = &(struct clk_init_data){
+			.name = "pcm_clk",
+			.parent_data = (const struct clk_parent_data[]){
+				{ .hw = &pcm_clk_out.clkr.hw },
+				{ .fw_name = "pcm_codec_clk", .name = "pcm_codec_clk" },
+			},
+			.num_parents = 2,
+			.ops = &clk_regmap_mux_closest_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_rcg slimbus_src = {
+	.ns_reg = 0xcc,
+	.md_reg = 0xd0,
+	.mn = {
+		.mnctr_en_bit = 8,
+		.mnctr_reset_bit = 7,
+		.mnctr_mode_shift = 5,
+		.n_val_shift = 24,
+		.m_val_shift = 8,
+		.width = 8,
+	},
+	.p = {
+		.pre_div_shift = 3,
+		.pre_div_width = 2,
+	},
+	.s = {
+		.src_sel_shift = 0,
+		.parent_map = lcc_pxo_pll4_map,
+	},
+	.freq_tbl = clk_tbl_aif_osr_393,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(9),
+		.hw.init = &(struct clk_init_data){
+			.name = "slimbus_src",
+			.parent_data = lcc_pxo_pll4,
+			.num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+			.ops = &clk_rcg_ops,
+			.flags = CLK_SET_RATE_GATE,
+		},
+	},
+};
+
+static struct clk_branch audio_slimbus_clk = {
+	.halt_reg = 0xd4,
+	.halt_bit = 0,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(10),
+		.hw.init = &(struct clk_init_data){
+			.name = "audio_slimbus_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&slimbus_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_branch sps_slimbus_clk = {
+	.halt_reg = 0xd4,
+	.halt_bit = 1,
+	.halt_check = BRANCH_HALT_ENABLE,
+	.clkr = {
+		.enable_reg = 0xcc,
+		.enable_mask = BIT(12),
+		.hw.init = &(struct clk_init_data){
+			.name = "sps_slimbus_clk",
+			.parent_hws = (const struct clk_hw*[]){
+				&slimbus_src.clkr.hw,
+			},
+			.num_parents = 1,
+			.ops = &clk_branch_ops,
+			.flags = CLK_SET_RATE_PARENT,
+		},
+	},
+};
+
+static struct clk_regmap *lcc_msm8660_clks[] = {
+	[PLL4] = &pll4.clkr,
+	[MI2S_OSR_SRC] = &mi2s_osr_src.clkr,
+	[MI2S_OSR_CLK] = &mi2s_osr_clk.clkr,
+	[MI2S_DIV_CLK] = &mi2s_div_clk.clkr,
+	[MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr,
+	[MI2S_BIT_CLK] = &mi2s_bit_clk.clkr,
+	[PCM_SRC] = &pcm_src.clkr,
+	[PCM_CLK_OUT] = &pcm_clk_out.clkr,
+	[PCM_CLK] = &pcm_clk.clkr,
+	[SLIMBUS_SRC] = &slimbus_src.clkr,
+	[AUDIO_SLIMBUS_CLK] = &audio_slimbus_clk.clkr,
+	[SPS_SLIMBUS_CLK] = &sps_slimbus_clk.clkr,
+	[CODEC_I2S_MIC_OSR_SRC] = &codec_i2s_mic_osr_src.clkr,
+	[CODEC_I2S_MIC_OSR_CLK] = &codec_i2s_mic_osr_clk.clkr,
+	[CODEC_I2S_MIC_DIV_CLK] = &codec_i2s_mic_div_clk.clkr,
+	[CODEC_I2S_MIC_BIT_DIV_CLK] = &codec_i2s_mic_bit_div_clk.clkr,
+	[CODEC_I2S_MIC_BIT_CLK] = &codec_i2s_mic_bit_clk.clkr,
+	[SPARE_I2S_MIC_OSR_SRC] = &spare_i2s_mic_osr_src.clkr,
+	[SPARE_I2S_MIC_OSR_CLK] = &spare_i2s_mic_osr_clk.clkr,
+	[SPARE_I2S_MIC_DIV_CLK] = &spare_i2s_mic_div_clk.clkr,
+	[SPARE_I2S_MIC_BIT_DIV_CLK] = &spare_i2s_mic_bit_div_clk.clkr,
+	[SPARE_I2S_MIC_BIT_CLK] = &spare_i2s_mic_bit_clk.clkr,
+	[CODEC_I2S_SPKR_OSR_SRC] = &codec_i2s_spkr_osr_src.clkr,
+	[CODEC_I2S_SPKR_OSR_CLK] = &codec_i2s_spkr_osr_clk.clkr,
+	[CODEC_I2S_SPKR_DIV_CLK] = &codec_i2s_spkr_div_clk.clkr,
+	[CODEC_I2S_SPKR_BIT_DIV_CLK] = &codec_i2s_spkr_bit_div_clk.clkr,
+	[CODEC_I2S_SPKR_BIT_CLK] = &codec_i2s_spkr_bit_clk.clkr,
+	[SPARE_I2S_SPKR_OSR_SRC] = &spare_i2s_spkr_osr_src.clkr,
+	[SPARE_I2S_SPKR_OSR_CLK] = &spare_i2s_spkr_osr_clk.clkr,
+	[SPARE_I2S_SPKR_DIV_CLK] = &spare_i2s_spkr_div_clk.clkr,
+	[SPARE_I2S_SPKR_BIT_DIV_CLK] = &spare_i2s_spkr_bit_div_clk.clkr,
+	[SPARE_I2S_SPKR_BIT_CLK] = &spare_i2s_spkr_bit_clk.clkr,
+};
+
+static const struct regmap_config lcc_msm8660_regmap_config = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.max_register	= 0xfc,
+	.fast_io	= true,
+};
+
+static const struct qcom_cc_desc lcc_msm8660_desc = {
+	.config = &lcc_msm8660_regmap_config,
+	.clks = lcc_msm8660_clks,
+	.num_clks = ARRAY_SIZE(lcc_msm8660_clks),
+};
+
+static const struct of_device_id lcc_msm8660_match_table[] = {
+	{ .compatible = "qcom,lcc-msm8260" },
+	{ .compatible = "qcom,lcc-msm8660" },
+	{ .compatible = "qcom,lcc-apq8060" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, lcc_msm8660_match_table);
+
+static int lcc_msm8660_probe(struct platform_device *pdev)
+{
+	const struct freq_tbl *aif_osr_tbl, *pcm_tbl;
+	struct regmap *regmap;
+	const char *plan_name;
+	u32 val;
+	int ret;
+
+	regmap = qcom_cc_map(pdev, &lcc_msm8660_desc);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	/*
+	 * MSM8x60 should always boot with PLL4 L=22 (540.672 MHz).
+	 * Detect anyway so a board with a non-standard L value still gets a
+	 * coherent frequency plan instead of silently producing wrong rates.
+	 */
+	ret = regmap_read(regmap, 0x4, &val);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to read PLL4 L register\n");
+
+	if (val == 0x16) {
+		aif_osr_tbl = clk_tbl_aif_osr_540;
+		pcm_tbl = clk_tbl_pcm_540;
+		plan_name = "540MHz";
+	} else {
+		aif_osr_tbl = clk_tbl_aif_osr_393;
+		pcm_tbl = clk_tbl_pcm_393;
+		plan_name = "fallback 393MHz";
+	}
+
+	/*
+	 * Pick the matching frequency table on both branches; assigning
+	 * unconditionally also restores the default if this driver is
+	 * ever rebound on a system whose PLL4 has been reprogrammed.
+	 */
+	slimbus_src.freq_tbl		   = aif_osr_tbl;
+	mi2s_osr_src.freq_tbl		   = aif_osr_tbl;
+	codec_i2s_mic_osr_src.freq_tbl	   = aif_osr_tbl;
+	spare_i2s_mic_osr_src.freq_tbl	   = aif_osr_tbl;
+	codec_i2s_spkr_osr_src.freq_tbl	   = aif_osr_tbl;
+	spare_i2s_spkr_osr_src.freq_tbl	   = aif_osr_tbl;
+	pcm_src.freq_tbl		   = pcm_tbl;
+
+	dev_info(&pdev->dev, "PLL4 L=0x%x, using %s frequency plan\n",
+		 val, plan_name);
+
+	/* Enable PLL4 source on the LPASS Primary PLL Mux */
+	regmap_write(regmap, 0xc4, 0x1);
+
+	return qcom_cc_really_probe(&pdev->dev, &lcc_msm8660_desc, regmap);
+}
+
+static struct platform_driver lcc_msm8660_driver = {
+	.probe		= lcc_msm8660_probe,
+	.driver		= {
+		.name	= "lcc-msm8660",
+		.of_match_table = lcc_msm8660_match_table,
+	},
+};
+module_platform_driver(lcc_msm8660_driver);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 LPASS Clock Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lcc-msm8660");
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
@ 2026-05-31  7:58     ` Krzysztof Kozlowski
  0 siblings, 0 replies; 14+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31  7:58 UTC (permalink / raw)
  To: Herman van Hazendonk, Bjorn Andersson, Conor Dooley, devicetree,
	Krzysztof Kozlowski, linux-arm-msm, linux-clk, linux-kernel,
	Michael Turquette, Rob Herring, Stephen Boyd

On 31/05/2026 06:09, Herman van Hazendonk wrote:
> Add "qcom,lcc-msm8260", "qcom,lcc-msm8660" and "qcom,lcc-apq8060" to
> the qcom,lcc enum and to the existing parent-clock-name conditional
> block that already covers apq8064/msm8960.
> 
> The MSM8x60 family (MSM8260/MSM8660/APQ8060) is the Scorpion-class
> generation preceding MSM8960; its LPASS Clock Controller is fed by
> the same pxo board source and pll4_vote parents as apq8064/msm8960,
> and the codec-fed slave-mode I2S/PCM clocks have the same names, so
> the existing apq8064/msm8960 conditional block applies unchanged
> once the new compatibles are listed.
> 
> This allows board DTs using the new compatibles to pass
> dt_binding_check; the actual driver support is added by a later
> patch in this series.
> 
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  Documentation/devicetree/bindings/clock/qcom,lcc.yaml | 6 ++++++
>  1 file changed, 6 insertions(+)

This is a complete mess - one thread has like 10 different patchsets and
versions.

I have no clue what to do here. I am dropping everything from DT patchwork.

Please read carefully submitting patches.

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller
  2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
                   ` (5 preceding siblings ...)
  2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-31 15:36 ` Dmitry Baryshkov
  6 siblings, 0 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2026-05-31 15:36 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

On Sat, May 30, 2026 at 03:59:56PM +0200, Herman van Hazendonk wrote:
> Hi all,
> 
> This series adds the LPASS (Low Power Audio SubSystem) Clock Controller
> driver for the MSM8x60 family of SoCs (MSM8260/MSM8660/APQ8060) - the
> Scorpion-class generation that preceded MSM8960's Krait CPUs.
> 
> The register layout, parent muxing and divider topology of the LPASS
> PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
> driver is simpler than parameterising lcc-msm8960.c. Both drivers can
> coexist (different Kconfig, match table and compatible).
> 
> Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP needs
> functional MI2S / SLIMBus / PCM clocks before audio playback or capture
> works.
> 
> The new binding header is dual-licensed (GPL-2.0-only OR BSD-2-Clause)
> per current qcom-binding convention.
> 
> Companion to the MSM8x60 MMCC series.
> 
> Thanks,
> Herman
> 
> Herman van Hazendonk (2):
>   dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
>   clk: qcom: add MSM8x60 LCC (LPASS) driver

ALl of your series got entangled and linked to the same thread. Please
make sure to send each patch series separately. Never send two series or
two versions as reply to each other (yes, new version should also start
a new thread).

> 
>  drivers/clk/qcom/Kconfig                     |   9 +
>  drivers/clk/qcom/Makefile                    |   1 +
>  drivers/clk/qcom/lcc-msm8660.c               | 517 +++++++++++++++++++
>  include/dt-bindings/clock/qcom,lcc-msm8660.h |  48 ++
>  4 files changed, 575 insertions(+)
>  create mode 100644 drivers/clk/qcom/lcc-msm8660.c
>  create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h
> 
> -- 
> 2.43.0
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs
  2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
@ 2026-05-31 15:39   ` Dmitry Baryshkov
  0 siblings, 0 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2026-05-31 15:39 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-arm-msm,
	linux-clk, devicetree

On Sat, May 30, 2026 at 03:58:10PM +0200, Herman van Hazendonk wrote:
> Add the dt-binding clock-ID header for the MSM8x60 family
> (MSM8260/MSM8660/APQ8060) Multimedia Clock Controller (MMCC). The
> header enumerates the clocks and power-domains consumed by the
> multimedia subsystem (MDP4 display, Adreno A220 GPU, CAMSS image
> pipeline, VFE, Gemini JPEG, video codec, rotator, VPE and the GFX2D
> Z180 cores).
> 
> IDs intentionally match the numeric values used by the original
> shared mmcc-msm8960.h so the driver's clk array indexing is preserved;

Why?

> only the clocks actually implemented by mmcc-msm8660.c are defined.

Please also extend the qcom,mmcc.yaml to define pecularities of the
device.

> 
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  include/dt-bindings/clock/qcom,mmcc-msm8660.h | 126 ++++++++++++++++++
>  1 file changed, 126 insertions(+)
>  create mode 100644 include/dt-bindings/clock/qcom,mmcc-msm8660.h
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver
  2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
@ 2026-05-31 15:46   ` Dmitry Baryshkov
  0 siblings, 0 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2026-05-31 15:46 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
	linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
	Rob Herring, Stephen Boyd

On Sat, May 30, 2026 at 03:59:58PM +0200, Herman van Hazendonk wrote:
> Add a clock driver for the LPASS (Low Power Audio SubSystem) Clock
> Controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060) - the
> Scorpion-class generation that preceded MSM8960's Krait CPUs.
> 
> The register layout, parent muxing and divider topology of the LPASS
> PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
> driver is simpler than parameterising mmcc-msm8960.c. Both drivers
> can coexist in tree (different KConfig options, different match
> table, different device-tree compatible).
> 
> Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP
> needs functional MI2S / SLIMBus / PCM clocks before audio playback
> or capture works.

Could you please merge these changes into lcc-msm8960.c? There are
minimal differences, so you can just patch the definitions if the device
is compatible with msm8660.

> 
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  drivers/clk/qcom/Kconfig       |   9 +
>  drivers/clk/qcom/Makefile      |   1 +
>  drivers/clk/qcom/lcc-msm8660.c | 517 +++++++++++++++++++++++++++++++++
>  3 files changed, 527 insertions(+)
>  create mode 100644 drivers/clk/qcom/lcc-msm8660.c
> 

-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-05-31 15:46 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
2026-05-31 15:39   ` Dmitry Baryshkov
2026-05-30 13:58 ` [PATCH 3/3] clk: qcom: add MSM8x60 MMCC driver Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2026-05-31 15:46   ` Dmitry Baryshkov
2026-05-31  4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
2026-05-31  7:58     ` Krzysztof Kozlowski
2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2026-05-31 15:36 ` [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Dmitry Baryshkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox