#define CPUFREQ_TBL_STEP_HZ     (50 * KHZ * KHZ)
 #define MAX_CNT                 ~0U
 
+#define NDIV_MASK              0x1FF
+
+#define CORE_OFFSET(cpu)                       (cpu * 8)
+#define CMU_CLKS_BASE                          0x2000
+#define SCRATCH_FREQ_CORE_REG(data, cpu)       (data->regs + CMU_CLKS_BASE + CORE_OFFSET(cpu))
+
+#define MMCRAB_CLUSTER_BASE(cl)                        (0x30000 + (cl * 0x10000))
+#define CLUSTER_ACTMON_BASE(data, cl) \
+                       (data->regs + (MMCRAB_CLUSTER_BASE(cl) + data->soc->actmon_cntr_base))
+#define CORE_ACTMON_CNTR_REG(data, cl, cpu)    (CLUSTER_ACTMON_BASE(data, cl) + CORE_OFFSET(cpu))
+
 /* cpufreq transisition latency */
 #define TEGRA_CPUFREQ_TRANSITION_LATENCY (300 * 1000) /* unit in nanoseconds */
 
 struct tegra_cpufreq_soc {
        struct tegra_cpufreq_ops *ops;
        int maxcpus_per_cluster;
+       phys_addr_t actmon_cntr_base;
 };
 
 struct tegra194_cpufreq_data {
        *((u64 *)mpidr) = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
 }
 
+static void tegra234_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
+{
+       u64 mpidr;
+
+       smp_call_function_single(cpu, tegra_get_cpu_mpidr, &mpidr, true);
+
+       if (cpuid)
+               *cpuid = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+       if (clusterid)
+               *clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 2);
+}
+
+static int tegra234_get_cpu_ndiv(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv)
+{
+       struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+       void __iomem *freq_core_reg;
+       u64 mpidr_id;
+
+       /* use physical id to get address of per core frequency register */
+       mpidr_id = (clusterid * data->soc->maxcpus_per_cluster) + cpuid;
+       freq_core_reg = SCRATCH_FREQ_CORE_REG(data, mpidr_id);
+
+       *ndiv = readl(freq_core_reg) & NDIV_MASK;
+
+       return 0;
+}
+
+static void tegra234_set_cpu_ndiv(struct cpufreq_policy *policy, u64 ndiv)
+{
+       struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+       void __iomem *freq_core_reg;
+       u32 cpu, cpuid, clusterid;
+       u64 mpidr_id;
+
+       for_each_cpu_and(cpu, policy->cpus, cpu_online_mask) {
+               data->soc->ops->get_cpu_cluster_id(cpu, &cpuid, &clusterid);
+
+               /* use physical id to get address of per core frequency register */
+               mpidr_id = (clusterid * data->soc->maxcpus_per_cluster) + cpuid;
+               freq_core_reg = SCRATCH_FREQ_CORE_REG(data, mpidr_id);
+
+               writel(ndiv, freq_core_reg);
+       }
+}
+
+/*
+ * This register provides access to two counter values with a single
+ * 64-bit read. The counter values are used to determine the average
+ * actual frequency a core has run at over a period of time.
+ *     [63:32] PLLP counter: Counts at fixed frequency (408 MHz)
+ *     [31:0] Core clock counter: Counts on every core clock cycle
+ */
+static void tegra234_read_counters(struct tegra_cpu_ctr *c)
+{
+       struct tegra194_cpufreq_data *data = cpufreq_get_driver_data();
+       void __iomem *actmon_reg;
+       u32 cpuid, clusterid;
+       u64 val;
+
+       data->soc->ops->get_cpu_cluster_id(c->cpu, &cpuid, &clusterid);
+       actmon_reg = CORE_ACTMON_CNTR_REG(data, clusterid, cpuid);
+
+       val = readq(actmon_reg);
+       c->last_refclk_cnt = upper_32_bits(val);
+       c->last_coreclk_cnt = lower_32_bits(val);
+       udelay(US_DELAY);
+       val = readq(actmon_reg);
+       c->refclk_cnt = upper_32_bits(val);
+       c->coreclk_cnt = lower_32_bits(val);
+}
+
+static struct tegra_cpufreq_ops tegra234_cpufreq_ops = {
+       .read_counters = tegra234_read_counters,
+       .get_cpu_cluster_id = tegra234_get_cpu_cluster_id,
+       .get_cpu_ndiv = tegra234_get_cpu_ndiv,
+       .set_cpu_ndiv = tegra234_set_cpu_ndiv,
+};
+
+const struct tegra_cpufreq_soc tegra234_cpufreq_soc = {
+       .ops = &tegra234_cpufreq_ops,
+       .actmon_cntr_base = 0x9000,
+       .maxcpus_per_cluster = 4,
+};
+
 static void tegra194_get_cpu_cluster_id(u32 cpu, u32 *cpuid, u32 *clusterid)
 {
        u64 mpidr;
        if (!data->tables)
                return -ENOMEM;
 
+       if (soc->actmon_cntr_base) {
+               /* mmio registers are used for frequency request and re-construction */
+               data->regs = devm_platform_ioremap_resource(pdev, 0);
+               if (IS_ERR(data->regs))
+                       return PTR_ERR(data->regs);
+       }
+
        platform_set_drvdata(pdev, data);
 
        bpmp = tegra_bpmp_get(&pdev->dev);
 
 static const struct of_device_id tegra194_cpufreq_of_match[] = {
        { .compatible = "nvidia,tegra194-ccplex", .data = &tegra194_cpufreq_soc },
+       { .compatible = "nvidia,tegra234-ccplex-cluster", .data = &tegra234_cpufreq_soc },
        { /* sentinel */ }
 };