// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2022 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #define PORT_MAJOR 32 #define DEFAULT_XTAL_FREQ 24000000UL #define DMC_QOS_IRQ BIT(30) /* DMC bandwidth monitor register address offset */ #define DMC_MON_G12_CTRL0 (0x0 << 2) #define DMC_MON_G12_CTRL1 (0x1 << 2) #define DMC_MON_G12_CTRL2 (0x2 << 2) #define DMC_MON_G12_CTRL3 (0x3 << 2) #define DMC_MON_G12_CTRL4 (0x4 << 2) #define DMC_MON_G12_CTRL5 (0x5 << 2) #define DMC_MON_G12_CTRL6 (0x6 << 2) #define DMC_MON_G12_CTRL7 (0x7 << 2) #define DMC_MON_G12_CTRL8 (0x8 << 2) #define DMC_MON_G12_ALL_REQ_CNT (0x9 << 2) #define DMC_MON_G12_ALL_GRANT_CNT (0xa << 2) #define DMC_MON_G12_ONE_GRANT_CNT (0xb << 2) #define DMC_MON_G12_SEC_GRANT_CNT (0xc << 2) #define DMC_MON_G12_THD_GRANT_CNT (0xd << 2) #define DMC_MON_G12_FOR_GRANT_CNT (0xe << 2) #define DMC_MON_G12_TIMER (0xf << 2) /* Each bit represent a axi line */ PMU_FORMAT_ATTR(event, "config:0-7"); PMU_FORMAT_ATTR(arm, "config1:0"); PMU_FORMAT_ATTR(gpu, "config1:1"); PMU_FORMAT_ATTR(pcie, "config1:2"); PMU_FORMAT_ATTR(hdcp, "config1:3"); PMU_FORMAT_ATTR(hevc_front, "config1:4"); PMU_FORMAT_ATTR(usb3_0, "config1:6"); PMU_FORMAT_ATTR(device, "config1:7"); PMU_FORMAT_ATTR(hevc_back, "config1:8"); PMU_FORMAT_ATTR(h265enc, "config1:9"); PMU_FORMAT_ATTR(vpu_read1, "config1:16"); PMU_FORMAT_ATTR(vpu_read2, "config1:17"); PMU_FORMAT_ATTR(vpu_read3, "config1:18"); PMU_FORMAT_ATTR(vpu_write1, "config1:19"); PMU_FORMAT_ATTR(vpu_write2, "config1:20"); PMU_FORMAT_ATTR(vdec, "config1:21"); PMU_FORMAT_ATTR(hcodec, "config1:22"); PMU_FORMAT_ATTR(ge2d, "config1:23"); PMU_FORMAT_ATTR(spicc1, "config1:32"); PMU_FORMAT_ATTR(usb0, "config1:33"); PMU_FORMAT_ATTR(dma, "config1:34"); PMU_FORMAT_ATTR(arb0, "config1:35"); PMU_FORMAT_ATTR(sd_emmc_b, "config1:36"); PMU_FORMAT_ATTR(usb1, "config1:37"); PMU_FORMAT_ATTR(audio, "config1:38"); PMU_FORMAT_ATTR(aififo, "config1:39"); PMU_FORMAT_ATTR(parser, "config1:41"); PMU_FORMAT_ATTR(ao_cpu, "config1:42"); PMU_FORMAT_ATTR(sd_emmc_c, "config1:43"); PMU_FORMAT_ATTR(spicc2, "config1:44"); PMU_FORMAT_ATTR(ethernet, "config1:45"); PMU_FORMAT_ATTR(sana, "config1:46"); /* for sm1 and g12b */ PMU_FORMAT_ATTR(nna, "config1:10"); /* for g12b only */ PMU_FORMAT_ATTR(gdc, "config1:11"); PMU_FORMAT_ATTR(mipi_isp, "config1:12"); PMU_FORMAT_ATTR(arm1, "config1:13"); PMU_FORMAT_ATTR(sd_emmc_a, "config1:40"); static struct attribute *g12_pmu_format_attrs[] = { &format_attr_event.attr, &format_attr_arm.attr, &format_attr_gpu.attr, &format_attr_nna.attr, &format_attr_gdc.attr, &format_attr_arm1.attr, &format_attr_mipi_isp.attr, &format_attr_sd_emmc_a.attr, &format_attr_pcie.attr, &format_attr_hdcp.attr, &format_attr_hevc_front.attr, &format_attr_usb3_0.attr, &format_attr_device.attr, &format_attr_hevc_back.attr, &format_attr_h265enc.attr, &format_attr_vpu_read1.attr, &format_attr_vpu_read2.attr, &format_attr_vpu_read3.attr, &format_attr_vpu_write1.attr, &format_attr_vpu_write2.attr, &format_attr_vdec.attr, &format_attr_hcodec.attr, &format_attr_ge2d.attr, &format_attr_spicc1.attr, &format_attr_usb0.attr, &format_attr_dma.attr, &format_attr_arb0.attr, &format_attr_sd_emmc_b.attr, &format_attr_usb1.attr, &format_attr_audio.attr, &format_attr_aififo.attr, &format_attr_parser.attr, &format_attr_ao_cpu.attr, &format_attr_sd_emmc_c.attr, &format_attr_spicc2.attr, &format_attr_ethernet.attr, &format_attr_sana.attr, NULL, }; /* calculate ddr clock */ static unsigned long dmc_g12_get_freq_quick(struct dmc_info *info) { unsigned int val; unsigned int n, m, od1; unsigned int od_div = 0xfff; unsigned long freq = 0; val = readl(info->pll_reg); val = val & 0xfffff; switch ((val >> 16) & 7) { case 0: od_div = 2; break; case 1: od_div = 3; break; case 2: od_div = 4; break; case 3: od_div = 6; break; case 4: od_div = 8; break; default: break; } m = val & 0x1ff; n = ((val >> 10) & 0x1f); od1 = (((val >> 19) & 0x1)) == 1 ? 2 : 1; freq = DEFAULT_XTAL_FREQ / 1000; /* avoid overflow */ if (n) freq = ((((freq * m) / n) >> od1) / od_div) * 1000; return freq; } #ifdef DEBUG static void g12_dump_reg(struct dmc_info *db) { int s = 0, i; unsigned int r; for (i = 0; i < 9; i++) { r = readl(db->ddr_reg[0] + (DMC_MON_G12_CTRL0 + (i << 2))); pr_notice("DMC_MON_CTRL%d: %08x\n", i, r); } r = readl(db->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); pr_notice("DMC_MON_ALL_REQ_CNT: %08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); pr_notice("DMC_MON_ALL_GRANT_CNT:%08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_ONE_GRANT_CNT); pr_notice("DMC_MON_ONE_GRANT_CNT:%08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_SEC_GRANT_CNT); pr_notice("DMC_MON_SEC_GRANT_CNT:%08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_THD_GRANT_CNT); pr_notice("DMC_MON_THD_GRANT_CNT:%08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_FOR_GRANT_CNT); pr_notice("DMC_MON_FOR_GRANT_CNT:%08x\n", r); r = readl(db->ddr_reg[0] + DMC_MON_G12_TIMER); pr_notice("DMC_MON_TIMER: %08x\n", r); } #endif static void dmc_g12_counter_enable(struct dmc_info *info) { unsigned int val; unsigned long clock_count = dmc_g12_get_freq_quick(info) / 10; /* 100ms */ writel(clock_count, info->ddr_reg[0] + DMC_MON_G12_TIMER); val = readl(info->ddr_reg[0] + DMC_MON_G12_CTRL0); /* enable all channel */ val = BIT(31) | /* enable bit */ BIT(20) | /* use timer */ 0x0f; /* 4 channels */ writel(val, info->ddr_reg[0] + DMC_MON_G12_CTRL0); #ifdef DEBUG g12_dump_reg(info); #endif } static void dmc_g12_config_fiter(struct dmc_info *info, int port, int channel) { u32 val; u32 rp[MAX_CHANNEL_NUM] = {DMC_MON_G12_CTRL1, DMC_MON_G12_CTRL3, DMC_MON_G12_CTRL5, DMC_MON_G12_CTRL7}; u32 rs[MAX_CHANNEL_NUM] = {DMC_MON_G12_CTRL2, DMC_MON_G12_CTRL4, DMC_MON_G12_CTRL6, DMC_MON_G12_CTRL8}; int subport = -1; /* clear all port mask */ if (port < 0) { writel(0, info->ddr_reg[0] + rp[channel]); writel(0, info->ddr_reg[0] + rs[channel]); return; } if (port >= PORT_MAJOR) subport = port - PORT_MAJOR; if (subport < 0) { val = readl(info->ddr_reg[0] + rp[channel]); val |= (1 << port); writel(val, info->ddr_reg[0] + rp[channel]); val = 0xffff; writel(val, info->ddr_reg[0] + rs[channel]); } else { val = BIT(23); /* select device */ writel(val, info->ddr_reg[0] + rp[channel]); val = readl(info->ddr_reg[0] + rs[channel]); val |= (1 << subport); writel(val, info->ddr_reg[0] + rs[channel]); } } static void dmc_g12_set_axi_filter(struct dmc_info *info, int axi_id, int channel) { if (channel > info->hw_info->chann_nr) return; dmc_g12_config_fiter(info, axi_id, channel); } static void dmc_g12_counter_disable(struct dmc_info *info) { int i; /* clear timer */ writel(0, info->ddr_reg[0] + DMC_MON_G12_CTRL0); writel(0, info->ddr_reg[0] + DMC_MON_G12_TIMER); writel(0, info->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); writel(0, info->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); writel(0, info->ddr_reg[0] + DMC_MON_G12_ONE_GRANT_CNT); writel(0, info->ddr_reg[0] + DMC_MON_G12_SEC_GRANT_CNT); writel(0, info->ddr_reg[0] + DMC_MON_G12_THD_GRANT_CNT); writel(0, info->ddr_reg[0] + DMC_MON_G12_FOR_GRANT_CNT); /* clear port channel mapping */ for (i = 0; i < info->hw_info->chann_nr; i++) dmc_g12_config_fiter(info, -1, i); } static void dmc_g12_get_counters(struct dmc_info *info, struct dmc_counter *counter) { int i; unsigned int reg; counter->all_cnt = readl(info->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); counter->all_req = readl(info->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); for (i = 0; i < info->hw_info->chann_nr; i++) { reg = DMC_MON_G12_ONE_GRANT_CNT + (i << 2); counter->channel_cnt[i] = readl(info->ddr_reg[0] + reg); } } static int dmc_g12_irq_handler(struct dmc_info *info, struct dmc_counter *counter) { unsigned int val; int ret = -EINVAL; val = readl(info->ddr_reg[0] + DMC_MON_G12_CTRL0); if (val & DMC_QOS_IRQ) { dmc_g12_get_counters(info, counter); /* clear irq flags */ writel(val, info->ddr_reg[0] + DMC_MON_G12_CTRL0); ret = 0; } return ret; } static const struct dmc_hw_info g12a_dmc_info = { .enable = dmc_g12_counter_enable, .disable = dmc_g12_counter_disable, .irq_handler = dmc_g12_irq_handler, .get_counters = dmc_g12_get_counters, .set_axi_filter = dmc_g12_set_axi_filter, .dmc_nr = 1, .chann_nr = 4, .capability = {0X7EFF00FF03DF, 0}, .fmt_attr = g12_pmu_format_attrs, }; static const struct dmc_hw_info g12b_dmc_info = { .enable = dmc_g12_counter_enable, .disable = dmc_g12_counter_disable, .irq_handler = dmc_g12_irq_handler, .get_counters = dmc_g12_get_counters, .set_axi_filter = dmc_g12_set_axi_filter, .dmc_nr = 1, .chann_nr = 4, .capability = {0X7FFF00FF3FDF, 0}, .fmt_attr = g12_pmu_format_attrs, }; static const struct dmc_hw_info sm1_dmc_info = { .enable = dmc_g12_counter_enable, .disable = dmc_g12_counter_disable, .irq_handler = dmc_g12_irq_handler, .get_counters = dmc_g12_get_counters, .set_axi_filter = dmc_g12_set_axi_filter, .dmc_nr = 1, .chann_nr = 4, .capability = {0X7EFF00FF07DF, 0}, .fmt_attr = g12_pmu_format_attrs, }; static int g12_ddr_pmu_probe(struct platform_device *pdev) { return meson_ddr_pmu_create(pdev); } static void g12_ddr_pmu_remove(struct platform_device *pdev) { meson_ddr_pmu_remove(pdev); } static const struct of_device_id meson_ddr_pmu_dt_match[] = { { .compatible = "amlogic,g12a-ddr-pmu", .data = &g12a_dmc_info, }, { .compatible = "amlogic,g12b-ddr-pmu", .data = &g12b_dmc_info, }, { .compatible = "amlogic,sm1-ddr-pmu", .data = &sm1_dmc_info, }, {} }; MODULE_DEVICE_TABLE(of, meson_ddr_pmu_dt_match); static struct platform_driver g12_ddr_pmu_driver = { .probe = g12_ddr_pmu_probe, .remove = g12_ddr_pmu_remove, .driver = { .name = "meson-g12-ddr-pmu", .of_match_table = meson_ddr_pmu_dt_match, }, }; module_platform_driver(g12_ddr_pmu_driver); MODULE_AUTHOR("Jiucheng Xu"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Amlogic G12 series SoC DDR PMU");