| /* |
| * drivers/amlogic/cpufreq/meson-dvfs-id.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that 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. |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/export.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/pm_opp.h> |
| #include <linux/platform_device.h> |
| #include <linux/mutex.h> |
| #include <linux/of_platform.h> |
| #include <linux/topology.h> |
| #include <linux/delay.h> |
| #include <linux/amlogic/scpi_protocol.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/amlogic/efuse.h> |
| #include <linux/amlogic/cpu_version.h> |
| #include <linux/of_address.h> |
| #include <linux/io.h> |
| |
| #define GET_DVFS_TABLE_INDEX 0x82000088 |
| |
| static char dvfs_id; |
| static struct class *dvfsid_cls; |
| static char *dvfsid_dev = "dvfs_id"; |
| void __iomem *chip_ver_reg; |
| |
| static unsigned int get_chip_family(void) |
| { |
| unsigned int chip_id = 0; |
| |
| chip_id = readl(chip_ver_reg); |
| pr_info("chip family: 0x%x", (chip_id >> 24) & 0xFF); |
| return ((chip_id >> 24) & 0xFF); |
| } |
| |
| static unsigned int get_cpufreq_table_index(u64 function_id, |
| u64 arg0, u64 arg1, u64 arg2) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc((unsigned long)function_id, |
| (unsigned long)arg0, |
| (unsigned long)arg1, |
| (unsigned long)arg2, |
| 0, 0, 0, 0, &res); |
| |
| return res.a0; |
| } |
| |
| static ssize_t dvfs_id_show(struct device *parent, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = sprintf(buf, "%d\n", dvfs_id); |
| return ret; |
| } |
| static DEVICE_ATTR_RO(dvfs_id); |
| |
| static ssize_t ringmsr_show(struct device *parent, |
| struct device_attribute *attr, char *buf) |
| { |
| int i = 0; |
| int ret = 0; |
| char str[64] = {0}; |
| /* the return buf is 8 bytes: 4 bytes cornerinfo + 4 bytes vmininfo */ |
| uint8_t efuseinfo[8] = {0, 0, 0, 0, 0, 0, 0, 0}; |
| char *name[4] = {"unknown", "cpu_ring", "iddee", "iddcpu"}; |
| |
| if (get_chip_family() == MESON_CPU_MAJOR_ID_C2) { |
| if (bl31_get_cornerinfo(efuseinfo, |
| sizeof(efuseinfo) / sizeof(uint8_t)) != 0) { |
| pr_err("fail get corner efuse info!\n"); |
| return 0; |
| } |
| |
| i = 1; |
| /* dump the first 4 bytes cornerinfo */ |
| ret += sprintf(str + ret, "%s:%d KHz", |
| name[i], (efuseinfo[i] * 50)); |
| for (i = 2; i < 4; i++) |
| ret += sprintf(str + ret, ", %s:%d uA", |
| name[i], (efuseinfo[i] * 400)); |
| |
| ret = sprintf(buf, "%s\n", str); |
| } else { |
| pr_err("show corner efuse info: not support non-c2 chip!\n"); |
| return 0; |
| } |
| |
| return ret; |
| } |
| static DEVICE_ATTR_RO(ringmsr); |
| |
| static ssize_t vmin_show(struct device *parent, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| char str[64] = {0}; |
| u8 vmininfo[4] = {0, 0, 0, 0}; |
| |
| if (get_chip_family() == MESON_CPU_MAJOR_ID_C2) { |
| if (bl31_get_vmininfo(vmininfo, |
| sizeof(vmininfo) / sizeof(u8)) != 0) { |
| pr_err("fail get vmin efuse info!\n"); |
| return 0; |
| } |
| |
| ret += sprintf(str + ret, "vmin:%x %x %x %x", vmininfo[0], |
| vmininfo[1], vmininfo[2], vmininfo[3]); |
| |
| ret = sprintf(buf, "%s\n", str); |
| } else { |
| pr_err("show vmin efuse info: not support non-c2 chip!\n"); |
| return 0; |
| } |
| |
| return ret; |
| } |
| static DEVICE_ATTR_RO(vmin); |
| |
| static struct attribute *dvfs_id_attrs[] = { |
| &dev_attr_dvfs_id.attr, |
| &dev_attr_ringmsr.attr, |
| &dev_attr_vmin.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(dvfs_id); |
| |
| static int dvfsid_probe(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| |
| dvfs_id = get_cpufreq_table_index(GET_DVFS_TABLE_INDEX, 0, 0, 0); |
| pr_info("%s dvfs id:%d\n", __func__, dvfs_id); |
| |
| chip_ver_reg = of_iomap(np, 0); |
| dvfsid_cls = kzalloc(sizeof(struct class), GFP_KERNEL); |
| if (dvfsid_cls == NULL) { |
| pr_err("%s no mem for zalloc\n", __func__); |
| return -1; |
| } |
| dvfsid_cls->name = dvfsid_dev; |
| dvfsid_cls->class_groups = dvfs_id_groups; |
| if (class_register(dvfsid_cls) < 0) { |
| pr_err("failed to class_reg for dvfsid\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id dvfsid_dt_match[] = { |
| { .compatible = "amlogic, dvfs-id" }, |
| { /* sentinel */ }, |
| }; |
| |
| static struct platform_driver dvfsid_platform_driver = { |
| .probe = dvfsid_probe, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "dvfs-id", |
| .of_match_table = dvfsid_dt_match, |
| }, |
| }; |
| |
| static int __init meson_dvfsid_init(void) |
| { |
| return platform_driver_register(&dvfsid_platform_driver); |
| } |
| |
| static void __exit meson_dvfsid_exit(void) |
| { |
| kfree(dvfsid_cls); |
| platform_driver_unregister(&dvfsid_platform_driver); |
| } |
| module_init(meson_dvfsid_init); |
| module_exit(meson_dvfsid_exit); |