/* * * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define EDP_OC_OC1_STATS_0 0x4a8 #define EDP_OC_STATS(i) (EDP_OC_OC1_STATS_0 + ((i) * 4)) #define EDP_OC_OC1_THRESH_CNT_0 0x414 #define EDP_OC_THRESH_CNT(i) (EDP_OC_OC1_THRESH_CNT_0 + ((i) * 0x14)) #define EDP_OC_THROT_VEC_CNT SOCTHERM_THROT_VEC_INVALID struct oc_soc_data { unsigned int n_ocs; unsigned int n_throt_vecs; unsigned int cpu_offset; unsigned int gpu_offset; unsigned int priority_offset; unsigned int throttle_bank_size; unsigned int throttle_ctrl_base; unsigned int oc1_stats_offset; unsigned int stats_bank_size; unsigned int oc1_thresh_cnt_offset; unsigned int thresh_cnt_bank_size; const struct attribute_group **attr_groups; }; struct throttlectrl_info { unsigned int priority; unsigned int cpu_depth; unsigned int gpu_depth; }; struct edp_oc_info { unsigned int id; unsigned int irq_cnt; }; struct tegra_oc_event { struct device *hwmon; int32_t irq; void __iomem *hsp_base; void __iomem *soctherm_base; struct throttlectrl_info throttle_ctrl[EDP_OC_THROT_VEC_CNT]; struct edp_oc_info edp_oc[EDP_OC_THROT_VEC_CNT]; struct oc_soc_data soc_data; }; static struct tegra_oc_event tegra_oc; static unsigned int tegra_oc_readl(unsigned int offset) { return __raw_readl(tegra_oc.soctherm_base + offset); } static unsigned int tegra_oc_read_status_regs(void) { unsigned int oc_status = 0; int irq_cnt; int i; unsigned int status; unsigned int thresh_cnt; for (i = 0; i < SOCTHERM_EDP_OC_INVALID; i++) { status = tegra_oc_readl(tegra_oc.soc_data.oc1_stats_offset + (tegra_oc.soc_data.stats_bank_size * i)); thresh_cnt = tegra_oc_readl( tegra_oc.soc_data.oc1_thresh_cnt_offset + (tegra_oc.soc_data.thresh_cnt_bank_size * i)) + 1; irq_cnt = status / thresh_cnt; if (irq_cnt > tegra_oc.edp_oc[i].irq_cnt) { oc_status |= BIT(i); tegra_oc.edp_oc[i].irq_cnt = irq_cnt; } } return oc_status; } static irqreturn_t tegra_oc_event_notify(int irq, void *arg) { static unsigned long state; unsigned int oc_status = tegra_oc_read_status_regs(); if (printk_timed_ratelimit(&state, 1000)) pr_err("soctherm: OC ALARM 0x%08x\n", oc_status); return IRQ_HANDLED; } static irqreturn_t tegra_oc_event_raised(int irq, void *arg) { struct platform_device *pdev = arg; if (!pdev) return IRQ_NONE; /* Write 0 to TAG bit for HSP SM acknowledge */ __raw_writel(0, tegra_oc.hsp_base); return IRQ_WAKE_THREAD; } static void tegra_get_throtctrl_vectors(void) { int i; const struct oc_soc_data *soc_data = &tegra_oc.soc_data; unsigned int priority_off; unsigned int cpu_off; unsigned int gpu_off; for (i = 0; i < soc_data->n_throt_vecs; i++) { priority_off = soc_data->throttle_ctrl_base + (i * soc_data->throttle_bank_size) + soc_data->priority_offset; cpu_off = soc_data->throttle_ctrl_base + (i * soc_data->throttle_bank_size) + soc_data->cpu_offset; gpu_off = soc_data->throttle_ctrl_base + (i * soc_data->throttle_bank_size) + soc_data->gpu_offset; tegra_oc.throttle_ctrl[i].priority = tegra_oc_readl(priority_off); tegra_oc.throttle_ctrl[i].cpu_depth = tegra_oc_readl(cpu_off); tegra_oc.throttle_ctrl[i].gpu_depth = tegra_oc_readl(gpu_off); } } static ssize_t irq_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = container_of(attr, struct sensor_device_attribute, dev_attr); return sprintf(buf, "%u\n", tegra_oc.edp_oc[sensor_attr->index].irq_cnt); } static ssize_t priority_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = container_of(attr, struct sensor_device_attribute, dev_attr); return sprintf(buf, "%u\n", tegra_oc.throttle_ctrl[sensor_attr->index].priority); } static ssize_t cpu_thrtl_ctrl_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = container_of(attr, struct sensor_device_attribute, dev_attr); return sprintf(buf, "%u\n", tegra_oc.throttle_ctrl[sensor_attr->index].cpu_depth); } static ssize_t gpu_thrtl_ctrl_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = container_of(attr, struct sensor_device_attribute, dev_attr); return sprintf(buf, "%u\n", tegra_oc.throttle_ctrl[sensor_attr->index].gpu_depth); } static SENSOR_DEVICE_ATTR(oc1_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC1); static SENSOR_DEVICE_ATTR(oc1_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC1); static SENSOR_DEVICE_ATTR(oc1_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC1); static SENSOR_DEVICE_ATTR(oc1_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC1); static struct attribute *t194_oc1_attrs[] = { &sensor_dev_attr_oc1_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc1_priority.dev_attr.attr, &sensor_dev_attr_oc1_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc1_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc1_data = { .attrs = t194_oc1_attrs, NULL, }; static SENSOR_DEVICE_ATTR(oc2_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC2); static SENSOR_DEVICE_ATTR(oc2_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC2); static SENSOR_DEVICE_ATTR(oc2_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC2); static SENSOR_DEVICE_ATTR(oc2_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC2); static struct attribute *t194_oc2_attrs[] = { &sensor_dev_attr_oc2_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc2_priority.dev_attr.attr, &sensor_dev_attr_oc2_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc2_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc2_data = { .attrs = t194_oc2_attrs, NULL, }; static SENSOR_DEVICE_ATTR(oc3_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC3); static SENSOR_DEVICE_ATTR(oc3_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC3); static SENSOR_DEVICE_ATTR(oc3_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC3); static SENSOR_DEVICE_ATTR(oc3_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC3); static struct attribute *t194_oc3_attrs[] = { &sensor_dev_attr_oc3_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc3_priority.dev_attr.attr, &sensor_dev_attr_oc3_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc3_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc3_data = { .attrs = t194_oc3_attrs, NULL, }; static SENSOR_DEVICE_ATTR(oc4_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC4); static SENSOR_DEVICE_ATTR(oc4_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC4); static SENSOR_DEVICE_ATTR(oc4_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC4); static SENSOR_DEVICE_ATTR(oc4_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC4); static struct attribute *t194_oc4_attrs[] = { &sensor_dev_attr_oc4_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc4_priority.dev_attr.attr, &sensor_dev_attr_oc4_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc4_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc4_data = { .attrs = t194_oc4_attrs, NULL, }; static SENSOR_DEVICE_ATTR(oc5_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC5); static SENSOR_DEVICE_ATTR(oc5_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC5); static SENSOR_DEVICE_ATTR(oc5_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC5); static SENSOR_DEVICE_ATTR(oc5_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC5); static struct attribute *t194_oc5_attrs[] = { &sensor_dev_attr_oc5_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc5_priority.dev_attr.attr, &sensor_dev_attr_oc5_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc5_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc5_data = { .attrs = t194_oc5_attrs, NULL, }; static SENSOR_DEVICE_ATTR(oc6_irq_cnt, 0444, irq_count_show, NULL, SOCTHERM_EDP_OC6); static SENSOR_DEVICE_ATTR(oc6_priority, 0444, priority_show, NULL, SOCTHERM_EDP_OC6); static SENSOR_DEVICE_ATTR(oc6_cpu_throttle_ctrl, 0444, cpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC6); static SENSOR_DEVICE_ATTR(oc6_gpu_throttle_ctrl, 0444, gpu_thrtl_ctrl_show, NULL, SOCTHERM_EDP_OC6); static struct attribute *t194_oc6_attrs[] = { &sensor_dev_attr_oc6_irq_cnt.dev_attr.attr, &sensor_dev_attr_oc6_priority.dev_attr.attr, &sensor_dev_attr_oc6_cpu_throttle_ctrl.dev_attr.attr, &sensor_dev_attr_oc6_gpu_throttle_ctrl.dev_attr.attr, NULL, }; static const struct attribute_group oc6_data = { .attrs = t194_oc6_attrs, NULL, }; static const struct attribute_group *t186_oc_groups[] = { &oc1_data, &oc2_data, &oc3_data, &oc4_data, &oc5_data, &oc6_data, NULL, }; static const struct oc_soc_data t186_oc_soc_data = { .n_ocs = 6, .n_throt_vecs = 8, .cpu_offset = 0x30, .gpu_offset = 0x38, .priority_offset = 0x44, .throttle_bank_size = 0x30, .throttle_ctrl_base = 0x400, .oc1_stats_offset = 0x3a8, .stats_bank_size = 0x4, .oc1_thresh_cnt_offset = 0x314, .thresh_cnt_bank_size = 0x14, .attr_groups = t186_oc_groups, }; static const struct attribute_group *t194_oc_groups[] = { &oc1_data, &oc2_data, &oc3_data, &oc4_data, &oc5_data, &oc6_data, NULL, }; static const struct oc_soc_data t194_oc_soc_data = { .n_ocs = 6, .n_throt_vecs = 8, .cpu_offset = 0x30, .gpu_offset = 0x38, .priority_offset = 0x44, .throttle_bank_size = 0x30, .throttle_ctrl_base = 0x500, .oc1_stats_offset = 0x4a8, .stats_bank_size = 0x4, .oc1_thresh_cnt_offset = 0x414, .thresh_cnt_bank_size = 0x14, .attr_groups = t194_oc_groups, }; static const struct of_device_id tegra_oc_event_of_match[] = { { .compatible = "nvidia,tegra194-oc-event", .data = (void *)&t194_oc_soc_data }, { .compatible = "nvidia,tegra186-oc-event", .data = (void *)&t186_oc_soc_data }, {} }; MODULE_DEVICE_TABLE(of, tegra_oc_event_of_match); static int tegra_oc_event_remove(struct platform_device *pdev) { if (tegra_platform_is_silicon()) { iounmap(tegra_oc.soctherm_base); iounmap(tegra_oc.hsp_base); devm_hwmon_device_unregister(tegra_oc.hwmon); } dev_info(&pdev->dev, "remove\n"); return 0; } #define BPMP_NOC_SOC_THERM_BLF_CONTROL_REGISTER_0 0x0d640164 #define CPU_ACCESS_MASK 0x00000002 static int tegra_oc_event_probe(struct platform_device *pdev) { int ret; unsigned long flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED; const struct of_device_id *match; struct device_node *np = pdev->dev.of_node; unsigned int oc_status; void __iomem *blf; if (tegra_get_chip_id() == TEGRA194) { blf = devm_ioremap(&pdev->dev, BPMP_NOC_SOC_THERM_BLF_CONTROL_REGISTER_0, sizeof(u32)); if (blf == NULL) return -ENOMEM; if (!((readl(blf) & CPU_ACCESS_MASK) >> 1)) return -EPERM; } match = of_match_node(tegra_oc_event_of_match, np); if (!match) return -ENODEV; memcpy(&tegra_oc.soc_data, match->data, sizeof(struct oc_soc_data)); if (tegra_platform_is_silicon()) { tegra_oc.soctherm_base = of_iomap(pdev->dev.of_node, 0); if (!tegra_oc.soctherm_base) { dev_err(&pdev->dev, "Unable to map soctherm register memory"); return PTR_ERR(tegra_oc.soctherm_base); } tegra_oc.hsp_base = of_iomap(pdev->dev.of_node, 1); if (!tegra_oc.hsp_base) { dev_err(&pdev->dev, "Unable to map hsp register memory"); iounmap(tegra_oc.soctherm_base); return PTR_ERR(tegra_oc.soctherm_base); } tegra_get_throtctrl_vectors(); tegra_oc.hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "soctherm_oc", &tegra_oc, tegra_oc.soc_data.attr_groups); if (IS_ERR(tegra_oc.hwmon)) { dev_err(&pdev->dev, "Failed to register hwmon device\n"); iounmap(tegra_oc.soctherm_base); iounmap(tegra_oc.hsp_base); return PTR_ERR(tegra_oc.hwmon); } tegra_oc.irq = platform_get_irq(pdev, 0); ret = devm_request_threaded_irq(&pdev->dev, tegra_oc.irq, tegra_oc_event_raised, tegra_oc_event_notify, flags, "tegra-oc-event", pdev); if (ret < 0) { iounmap(tegra_oc.soctherm_base); iounmap(tegra_oc.hsp_base); devm_hwmon_device_unregister(tegra_oc.hwmon); dev_err(&pdev->dev, "Failed to request irq\n"); return ret; } /* Check if any OC events before probe */ oc_status = tegra_oc_read_status_regs(); if (oc_status) pr_err("soctherm: OC ALARM 0x%08x\n", oc_status); } dev_info(&pdev->dev, "OC driver initialized"); return 0; } static struct platform_driver tegra_oc_event_driver = { .driver = { .name = "tegra-oc-event", .owner = THIS_MODULE, .of_match_table = tegra_oc_event_of_match, }, .probe = tegra_oc_event_probe, .remove = tegra_oc_event_remove, }; module_platform_driver(tegra_oc_event_driver); MODULE_AUTHOR("Mantravadi Karthik "); MODULE_DESCRIPTION("NVIDIA Tegra Over Current Event Driver"); MODULE_LICENSE("GPL v2");