blob: bfc71187ef94321bd6b5e19aa7345d015d6116f0 [file] [log] [blame]
/*
* 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);