From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from outbound0.mail.transip.nl (outbound0.mail.transip.nl [149.210.149.69]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9B3B717A2F6; Sat, 30 May 2026 14:04:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=149.210.149.69 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780149855; cv=none; b=OSKEwhLwRsxN/Iz8YCizKSkfOw6XFhC/Q2VGeUJyGWFxK853AJbkUY39KDnQTR84kGt1Ffp25Me/v329ZzxIAe+qfWGrg/4iaA5Bj0kzglTOauT7ifue01WFaxZJ5kZcSfaDPFMgC8nIL3FqpVP1JdwjrdX5+4lOeCuuN8feP4E= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780149855; c=relaxed/simple; bh=V6Icuw8hl37yq7qHOTuSryUWAe9gXsn6pSsVegpU/E0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=evKwfUOg34blg4t7/2AFJ+iDKH1x8z6d4tXvd1RhBZEhc2HuJ2i2tHZLrPjLpIeK5itxArVodBMNqc6/7Lc2ZCdFPfNgsSeuPlw8XT37UVdktzufUw+dLVCq1Djv1YsOCcJ2JDpw15Omavf9J31JBQ/CRgJ9hHWgeLz4ObUOiJA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org; spf=pass smtp.mailfrom=herrie.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b=p9rgpxMK; arc=none smtp.client-ip=149.210.149.69 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=herrie.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b="p9rgpxMK" Received: from submission5.mail.transip.nl (unknown [10.103.8.156]) by outbound0.mail.transip.nl (Postfix) with ESMTP id 4gSMJQ5fgQzxNnS; Sat, 30 May 2026 15:58:30 +0200 (CEST) Received: from herrie-desktop.. (180-93-184-31.ftth.glasoperator.nl [31.184.93.180]) by submission5.mail.transip.nl (Postfix) with ESMTPA id 4gSMJP4qmdz2pRDjH; Sat, 30 May 2026 15:58:29 +0200 (CEST) From: Herman van Hazendonk To: Bjorn Andersson , Michael Turquette , Stephen Boyd , linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org Cc: Herman van Hazendonk Subject: [PATCH 3/3] clk: qcom: add MSM8x60 MMCC driver Date: Sat, 30 May 2026 15:58:12 +0200 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Scanned-By: ClueGetter at submission5.mail.transip.nl DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=transip-a; d=herrie.org; t=1780149509; h=from:subject:to:cc: references:in-reply-to:date:mime-version:content-type; bh=ZV2aehyzz5aTXbu0GiUcKaMME2LeyRPhHYoD8zM0VKE=; b=p9rgpxMKM/K6iMqCz17Zp1hZlntD/z/vtGvgMFr/1DJc/VZNDgyhsKP39RDfZ9ZOzi6rUa bYTL++M2ruAMZOEMUd6htRuMHJZ8H7vACV3wchx9p19syj8VHulfOszOuZiard2cTHOl80 W3HXOdpooH62Qv6jNOzMEubGLqtkwLo4vF3MV0LWcpnfpuhPhKTdG9vXi6+p8v7C2cNtyM ze79vGBoD9FCYsvgpIMXLfK3pWZ5AkYwDHNSB+MhqfK5wXc5LFvha6FxWen5pBFo8bwhve CS3jAABekGwyFrpvun+JF3nFvQSFxAvtoQ30smUyKSzfiMQilYqJDE0ZZ969DA== X-Report-Abuse-To: abuse@transip.nl 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 --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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] = &_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